Skip to content

Commit 259671d

Browse files
committed
v1.0.2 release
Feature improvement: - {^{for}} and {^{props}} tags incremental rendering improvements - New mapProps property for custom tags, like boundProps, specifying args or named props for which observable changes trigger a refresh, but here it is an incremental array refresh, not a full tag refresh. - New mapDepends property similar to the depends property, specifying dependent paths. But here, observable changes on the specified paths trigger incremental array refresh, not full tag refresh. - Sort and Filter handlers both now have tagCtx as this pointer Bug fixes: - #414 updateValue not working correctly for 2-way bound tag properties with a path expression containing index [] operator - #412 jqueryui accordion create event not triggered - Several other minor bug fixes Additional and improved documentation topics and samples... - See for example: https://www.jsviews.com/#samples/sort-filter Additional unit tests...
1 parent a4ad093 commit 259671d

32 files changed

+2444
-1324
lines changed

jquery.observable.js

Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
/*! JsObservable v1.0.1: http://jsviews.com/#jsobservable */
1+
/*! JsObservable v1.0.2: http://jsviews.com/#jsobservable */
22
/*
33
* Subcomponent of JsViews
44
* Data change events for data-linking
55
*
6-
* Copyright 2018, Boris Moore
6+
* Copyright 2019, Boris Moore
77
* Released under the MIT License.
88
*/
99

@@ -44,7 +44,7 @@ if (!$ || !$.fn) {
4444
throw "JsObservable requires jQuery"; // We require jQuery
4545
}
4646

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

@@ -1058,11 +1058,7 @@ if (!$.observe) {
10581058
if ((newItem = newItems[j]) === data[j-k]) {
10591059
insertAdded();
10601060
} else {
1061-
for (i=j-k; i<dataLength; i++) {
1062-
if (newItem === data[i]) {
1063-
break;
1064-
}
1065-
}
1061+
for (i=j-k; i<dataLength && newItem !== data[i]; i++) {}
10661062
if (i<dataLength) {
10671063
insertAdded();
10681064
num = 0;
@@ -1143,32 +1139,35 @@ if (!$.observe) {
11431139
}
11441140
if (typeof source === OBJECT || $isFunction(source)) {
11451141
map.src = source;
1146-
if (oldMapOrTarget) {
1147-
map.tgt = oldMapOrTarget.tgt || oldMapOrTarget; // Can provide an existing map, or a target array to be used on new map
1142+
if (unbound) {
1143+
map.tgt = mapDef.getTgt(source, options);
11481144
} else {
1149-
map.tgt = map.tgt || [];
1150-
}
1151-
map.options = options || map.options;
1152-
if (updatedMap = map.update()) {
1153-
map = updatedMap; // If updating returns another map, then we can replace this one (so no need to bind it)
1154-
} else if (!unbound) {
1155-
if (mapDef.obsSrc) {
1156-
$observable(map.src).observeAll(map.obs = function(ev, eventArgs) {
1157-
if (!changing && !eventArgs.refresh) {
1158-
changing = true;
1159-
mapDef.obsSrc(map, ev, eventArgs);
1160-
changing = undefined;
1161-
}
1162-
}, map.srcFlt);
1145+
if (oldMapOrTarget) {
1146+
map.tgt = oldMapOrTarget.tgt || $isArray(oldMapOrTarget) && oldMapOrTarget; // Can provide an existing map, or a target array to be used on new map
11631147
}
1164-
if (mapDef.obsTgt) {
1165-
$observable(map.tgt).observeAll(map.obt = function(ev, eventArgs) {
1166-
if (!changing && !map.tgt._updt) {
1167-
changing = true;
1168-
mapDef.obsTgt(map, ev, eventArgs);
1169-
changing = undefined;
1170-
}
1171-
}, map.tgtFlt);
1148+
map.tgt = map.tgt || [];
1149+
map.options = options || map.options;
1150+
if (updatedMap = map.update()) {
1151+
map = updatedMap; // If updating returns another map, then we can replace this one (so no need to bind it)
1152+
} else {
1153+
if (mapDef.obsSrc) {
1154+
$observable(map.src).observeAll(map.obs = function(ev, eventArgs) {
1155+
if (!changing && !eventArgs.refresh) {
1156+
changing = true;
1157+
mapDef.obsSrc(map, ev, eventArgs);
1158+
changing = undefined;
1159+
}
1160+
}, map.srcFlt);
1161+
}
1162+
if (mapDef.obsTgt) {
1163+
$observable(map.tgt).observeAll(map.obt = function(ev, eventArgs) {
1164+
if (!changing && !map.tgt._updt) {
1165+
changing = true;
1166+
mapDef.obsTgt(map, ev, eventArgs);
1167+
changing = undefined;
1168+
}
1169+
}, map.tgtFlt);
1170+
}
11721171
}
11731172
}
11741173
}
@@ -1207,6 +1206,22 @@ if (!$.observe) {
12071206
}
12081207
}
12091208
},
1209+
observe: function(deps, linkCtx) { // Listen to observable changes of mapProps, and call map.update when change happens
1210+
var map = this,
1211+
options = map.options;
1212+
if (map.obmp) {
1213+
// There is a previous handler observing the mapProps
1214+
$unobserve(map.obmp);
1215+
}
1216+
map.obmp = function() {
1217+
// Observe changes in the mapProps ("filter", "sort", "reverse", "start", "end")
1218+
var newTagCtx = linkCtx.fn(linkCtx.data, linkCtx.view, $sub)[options.index]; // Updated tagCtx props and args
1219+
$.extend(options.props, newTagCtx.props); // Update props to new values
1220+
options.args = newTagCtx.args; // Update args to new values
1221+
map.update(); // Update the map target array, based on new mapProp values
1222+
};
1223+
$observable._apply(1, linkCtx.data, dependsPaths(deps, linkCtx.tag, map.obmp), map.obmp, linkCtx._ctxCb);
1224+
},
12101225
unmap: function() {
12111226
var map = this;
12121227
if (map.src && map.obs) {
@@ -1215,6 +1230,9 @@ if (!$.observe) {
12151230
if (map.tgt && map.obt) {
12161231
$observable(map.tgt).unobserveAll(map.obt, map.tgtFlt);
12171232
}
1233+
if (map.obmp) {
1234+
$unobserve(map.obmp);
1235+
}
12181236
map.src = undefined;
12191237
},
12201238
map: Map,

jquery.observable.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jquery.observable.min.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jquery.views.js

Lines changed: 56 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*! jquery.views.js v1.0.1: http://jsviews.com/ */
1+
/*! jquery.views.js v1.0.2: http://jsviews.com/ */
22
/*
33
* Interactive data-driven views using JsRender templates.
44
* Subcomponent of JsViews
@@ -7,7 +7,7 @@
77
* Also requires jquery.observable.js
88
* See JsObservable at http://jsviews.com/#download and http://github.com/BorisMoore/jsviews
99
*
10-
* Copyright 2018, Boris Moore
10+
* Copyright 2019, Boris Moore
1111
* Released under the MIT License.
1212
*/
1313

@@ -44,7 +44,7 @@ var setGlobals = $ === false; // Only set globals if script block in browser (no
4444
jsr = jsr || setGlobals && global.jsrender;
4545
$ = $ || global.jQuery;
4646

47-
var versionNumber = "v1.0.1",
47+
var versionNumber = "v1.0.2",
4848
requiresStr = "JsViews requires ";
4949

5050
if (!$ || !$.fn) {
@@ -167,7 +167,7 @@ function updateValues(sourceValues, tagElse, bindId, ev) {
167167
// Called when linkedElem of a tag control changes: as updateValue(val, index, tagElse, bindId, ev) - this: undefined
168168
// Called directly as tag.updateValues(val1, val2, val3, ...) - this: tag
169169
var linkCtx, cvtBack, cnvtName, target, view, binding, sourceValue, origVals, sourceElem, sourceEl,
170-
oldLinkCtx, tos, to, tcpTag, exprOb, contextCb, l, m, tag;
170+
tos, to, tcpTag, exprOb, contextCb, l, m, tag;
171171

172172
if (bindId && bindId._tgId) {
173173
tag = bindId;
@@ -223,9 +223,6 @@ function updateValues(sourceValues, tagElse, bindId, ev) {
223223
// the first arg, but all of them by returning an array.
224224
}
225225

226-
// Set linkCtx on view, dynamically, just during this handler call
227-
oldLinkCtx = view._lc;
228-
view._lc = linkCtx;
229226
l = tos.length;
230227
while (l--) {
231228
if (to = tos[l]) {
@@ -273,7 +270,6 @@ function updateValues(sourceValues, tagElse, bindId, ev) {
273270
}
274271
}
275272
}
276-
view._lc = oldLinkCtx;
277273
}
278274
if (tag) {
279275
tag._.chg = undefined; // Clear marker
@@ -319,12 +315,12 @@ function onDataLinkedTagChange(ev, eventArgs) {
319315
oldLinkCtx = view._lc,
320316
onEvent = eventArgs && changeHandler(view, onBeforeChangeStr, tag);
321317

322-
// Set linkCtx on view, dynamically, just during this handler call
323-
view._lc = linkCtx;
324318
if (parentElem && (!onEvent || onEvent.call(tag || linkCtx, ev, eventArgs) !== false)
325319
// If data changed, the ev.data is set to be the path. Use that to filter the handler action...
326320
&& (!eventArgs || ev.data.prop === "*" || ev.data.prop === eventArgs.path)) {
327321

322+
// Set linkCtx on view, dynamically, just during this handler call
323+
view._lc = linkCtx;
328324
if (eventArgs) {
329325
linkCtx.eventArgs = eventArgs;
330326
}
@@ -371,12 +367,13 @@ function onDataLinkedTagChange(ev, eventArgs) {
371367
// from the sourceValue (which may optionally have been modifed in onUpdate()...) and then bind, and we are done
372368
observeAndBind(linkCtx, source, target);
373369
}
370+
// Remove dynamically added linkCtx from view
374371
view._lc = oldLinkCtx;
375372
if (eventArgs && (onEvent = changeHandler(view, onAfterChangeStr, tag))) {
376373
onEvent.call(tag || linkCtx, ev, eventArgs);
377374
}
378375
if (tag.tagCtx.props.dataMap) {
379-
tag.tagCtx.props.dataMap.map(tag.tagCtx.args[0], tag.tagCtx, tag.tagCtx.map, !tag._.bnd);
376+
tag.tagCtx.props.dataMap.map(tag.tagCtx.args[0], tag.tagCtx, tag.tagCtx.map, isRenderCall || !tag._.bnd);
380377
}
381378
return;
382379
}
@@ -456,7 +453,6 @@ function updateContent(sourceValue, linkCtx, attr, tag) {
456453
$target = $(target),
457454
view = linkCtx.view,
458455
targetVal = linkCtx._val,
459-
oldLinkCtx = view._lc,
460456
change = tag;
461457

462458
if (tag) {
@@ -545,8 +541,6 @@ function updateContent(sourceValue, linkCtx, attr, tag) {
545541

546542
if (setter = fnSetters[attr]) {
547543
if (attr === HTML) {
548-
// Set linkCtx on view, dynamically, just during this handler call
549-
view._lc = linkCtx;
550544
if (tag && tag.inline) {
551545
nodesToRemove = tag.nodes(true);
552546
if (tag._elCnt) {
@@ -595,8 +589,6 @@ function updateContent(sourceValue, linkCtx, attr, tag) {
595589
late = view.link(source, target, prevNode, nextNode, sourceValue, tag && {tag: tag._tgId});
596590
}
597591
}
598-
// Remove dynamically added linkCtx and ctx from view
599-
view._lc = oldLinkCtx;
600592
} else {
601593
if (change = change || targetVal !== sourceValue) {
602594
if (attr === "text" && target.children && !target.children[0]) {
@@ -865,15 +857,15 @@ function observeAndBind(linkCtx, source, target) {
865857
$observable._apply(1, [source], exprFnDeps, linkCtx._depends, handler, linkCtx._ctxCb, true);
866858
}
867859

868-
if (tag && tag.boundProps) {
860+
if (tag) {
869861
// Add dependency paths for declared boundProps (so no need to write ^myprop=... to get binding) and for linkedProp too if there is one
870862
l = tag.boundProps.length;
871863
while (l--) {
872864
prop = tag.boundProps[l];
873865
k = tag._.bnd.paths.length;
874-
while (k--) {
866+
while (k--) { // Iterate across tagCtxs
875867
propDeps = tag._.bnd.paths[k]["_" + prop];
876-
if (propDeps && propDeps.skp) { // Not already a bound prop ^prop=expression;
868+
if (propDeps && propDeps.length && propDeps.skp) { // Not already a bound prop ^prop=expression;
877869
exprFnDeps = exprFnDeps.concat(propDeps); // Add dependencies for this prop expression
878870
}
879871
}
@@ -1901,7 +1893,8 @@ function callAfterLink(tag, ev, eventArgs) {
19011893
}
19021894
}
19031895

1904-
var linkedElems, linkedElements, linkedElem, l, m, $linkCtxElem, linkCtxElem, linkedEl, linkedTag, tagCtxElse, props, val, oldVal, indexTo,
1896+
var linkedElems, linkedElements, linkedElem, l, m, $linkCtxElem, linkCtxElem, linkedEl, linkedTag,
1897+
tagCtxElse, props, val, oldVal, indexTo, i, mapDeps, propDeps,
19051898
tagCtx = tag.tagCtx,
19061899
tagCtxs = tag.tagCtxs,
19071900
tagCtxslength = tagCtxs && tagCtxs.length,
@@ -1970,6 +1963,22 @@ function callAfterLink(tag, ev, eventArgs) {
19701963
tagCtxElse = tagCtxs[m];
19711964
props = tagCtxElse.props;
19721965

1966+
if (tag._.unlinked && tagCtxElse.map && tag.mapProps) {
1967+
// Compile the dependency paths for observable changes in mapProps (e.g. start, end, filter)
1968+
i = tag.mapProps.length;
1969+
mapDeps = props.mapDepends || tag.mapDepends || []; // dependency paths
1970+
mapDeps = $isArray(mapDeps) ? mapDeps : [mapDeps];
1971+
while (i--) { // Iterate through mapProps
1972+
var prop = tag.mapProps[i];
1973+
propDeps = tag._.bnd.paths[m]["_" + prop]; // paths for mapProps on this tagCtx
1974+
if (propDeps && propDeps.length && propDeps.skp) { // Not already a bound prop ^prop=expression;
1975+
mapDeps = mapDeps.concat(propDeps); // Add dependencies for this prop expression
1976+
}
1977+
}
1978+
if (mapDeps.length) {
1979+
tagCtxElse.map.observe(mapDeps, linkCtx); // Listen to observable changes of mapProps, and call map.update when change happens
1980+
}
1981+
}
19731982
if (linkedElem = tagCtxElse.mainElem || !tag.mainElement && tagCtxElse.linkedElems && tagCtxElse.linkedElems[0]) {
19741983
// linkedElem is the mainElem (defaulting to linkedElem)
19751984
if (linkedElem[0] && props.id && !linkedElem[0].id) {
@@ -3264,12 +3273,12 @@ $extend($tags["for"], {
32643273
}
32653274
}
32663275
}),
3267-
boundProps: ["filter", "sort", "reverse", "start", "end"],
3276+
mapProps: ["filter", "sort", "reverse", "start", "end", "step"],
32683277
bindTo: ["paged", "sorted"],
32693278
bindFrom: [0],
32703279

32713280
onArrayChange: function(ev, eventArgs, tagCtx, linkCtx) {
3272-
var arrayView,
3281+
var arrayView, propsArr,
32733282
targetLength = ev.target.length,
32743283
tag = this;
32753284
if (!tag.rendering) {
@@ -3278,7 +3287,11 @@ $extend($tags["for"], {
32783287
eventArgs.change === "insert" && targetLength === eventArgs.items.length // inserting, and new length is same as inserted length, so going from 0 to n
32793288
|| eventArgs.change === "remove" && !targetLength) // removing, and new length 0, so going from n to 0
32803289
) {
3290+
propsArr = tagCtx.map && tagCtx.map.propsArr; // Used by {{props}}, which derives from {{for}}
32813291
tag.refresh();
3292+
if (propsArr) {
3293+
tagCtx.map.propsArr = propsArr; // Keep previous propsArr with new map
3294+
}
32823295
} else for (arrayView in tag._.arrVws) {
32833296
arrayView = tag._.arrVws[arrayView];
32843297
if (arrayView.data === ev.target) {
@@ -3317,7 +3330,6 @@ $extend($tags["for"], {
33173330
: tagCtx.args.length
33183331
? tagCtx.args[0] // or args[0]
33193332
: tagCtx.view.data; // or defaults to current data.
3320-
33213333
if (arrayBindings[i]) { // Is there was a previous binding on this tagCtx, (maybe with data different from new data)
33223334
$observe(arrayBindings[i], true); //unobserve previous array
33233335
delete arrayBindings[i];
@@ -3402,17 +3414,14 @@ $extend($tags["if"], {
34023414
});
34033415

34043416
function observeProps(map, ev, eventArgs) {
3405-
var props = map.options.tag.tagCtx.props;
3417+
var target, l, props = map.options.props;
3418+
updatePropsArr(map.propsArr, eventArgs.path, eventArgs.value, eventArgs.remove);
34063419
if (props.sort !== undefined || props.start !== undefined || props.end !== undefined || props.step !== undefined || props.filter || props.reverse) {
34073420
map.update(); // refresh sorting and filtering
34083421
} else if (eventArgs.change === "set") {
3409-
var target = map.tgt,
3410-
l = target.length;
3411-
while (l--) {
3412-
if (target[l].key === eventArgs.path) {
3413-
break;
3414-
}
3415-
}
3422+
target = map.tgt;
3423+
l = target.length;
3424+
while (l-- && target[l].key !== eventArgs.path) {}
34163425
if (l === -1) {
34173426
if (eventArgs.path && !eventArgs.remove) {
34183427
$observable(target).insert({key: eventArgs.path, prop: eventArgs.value});
@@ -3426,7 +3435,7 @@ function observeProps(map, ev, eventArgs) {
34263435
}
34273436

34283437
function observeMappedProps(map, ev, eventArgs) {
3429-
var items, l, key,
3438+
var items, l, key, remove,
34303439
source = map.src,
34313440
change = eventArgs.change;
34323441

@@ -3437,12 +3446,13 @@ function observeMappedProps(map, ev, eventArgs) {
34373446
$observable(source).removeProperty(eventArgs.oldValue); // When key is modified observably, remove old one and set new one
34383447
$observable(source).setProperty(eventArgs.value, ev.target.prop);
34393448
}
3440-
} else if (change === "insert" || change === "remove") {
3449+
} else if (change === "insert" || (remove = change === "remove")) {
34413450
items = eventArgs.items;
34423451
l = items.length;
34433452
while (l--) {
34443453
if (key = items[l].key) {
3445-
if (change === "remove") {
3454+
updatePropsArr(map.propsArr, key, items[l].prop, remove);
3455+
if (remove) {
34463456
$observable(source).removeProperty(key);
34473457
delete source[key];
34483458
} else {
@@ -3453,6 +3463,18 @@ function observeMappedProps(map, ev, eventArgs) {
34533463
}
34543464
}
34553465

3466+
function updatePropsArr(propsArr, key, prop, remove) {
3467+
var l = propsArr.length;
3468+
while (l-- && propsArr[l].key !== key) {}
3469+
if (l === -1) {
3470+
if (key && !remove) {
3471+
propsArr.push({key: key, prop: prop});
3472+
}
3473+
} else if (remove) {
3474+
propsArr.splice(l, 1);
3475+
}
3476+
}
3477+
34563478
function shallowArrayFilter(path /*, object, parentObs*/) { // Filter used by {{props}} for the mappedProps target array
34573479
return rShallowArrayPath.test(path); // No '.' in path
34583480
}

jquery.views.min.js

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jquery.views.min.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)