@geodaoyu/accessor 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,282 +1,361 @@
1
- # Accessor
2
-
3
- Class: `Accessor`
4
-
5
- Accessor is an abstract class that facilitates the access to instance properties as well as a mechanism to watch for property changes. Every sub-class of Accessor defines properties that are directly accessible or by using the **get()** and **set()** methods. It is possible to watch for a property changes by using the **watch()** method.
6
-
7
- ## Installation
8
-
9
- ``` shell
10
- npm install @geodaoyu/accessor
11
- ```
12
-
13
- ## Property Overview
14
-
15
- | Name | Type | Summary | Class |
16
- | ------------- | ------------------------------------------------------------ | ---------------------- | -------- |
17
- | declaredClass | [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) | The name of the class. | Accessor |
18
-
19
- ### Property Details
20
-
21
- <table><tr><td bgcolor=#ddd><b>declaredClass</b> <span>String</span> <span>readonly</span></td></tr></table>
22
-
23
- The name of the class.
24
-
25
- ## Method Overview
26
-
27
- | Name | Return Type | Summary | Class |
28
- | ------- | ----------- | --------------------------------------------- | -------- |
29
- | get() | * | Gets the value of a property. | Accessor |
30
- | set() | * | Sets the value of a property. | Accessor |
31
- | watch() | WatchHandle | Watches for property changes on the instance. | Accessor |
32
-
33
- ### Method Details
34
-
35
- <table><tr><td bgcolor=#ddd><b>get(path){*}</b></td></tr></table>
36
-
37
- Gets the value of a property.
38
-
39
- The name of the property can refer to a property in the instance.
40
-
41
- ```javascript
42
- view.get("scale");
43
- ```
44
-
45
- It can also be a path to a property deeper in the instance. `get()` returns `undefined` if a property in the path doesn't exist.
46
-
47
- ```javascript
48
- var title = map.get("basemap.title");
49
-
50
- // equivalent of
51
- var title = map.basemap && map.basemap.title || undefined;
52
- ```
53
-
54
- Parameter:
55
-
56
- | **path** | [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) |
57
- | -------------------------------- | ------------------------------------------------------------ |
58
- | The path of the property to get. | |
59
-
60
- Returns:
61
-
62
- | Type | Description |
63
- | ---- | --------------------- |
64
- | * | The property's value. |
65
-
66
- <table><tr><td bgcolor=#ddd><b>set(path, value){*}</b></td></tr></table>
67
-
68
- Sets the value of a property.
69
-
70
- Call `set()` with a property name and a value to change the value of the property.
71
-
72
- ``` javascript
73
- // setting the basemap of the map
74
- map.set("basemap", "topo-vector");
75
- // is equivalent to
76
- map.basemap = "topo-vector";
77
-
78
- // currying set
79
- var updateViewScale = view.set.bind(view, "scale");
80
- updateViewScale(5000);
81
- ```
82
-
83
- `set()` can be called with the path to a property and a value. The property is not set if a property in the path doesn't exist.
84
-
85
- ``` javascript
86
- // updating the title of the basemap
87
- map.set("basemap.title", "World Topographic Map");
88
-
89
- // is equivalent to
90
- if (map.basemap != null) {
91
- map.basemap.title = "World Topographic Map";
92
- }
93
- ```
94
-
95
- An object with key-value pairs may be passed into `set()` to update multiple properties at once.
96
-
97
- ```javascript
98
- // setting a viewpoint on the view
99
- view.set({
100
- center: [-4.4861, 48.3904],
101
- scale: 5000
102
- });
103
-
104
- // currying set
105
- var updateView = view.set.bind(view);
106
-
107
- updateView({
108
- center: [-4.4861, 48.3904],
109
- scale: 5000
110
- });
111
- ```
112
-
113
- Parameters:
114
-
115
- | **path** | [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) |
116
- | ------------------------------------------------------------ | ------------------------------------------------------------ |
117
- | The path to the property to set, or an object of key-value pairs. | |
118
- | **value** | * |
119
- | *The new value to set on the property. | |
120
-
121
- Returns:
122
-
123
- | Type | Description |
124
- | ---- | ------------- |
125
- | * | The instance. |
126
-
127
- <table><tr><td bgcolor=#ddd><b>watch(path, callback){WatchHandle}</b></td></tr></table>
128
-
129
- Watches for property changes on the instance.
130
-
131
- Watching for property changes is essential for tracking changes on objects. To start watching for changes on a property, call `watch()` with the property name and a callback function that will execute each time the property changes.
132
-
133
- ``` javascript
134
- var handle = mapview.watch("scale", function(newValue, oldValue, propertyName, target) {
135
- console.log(propertyName + " changed from " + oldValue + " to " + newValue);
136
- })
137
- ```
138
-
139
- To stop watching for changes, call the `remove()` method on the object that `watch()` returns.
140
-
141
- ``` javascript
142
- handle.remove();
143
- ```
144
-
145
- It is important to store the resulting objects from `watch()` to properly clean up the references.
146
-
147
- ```javascript
148
- var viewHandles = [];
149
- function setView(view) {
150
- // remove the handles for the current view.
151
- viewHandles.forEach(function(handle) {
152
- handle.remove();
153
- });
154
- viewHandles.length = 0;
155
-
156
- this.view = view;
157
-
158
- // watch for properties on the newly set view.
159
- if (view) {
160
- viewHandles.push(
161
- view.watch("scale", scaleWatcher);
162
- );
163
- }
164
- }
165
-
166
- setView(mapView);
167
- setView(null);
168
- ```
169
-
170
- Like `get()` and `set()`, it is possible to watch for a property deep in the object hierarchy by passing a path. If a property in the path doesn't exist the watch callback is called with `undefined`.
171
-
172
- ``` javascript
173
- var view = new SceneView({
174
- map: new Map({
175
- basemap: "streets-vector"
176
- })
177
- });
178
-
179
- view.watch("map.basemap.title", function(newValue, oldValue) {
180
- console.log("basemap's title changed from " + oldValue + " to " + newValue);
181
- });
182
-
183
- view.map.basemap = "topo-vector";
184
- // output: "basemap's title changed from Streets to Topographic"
185
-
186
- view.map = null;
187
- // output: "basemap's title changed from Topographic to undefined"
188
- ```
189
-
190
- Pass a comma delimited list of property paths, or an array of property paths, to watch multiple properties with the same callback. Use the third parameter of the callback call to determine what property changed.
191
-
192
- ``` javascript
193
- view.watch("center, scale, rotation", function(newValue, oldValue, propertyName) {
194
- console.log(propertyName + " changed");
195
- });
196
-
197
- // equivalent of
198
- view.watch(["center", "scale", "rotation"], function(newValue, oldValue, propertyName) {
199
- console.log(propertyName + " changed");
200
- });
201
-
202
- // equivalent of
203
- var callback = function(newValue, oldValue, propertyName) {
204
- console.log(propertyName + " changed");
205
- }
206
- view.watch("center", callback);
207
- view.watch("scale", callback);
208
- view.watch("rotation", callback);
209
- ```
210
-
211
- `Accessor` doesn't call the watch callbacks for a property immediately after its value changes. Instead, when a property's value changes and if that property is watched, `Accessor` schedules a notification which is then processed at a later time. Properties that change frequently like `view.scale` can be watched without having to throttle the callback.
212
-
213
- ``` javascript
214
- // Divides the view.scale three times
215
- view.watch("scale", function(newValue, oldValue) {
216
- console.log("view's scale changed from " + oldValue + " to " + newValue);
217
- });
218
- console.log("current view scale: " + view.scale);
219
- view.scale = view.scale / 2;
220
- view.scale = view.scale / 2;
221
- view.scale = view.scale / 2;
222
- console.log("current view scale: " + view.scale);
223
-
224
- // output the following:
225
- // current view scale: 36978595.474472
226
- // current view scale: 4622324.434309
227
- // view's scale changed from 36978595.474472 to 4622324.434309
228
- ```
229
-
230
- Parameters:
231
-
232
- | **path** | [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| String[] |
233
- | ------------------------------------------------------------ | ------------------------------------------------------------ |
234
- | The property or properties to watch. Multiple properties can be specified as a comma-separated list. | |
235
- | **callback** | watchCallback |
236
- | The callback to execute when the property value has changed. | |
237
-
238
- Returns:
239
-
240
- | Type | Description |
241
- | ----------- | -------------- |
242
- | WatchHandle | A watch handle |
243
-
244
- ## Type Definitions
245
-
246
- <table><tr><td bgcolor=#ddd><b>watchCallback(newValue, oldValue, propertyName, target)</b></td></tr></table>
247
-
248
- Callback to be called when a watched property changes.
249
-
250
- Parameters:
251
-
252
- | **newValue** | * |
253
- | ------------------------------------------------- | ------------------------------------------------------------ |
254
- | The new value of the watched property. | |
255
- | **oldValue** | * |
256
- | The old value of the watched property. | |
257
- | **propertyName** | [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) |
258
- | The property name. | |
259
- | **target** | Accessor |
260
- | The object containing the property being watched. | |
261
-
262
- <table><tr><td bgcolor=#ddd><b>WatchHandle</b> <span>Object</span></td></tr></table>
263
-
264
- Represents a watch created when an object invokes **watch()**.
265
-
266
- Property:
267
-
268
- | **remove** | [Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function) |
269
- | ------------------------- | ------------------------------------------------------------ |
270
- | Removes the watch handle. | |
271
-
272
- Example:
273
-
274
- ``` javascript
275
- var handle = map.watch('basemap', function(newVal){
276
- // Each time the value of map.basemap changes, it is logged in the console
277
- console.log("new basemap: ", newVal);
278
- });
279
-
280
- // When remove() is called on the watch handle, the map no longer watches for changes to basemap
281
- handle.remove();
282
- ```
1
+ # Accessor
2
+
3
+ ## Overview
4
+
5
+ Accessor is an abstract class that facilitates the access to instance properties as well as a mechanism to watch for property changes. Every sub-class of Accessor defines properties that are directly accessible or by using the **get()** and **set()** methods. It is possible to watch for a property changes by using the **watch()** method.
6
+
7
+ ## Property Overview
8
+
9
+ | Name | Type | Summary | Class |
10
+ | ------------- | ------------------------------------------------------------ | ---------------------- | -------- |
11
+ | declaredClass | [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) | The name of the class. | Accessor |
12
+
13
+ ### Property Details
14
+
15
+ <table><tr><td bgcolor=#ddd><b>declaredClass</b> <span>String</span> <span>readonly</span></td></tr></table>
16
+
17
+ The name of the class.
18
+
19
+ ## Method Overview
20
+
21
+ | Name | Return Type | Summary | Class |
22
+ | ----- | ----------- | ----------------------------- | -------- |
23
+ | set() | * | Sets the value of a property. | Accessor |
24
+
25
+ ### Method Details
26
+
27
+ <table><tr><td bgcolor=#ddd><b>set(path, value) {*}</b></td></tr></table>
28
+
29
+ Sets the value of a property.
30
+
31
+ Call `set()` with a property name and a value to change the value of the property.
32
+
33
+ ``` javascript
34
+ // setting the basemap of the map
35
+ map.set("basemap", "topo-vector");
36
+ // is equivalent to
37
+ map.basemap = "topo-vector";
38
+
39
+ // currying set
40
+ const updateViewScale = view.set.bind(view, "scale");
41
+ updateViewScale(5000);
42
+ ```
43
+
44
+ `set()` can be called with the path to a property and a value. The property is not set if a property in the path doesn't exist.
45
+
46
+ ``` javascript
47
+ // updating the title of the basemap
48
+ map.set("basemap.title", "World Topographic Map");
49
+
50
+ // is equivalent to
51
+ if (map.basemap != null) {
52
+ map.basemap.title = "World Topographic Map";
53
+ }
54
+ ```
55
+
56
+ An object with key-value pairs may be passed into `set()` to update multiple properties at once.
57
+
58
+ ```javascript
59
+ // setting a viewpoint on the view
60
+ view.set({
61
+ center: [-4.4861, 48.3904],
62
+ scale: 5000
63
+ });
64
+
65
+ // currying set
66
+ const updateView = view.set.bind(view);
67
+
68
+ updateView({
69
+ center: [-4.4861, 48.3904],
70
+ scale: 5000
71
+ });
72
+ ```
73
+
74
+ Parameters:
75
+
76
+ | **path** | [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) |
77
+ | ------------------------------------------------------------ | ------------------------------------------------------------ |
78
+ | The path to the property to set, or an object of key-value pairs. | |
79
+ | **value** | * |
80
+ | The new value to set on the property. | |
81
+
82
+ Returns:
83
+
84
+ | Type | Description |
85
+ | ---- | ------------- |
86
+ | * | The instance. |
87
+
88
+ # reactiveUtils
89
+
90
+ ## Overview
91
+
92
+ `reactiveUtils` provide capabilities for observing changes to the state of the SDK's properties, and is an important part of managing your application's life-cycle. State can be observed on a variety of different data types and structures including strings, numbers, arrays, booleans, collections, and objects.
93
+
94
+ ## Using reactiveUtils
95
+
96
+ `reactiveUtils` provides five methods that offer different patterns and capabilities for observing state: [on()](https://developers.arcgis.com/javascript/latest/api-reference/esri-core-reactiveUtils.html#on), [once()](https://developers.arcgis.com/javascript/latest/api-reference/esri-core-reactiveUtils.html#once), [watch()](https://developers.arcgis.com/javascript/latest/api-reference/esri-core-reactiveUtils.html#watch), [when()](https://developers.arcgis.com/javascript/latest/api-reference/esri-core-reactiveUtils.html#when) and [whenOnce()](https://developers.arcgis.com/javascript/latest/api-reference/esri-core-reactiveUtils.html#whenOnce).
97
+
98
+ The following is a basic example using [reactiveUtils.watch()](https://developers.arcgis.com/javascript/latest/api-reference/esri-core-reactiveUtils.html#watch). It demonstrates how to track the Map component [updating](https://developers.arcgis.com/javascript/latest/references/map-components/arcgis-map/#updating) property and then send a message to the console when the property changes. This snippet uses a `getValue` function as an expression that evaluates the `updating` property, and when a change is observed the new value is passed to the callback:
99
+
100
+ ```
101
+ // Basic example of watching for changes on a boolean property
102
+ const viewElement = document.querySelector("arcgis-map");
103
+ reactiveUtils.watch(
104
+ // getValue function
105
+ () => viewElement.updating,
106
+ // callback
107
+ (updating) => {
108
+ console.log(updating)
109
+ });
110
+ ```
111
+
112
+
113
+
114
+ ### Working with collections
115
+
116
+ `reactiveUtils` can be used to observe changes within a collection, such as [Map.allLayers](https://developers.arcgis.com/javascript/latest/api-reference/esri-Map.html#allLayers). Out-of-the-box JavaScript methods such as [`.map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) and [`.filter()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) can be used as expressions to be evaluated in the `getValue` function.
117
+
118
+ ```
119
+ // Watching for changes within a collection
120
+ // whenever a new layer is added to the map
121
+ const viewElement = document.querySelector("arcgis-map");
122
+ reactiveUtils.watch(
123
+ () => viewElement.map.allLayers.map( layer => layer.id),
124
+ (ids) => {
125
+ console.log(`FeatureLayer IDs ${ids}`);
126
+ });
127
+ ```
128
+
129
+
130
+
131
+ ### Working with objects
132
+
133
+ With `reactiveUtils` you can track named object properties through dot notation (e.g. `viewElement.updating`) or through bracket notation (e.g. `viewElement["updating"]`). You can also use the [optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) operator (`?.`). This operator simplifies the process of verifying that properties used in the `getValue` function are not `undefined` or `null`.
134
+
135
+ ```
136
+ // Watch for changes in an object using optional chaining
137
+ // whenever the map's extent changes
138
+ const viewElement = document.querySelector("arcgis-map");
139
+ reactiveUtils.watch(
140
+ () => viewElement?.extent?.xmin,
141
+ (xmin) => {
142
+ console.log(`Extent change xmin = ${xmin}`)
143
+ });
144
+ ```
145
+
146
+
147
+
148
+ ### WatchHandles and Promises
149
+
150
+ The [watch()](https://developers.arcgis.com/javascript/latest/api-reference/esri-core-reactiveUtils.html#watch), [on()](https://developers.arcgis.com/javascript/latest/api-reference/esri-core-reactiveUtils.html#on) and [when()](https://developers.arcgis.com/javascript/latest/api-reference/esri-core-reactiveUtils.html#when) methods return a [WatchHandle](https://developers.arcgis.com/javascript/latest/api-reference/esri-core-Accessor.html#WatchHandle). Be sure to remove watch handles when they are no longer needed to avoid memory leaks.
151
+
152
+ ```
153
+ // Use a WatchHandle to stop watching
154
+ const viewElement = document.querySelector("arcgis-map");
155
+ const handle = reactiveUtils.watch(
156
+ () => viewElement?.extent?.xmin,
157
+ (xmin) => {
158
+ console.log(`Extent change xmin = ${xmin}`)
159
+ });
160
+
161
+ // In another function
162
+ handle.remove()
163
+ ```
164
+
165
+ The [once()](https://developers.arcgis.com/javascript/latest/api-reference/esri-core-reactiveUtils.html#once) and [whenOnce()](https://developers.arcgis.com/javascript/latest/api-reference/esri-core-reactiveUtils.html#whenOnce) methods return a Promise instead of a `WatchHandle`. In some advanced use cases where an API action may take additional time, these methods also offer the option to cancel the async callback via an [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal). Be aware that if the returned Promise is not resolved, it can also result in a memory leak.
166
+
167
+ ```
168
+ // Use an AbortSignal to cancel an async callback
169
+ // during view animation
170
+ const abortController = new AbortController();
171
+
172
+ // Observe the View's animation state
173
+ reactiveUtils.whenOnce(
174
+ () => view?.animation, {signal: abortController.signal})
175
+ .then((animation) => {
176
+ console.log(`View animation state is ${animation.state}`)
177
+ });
178
+
179
+ // Cancel the async callback
180
+ const someFunction = () => {
181
+ abortController.abort();
182
+ }
183
+ ```
184
+
185
+
186
+
187
+ ### Working with truthy values
188
+
189
+ The [when()](https://developers.arcgis.com/javascript/latest/api-reference/esri-core-reactiveUtils.html#when) and [whenOnce()](https://developers.arcgis.com/javascript/latest/api-reference/esri-core-reactiveUtils.html#whenOnce) methods watch for *truthy* values, these are values that evaluate to `true` in boolean contexts. To learn more about using truthy, visit this [MDN Web doc](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) article. The snippets below use the [Popup.visible](https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-Popup.html) property, which is a boolean.
190
+
191
+ ```
192
+ // Observe changes on a boolean property
193
+ const viewElement = document.querySelector("arcgis-map");
194
+ reactiveUtils.when(() => viewElement.popup?.visible, () => console.log("Truthy"));
195
+ reactiveUtils.when(() => !viewElement.popup?.visible, () => console.log("Not truthy"));
196
+ reactiveUtils.when(() => viewElement.popup?.visible === true, () => console.log("True"));
197
+ reactiveUtils.when(() => viewElement.popup?.visible !== undefined, () => console.log("Defined"));
198
+ reactiveUtils.when(() => viewElement.popup?.visible === undefined, () => console.log("Undefined"));
199
+ ```
200
+
201
+ ## Method Overview
202
+
203
+ | Name | Return Type | Summary | Object |
204
+ | ------- | ----------- | ------------------------------------------------------------ | ------------- |
205
+ | watch() | WatchHandle | Tracks any properties accessed in the `getValue` function and calls the callback when any of them change. | reactiveUtils |
206
+
207
+ ### Method Details
208
+ <table><tr><td bgcolor=#ddd><b>watch(getValue, callback, options?){WatchHandle}</b></td></tr></table>
209
+
210
+ Tracks any properties accessed in the `getValue` function and calls the callback when any of them change.
211
+
212
+ Parameters:
213
+
214
+ | **getValue** | ReactiveWatchExpression |
215
+ | ------------------------------------------------------------ | ------------------------- |
216
+ | Function used to get the current value. All accessed properties will be tracked. | |
217
+ | **callback** | **ReactiveWatchCallback** |
218
+ | The function to call when there are changes. | |
219
+ | **options** | **ReactiveWatchOptions** |
220
+ | Options used to configure how the tracking happens and how the callback is to be called. | |
221
+
222
+ Returns:
223
+
224
+ | Type | Description |
225
+ | ----------- | -------------- |
226
+ | WatchHandle | A watch handle |
227
+
228
+ Examples
229
+
230
+ ```js
231
+ // Watching for changes in a boolean value
232
+ // Equivalent to watchUtils.watch()
233
+ const viewElement = document.querySelector("arcgis-map");
234
+ reactiveUtils.watch(
235
+ () => viewElement.popup?.visible,
236
+ () => {
237
+ console.log(`Popup visible: ${viewElement.popup.visible}`);
238
+ });
239
+ // Watching for changes within a Collection
240
+ const viewElement = document.querySelector("arcgis-map");
241
+ reactiveUtils.watch(
242
+ () => viewElement.map.allLayers.length,
243
+ () => {
244
+ console.log(`Layer collection length changed: ${viewElement.map.allLayers.length}`);
245
+ });
246
+ // Watch for changes in a numerical value.
247
+ // Providing `initial: true` in ReactiveWatchOptions
248
+ // checks immediately after initialization
249
+ // Equivalent to watchUtils.init()
250
+ const viewElement = document.querySelector("arcgis-map");
251
+ reactiveUtils.watch(
252
+ () => viewElement.zoom,
253
+ () => {
254
+ console.log(`zoom changed to ${viewElement.zoom}`);
255
+ },
256
+ {
257
+ initial: true
258
+ });
259
+ // Watch properties from multiple sources
260
+ const viewElement = document.querySelector("arcgis-map");
261
+ const handle = reactiveUtils.watch(
262
+ () => [viewElement.stationary, viewElement.zoom],
263
+ ([stationary, zoom]) => {
264
+ // Only print the new zoom value when the map component is stationary
265
+ if(stationary){
266
+ console.log(`Change in zoom level: ${zoom}`);
267
+ }
268
+ }
269
+ );
270
+ ```
271
+
272
+ ## Type Definitions
273
+
274
+ <table><tr><td bgcolor=#ddd><b>WatchHandle</b> <span>Object</span></td></tr></table>
275
+
276
+ Represents a watch or event handler which can be removed.
277
+
278
+ Property:
279
+
280
+ | **remove** | [Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function) |
281
+ | ------------------------- | ------------------------------------------------------------ |
282
+ | Removes the watch handle. | |
283
+
284
+ Example:
285
+
286
+ ``` javascript
287
+ const handle = reactiveUtils.watch(() => map.basemap, (newVal) => {
288
+ // Each time the value of map.basemap changes, it is logged in the console
289
+ console.log("new basemap: ", newVal);
290
+ });
291
+
292
+ // When remove() is called on the watch handle, the map no longer watches for changes to basemap
293
+ handle.remove();
294
+ ```
295
+
296
+ <table><tr><td bgcolor=#ddd><b>ReactiveEqualityFunction(newValue, oldValue) {Boolean}</b></td></tr></table>
297
+
298
+ Function used to check whether two values are the same, in which case the watch callback isn't called.
299
+
300
+ Parameters:
301
+
302
+ | **newValue** | * |
303
+ | -------------- | ---- |
304
+ | The new value. | |
305
+ | **oldValue** | * |
306
+ | The old value. | |
307
+
308
+ Returns:
309
+
310
+ | Type | Description |
311
+ | ------- | ------------------------------------------------ |
312
+ | Boolean | Whether the new value is equal to the old value. |
313
+
314
+ <table><tr><td bgcolor=#ddd><b>ReactiveListenerChangeCallback(target?)</b></td></tr></table>
315
+
316
+ Function used to check whether two values are the same, in which case the watch callback isn't called.
317
+
318
+ Parameters:
319
+
320
+ | **target** | * |
321
+ | ------------------------------------------------------------ | ---- |
322
+ | The event target to which the listener was added or from which it was removed. | |
323
+
324
+ <table><tr><td bgcolor=#ddd><b>ReactiveWatchCallback(newValue, oldValue) {Boolean}</b></td></tr></table>
325
+
326
+ Function to be called when a value changes.
327
+
328
+ Parameters:
329
+
330
+ | **newValue** | * |
331
+ | -------------- | ---- |
332
+ | The new value. | |
333
+ | **oldValue** | * |
334
+ | The old value. | |
335
+
336
+ <table><tr><td bgcolor=#ddd><b>ReactiveWatchExpression(){*}</b></td></tr></table>
337
+
338
+ Function which is auto-tracked and should return a value to pass to the ReactiveWatchCallback
339
+
340
+ Returns:
341
+
342
+ | Type | Description |
343
+ | ---- | -------------- |
344
+ | * | The new value. |
345
+
346
+ <table><tr><td bgcolor=#ddd><b>ReactiveWatchOptions</b> <span>Object</span></td></tr></table>
347
+
348
+ Options used to configure how auto-tracking is performed and how the callback should be called.
349
+
350
+ Property:
351
+
352
+ | **initial** | Boolean |
353
+ | ------------------------------------------------------------ | ---------------------------- |
354
+ | Default Value:false<br />Whether to fire the callback immediately after initialization, if the necessary conditions are met. | |
355
+ | **sync** | **Boolean** |
356
+ | Default Value:false<br />Whether to fire the callback synchronously or on the next tick. | |
357
+ | **once** | **Boolean** |
358
+ | Default Value:false<br />Whether to fire the callback only once. | |
359
+ | **equals** | **ReactiveEqualityFunction** |
360
+ | Function used to check whether two values are the same, in which case the callback isn't called. Checks whether two objects, arrays or primitive values are shallow equal, e.g. one level deep. Non-plain objects are considered equal if they are strictly equal (===). | |
361
+
package/package.json CHANGED
@@ -1,27 +1,28 @@
1
- {
2
- "name": "@geodaoyu/accessor",
3
- "version": "1.0.1",
4
- "description": "Implement esri/core/Accessor by myself.",
5
- "main": "index.js",
6
- "module": "dist/index.js",
7
- "scripts": {
8
- "build": "npx tsc",
9
- "test": "mocha"
10
- },
11
- "keywords": [
12
- "Accessor",
13
- "Esri",
14
- "ArcGIS"
15
- ],
16
- "author": "GeoDaoyu",
17
- "license": "MIT",
18
- "type": "module",
19
- "repository": {
20
- "type": "git",
21
- "url": "https://github.com/GeoDaoyu/Accessor.git"
22
- },
23
- "devDependencies": {
24
- "mocha": "^8.3.2",
25
- "typescript": "^4.2.4"
26
- }
27
- }
1
+ {
2
+ "name": "@geodaoyu/accessor",
3
+ "version": "2.0.0",
4
+ "description": "mini @arcgis/core/core/Accessor & @arcgis/core/core/reactiveUtils",
5
+ "main": "index.js",
6
+ "module": "dist/index.js",
7
+ "scripts": {
8
+ "build": "npx tsc",
9
+ "test": "mocha"
10
+ },
11
+ "keywords": [
12
+ "Accessor",
13
+ "Esri",
14
+ "ArcGIS",
15
+ "Proxy"
16
+ ],
17
+ "author": "GeoDaoyu",
18
+ "license": "MIT",
19
+ "type": "module",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/GeoDaoyu/Accessor.git"
23
+ },
24
+ "devDependencies": {
25
+ "mocha": "^11.7.1",
26
+ "typescript": "^5.8.3"
27
+ }
28
+ }
package/src/accessor.ts CHANGED
@@ -1,125 +1,119 @@
1
- type WatchCallback = (
2
- newValue: any,
3
- oldValue: any,
4
- propertyName: string,
5
- target: Accessor
6
- ) => void;
7
-
8
- interface WatchHandle extends Object {
9
- /**
10
- * Removes the watch handle.
11
- */
12
- remove(): void;
13
- }
14
-
15
- interface Handle {
16
- path: string;
17
- callback: Function;
18
- }
19
-
20
- /**
21
- * observe class
22
- * @param cls class
23
- * @returns Proxy
24
- */
25
- function observe(cls) {
26
- return new Proxy(cls, {
27
- construct(target, args) {
28
- const obj = new target(...args);
29
- return new Proxy(obj, {
30
- set: (target, key, value, receiver) => {
31
- const oldValue = target[key];
32
- target._handles.forEach((handle) => {
33
- if (handle.path === key) {
34
- handle.callback(value, oldValue, key, target);
35
- }
36
- });
37
- return Reflect.set(target, key, value, receiver);
38
- },
39
- });
40
- },
41
- });
42
- }
43
-
44
- class Accessor {
45
- declaredClass: string;
46
- private _handles: Set<Handle>;
47
-
48
- constructor(props: object) {
49
- for (let prop in props) {
50
- this[prop] = props[prop];
51
- }
52
- this.declaredClass = "Accessor";
53
- this._handles = new Set();
54
- }
55
- get(path: string): any {
56
- const dotIndex = path.indexOf(".");
57
- if (dotIndex !== -1) {
58
- const key = path.slice(0, dotIndex);
59
- const value = path.slice(dotIndex + 1);
60
- return this[key] && this[key].get(value);
61
- }
62
- return this[path];
63
- }
64
- set(path: string | object, value: any): this {
65
- if (typeof path === "string") {
66
- const dotIndex = path.indexOf(".");
67
- if (dotIndex !== -1) {
68
- const key = path.slice(0, dotIndex);
69
- const childPath = path.slice(dotIndex + 1);
70
- if (this[key]) {
71
- this[key].set(childPath, value);
72
- }
73
- } else {
74
- this[path] = value;
75
- }
76
- } else {
77
- for (const key in path) {
78
- this.set(key, path[key]);
79
- }
80
- }
81
-
82
- return this;
83
- }
84
- watch(path: string | string[], callback: WatchCallback): WatchHandle {
85
- const handles = [];
86
- const pathArray = [];
87
- if (typeof path === "object") {
88
- pathArray.push(...path);
89
- }
90
- if (typeof path === "string") {
91
- if (path.includes(",")) {
92
- pathArray.push(...path.replace(" ", "").split(","));
93
- } else {
94
- pathArray.push(path);
95
- }
96
- }
97
- pathArray.forEach((item) => {
98
- let handle;
99
- const dotIndex = item.indexOf(".");
100
- if (dotIndex !== -1) {
101
- const key = item.slice(0, dotIndex);
102
- const value = item.slice(dotIndex + 1);
103
- handle = this[key].watch(value, callback);
104
- } else {
105
- handle = {
106
- path: item,
107
- callback,
108
- };
109
- }
110
- handles.push(handle);
111
- this._handles.add(handle);
112
- });
113
-
114
- const watchHandle = {
115
- remove: () => {
116
- handles.forEach((handle) => {
117
- this._handles.delete(handle);
118
- });
119
- },
120
- };
121
- return watchHandle;
122
- }
123
- }
124
-
125
- export default observe(Accessor);
1
+ type WatchCallback = (
2
+ newValue: any,
3
+ oldValue: any,
4
+ propertyName: string,
5
+ target: Accessor
6
+ ) => void;
7
+
8
+ interface WatchHandle extends Object {
9
+ /**
10
+ * Removes the watch handle.
11
+ */
12
+ remove(): void;
13
+ }
14
+
15
+ interface Handle {
16
+ path: string;
17
+ callback: Function;
18
+ }
19
+
20
+ class Accessor {
21
+ declaredClass: string;
22
+ private _handles: Set<Handle>;
23
+
24
+ constructor(props: object = {}) {
25
+ // 为当前实例创建一个代理
26
+ const proxy = new Proxy(this, {
27
+ set: (target, key, value, receiver) => {
28
+ if (key !== '_handles' && target._handles) {
29
+ const oldValue = target[key];
30
+ target._handles.forEach((handle) => {
31
+ if (handle.path === key) {
32
+ handle.callback(value, oldValue, key, target);
33
+ }
34
+ });
35
+ }
36
+ return Reflect.set(target, key, value, receiver);
37
+ }
38
+ });
39
+
40
+ // 初始化属性
41
+ for (let prop in props) {
42
+ proxy[prop] = props[prop];
43
+ }
44
+ proxy.declaredClass = "Accessor";
45
+ proxy._handles = new Set();
46
+
47
+ return proxy;
48
+ }
49
+
50
+ get(path: string): any {
51
+ const dotIndex = path.indexOf(".");
52
+ if (dotIndex !== -1) {
53
+ const key = path.slice(0, dotIndex);
54
+ const value = path.slice(dotIndex + 1);
55
+ return this[key] && this[key].get(value);
56
+ }
57
+ return this[path];
58
+ }
59
+
60
+ set(path: string | object, value: any): this {
61
+ if (typeof path === "string") {
62
+ const dotIndex = path.indexOf(".");
63
+ if (dotIndex !== -1) {
64
+ const key = path.slice(0, dotIndex);
65
+ const childPath = path.slice(dotIndex + 1);
66
+ if (this[key]) {
67
+ this[key].set(childPath, value);
68
+ }
69
+ } else {
70
+ this[path] = value;
71
+ }
72
+ } else {
73
+ for (const key in path) {
74
+ this.set(key, path[key]);
75
+ }
76
+ }
77
+
78
+ return this;
79
+ }
80
+
81
+ watch(path: string | string[], callback: WatchCallback): WatchHandle {
82
+ const handles = [];
83
+ const pathArray = [];
84
+ if (typeof path === "object") {
85
+ pathArray.push(...path);
86
+ }
87
+ if (typeof path === "string") {
88
+ if (path.includes(",")) {
89
+ pathArray.push(...path.replace(" ", "").split(","));
90
+ } else {
91
+ pathArray.push(path);
92
+ }
93
+ }
94
+ pathArray.forEach((item) => {
95
+ const dotIndex = item.indexOf(".");
96
+ const handle =
97
+ dotIndex !== -1
98
+ ? this[item.slice(0, dotIndex)].watch(
99
+ item.slice(dotIndex + 1),
100
+ callback
101
+ )
102
+ : { path: item, callback };
103
+
104
+ handles.push(handle);
105
+ this._handles.add(handle);
106
+ });
107
+
108
+ const watchHandle = {
109
+ remove: () => {
110
+ handles.forEach((handle) => {
111
+ this._handles.delete(handle);
112
+ });
113
+ },
114
+ };
115
+ return watchHandle;
116
+ }
117
+ }
118
+
119
+ export default Accessor;
package/src/index.ts CHANGED
@@ -1,3 +1,3 @@
1
- import Accessor from './accessor';
1
+ import Accessor from './accessor.js';
2
2
 
3
3
  export default Accessor;
@@ -1,5 +1,14 @@
1
1
  import Accessor from "../dist/index.js";
2
2
  import assert from "assert";
3
+ class Counter extends Accessor {
4
+ constructor() {
5
+ super();
6
+ this.number = 0;
7
+ }
8
+ setNumber = (value) => {
9
+ this.number = value;
10
+ };
11
+ }
3
12
 
4
13
  describe("#watch()", function () {
5
14
  it("watch property change", function () {
@@ -119,4 +128,41 @@ describe("#watch()", function () {
119
128
  view.zoom = 5;
120
129
  assert.deepStrictEqual(result, []);
121
130
  });
131
+ /**
132
+ * 子类上的方法也可以被监听
133
+ */
134
+ it("watch subclass member", function () {
135
+ const counter = new Counter();
136
+ const result = [];
137
+ const callback = (newValue, oldValue, propertyName, target) => {
138
+ result.push(newValue, oldValue, propertyName, target);
139
+ };
140
+ counter.number = 4;
141
+ counter.watch("number", callback);
142
+ counter.setNumber(5);
143
+
144
+ assert.deepStrictEqual(result, [5, 4, "number", counter]);
145
+ });
146
+
147
+ /**
148
+ * 子类上的方法 监听变更次数
149
+ */
150
+ it("watch subclass member changed times", function () {
151
+ const counter = new Counter();
152
+ let times = 0;
153
+ const callback = () => {
154
+ times++;
155
+ };
156
+ counter.watch("number", callback);
157
+ counter.number = 1; // +1;
158
+ counter.number = 1; // +1;
159
+ counter.set("number", 2); // +1;
160
+ counter.set("number", 2); // +1;
161
+ counter.set({ number: 3 }); // +1;
162
+ counter.set({ number: 3 }); // +1;
163
+ counter.setNumber(4); // +1;
164
+ counter.setNumber(4); // +1;
165
+
166
+ assert.equal(times, 8);
167
+ });
122
168
  });
package/tsconfig.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "experimentalDecorators": true,
4
- "module": "ESNext",
4
+ "module": "Node16",
5
5
  "target": "ESNext",
6
6
  "sourceMap": false,
7
7
  "rootDir": "./src",
@@ -9,7 +9,8 @@
9
9
  "esModuleInterop": true,
10
10
  "declaration": true,
11
11
  "skipLibCheck": true,
12
- "moduleResolution": "node",
12
+ "moduleResolution": "node16",
13
+ "verbatimModuleSyntax": true
13
14
  },
14
15
  "include": [
15
16
  "src/**/*.ts",
@@ -1,2 +0,0 @@
1
- declare const _default: any;
2
- export default _default;
package/dist/accessor.js DELETED
@@ -1,103 +0,0 @@
1
- /**
2
- * observe class
3
- * @param cls class
4
- * @returns Proxy
5
- */
6
- function observe(cls) {
7
- return new Proxy(cls, {
8
- construct(target, args) {
9
- const obj = new target(...args);
10
- return new Proxy(obj, {
11
- set: (target, key, value, receiver) => {
12
- const oldValue = target[key];
13
- target._handles.forEach((handle) => {
14
- if (handle.path === key) {
15
- handle.callback(value, oldValue, key, target);
16
- }
17
- });
18
- return Reflect.set(target, key, value, receiver);
19
- },
20
- });
21
- },
22
- });
23
- }
24
- class Accessor {
25
- constructor(props) {
26
- for (let prop in props) {
27
- this[prop] = props[prop];
28
- }
29
- this.declaredClass = "Accessor";
30
- this._handles = new Set();
31
- }
32
- get(path) {
33
- const dotIndex = path.indexOf(".");
34
- if (dotIndex !== -1) {
35
- const key = path.slice(0, dotIndex);
36
- const value = path.slice(dotIndex + 1);
37
- return this[key] && this[key].get(value);
38
- }
39
- return this[path];
40
- }
41
- set(path, value) {
42
- if (typeof path === "string") {
43
- const dotIndex = path.indexOf(".");
44
- if (dotIndex !== -1) {
45
- const key = path.slice(0, dotIndex);
46
- const childPath = path.slice(dotIndex + 1);
47
- if (this[key]) {
48
- this[key].set(childPath, value);
49
- }
50
- }
51
- else {
52
- this[path] = value;
53
- }
54
- }
55
- else {
56
- for (const key in path) {
57
- this.set(key, path[key]);
58
- }
59
- }
60
- return this;
61
- }
62
- watch(path, callback) {
63
- const handles = [];
64
- const pathArray = [];
65
- if (typeof path === "object") {
66
- pathArray.push(...path);
67
- }
68
- if (typeof path === "string") {
69
- if (path.includes(",")) {
70
- pathArray.push(...path.replace(" ", "").split(","));
71
- }
72
- else {
73
- pathArray.push(path);
74
- }
75
- }
76
- pathArray.forEach((item) => {
77
- let handle;
78
- const dotIndex = item.indexOf(".");
79
- if (dotIndex !== -1) {
80
- const key = item.slice(0, dotIndex);
81
- const value = item.slice(dotIndex + 1);
82
- handle = this[key].watch(value, callback);
83
- }
84
- else {
85
- handle = {
86
- path: item,
87
- callback,
88
- };
89
- }
90
- handles.push(handle);
91
- this._handles.add(handle);
92
- });
93
- const watchHandle = {
94
- remove: () => {
95
- handles.forEach((handle) => {
96
- this._handles.delete(handle);
97
- });
98
- },
99
- };
100
- return watchHandle;
101
- }
102
- }
103
- export default observe(Accessor);
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- import Accessor from './accessor';
2
- export default Accessor;
package/dist/index.js DELETED
@@ -1,2 +0,0 @@
1
- import Accessor from './accessor.js';
2
- export default Accessor;