@lumjs/core 1.33.0 → 1.35.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/lib/observable.js CHANGED
@@ -1,15 +1,36 @@
1
- // Defining these after observable.
2
- const {B,F,S,def,isObj,isComplex,TYPES} = require('./types');
3
- const {duplicateAll: clone} = require('./obj/copyall');
4
- const lock = Object.freeze;
1
+ /**
2
+ * Observable API module
3
+ * @module @lumjs/core/observable
4
+ */
5
+ "use strict";
6
+
7
+ const {B,S,def} = require('./types');
8
+ const orig = require('./old/observable');
9
+ const evob = require('./events/observable');
5
10
 
6
11
  /**
7
- * Make a target object (or function) support the *Observable* API.
8
- *
9
- * Adds `on()`, `off()`, `one()`, and `trigger()` methods.
12
+ * Make a target object (or function) support the observable API.
13
+ *
14
+ * There are currently two implementations of the observable API,
15
+ * and this function will determine which one to call based on the
16
+ * arguments passed (if old options or positional arguments are
17
+ * detected, the original implementation will be called, otherwise
18
+ * the new events-based implementation will be called.)
10
19
  *
11
- * @param {(object|function)} el - The target we are making observable.
20
+ * When the older implementation is removed, this function will be
21
+ * replaced by a simple alias to `makeObservable`.
22
+ *
23
+ * NOTE: this function is the *actual* exported value of the `observable`
24
+ * module, but it's also available as `observable.auto` and that is how
25
+ * it's being documented (as jsdoc doesn't like functions being treated
26
+ * like objects with methods defined on them, but I do that a lot.)
27
+ *
28
+ * @param {(object|function)} el - The target to add observable API to.
12
29
  * @param {object} [opts] Options that define behaviours.
30
+ *
31
+ * If the `addre` and `reinherit` options are detected, then the original
32
+ * implementation will be used.
33
+ *
13
34
  * @param {string} [opts.wildcard='*'] The event name used as a wildcard.
14
35
  *
15
36
  * @param {boolean} [opts.wrapargs=false] If `true`, the event handlers will
@@ -76,335 +97,57 @@ const lock = Object.freeze;
76
97
  * to the `el` object, which is a version of `observable()` with the
77
98
  * default options being the same as the current `opts`.
78
99
  *
79
- * @param {string} [opts.addre] If set, add a method with this name
80
- * to the `el` object, which is a function that can re-build the
81
- * observable API methods with new `opts` replacing the old ones.
82
- *
83
- * The method added takes two arguments, the first being an `object`
84
- * representing the new options to set on the target.
100
+ * @param {(boolean|object)} [arg3] Version dependent argument.
85
101
  *
86
- * The second is an optional `boolean` value that determines if the
87
- * existing `opts` should be used as defaults for any options not
88
- * specified in the first argument.
102
+ * If this is a `boolean` then the original observable implementation
103
+ * will be called and this will be the `redefine` argument.
89
104
  *
90
- * @param {boolean} [opts.reinherit=false] Used as the default value of
91
- * the second argument of the method added by `opts.addre`.
105
+ * Otherwise it's assumed to be the `ro` argument of the `makeObservable`
106
+ * compatibility implementation.
92
107
  *
93
- * @param {boolean} [redefine=false] If `true` allow targets already
94
- * implementing the `on()` and `trigger()` methods to be re-initialized.
95
- *
96
- * Generally only needed if you need to change the `opts` for some reason.
97
- * This is forced to `true` by the method added by `opts.addre`.
98
- *
99
108
  * @returns {object} el
100
109
  *
101
- * @exports module:@lumjs/core/observable
110
+ * @see module:@lumjs/core/observable.orig
111
+ * @see module:@lumjs/core/events/observable.makeObservable
112
+ * @alias module:@lumjs/core/observable.auto
102
113
  */
103
- function observable (el={}, opts={}, redefine=false)
114
+ function observable (el={}, opts={}, arg3)
104
115
  {
105
- //console.debug("observable", el, opts);
106
-
107
- if (!isComplex(el))
108
- { // Don't know how to handle this, sorry.
109
- throw new Error("non-object sent to observable()");
110
- }
111
-
112
- if (isObservable(el) && !redefine)
113
- { // It's already observable.
114
- return el;
115
- }
116
-
117
- if (typeof opts === B)
118
- { // Assume it's the wrapthis option.
119
- opts = {wrapthis: opts};
120
- }
121
- else if (!isObj(opts))
122
- {
123
- opts = {};
124
- }
125
-
126
- const noSpace = /^\S+$/;
127
-
128
- const wildcard = (typeof opts.wildcard === S
129
- && noSpace.test(opts.wildcard))
130
- ? opts.wildcard
131
- : '*';
132
-
133
- const wrapsetup = (typeof opts.wrapsetup === F)
134
- ? opts.wrapsetup
135
- : null;
136
-
137
- const wrapthis = (typeof opts.wrapthis === B)
138
- ? opts.wrapthis
139
- : false;
140
-
141
- const wrapargs = (typeof opts.wrapargs === B)
142
- ? opts.wrapargs
143
- : (wrapsetup ? !wrapthis : false);
144
-
145
- const wrapped = (wrapthis || wrapargs);
146
-
147
- const wraplock = (typeof opts.wraplock === B)
148
- ? opts.wraplock
149
- : true;
150
-
151
- const addname = (typeof opts.addname === B)
152
- ? opts.addname
153
- : !wrapped;
154
-
155
- const addis = (typeof opts.addis === B)
156
- ? opts.addis
157
- : wrapped;
158
-
159
- const validIdent = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/;
160
-
161
- const addme = (typeof opts.addme === S
162
- && validIdent.test(opts.addme))
163
- ? opts.addme
164
- : null;
165
-
166
- const addre = (typeof opts.addre === S
167
- && validIdent.test(opts.addre))
168
- ? opts.addre
169
- : null;
170
-
171
- const slice = Array.prototype.slice;
172
-
173
- function onEachEvent (e, fn)
116
+ if (typeof arg3 === B
117
+ || typeof opts.addre === S
118
+ || typeof opts.reinherit === B)
174
119
  {
175
- const es = e.split(/\s+/);
176
- const me = es.length > 1;
177
- for (e of es)
178
- {
179
- fn(e, me);
180
- }
181
- }
182
-
183
- const add = def(el);
184
-
185
- function runCallback (name, fn, args)
186
- {
187
- if (fn.busy) return;
188
- fn.busy = 1;
189
-
190
- let fobj;
191
-
192
- if (wrapped)
193
- { // Something is going to use our wrapper object.
194
- const isWild = (name === wildcard);
195
- const fname = isWild ? (addname ? args[0] : args.shift()) : name;
196
-
197
- fobj =
198
- {
199
- isObservable: lock({event: true, target: false}),
200
- self: el,
201
- target: el,
202
- name: fname,
203
- type: fname,
204
- func: fn,
205
- wildcard: isWild,
206
- args,
207
- };
208
-
209
- if (wrapsetup)
210
- {
211
- wrapsetup.call(el, fobj);
212
- }
213
-
214
- if (wraplock)
215
- {
216
- lock(fobj);
217
- }
218
- }
219
-
220
- const fthis = wrapthis ? fobj : el;
221
- const fargs = wrapargs ? [fobj]
222
- : ((fn.typed && addname) ? [name].concat(args) : args);
223
-
224
- fn.apply(fthis, fargs);
225
- fn.busy = 0;
120
+ return orig(el, opts, arg3);
226
121
  }
227
-
228
- let callbacks = {};
229
-
230
- /**
231
- * Assign an event handler
232
- *
233
- * Listen to the given space separated list of `events` and execute
234
- * the `callback` each time an event is triggered.
235
- * @param {string} events - events ids
236
- * @param {function} fn - callback function
237
- * @returns {object} el
238
- */
239
- add('on', function(events, fn)
240
- {
241
- if (typeof fn !== F)
242
- {
243
- console.error("non-function passed to on()");
244
- return el;
245
- }
246
-
247
- onEachEvent(events, function(name, typed)
248
- {
249
- (callbacks[name] = callbacks[name] || []).push(fn);
250
- fn.typed = typed;
251
- });
252
-
253
- return el;
254
- });
255
-
256
- /**
257
- * Removes the given space separated list of `events` listeners
258
- *
259
- * @param {string} events - events ids
260
- * @param {function} fn - callback function
261
- * @returns {object} el
262
- */
263
- add('off', function(events, fn)
264
- {
265
- if (events === wildcard && !fn)
266
- { // Clear all callbacks.
267
- callbacks = {};
268
- }
269
- else
270
- {
271
- onEachEvent(events, function(name)
272
- {
273
- if (fn)
274
- { // Find a specific callback to remove.
275
- var arr = callbacks[name]
276
- for (var i = 0, cb; cb = arr && arr[i]; ++i)
277
- {
278
- if (cb == fn) arr.splice(i--, 1);
279
- }
280
- }
281
- else
282
- { // Remove all callbacks for this event.
283
- delete callbacks[name];
284
- }
285
- });
286
- }
287
- return el
288
- });
289
-
290
- /**
291
- * Add a one-shot event handler.
292
- *
293
- * Listen to the given space separated list of `events` and execute
294
- * the `callback` at most once.
295
- *
296
- * @param {string} events - events ids
297
- * @param {function} fn - callback function
298
- * @returns {object} el
299
- */
300
- add('one', function(events, fn)
301
- {
302
- function on()
303
- {
304
- el.off(events, on)
305
- fn.apply(el, arguments)
306
- }
307
- return el.on(events, on);
308
- });
309
-
310
- /**
311
- * Execute all callback functions that listen to the given space
312
- * separated list of `events`
313
- * @param {string} events - events ids
314
- * @returns {object} el
315
- */
316
- add('trigger', function(events)
317
- {
318
- // getting the arguments
319
- // skipping the first one
320
- const args = slice.call(arguments, 1);
321
-
322
- onEachEvent(events, function(name)
323
- {
324
- const fns = slice.call(callbacks[name] || [], 0);
325
-
326
- for (var i = 0, fn; fn = fns[i]; ++i)
327
- {
328
- runCallback(name, fn, args);
329
- if (fns[i] !== fn) { i-- }
330
- }
331
-
332
- if (callbacks[wildcard] && name != wildcard)
333
- { // Trigger the wildcard.
334
- el.trigger.apply(el, ['*', name].concat(args));
335
- }
336
-
337
- });
338
-
339
- return el
340
- });
341
-
342
- if (addis)
122
+ else
343
123
  {
344
- add('isObservable', lock({event: false, target: true}));
345
- }
346
-
347
- if (addme)
348
- { // Add a wrapper for observable() that sets new default options.
349
- add(addme, function (obj=null, mopts={})
350
- {
351
- return observable(obj, clone(opts, mopts));
352
- });
353
- }
354
-
355
- if (addre)
356
- { // Add a method to change the observable options.
357
- const reinherit = opts.reinherit ?? false;
358
-
359
- add(addre, function(replacementOpts={}, inherit=reinherit)
360
- {
361
- const newOpts
362
- = inherit
363
- ? Object.assign({}, opts, replacementOpts)
364
- : replacementOpts;
365
- return observable(el, replacementOpts, true);
366
- });
124
+ return evob.makeObservable(el, opts, arg3);
367
125
  }
368
-
369
- // Metadata
370
- add('$$observable$$', lock({opts, observable}));
371
-
372
- return el
373
-
374
- } // observable()
126
+ }
375
127
 
376
128
  module.exports = observable;
377
129
 
378
- /**
379
- * See if a value implements the *Observable* interface.
380
- *
381
- * @function module:@lumjs/core/observable.is
382
- * @param {*} obj - The expected object/function to test.
383
- * @returns {boolean}
384
- */
385
- function isObservable(obj)
386
- {
387
- return (isComplex(obj)
388
- && typeof obj.trigger === F
389
- && typeof obj.on === F);
390
- }
391
-
392
- // Add an 'is()' method to `observable` itself.
393
- def(observable, 'is', isObservable);
130
+ def(observable)
131
+ ('is', evob.isObservable)
132
+ ('auto', observable)
133
+ ('orig', orig)
134
+ ('wrap', evob.makeObservable)
394
135
 
395
136
  /**
396
- * Does a value implement the Observable interface?
397
- * @name module:@lumjs/core/types.doesObservable
398
- * @function
399
- * @param {*} v - The expected object/function to test.
137
+ * Does a value implement the observable interface?
138
+ * @function module:@lumjs/core/observable.is
139
+ * @param {*} v - Value to test
400
140
  * @returns {boolean}
401
- * @see module:@lumjs/core/observable.is
141
+ * @see module:@lumjs/core/events/observable.isObservable
402
142
  */
403
143
 
404
144
  /**
405
- * Extension type for the {@link module:@lumjs/core/observable} interface.
406
- * @memberof module:@lumjs/core/types.TYPES
407
- * @member {string} OBSERV - Implements the *Observable* interface.
145
+ * An alias to
146
+ * {@link module:@lumjs/core/events/observable.makeObservable makeObservable()}
147
+ *
148
+ * @function module:@lumjs/core/observable.wrap
149
+ * @param {(object|function)} el
150
+ * @param {object} oo
151
+ * @param {object} ro
152
+ * @returns {object}
408
153
  */
409
-
410
- TYPES.add('OBSERV', 'observable', isObservable, 'doesObservable');
@@ -0,0 +1,291 @@
1
+ // Defining these after observable.
2
+ const {B,F,S,def,isObj,isComplex} = require('../types');
3
+ const {isObservable} = require('../events/observable');
4
+ const lock = Object.freeze;
5
+ const copy = Object.assign;
6
+
7
+ /**
8
+ * The original implementation of the observable API.
9
+ *
10
+ * This older implementation will be removed entirely when `v2.0` is
11
+ * released, and possibly sooner if I don't find anything broken with
12
+ * the compatibility wrapper. For now it's able to be loaded on demand.
13
+ *
14
+ * @param {(object|function)} el - The target to add observable API to.
15
+ *
16
+ * @param {object} [opts]
17
+ * {@link module:@lumjs/core/observable.observable Observable} options.
18
+ *
19
+ * Includes a few additional options no longer supported by the
20
+ * new events-based implementation. Only the options exclusive to this
21
+ * version will be documented here.
22
+ *
23
+ * @param {string} [opts.addre] If set, add a method with this name
24
+ * to the `el` object, which is a function that can re-build the
25
+ * observable API methods with new `opts` replacing the old ones.
26
+ *
27
+ * The method added takes two arguments, the first being an `object`
28
+ * representing the new options to set on the target.
29
+ *
30
+ * The second is an optional `boolean` value that determines if the
31
+ * existing `opts` should be used as defaults for any options not
32
+ * specified in the first argument.
33
+ *
34
+ * @param {boolean} [opts.reinherit=false] Used as the default value of
35
+ * the second argument of the method added by `opts.addre`.
36
+ *
37
+ * @param {boolean} [redefine=false] If `true` allow targets already
38
+ * implementing the `on()` and `trigger()` methods to be re-initialized.
39
+ *
40
+ * Generally only needed if you need to change the `opts` for some reason.
41
+ * This is forced to `true` by the method added by `opts.addre`.
42
+ *
43
+ * @returns {object} el
44
+ * @see module:@lumjs/core/observable~API
45
+ * @alias module:@lumjs/core/observable.orig
46
+ */
47
+ function observable (el={}, opts={}, redefine=false)
48
+ {
49
+ //console.debug("observable", el, opts);
50
+
51
+ if (!isComplex(el))
52
+ { // Don't know how to handle this, sorry.
53
+ throw new Error("non-object sent to observable()");
54
+ }
55
+
56
+ if (isObservable(el) && !redefine)
57
+ { // It's already observable.
58
+ return el;
59
+ }
60
+
61
+ if (typeof opts === B)
62
+ { // Assume it's the wrapthis option.
63
+ opts = {wrapthis: opts};
64
+ }
65
+ else if (!isObj(opts))
66
+ {
67
+ opts = {};
68
+ }
69
+
70
+ const noSpace = /^\S+$/;
71
+
72
+ const wildcard = (typeof opts.wildcard === S
73
+ && noSpace.test(opts.wildcard))
74
+ ? opts.wildcard
75
+ : '*';
76
+
77
+ const wrapsetup = (typeof opts.wrapsetup === F)
78
+ ? opts.wrapsetup
79
+ : null;
80
+
81
+ const wrapthis = (typeof opts.wrapthis === B)
82
+ ? opts.wrapthis
83
+ : false;
84
+
85
+ const wrapargs = (typeof opts.wrapargs === B)
86
+ ? opts.wrapargs
87
+ : (wrapsetup ? !wrapthis : false);
88
+
89
+ const wrapped = (wrapthis || wrapargs);
90
+
91
+ const wraplock = (typeof opts.wraplock === B)
92
+ ? opts.wraplock
93
+ : true;
94
+
95
+ const addname = (typeof opts.addname === B)
96
+ ? opts.addname
97
+ : !wrapped;
98
+
99
+ const addis = (typeof opts.addis === B)
100
+ ? opts.addis
101
+ : wrapped;
102
+
103
+ const validIdent = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/;
104
+
105
+ const addme = (typeof opts.addme === S
106
+ && validIdent.test(opts.addme))
107
+ ? opts.addme
108
+ : null;
109
+
110
+ const addre = (typeof opts.addre === S
111
+ && validIdent.test(opts.addre))
112
+ ? opts.addre
113
+ : null;
114
+
115
+ const slice = Array.prototype.slice;
116
+
117
+ function onEachEvent (e, fn)
118
+ {
119
+ const es = e.split(/\s+/);
120
+ const me = es.length > 1;
121
+ for (e of es)
122
+ {
123
+ fn(e, me);
124
+ }
125
+ }
126
+
127
+ const add = def(el);
128
+
129
+ function runCallback (name, fn, args)
130
+ {
131
+ if (fn.busy) return;
132
+ fn.busy = 1;
133
+
134
+ let fobj;
135
+
136
+ if (wrapped)
137
+ { // Something is going to use our wrapper object.
138
+ const isWild = (name === wildcard);
139
+ const fname = isWild ? (addname ? args[0] : args.shift()) : name;
140
+
141
+ fobj =
142
+ {
143
+ isObservable: lock({event: true, target: false}),
144
+ self: el,
145
+ target: el,
146
+ name: fname,
147
+ type: fname,
148
+ func: fn,
149
+ wildcard: isWild,
150
+ args,
151
+ };
152
+
153
+ if (wrapsetup)
154
+ {
155
+ wrapsetup.call(el, fobj);
156
+ }
157
+
158
+ if (wraplock)
159
+ {
160
+ lock(fobj);
161
+ }
162
+ }
163
+
164
+ const fthis = wrapthis ? fobj : el;
165
+ const fargs = wrapargs ? [fobj]
166
+ : ((fn.typed && addname) ? [name].concat(args) : args);
167
+
168
+ fn.apply(fthis, fargs);
169
+ fn.busy = 0;
170
+ }
171
+
172
+ let callbacks = {};
173
+
174
+ add('on', function(events, fn)
175
+ {
176
+ if (typeof fn !== F)
177
+ {
178
+ console.error("non-function passed to on()");
179
+ return el;
180
+ }
181
+
182
+ onEachEvent(events, function(name, typed)
183
+ {
184
+ (callbacks[name] = callbacks[name] || []).push(fn);
185
+ fn.typed = typed;
186
+ });
187
+
188
+ return el;
189
+ });
190
+
191
+ add('off', function(events, fn)
192
+ {
193
+ if (events === wildcard && !fn)
194
+ { // Clear all callbacks.
195
+ callbacks = {};
196
+ }
197
+ else
198
+ {
199
+ onEachEvent(events, function(name)
200
+ {
201
+ if (fn)
202
+ { // Find a specific callback to remove.
203
+ var arr = callbacks[name]
204
+ for (var i = 0, cb; cb = arr && arr[i]; ++i)
205
+ {
206
+ if (cb == fn) arr.splice(i--, 1);
207
+ }
208
+ }
209
+ else
210
+ { // Remove all callbacks for this event.
211
+ delete callbacks[name];
212
+ }
213
+ });
214
+ }
215
+ return el
216
+ });
217
+
218
+ add('one', function(events, fn)
219
+ {
220
+ function on()
221
+ {
222
+ el.off(events, on)
223
+ fn.apply(el, arguments)
224
+ }
225
+ return el.on(events, on);
226
+ });
227
+
228
+ add('trigger', function(events)
229
+ {
230
+ // getting the arguments
231
+ // skipping the first one
232
+ const args = slice.call(arguments, 1);
233
+
234
+ onEachEvent(events, function(name)
235
+ {
236
+ const fns = slice.call(callbacks[name] || [], 0);
237
+
238
+ for (var i = 0, fn; fn = fns[i]; ++i)
239
+ {
240
+ runCallback(name, fn, args);
241
+ if (fns[i] !== fn) { i-- }
242
+ }
243
+
244
+ if (callbacks[wildcard] && name != wildcard)
245
+ { // Trigger the wildcard.
246
+ el.trigger.apply(el, ['*', name].concat(args));
247
+ }
248
+
249
+ });
250
+
251
+ return el
252
+ });
253
+
254
+ if (addis)
255
+ {
256
+ add('isObservable', lock({event: false, target: true}));
257
+ }
258
+
259
+ if (addme)
260
+ { // Add a wrapper for observable() that sets new default options.
261
+ add(addme, function (obj=null, mopts)
262
+ {
263
+ return observable(obj, copy({}, opts, mopts));
264
+ });
265
+ }
266
+
267
+ if (addre)
268
+ { // Add a method to change the observable options.
269
+ const reinherit = opts.reinherit ?? false;
270
+
271
+ add(addre, function(replacementOpts={}, inherit=reinherit)
272
+ {
273
+ const newOpts
274
+ = inherit
275
+ ? Object.assign({}, opts, replacementOpts)
276
+ : replacementOpts;
277
+ return observable(el, replacementOpts, true);
278
+ });
279
+ }
280
+
281
+ // Metadata
282
+ add('$$observable$$', lock({opts, observable}));
283
+
284
+ return el
285
+
286
+ } // observable()
287
+
288
+ // Add an 'is()' method to `observable` itself.
289
+ def(observable, 'is', isObservable);
290
+
291
+ module.exports = observable;