Skip to content

Commit 83c9dd3

Browse files
committed
v1.0.3 release
Feature improvements: Parens are now supported within $.observe() paths - e.g. "person.address().street()" - https://www.jsviews.com/#jsvmodel@chain - https://www.jsviews.com/#jsvviewmodelsapi@ismanagersample - https://www.jsviews.com/#computed@observe-computed - https://www.jsviews.com/#computed@depend-computed Improved features for managing how tags respond to onArrayChange events - https://www.jsviews.com/#tagoptions@onarraychange New support for referencing the parent object in hierarchical View Model scenarios (parentRef): - https://www.jsviews.com/#jsrmodel@parentref - https://www.jsviews.com/#viewmodelsapi@accessparent - https://www.jsviews.com/#jsvviewmodelsapi@ismanagersample New support for asynchronous or batched observable change events (asyncObserve): - https://www.jsviews.com/#delay New features available for scenarios using sorting and filtering (mapProps and mapDepends): - https://www.jsviews.com/#tagoptions@mapprops Bug fixes: - #415 (Markup within element tag - incorrect whitespace) - #417 (Side-effect when calling Tag.updateValue during setProperty call) - #419 (Regression: checkbox widget not working since v1.0.0) - #420 (Template tag responses to array changes) - Several other minor bug fixes Additional and improved documentation topics and samples... Updated tree view samples: - https://www.jsviews.com/#samples/tag-controls/tree Updated Typescript definitions (soon to be deployed DefinitelyTyped): - https://www.jsviews.com/#typescript Many additional unit tests...
1 parent 259671d commit 83c9dd3

35 files changed

+35271
-2051
lines changed

MIT-LICENSE.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2015 Boris Moore https://github.com/BorisMoore/jsviews
1+
Copyright (c) 2019 Boris Moore https://github.com/BorisMoore/jsviews
22

33
Permission is hereby granted, free of charge, to any person obtaining
44
a copy of this software and associated documentation files (the

jquery.observable.js

Lines changed: 134 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*! JsObservable v1.0.2: http://jsviews.com/#jsobservable */
1+
/*! JsObservable v1.0.3: http://jsviews.com/#jsobservable */
22
/*
33
* Subcomponent of JsViews
44
* Data change events for data-linking
@@ -44,7 +44,7 @@ if (!$ || !$.fn) {
4444
throw "JsObservable requires jQuery"; // We require jQuery
4545
}
4646

47-
var versionNumber = "v1.0.2",
47+
var versionNumber = "v1.0.3",
4848
_ocp = "_ocp", // Observable contextual parameter
4949
$observe, $observable,
5050

@@ -101,6 +101,7 @@ if (!$.observe) {
101101
observeInnerCbKey = 1,
102102
$data = $.data,
103103
remove = {}, // flag for removeProperty
104+
asyncBatch = [],
104105

105106
//========================== Top-level functions ==========================
106107

@@ -247,13 +248,12 @@ if (!$.observe) {
247248

248249
function filterAndObserveAll(obj, prop, unobs, nestedArray) {
249250
var newObject, newParentObs;
250-
if (prop !== $expando && (newObject = $observable._fltr(newAllPath, obj[prop], nextParentObs, filter))) {
251+
if ((+prop === prop || prop !== $expando) && (newObject = $observable._fltr(newAllPath, obj[prop], nextParentObs, filter))) {
251252
newParentObs = nextParentObs.slice();
252253
if (nestedArray && updatedTgt && newParentObs[0] !== updatedTgt) {
253254
newParentObs.unshift(updatedTgt); // For array change events when observing an array which is not the root, need to add updated array to parentObs
254255
}
255256
observeAll(namespace, newObject, cb, filter || (nestedArray ? undefined : 0), newParentObs, newAllPath, unobs, objMap);
256-
// If nested array, need to observe the array too - so set filter to undefined
257257
}
258258
}
259259

@@ -345,6 +345,19 @@ if (!$.observe) {
345345
$unobserve = function() {
346346
[].push.call(arguments, true); // Add true as additional final argument
347347
return $observe.apply(undefined, arguments);
348+
},
349+
350+
batchTrigger = function(async) {
351+
var event,
352+
batch = this.slice();
353+
this.length = 0;
354+
this._go = 0;
355+
while (event = batch.shift()) {
356+
if (!event.skip) {
357+
event[0]._trigger(event[1], event[2], true);
358+
}
359+
}
360+
this.paths = {};
348361
};
349362

350363
$observe = function() {
@@ -367,7 +380,7 @@ if (!$.observe) {
367380
}
368381

369382
function observeOnOff(cb, object, fullPath, namespace, pathStr, isArrayBinding, off) {
370-
var j, evData,
383+
var j, evData, dataOb,
371384
boundObOrArr = wrapArray(object),
372385
prntObs = parentObs,
373386
allPth = allPath;
@@ -422,18 +435,17 @@ if (!$.observe) {
422435
};
423436
}
424437
$(boundObOrArr).on(namespace, null, evData, onDataChange);
425-
426438
if (cbBindings) {
427439
// Add object to cbBindings
428-
cbBindings[$data(object).obId || $data(object, "obId", observeObjKey++)] = object;
440+
cbBindings[(dataOb = $data(object)).obId || (dataOb.obId = observeObjKey++)] = object;
429441
}
430442
}
431443
}
432444

433445
function bindArray(cb, arr, unbind, isArray, relPath) {
434446
if (allowArray) {
435447
// allowArray is 1 if this is a call to observe that does not come from observeAndBind (tag binding), or is from a 'depends' path,
436-
// so we allow arrayChange binding. Otherwise allowArray is zero.
448+
// or for a tag with tag.onArrayChange = true - so we allow arrayChange binding. Otherwise allowArray is zero.
437449
var object,
438450
prevAllPath = allPath;
439451

@@ -510,7 +522,7 @@ if (!$.observe) {
510522
// remove previous observeAll wrapped callback, if inner callback was the same;
511523
}
512524

513-
var arrIndex, skip, dep, obArr, prt,
525+
var arrIndex, skip, dep, obArr, prt, fnProp, isGet,
514526
obj = object;
515527
if (object && object._cxp) {
516528
return observeObjectPaths(object[0], [object[1]], callback, contextCb);
@@ -521,6 +533,10 @@ if (!$.observe) {
521533
if (prop === "") {
522534
continue;
523535
}
536+
if (prop.slice(-2) === "()") {
537+
prop = prop.slice(0, -2);
538+
isGet = true;
539+
}
524540
if ((prts.length < depth + 1) && !obj.nodeType) {
525541
// Add observer for each token in path starting at depth, and on to the leaf
526542
if (!unobserve && (events = $._data(obj).events)) {
@@ -533,6 +549,7 @@ if (!$.observe) {
533549
&& data.ns === initialNs
534550
&& data.cb._cId === callback._cId
535551
&& data.cb._inId === callback._inId
552+
&& !data._arOk === !allowArray
536553
&& (data.prop === prop || data.prop === "*" || data.prop === "**")) {
537554
if (prt = prts.join(".")) {
538555
data.paths.push(prt); // We will skip this binding, but if it is not a leaf binding,
@@ -596,7 +613,8 @@ if (!$.observe) {
596613
}
597614
}
598615
if ($isFunction(prop)) {
599-
if (dep = prop.depends) {
616+
fnProp = prop;
617+
if (dep = fnProp.depends) {
600618
// This is a computed observable. We will observe any declared dependencies.
601619
if (obj._vw && obj._ocp) {
602620
// Observable contextual parameter, so context was ocp object. Now move context to view.data for dependencies
@@ -609,7 +627,17 @@ if (!$.observe) {
609627
}
610628
observeObjects(concat.apply([], [[obj], dependsPaths(dep, obj, callback)]));
611629
}
612-
break;
630+
631+
if (isGet) {
632+
if (!prts[0]) {
633+
bindArray(callback, fnProp.call(obj), unobserve);
634+
break;
635+
}
636+
prop = fnProp.call(obj);
637+
if (!prop) {
638+
break;
639+
}
640+
}
613641
}
614642
obj = prop;
615643
}
@@ -721,6 +749,8 @@ if (!$.observe) {
721749
}
722750
}
723751

752+
//END OF FUNCTIONS
753+
724754
var ns = observeStr,
725755
paths = this != 1 // Using != for IE<10 bug- see jsviews/issues/237
726756
? concat.apply([], arguments) // Flatten the arguments - this is a 'recursive call' with params using the 'wrapped array'
@@ -729,7 +759,6 @@ if (!$.observe) {
729759
lastArg = paths.pop() || false,
730760
m = paths.length;
731761

732-
//END OF FUNCTIONS
733762
if (lastArg + "" === lastArg) { // If last arg is a string then this observe call is part of an observeAll call,
734763
allPath = lastArg; // and the last three args are the parentObs array, the filter, and the allPath string.
735764
parentObs = paths.pop();
@@ -792,9 +821,10 @@ if (!$.observe) {
792821
}
793822

794823
var initialNs,
795-
allowArray = this == 1 ? 0 : 1, // If this == 1, this is a call from observeAndBind - doing binding of datalink expressions. We don't bind
796-
// arrayChange events in this scenario. Instead, {^{for}} and similar do specific arrayChange binding to the tagCtx.args[0] value, in onAfterLink.
797-
// Note deliberately using this == 1, rather than this === 1 because of IE<10 bug- see jsviews/issues/237
824+
allowArray = this == 1 ? 0 : 1, // If this == 1, this is a call from observeAndBind (doing binding of datalink expressions),
825+
// and tag.onArrayChange is not set to true. We don't bind arrayChange events in this scenario. Instead, {^{for}} and similar
826+
// do specific arrayChange binding to the tagCtx.args[0] value, in onAfterLink.
827+
// Note deliberately using this == 1, rather than this === 1 because of IE<10 bug - see jsviews/issues/237
798828
paths = slice.call(arguments),
799829
pth = paths[0];
800830

@@ -805,14 +835,42 @@ if (!$.observe) {
805835
return innerObserve.apply(1, paths);
806836
};
807837

808-
$observable = function(ns, data) {
809-
if (arguments.length === 1) {
838+
asyncBatch.wait = function() {
839+
var batch = this;
840+
batch._go = 1;
841+
setTimeout(function() {
842+
batch.trigger(true);
843+
batch._go = 0;
844+
batch.paths = {};
845+
});
846+
};
847+
848+
$observable = function(ns, data, delay) {
849+
if (ns + "" !== ns) {
850+
delay = data;
810851
data = ns;
811852
ns = "";
812853
}
813-
return $isArray(data)
854+
delay = delay === undefined ? $subSettingsAdvanced.asyncObserve : delay;
855+
var observable = $isArray(data)
814856
? new ArrayObservable(ns, data)
815857
: new ObjectObservable(ns, data);
858+
if (delay) {
859+
if (delay === true) {
860+
observable.async = true;
861+
delay = asyncBatch;
862+
}
863+
if (!delay.trigger) {
864+
if ($isArray(delay)) {
865+
delay.trigger = batchTrigger;
866+
delay.paths = {};
867+
} else {
868+
delay = undefined;
869+
}
870+
}
871+
observable._batch = delay;
872+
}
873+
return observable;
816874
};
817875

818876
//========================== Initialize ==========================
@@ -848,10 +906,11 @@ if (!$.observe) {
848906

849907
setProperty: function(path, value, nonStrict) {
850908
path = path || "";
851-
var key, pair, parts,
909+
var key, pair, parts, tempBatch,
852910
multi = path + "" !== path, // Hash of paths
853911
self = this,
854-
object = self._data;
912+
object = self._data,
913+
batch = self._batch;
855914

856915
if (object) {
857916
if (multi) {
@@ -865,10 +924,18 @@ if (!$.observe) {
865924
self.setProperty(pair.name, pair.value, nonStrict === undefined || nonStrict); //If nonStrict not specified, default to true;
866925
}
867926
} else {
868-
// Object representation where property name is path and property value is value.
869-
for (key in path) {
927+
if (!batch) {
928+
self._batch = tempBatch = [];
929+
tempBatch.trigger = batchTrigger;
930+
tempBatch.paths = {};
931+
}
932+
for (key in path) { // Object representation where property name is path and property value is value.
870933
self.setProperty(key, path[key], nonStrict);
871934
}
935+
if (tempBatch) {
936+
self._batch.trigger();
937+
self._batch = undefined;
938+
}
872939
}
873940
} else if (path !== $expando) {
874941
// Simple single property case.
@@ -931,8 +998,24 @@ if (!$.observe) {
931998
}
932999
},
9331000

934-
_trigger: function(target, eventArgs) {
935-
$(target).triggerHandler(propertyChangeStr + (this._ns ? "." + /^\S+/.exec(this._ns)[0] : ""), eventArgs); // If white-space separated namespaces, use first one only
1001+
_trigger: function(target, eventArgs, force) {
1002+
var key, batch, previous,
1003+
self = this;
1004+
if ($.hasData(target)) {
1005+
if (!force && (batch = self._batch)) {
1006+
if (self.async && !batch._go) {
1007+
batch.wait();
1008+
}
1009+
batch.push([self, target, eventArgs]);
1010+
key = $data(target).obId + eventArgs.path;
1011+
if (previous = batch.paths[key]) {
1012+
batch[previous-1].skip = 1;
1013+
}
1014+
batch.paths[key] = batch.length;
1015+
} else {
1016+
$(target).triggerHandler(propertyChangeStr + (this._ns ? "." + /^\S+/.exec(this._ns)[0] : ""), eventArgs); // If white-space separated namespaces, use first one only
1017+
}
1018+
}
9361019
}
9371020
};
9381021

@@ -1082,17 +1165,28 @@ if (!$.observe) {
10821165
return self;
10831166
},
10841167

1085-
_trigger: function(eventArgs, oldLength) {
1086-
var self = this,
1087-
_data = self._data,
1088-
length = _data.length,
1089-
$_data = $([_data]);
1090-
if (self._srt) {
1091-
eventArgs.refresh = true; // We are sorting during refresh
1092-
} else if (length !== oldLength) { // We have finished sort operations during refresh
1093-
$_data.triggerHandler(propertyChangeStr, {change: "set", path: "length", value: length, oldValue: oldLength});
1168+
_trigger: function(eventArgs, oldLength, force) {
1169+
var length, _data, batch,
1170+
self = this;
1171+
if ($.hasData(_data = self._data)) {
1172+
if (!force && (batch = self._batch)) {
1173+
eventArgs._dly = true; // Delayed event (async or batch change)
1174+
batch.push([self, eventArgs, oldLength]);
1175+
if (self.async && !batch._go) {
1176+
batch.wait();
1177+
}
1178+
} else {
1179+
length = _data.length;
1180+
_data = $([_data]);
1181+
1182+
if (self._srt) {
1183+
eventArgs.refresh = true; // We are sorting during refresh
1184+
} else if (length !== oldLength) { // We have finished sort operations during refresh
1185+
_data.triggerHandler(propertyChangeStr, {change: "set", path: "length", value: length, oldValue: oldLength});
1186+
}
1187+
_data.triggerHandler(arrayChangeStr + (self._ns ? "." + /^\S+/.exec(self._ns)[0] : ""), eventArgs); // If white-space separated namespaces, use first one only
1188+
}
10941189
}
1095-
$_data.triggerHandler(arrayChangeStr + (self._ns ? "." + /^\S+/.exec(self._ns)[0] : ""), eventArgs); // If white-space separated namespaces, use first one only
10961190
}
10971191
};
10981192

@@ -1243,8 +1337,9 @@ if (!$.observe) {
12431337
};
12441338

12451339
$sub.advSet = function() { // refresh advanced settings
1340+
$subSettingsAdvanced = $subSettings.advanced;
12461341
$sub._gccb = this._gccb; // getContextCallback method
1247-
global._jsv = $subSettings.advanced._jsv
1342+
global._jsv = $subSettingsAdvanced._jsv
12481343
? { // create global _jsv, for accessing views, etc
12491344
cbBindings: cbBindingsStore
12501345
}
@@ -1253,6 +1348,10 @@ if (!$.observe) {
12531348
$sub._dp = dependsPaths;
12541349
$sub._gck = getCbKey;
12551350
$sub._obs = $observe;
1351+
$subSettingsAdvanced = $subSettings.advanced = $subSettingsAdvanced || {
1352+
useViews: false,
1353+
_jsv: false // For global access to JsViews store
1354+
};
12561355
}
12571356

12581357
return $;

0 commit comments

Comments
 (0)