Skip to content

Commit

Permalink
v1.0.2 release
Browse files Browse the repository at this point in the history
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:

- BorisMoore#414
  updateValue not working correctly for 2-way bound tag properties
  with a path expression containing index [] operator

- BorisMoore#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...
  • Loading branch information
BorisMoore committed Feb 11, 2019
1 parent a4ad093 commit 259671d
Show file tree
Hide file tree
Showing 32 changed files with 2,444 additions and 1,324 deletions.
82 changes: 50 additions & 32 deletions jquery.observable.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/*! JsObservable v1.0.1: http://jsviews.com/#jsobservable */
/*! JsObservable v1.0.2: http://jsviews.com/#jsobservable */
/*
* Subcomponent of JsViews
* Data change events for data-linking
*
* Copyright 2018, Boris Moore
* Copyright 2019, Boris Moore
* Released under the MIT License.
*/

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

var versionNumber = "v1.0.1",
var versionNumber = "v1.0.2",
_ocp = "_ocp", // Observable contextual parameter
$observe, $observable,

Expand Down Expand Up @@ -1058,11 +1058,7 @@ if (!$.observe) {
if ((newItem = newItems[j]) === data[j-k]) {
insertAdded();
} else {
for (i=j-k; i<dataLength; i++) {
if (newItem === data[i]) {
break;
}
}
for (i=j-k; i<dataLength && newItem !== data[i]; i++) {}
if (i<dataLength) {
insertAdded();
num = 0;
Expand Down Expand Up @@ -1143,32 +1139,35 @@ if (!$.observe) {
}
if (typeof source === OBJECT || $isFunction(source)) {
map.src = source;
if (oldMapOrTarget) {
map.tgt = oldMapOrTarget.tgt || oldMapOrTarget; // Can provide an existing map, or a target array to be used on new map
if (unbound) {
map.tgt = mapDef.getTgt(source, options);
} else {
map.tgt = map.tgt || [];
}
map.options = options || map.options;
if (updatedMap = map.update()) {
map = updatedMap; // If updating returns another map, then we can replace this one (so no need to bind it)
} else if (!unbound) {
if (mapDef.obsSrc) {
$observable(map.src).observeAll(map.obs = function(ev, eventArgs) {
if (!changing && !eventArgs.refresh) {
changing = true;
mapDef.obsSrc(map, ev, eventArgs);
changing = undefined;
}
}, map.srcFlt);
if (oldMapOrTarget) {
map.tgt = oldMapOrTarget.tgt || $isArray(oldMapOrTarget) && oldMapOrTarget; // Can provide an existing map, or a target array to be used on new map
}
if (mapDef.obsTgt) {
$observable(map.tgt).observeAll(map.obt = function(ev, eventArgs) {
if (!changing && !map.tgt._updt) {
changing = true;
mapDef.obsTgt(map, ev, eventArgs);
changing = undefined;
}
}, map.tgtFlt);
map.tgt = map.tgt || [];
map.options = options || map.options;
if (updatedMap = map.update()) {
map = updatedMap; // If updating returns another map, then we can replace this one (so no need to bind it)
} else {
if (mapDef.obsSrc) {
$observable(map.src).observeAll(map.obs = function(ev, eventArgs) {
if (!changing && !eventArgs.refresh) {
changing = true;
mapDef.obsSrc(map, ev, eventArgs);
changing = undefined;
}
}, map.srcFlt);
}
if (mapDef.obsTgt) {
$observable(map.tgt).observeAll(map.obt = function(ev, eventArgs) {
if (!changing && !map.tgt._updt) {
changing = true;
mapDef.obsTgt(map, ev, eventArgs);
changing = undefined;
}
}, map.tgtFlt);
}
}
}
}
Expand Down Expand Up @@ -1207,6 +1206,22 @@ if (!$.observe) {
}
}
},
observe: function(deps, linkCtx) { // Listen to observable changes of mapProps, and call map.update when change happens
var map = this,
options = map.options;
if (map.obmp) {
// There is a previous handler observing the mapProps
$unobserve(map.obmp);
}
map.obmp = function() {
// Observe changes in the mapProps ("filter", "sort", "reverse", "start", "end")
var newTagCtx = linkCtx.fn(linkCtx.data, linkCtx.view, $sub)[options.index]; // Updated tagCtx props and args
$.extend(options.props, newTagCtx.props); // Update props to new values
options.args = newTagCtx.args; // Update args to new values
map.update(); // Update the map target array, based on new mapProp values
};
$observable._apply(1, linkCtx.data, dependsPaths(deps, linkCtx.tag, map.obmp), map.obmp, linkCtx._ctxCb);
},
unmap: function() {
var map = this;
if (map.src && map.obs) {
Expand All @@ -1215,6 +1230,9 @@ if (!$.observe) {
if (map.tgt && map.obt) {
$observable(map.tgt).unobserveAll(map.obt, map.tgtFlt);
}
if (map.obmp) {
$unobserve(map.obmp);
}
map.src = undefined;
},
map: Map,
Expand Down
4 changes: 2 additions & 2 deletions jquery.observable.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion jquery.observable.min.js.map

Large diffs are not rendered by default.

90 changes: 56 additions & 34 deletions jquery.views.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*! jquery.views.js v1.0.1: http://jsviews.com/ */
/*! jquery.views.js v1.0.2: http://jsviews.com/ */
/*
* Interactive data-driven views using JsRender templates.
* Subcomponent of JsViews
Expand All @@ -7,7 +7,7 @@
* Also requires jquery.observable.js
* See JsObservable at http://jsviews.com/#download and http://github.com/BorisMoore/jsviews
*
* Copyright 2018, Boris Moore
* Copyright 2019, Boris Moore
* Released under the MIT License.
*/

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

var versionNumber = "v1.0.1",
var versionNumber = "v1.0.2",
requiresStr = "JsViews requires ";

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

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

// Set linkCtx on view, dynamically, just during this handler call
oldLinkCtx = view._lc;
view._lc = linkCtx;
l = tos.length;
while (l--) {
if (to = tos[l]) {
Expand Down Expand Up @@ -273,7 +270,6 @@ function updateValues(sourceValues, tagElse, bindId, ev) {
}
}
}
view._lc = oldLinkCtx;
}
if (tag) {
tag._.chg = undefined; // Clear marker
Expand Down Expand Up @@ -319,12 +315,12 @@ function onDataLinkedTagChange(ev, eventArgs) {
oldLinkCtx = view._lc,
onEvent = eventArgs && changeHandler(view, onBeforeChangeStr, tag);

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

// Set linkCtx on view, dynamically, just during this handler call
view._lc = linkCtx;
if (eventArgs) {
linkCtx.eventArgs = eventArgs;
}
Expand Down Expand Up @@ -371,12 +367,13 @@ function onDataLinkedTagChange(ev, eventArgs) {
// from the sourceValue (which may optionally have been modifed in onUpdate()...) and then bind, and we are done
observeAndBind(linkCtx, source, target);
}
// Remove dynamically added linkCtx from view
view._lc = oldLinkCtx;
if (eventArgs && (onEvent = changeHandler(view, onAfterChangeStr, tag))) {
onEvent.call(tag || linkCtx, ev, eventArgs);
}
if (tag.tagCtx.props.dataMap) {
tag.tagCtx.props.dataMap.map(tag.tagCtx.args[0], tag.tagCtx, tag.tagCtx.map, !tag._.bnd);
tag.tagCtx.props.dataMap.map(tag.tagCtx.args[0], tag.tagCtx, tag.tagCtx.map, isRenderCall || !tag._.bnd);
}
return;
}
Expand Down Expand Up @@ -456,7 +453,6 @@ function updateContent(sourceValue, linkCtx, attr, tag) {
$target = $(target),
view = linkCtx.view,
targetVal = linkCtx._val,
oldLinkCtx = view._lc,
change = tag;

if (tag) {
Expand Down Expand Up @@ -545,8 +541,6 @@ function updateContent(sourceValue, linkCtx, attr, tag) {

if (setter = fnSetters[attr]) {
if (attr === HTML) {
// Set linkCtx on view, dynamically, just during this handler call
view._lc = linkCtx;
if (tag && tag.inline) {
nodesToRemove = tag.nodes(true);
if (tag._elCnt) {
Expand Down Expand Up @@ -595,8 +589,6 @@ function updateContent(sourceValue, linkCtx, attr, tag) {
late = view.link(source, target, prevNode, nextNode, sourceValue, tag && {tag: tag._tgId});
}
}
// Remove dynamically added linkCtx and ctx from view
view._lc = oldLinkCtx;
} else {
if (change = change || targetVal !== sourceValue) {
if (attr === "text" && target.children && !target.children[0]) {
Expand Down Expand Up @@ -865,15 +857,15 @@ function observeAndBind(linkCtx, source, target) {
$observable._apply(1, [source], exprFnDeps, linkCtx._depends, handler, linkCtx._ctxCb, true);
}

if (tag && tag.boundProps) {
if (tag) {
// Add dependency paths for declared boundProps (so no need to write ^myprop=... to get binding) and for linkedProp too if there is one
l = tag.boundProps.length;
while (l--) {
prop = tag.boundProps[l];
k = tag._.bnd.paths.length;
while (k--) {
while (k--) { // Iterate across tagCtxs
propDeps = tag._.bnd.paths[k]["_" + prop];
if (propDeps && propDeps.skp) { // Not already a bound prop ^prop=expression;
if (propDeps && propDeps.length && propDeps.skp) { // Not already a bound prop ^prop=expression;
exprFnDeps = exprFnDeps.concat(propDeps); // Add dependencies for this prop expression
}
}
Expand Down Expand Up @@ -1901,7 +1893,8 @@ function callAfterLink(tag, ev, eventArgs) {
}
}

var linkedElems, linkedElements, linkedElem, l, m, $linkCtxElem, linkCtxElem, linkedEl, linkedTag, tagCtxElse, props, val, oldVal, indexTo,
var linkedElems, linkedElements, linkedElem, l, m, $linkCtxElem, linkCtxElem, linkedEl, linkedTag,
tagCtxElse, props, val, oldVal, indexTo, i, mapDeps, propDeps,
tagCtx = tag.tagCtx,
tagCtxs = tag.tagCtxs,
tagCtxslength = tagCtxs && tagCtxs.length,
Expand Down Expand Up @@ -1970,6 +1963,22 @@ function callAfterLink(tag, ev, eventArgs) {
tagCtxElse = tagCtxs[m];
props = tagCtxElse.props;

if (tag._.unlinked && tagCtxElse.map && tag.mapProps) {
// Compile the dependency paths for observable changes in mapProps (e.g. start, end, filter)
i = tag.mapProps.length;
mapDeps = props.mapDepends || tag.mapDepends || []; // dependency paths
mapDeps = $isArray(mapDeps) ? mapDeps : [mapDeps];
while (i--) { // Iterate through mapProps
var prop = tag.mapProps[i];
propDeps = tag._.bnd.paths[m]["_" + prop]; // paths for mapProps on this tagCtx
if (propDeps && propDeps.length && propDeps.skp) { // Not already a bound prop ^prop=expression;
mapDeps = mapDeps.concat(propDeps); // Add dependencies for this prop expression
}
}
if (mapDeps.length) {
tagCtxElse.map.observe(mapDeps, linkCtx); // Listen to observable changes of mapProps, and call map.update when change happens
}
}
if (linkedElem = tagCtxElse.mainElem || !tag.mainElement && tagCtxElse.linkedElems && tagCtxElse.linkedElems[0]) {
// linkedElem is the mainElem (defaulting to linkedElem)
if (linkedElem[0] && props.id && !linkedElem[0].id) {
Expand Down Expand Up @@ -3264,12 +3273,12 @@ $extend($tags["for"], {
}
}
}),
boundProps: ["filter", "sort", "reverse", "start", "end"],
mapProps: ["filter", "sort", "reverse", "start", "end", "step"],
bindTo: ["paged", "sorted"],
bindFrom: [0],

onArrayChange: function(ev, eventArgs, tagCtx, linkCtx) {
var arrayView,
var arrayView, propsArr,
targetLength = ev.target.length,
tag = this;
if (!tag.rendering) {
Expand All @@ -3278,7 +3287,11 @@ $extend($tags["for"], {
eventArgs.change === "insert" && targetLength === eventArgs.items.length // inserting, and new length is same as inserted length, so going from 0 to n
|| eventArgs.change === "remove" && !targetLength) // removing, and new length 0, so going from n to 0
) {
propsArr = tagCtx.map && tagCtx.map.propsArr; // Used by {{props}}, which derives from {{for}}
tag.refresh();
if (propsArr) {
tagCtx.map.propsArr = propsArr; // Keep previous propsArr with new map
}
} else for (arrayView in tag._.arrVws) {
arrayView = tag._.arrVws[arrayView];
if (arrayView.data === ev.target) {
Expand Down Expand Up @@ -3317,7 +3330,6 @@ $extend($tags["for"], {
: tagCtx.args.length
? tagCtx.args[0] // or args[0]
: tagCtx.view.data; // or defaults to current data.

if (arrayBindings[i]) { // Is there was a previous binding on this tagCtx, (maybe with data different from new data)
$observe(arrayBindings[i], true); //unobserve previous array
delete arrayBindings[i];
Expand Down Expand Up @@ -3402,17 +3414,14 @@ $extend($tags["if"], {
});

function observeProps(map, ev, eventArgs) {
var props = map.options.tag.tagCtx.props;
var target, l, props = map.options.props;
updatePropsArr(map.propsArr, eventArgs.path, eventArgs.value, eventArgs.remove);
if (props.sort !== undefined || props.start !== undefined || props.end !== undefined || props.step !== undefined || props.filter || props.reverse) {
map.update(); // refresh sorting and filtering
} else if (eventArgs.change === "set") {
var target = map.tgt,
l = target.length;
while (l--) {
if (target[l].key === eventArgs.path) {
break;
}
}
target = map.tgt;
l = target.length;
while (l-- && target[l].key !== eventArgs.path) {}
if (l === -1) {
if (eventArgs.path && !eventArgs.remove) {
$observable(target).insert({key: eventArgs.path, prop: eventArgs.value});
Expand All @@ -3426,7 +3435,7 @@ function observeProps(map, ev, eventArgs) {
}

function observeMappedProps(map, ev, eventArgs) {
var items, l, key,
var items, l, key, remove,
source = map.src,
change = eventArgs.change;

Expand All @@ -3437,12 +3446,13 @@ function observeMappedProps(map, ev, eventArgs) {
$observable(source).removeProperty(eventArgs.oldValue); // When key is modified observably, remove old one and set new one
$observable(source).setProperty(eventArgs.value, ev.target.prop);
}
} else if (change === "insert" || change === "remove") {
} else if (change === "insert" || (remove = change === "remove")) {
items = eventArgs.items;
l = items.length;
while (l--) {
if (key = items[l].key) {
if (change === "remove") {
updatePropsArr(map.propsArr, key, items[l].prop, remove);
if (remove) {
$observable(source).removeProperty(key);
delete source[key];
} else {
Expand All @@ -3453,6 +3463,18 @@ function observeMappedProps(map, ev, eventArgs) {
}
}

function updatePropsArr(propsArr, key, prop, remove) {
var l = propsArr.length;
while (l-- && propsArr[l].key !== key) {}
if (l === -1) {
if (key && !remove) {
propsArr.push({key: key, prop: prop});
}
} else if (remove) {
propsArr.splice(l, 1);
}
}

function shallowArrayFilter(path /*, object, parentObs*/) { // Filter used by {{props}} for the mappedProps target array
return rShallowArrayPath.test(path); // No '.' in path
}
Expand Down
6 changes: 3 additions & 3 deletions jquery.views.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion jquery.views.min.js.map

Large diffs are not rendered by default.

Loading

0 comments on commit 259671d

Please sign in to comment.