@geodaoyu/accessor 1.0.2 → 2.0.1

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,15 +1,9 @@
1
1
  # Accessor
2
2
 
3
- Class: `Accessor`
3
+ ## Overview
4
4
 
5
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
6
 
7
- ## Installation
8
-
9
- ``` shell
10
- npm install @geodaoyu/accessor
11
- ```
12
-
13
7
  ## Property Overview
14
8
 
15
9
  | Name | Type | Summary | Class |
@@ -24,46 +18,13 @@ The name of the class.
24
18
 
25
19
  ## Method Overview
26
20
 
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 |
21
+ | Name | Return Type | Summary | Class |
22
+ | ----- | ----------- | ----------------------------- | -------- |
23
+ | set() | * | Sets the value of a property. | Accessor |
32
24
 
33
25
  ### Method Details
34
26
 
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>
27
+ <table><tr><td bgcolor=#ddd><b>set(path, value) {*}</b></td></tr></table>
67
28
 
68
29
  Sets the value of a property.
69
30
 
@@ -76,7 +37,7 @@ map.set("basemap", "topo-vector");
76
37
  map.basemap = "topo-vector";
77
38
 
78
39
  // currying set
79
- var updateViewScale = view.set.bind(view, "scale");
40
+ const updateViewScale = view.set.bind(view, "scale");
80
41
  updateViewScale(5000);
81
42
  ```
82
43
 
@@ -102,7 +63,7 @@ view.set({
102
63
  });
103
64
 
104
65
  // currying set
105
- var updateView = view.set.bind(view);
66
+ const updateView = view.set.bind(view);
106
67
 
107
68
  updateView({
108
69
  center: [-4.4861, 48.3904],
@@ -116,7 +77,7 @@ updateView({
116
77
  | ------------------------------------------------------------ | ------------------------------------------------------------ |
117
78
  | The path to the property to set, or an object of key-value pairs. | |
118
79
  | **value** | * |
119
- | *The new value to set on the property. | |
80
+ | The new value to set on the property. | |
120
81
 
121
82
  Returns:
122
83
 
@@ -124,116 +85,139 @@ Returns:
124
85
  | ---- | ------------- |
125
86
  | * | The instance. |
126
87
 
127
- <table><tr><td bgcolor=#ddd><b>watch(path, callback){WatchHandle}</b></td></tr></table>
88
+ # reactiveUtils
128
89
 
129
- Watches for property changes on the instance.
90
+ ## Overview
130
91
 
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.
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.
132
93
 
133
- ``` javascript
134
- var handle = mapview.watch("scale", function(newValue, oldValue, propertyName, target) {
135
- console.log(propertyName + " changed from " + oldValue + " to " + newValue);
136
- })
137
- ```
94
+ ## Using reactiveUtils
138
95
 
139
- To stop watching for changes, call the `remove()` method on the object that `watch()` returns.
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:
140
99
 
141
- ``` javascript
142
- handle.remove();
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
+ });
143
110
  ```
144
111
 
145
- It is important to store the resulting objects from `watch()` to properly clean up the references.
146
112
 
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();
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}`);
153
126
  });
154
- viewHandles.length = 0;
127
+ ```
155
128
 
156
- this.view = view;
157
129
 
158
- // watch for properties on the newly set view.
159
- if (view) {
160
- viewHandles.push(
161
- view.watch("scale", scaleWatcher);
162
- );
163
- }
164
- }
165
130
 
166
- setView(mapView);
167
- setView(null);
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
+ });
168
144
  ```
169
145
 
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
146
 
172
- ``` javascript
173
- var view = new SceneView({
174
- map: new Map({
175
- basemap: "streets-vector"
176
- })
177
- });
178
147
 
179
- view.watch("map.basemap.title", function(newValue, oldValue) {
180
- console.log("basemap's title changed from " + oldValue + " to " + newValue);
181
- });
148
+ ### WatchHandles and Promises
182
149
 
183
- view.map.basemap = "topo-vector";
184
- // output: "basemap's title changed from Streets to Topographic"
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.
185
151
 
186
- view.map = null;
187
- // output: "basemap's title changed from Topographic to undefined"
188
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
+ });
189
160
 
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.
161
+ // In another function
162
+ handle.remove()
163
+ ```
191
164
 
192
- ``` javascript
193
- view.watch("center, scale, rotation", function(newValue, oldValue, propertyName) {
194
- console.log(propertyName + " changed");
195
- });
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.
196
166
 
197
- // equivalent of
198
- view.watch(["center", "scale", "rotation"], function(newValue, oldValue, propertyName) {
199
- console.log(propertyName + " changed");
200
- });
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
+ });
201
178
 
202
- // equivalent of
203
- var callback = function(newValue, oldValue, propertyName) {
204
- console.log(propertyName + " changed");
179
+ // Cancel the async callback
180
+ const someFunction = () => {
181
+ abortController.abort();
205
182
  }
206
- view.watch("center", callback);
207
- view.watch("scale", callback);
208
- view.watch("rotation", callback);
209
183
  ```
210
184
 
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
185
 
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
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
+
228
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.
229
211
 
230
212
  Parameters:
231
213
 
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. | |
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. | |
237
221
 
238
222
  Returns:
239
223
 
@@ -241,27 +225,55 @@ Returns:
241
225
  | ----------- | -------------- |
242
226
  | WatchHandle | A watch handle |
243
227
 
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:
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
+ ```
251
271
 
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. | |
272
+ ## Type Definitions
261
273
 
262
274
  <table><tr><td bgcolor=#ddd><b>WatchHandle</b> <span>Object</span></td></tr></table>
263
275
 
264
- Represents a watch created when an object invokes **watch()**.
276
+ Represents a watch or event handler which can be removed.
265
277
 
266
278
  Property:
267
279
 
@@ -272,11 +284,78 @@ Property:
272
284
  Example:
273
285
 
274
286
  ``` javascript
275
- var handle = map.watch('basemap', function(newVal){
287
+ const handle = reactiveUtils.watch(() => map.basemap, (newVal) => {
276
288
  // Each time the value of map.basemap changes, it is logged in the console
277
289
  console.log("new basemap: ", newVal);
278
290
  });
279
291
 
280
292
  // When remove() is called on the watch handle, the map no longer watches for changes to basemap
281
293
  handle.remove();
282
- ```
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
+
@@ -1,2 +1,16 @@
1
- declare const _default: any;
2
- export default _default;
1
+ type WatchCallback = (newValue: any, oldValue: any, propertyName: string, target: Accessor) => void;
2
+ interface WatchHandle extends Object {
3
+ /**
4
+ * Removes the watch handle.
5
+ */
6
+ remove(): void;
7
+ }
8
+ declare class Accessor {
9
+ declaredClass: string;
10
+ private _handles;
11
+ constructor(props?: object);
12
+ get(path: string): any;
13
+ set(path: string | object, value: any): this;
14
+ watch(path: string | string[], callback: WatchCallback): WatchHandle;
15
+ }
16
+ export default Accessor;
package/dist/accessor.js CHANGED
@@ -1,103 +1,89 @@
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);
1
+ class Accessor {
2
+ declaredClass;
3
+ _handles;
4
+ constructor(props = {}) {
5
+ // 为当前实例创建一个代理
6
+ const proxy = new Proxy(this, {
7
+ set: (target, key, value, receiver) => {
8
+ if (key !== '_handles' && target._handles) {
9
+ const oldValue = target[key];
10
+ target._handles.forEach((handle) => {
11
+ if (handle.path === key) {
12
+ handle.callback(value, oldValue, key, target);
13
+ }
14
+ });
15
+ }
16
+ return Reflect.set(target, key, value, receiver);
17
+ }
18
+ });
19
+ // 初始化属性
20
+ for (let prop in props) {
21
+ proxy[prop] = props[prop];
22
+ }
23
+ proxy.declaredClass = "Accessor";
24
+ proxy._handles = new Set();
25
+ return proxy;
26
+ }
27
+ get(path) {
28
+ const dotIndex = path.indexOf(".");
29
+ if (dotIndex !== -1) {
30
+ const key = path.slice(0, dotIndex);
31
+ const value = path.slice(dotIndex + 1);
32
+ return this[key] && this[key].get(value);
33
+ }
34
+ return this[path];
35
+ }
36
+ set(path, value) {
37
+ if (typeof path === "string") {
38
+ const dotIndex = path.indexOf(".");
39
+ if (dotIndex !== -1) {
40
+ const key = path.slice(0, dotIndex);
41
+ const childPath = path.slice(dotIndex + 1);
42
+ if (this[key]) {
43
+ this[key].set(childPath, value);
44
+ }
45
+ }
46
+ else {
47
+ this[path] = value;
48
+ }
49
+ }
50
+ else {
51
+ for (const key in path) {
52
+ this.set(key, path[key]);
53
+ }
54
+ }
55
+ return this;
56
+ }
57
+ watch(path, callback) {
58
+ const handles = [];
59
+ const pathArray = [];
60
+ if (typeof path === "object") {
61
+ pathArray.push(...path);
62
+ }
63
+ if (typeof path === "string") {
64
+ if (path.includes(",")) {
65
+ pathArray.push(...path.replace(" ", "").split(","));
66
+ }
67
+ else {
68
+ pathArray.push(path);
69
+ }
70
+ }
71
+ pathArray.forEach((item) => {
72
+ const dotIndex = item.indexOf(".");
73
+ const handle = dotIndex !== -1
74
+ ? this[item.slice(0, dotIndex)].watch(item.slice(dotIndex + 1), callback)
75
+ : { path: item, callback };
76
+ handles.push(handle);
77
+ this._handles.add(handle);
78
+ });
79
+ const watchHandle = {
80
+ remove: () => {
81
+ handles.forEach((handle) => {
82
+ this._handles.delete(handle);
83
+ });
84
+ },
85
+ };
86
+ return watchHandle;
87
+ }
88
+ }
89
+ export default Accessor;
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import Accessor from './accessor';
2
- export default Accessor;
1
+ import Accessor from './accessor.js';
2
+ export default Accessor;
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import Accessor from './accessor.js';
2
- export default Accessor;
1
+ import Accessor from './accessor.js';
2
+ export default Accessor;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@geodaoyu/accessor",
3
- "version": "1.0.2",
4
- "description": "Implement esri/core/Accessor by myself.",
3
+ "version": "2.0.1",
4
+ "description": "mini @arcgis/core/core/Accessor & @arcgis/core/core/reactiveUtils",
5
5
  "main": "index.js",
6
6
  "module": "dist/index.js",
7
7
  "scripts": {
@@ -11,11 +11,18 @@
11
11
  "keywords": [
12
12
  "Accessor",
13
13
  "Esri",
14
- "ArcGIS"
14
+ "ArcGIS",
15
+ "Proxy"
15
16
  ],
16
17
  "author": "GeoDaoyu",
17
18
  "license": "MIT",
18
19
  "type": "module",
20
+ "files": [
21
+ "dist/**/*",
22
+ "src/**/*",
23
+ "README.md",
24
+ "LICENSE"
25
+ ],
19
26
  "repository": {
20
27
  "type": "git",
21
28
  "url": "https://github.com/GeoDaoyu/Accessor.git"
package/src/accessor.ts CHANGED
@@ -1,122 +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
- const dotIndex = item.indexOf(".");
99
- const handle =
100
- dotIndex !== -1
101
- ? this[item.slice(0, dotIndex)].watch(
102
- item.slice(dotIndex + 1),
103
- callback
104
- )
105
- : { path: item, callback };
106
-
107
- handles.push(handle);
108
- this._handles.add(handle);
109
- });
110
-
111
- const watchHandle = {
112
- remove: () => {
113
- handles.forEach((handle) => {
114
- this._handles.delete(handle);
115
- });
116
- },
117
- };
118
- return watchHandle;
119
- }
120
- }
121
-
122
- 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,31 +0,0 @@
1
- import Accessor from '../dist/index.js';
2
- import assert from 'assert';
3
-
4
- describe('#constructor()', function() {
5
- it('typeof accessor should be "object"', function() {
6
- const accessor = new Accessor();
7
- assert.strictEqual(typeof accessor, 'object');
8
- });
9
- /**
10
- * 可以通过 对象的形式给类 赋初值
11
- */
12
- it('accessor\'s props can be an object', function() {
13
- const view = new Accessor({
14
- zoom: 4,
15
- });
16
- assert.strictEqual(view.zoom, 4);
17
- });
18
- /**
19
- * 子类可以继承Accessor
20
- */
21
- it('subclass can extend from Accessor', function() {
22
- class View extends Accessor {
23
- constructor() {
24
- super();
25
- this.declaredClass = "View";
26
- }
27
- }
28
- const view = new View();
29
- assert.strictEqual(view instanceof Accessor, true);
30
- });
31
- });
package/test/get.test.js DELETED
@@ -1,34 +0,0 @@
1
- import Accessor from '../dist/index.js';
2
- import assert from 'assert';
3
-
4
- describe('#get()', function() {
5
- /**
6
- * 属性不存在,返回undefined
7
- */
8
- it('should return undefined when the property in the path does not exist', function() {
9
- const accessor = new Accessor();
10
- assert.strictEqual(accessor.get('map'), undefined);
11
- });
12
- /**
13
- * 设值取值
14
- */
15
- it('should return 4 when the property is set to 4', function() {
16
- const view = new Accessor();
17
- view.set('zoom', 4);
18
- assert.strictEqual(view.get('zoom'), 4);
19
- });
20
- it('deep path:should return undefined when the property in the path does not exist', function() {
21
- const view = new Accessor();
22
- assert.strictEqual(view.get('map.basemap'), undefined);
23
- });
24
- /**
25
- * 获取属性的属性
26
- */
27
- it('should return the property in the deep path', function() {
28
- const map = new Accessor();
29
- const basemap = new Accessor();
30
- map.set('basemap', basemap);
31
- basemap.set('title', 'World Topographic Map');
32
- assert.strictEqual(map.get('basemap.title'), 'World Topographic Map');
33
- });
34
- });
package/test/set.test.js DELETED
@@ -1,56 +0,0 @@
1
- import Accessor from "../dist/index.js";
2
- import assert from "assert";
3
-
4
- describe("#set()", function () {
5
- it("should return 4 when the property is set to 4", function () {
6
- const view = new Accessor();
7
- view.set("zoom", 4);
8
- assert.strictEqual(view.get("zoom"), 4);
9
- });
10
- it("should return 4 when the property is 4", function () {
11
- const view = new Accessor();
12
- view.zoom = 4;
13
- assert.strictEqual(view.zoom, 4);
14
- });
15
- /**
16
- * 设置属性的属性
17
- */
18
- it("deep path set property", function () {
19
- const map = new Accessor();
20
- const basemap = new Accessor();
21
- map.set("basemap", basemap);
22
- map.set("basemap.title", "World Topographic Map");
23
- assert.strictEqual(map.get("basemap.title"), "World Topographic Map");
24
- });
25
- /**
26
- * 设置属性的属性,当属性不存在的时候,不设置
27
- */
28
- it("deep path set property which does not exist", function () {
29
- const map = new Accessor();
30
- map.set("basemap.title", "World Topographic Map");
31
- assert.strictEqual(map.get("basemap.title"), undefined);
32
- });
33
- /**
34
- * 可以通过对象的形式批量设置属性
35
- */
36
- it("An object with key-value pairs can be passed into", function () {
37
- const view = new Accessor();
38
- view.set({
39
- center: [-4.4861, 48.3904],
40
- scale: 5000,
41
- });
42
- assert.strictEqual(view.get("scale"), 5000);
43
- });
44
- /**
45
- * 柯里化
46
- */
47
- it("An object with key-value pairs can be passed into", function () {
48
- const view = new Accessor();
49
- const updateView = view.set.bind(view);
50
- updateView({
51
- center: [-4.4861, 48.3904],
52
- scale: 5000,
53
- });
54
- assert.strictEqual(view.get("scale"), 5000);
55
- });
56
- });
@@ -1,168 +0,0 @@
1
- import Accessor from "../dist/index.js";
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
- }
12
-
13
- describe("#watch()", function () {
14
- it("watch property change", function () {
15
- const result = [];
16
- const callback = (newValue, oldValue, propertyName, target) => {
17
- result.push(newValue, oldValue, propertyName, target);
18
- };
19
- const view = new Accessor();
20
- view.zoom = 4;
21
- view.watch("zoom", callback);
22
- view.zoom = 5;
23
- assert.deepStrictEqual(result, [5, 4, "zoom", view]);
24
- });
25
- /**
26
- * 深层属性的变更的监听
27
- * 注意,此时callback的target是指向 拥有这个属性的类,而不是最外层的类
28
- */
29
- it("watch deep path property change", function () {
30
- const result = [];
31
- const callback = (newValue, oldValue, propertyName, target) => {
32
- result.push(newValue, oldValue, propertyName, target);
33
- };
34
- const view = new Accessor({
35
- map: new Accessor({
36
- basemap: new Accessor({
37
- title: "streets-vector",
38
- }),
39
- }),
40
- });
41
- view.watch("map.basemap.title", callback);
42
- view.map.basemap.title = "topo-vector";
43
- assert.deepStrictEqual(result, [
44
- "topo-vector",
45
- "streets-vector",
46
- "title",
47
- view.map.basemap,
48
- ]);
49
- });
50
- /**
51
- * 一个callback监听多个属性, 字符串形式
52
- */
53
- it("watch multiple propertys change in string", function () {
54
- const result = [];
55
- const callback = (newValue, oldValue, propertyName, target) => {
56
- result.push(newValue, oldValue, propertyName, target);
57
- };
58
- const view = new Accessor({
59
- zoom: 12,
60
- scale: 144447.638572,
61
- });
62
- view.watch("zoom, scale", callback);
63
- view.zoom = 11;
64
- view.scale = 288895.277144;
65
- assert.deepStrictEqual(result, [
66
- 11,
67
- 12,
68
- "zoom",
69
- view,
70
- 288895.277144,
71
- 144447.638572,
72
- "scale",
73
- view,
74
- ]);
75
- });
76
- /**
77
- * 一个callback监听多个属性, 数组形式
78
- */
79
- it("watch multiple propertys change in string array", function () {
80
- const result = [];
81
- const callback = (newValue, oldValue, propertyName, target) => {
82
- result.push(newValue, oldValue, propertyName, target);
83
- };
84
- const view = new Accessor({
85
- zoom: 12,
86
- scale: 144447.638572,
87
- });
88
- view.watch(["zoom", "scale"], callback);
89
- view.zoom = 11;
90
- view.scale = 288895.277144;
91
- assert.deepStrictEqual(result, [
92
- 11,
93
- 12,
94
- "zoom",
95
- view,
96
- 288895.277144,
97
- 144447.638572,
98
- "scale",
99
- view,
100
- ]);
101
- });
102
- /**
103
- * 调用remove方法移除handle,callback将不执行
104
- */
105
- it("remove watch handle", function () {
106
- const result = [];
107
- const callback = (newValue, oldValue, propertyName, target) => {
108
- result.push(newValue, oldValue, propertyName, target);
109
- };
110
- const view = new Accessor();
111
- view.zoom = 4;
112
- const handle = view.watch("zoom", callback);
113
- handle.remove();
114
- view.zoom = 5;
115
- assert.deepStrictEqual(result, []);
116
- });
117
- /**
118
- * 只在注册的属性变更后执行callback
119
- */
120
- it("only watch registered property", function () {
121
- const result = [];
122
- const callback = (newValue, oldValue, propertyName, target) => {
123
- result.push(newValue, oldValue, propertyName, target);
124
- };
125
- const view = new Accessor();
126
- view.zoom = 4;
127
- view.watch("scale", callback);
128
- view.zoom = 5;
129
- assert.deepStrictEqual(result, []);
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
- });
168
- });
package/tsconfig.json DELETED
@@ -1,18 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "experimentalDecorators": true,
4
- "module": "ESNext",
5
- "target": "ESNext",
6
- "sourceMap": false,
7
- "rootDir": "./src",
8
- "outDir": "./dist",
9
- "esModuleInterop": true,
10
- "declaration": true,
11
- "skipLibCheck": true,
12
- "moduleResolution": "node",
13
- },
14
- "include": [
15
- "src/**/*.ts",
16
- ],
17
- "exclude": []
18
- }