Skip to content

Commit fd836ea

Browse files
committed
Commit 81 (v0.9.81 - Beta)
Feature improvements: - Custom tag control improvements (documentation to follow soon): tag.linkedElement property onBind event onUnbind event onAfterLink signature: (tagCtx, linkCtx, tag.ctx, ev, eventArgs) Improvements to tag controls based on JQueryUI widgets (with updated jsviews-jqueryui-widgets.js providing support for jQuery UI 1.12.1, including new {{controlgroup}} and {{checkboxradio}} tag controls based on corresponding widgets Breaking Change: - The undocumented onBeforeLink and onAfterBind events on custom tag controls have been removed Documentation: - New documentation topic on data-linking using tag bindings: http://www.jsviews.com/#link-tags Bug fixes: - #346 - Several minor bug fixes Unit tests: - Several additional unit tests
1 parent 65112e9 commit fd836ea

29 files changed

+1154
-997
lines changed

jquery.observable.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*! JsObservable v0.9.80 (Beta): http://jsviews.com/#jsobservable */
1+
/*! JsObservable v0.9.81 (Beta): 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 = "v0.9.74",
47+
var versionNumber = "v0.9.81",
4848
$observe, $observable,
4949

5050
$views = $.views =

jquery.observable.min.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jquery.observable.min.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jquery.views.js

+78-75
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*! jquery.views.js v0.9.80 (Beta): http://jsviews.com/ */
1+
/*! jquery.views.js v0.9.81 (Beta): http://jsviews.com/ */
22
/*
33
* Interactive data-driven views using JsRender templates.
44
* Subcomponent of JsViews
@@ -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 = "v0.9.80",
47+
var versionNumber = "v0.9.81",
4848
requiresStr = "JsViews requires ";
4949

5050
if (!$ || !$.fn) {
@@ -166,7 +166,7 @@ function elemChangeHandler(ev, params, sourceValue) {
166166
// The binding has a 'to' field, which is of the form [[targetObject, toPath], cvtBack]
167167
linkCtx = binding.linkCtx;
168168
view = linkCtx.view;
169-
tag = linkCtx.tag;
169+
tag = linkCtx.tag || view.tag;
170170
$source = $(source);
171171
onBeforeChange = view.hlp(onBeforeChangeStr); // TODO Can we optimize this and other instances of same?
172172
onAfterChange = view.hlp(onAfterChangeStr); // TODO Can we optimize this and other instances of same
@@ -298,10 +298,7 @@ function propertyChangeHandler(ev, eventArgs, linkFn) {
298298
// onUpdate returned false, or attr === "none", or this is an update coming from the tag's own change event
299299
// - so don't refresh the tag: we just use the new tagCtxs merged from the sourceValue,
300300
// (which may optionally have been modifed in onUpdate()...) and then bind, and we are done
301-
if (attr === HTML && tag.onBeforeLink) {
302-
tag.onBeforeLink();
303-
}
304-
callAfterLink(tag);
301+
callAfterLink(tag, ev, eventArgs);
305302
observeAndBind(linkCtx, source, target);
306303
view.linkCtx = oldLinkCtx;
307304
return;
@@ -310,6 +307,10 @@ function propertyChangeHandler(ev, eventArgs, linkFn) {
310307
return;
311308
}
312309

310+
if (tag.onUnbind) {
311+
tag.onUnbind(tag.tagCtx, linkCtx, tag.ctx, ev, eventArgs);
312+
}
313+
313314
sourceValue = tag.tagName === ":" // Call convertVal if it is a {{cvt:...}} - otherwise call renderTag
314315
? $sub._cnvt(tag.cvt, view, sourceValue[0])
315316
: $sub._tag(tag, view, view.tmpl, sourceValue, true, onError);
@@ -335,7 +336,7 @@ function propertyChangeHandler(ev, eventArgs, linkFn) {
335336

336337
if (tag) {
337338
tag._er = hasError;
338-
callAfterLink(tag, eventArgs);
339+
callAfterLink(tag, ev, eventArgs);
339340
}
340341
}
341342

@@ -410,14 +411,12 @@ function updateContent(sourceValue, linkCtx, attr, tag) {
410411

411412
if (tag) {
412413
// Initialize the tag with element references
414+
tag._.unlinked = true; // Set to unlinked, so initialization is triggered after re-rendering, e.g. for setting linkedElem, and calling onBind
413415
tag.parentElem = tag.parentElem || (linkCtx.expr || tag._elCnt) ? target : targetParent;
414416
prevNode = tag._prv;
415417
nextNode = tag._nxt;
416418
}
417419
if (!renders) {
418-
if (attr === HTML && tag && tag.onBeforeLink) {
419-
tag.onBeforeLink();
420-
}
421420
return;
422421
}
423422

@@ -530,9 +529,6 @@ function updateContent(sourceValue, linkCtx, attr, tag) {
530529
// Remove HTML nodes
531530
$(nodesToRemove).remove(); // Note if !tag._elCnt removing the nodesToRemove will process and dispose view and tag bindings contained within the updated tag control
532531

533-
if (tag && tag.onBeforeLink) {
534-
tag.onBeforeLink();
535-
}
536532
// Insert and link new content
537533
promise = view.link(view.data, target, prevNode, nextNode, sourceValue, tag && {tag: tag._tgId, lazyLink: tag.tagCtx.props.lazyLink});
538534
} else {
@@ -541,9 +537,6 @@ function updateContent(sourceValue, linkCtx, attr, tag) {
541537
if (renders) {
542538
$target.empty();
543539
}
544-
if (tag && tag.onBeforeLink) {
545-
tag.onBeforeLink();
546-
}
547540
if (renders) {
548541
promise = view.link(source, target, prevNode, nextNode, sourceValue, tag && {tag: tag._tgId});
549542
}
@@ -800,7 +793,7 @@ function observeAndBind(linkCtx, source, target) { //TODO? linkFnArgs) {;
800793

801794
if (tag) {
802795
// Use the 'depends' paths set on linkCtx.tag - which may have been set on declaration
803-
// or in events: init, render, onBeforeLink, onAfterLink etc.
796+
// or in events: init, render, onAfterLink etc.
804797
depends = tag.depends || depends;
805798
depends = $isFunction(depends) ? tag.depends(tag) : depends;
806799
linkedElem = tag.linkedElem;
@@ -854,20 +847,13 @@ function observeAndBind(linkCtx, source, target) { //TODO? linkFnArgs) {;
854847
bindTo(binding, tag, linkedElem && linkedElem[0] || target, cvtBk);
855848
}
856849
if (tag) {
857-
if (tag.onAfterBind) {
858-
tag.onAfterBind(binding);
859-
}
860850
if (!tag.flow && !tag._.inline) {
861851
target.setAttribute(jsvAttrStr, (target.getAttribute(jsvAttrStr)||"") + "#" + bindId + "^/" + bindId + "^");
862852
tag._tgId = "" + bindId;
863853
}
864854
}
865855
}
866856
if (linkedElem && linkedElem[0]) {
867-
if (tag._.radio) {
868-
linkedElem = linkedElem.find(RADIOINPUT);
869-
}
870-
871857
l = linkedElem.length;
872858
while (l--) {
873859
linkedElem[l]._jsvBnd = linkedElem[l]._jsvBnd || (target._jsvBnd + "+");
@@ -1421,9 +1407,6 @@ function viewLink(outerData, parentNode, prevNode, nextNode, html, refresh, cont
14211407
tag._prv = elem;
14221408
}
14231409
tag._elCnt = linkInfo.elCnt;
1424-
if (tag.onBeforeLink) {
1425-
tag.onBeforeLink();
1426-
}
14271410
// We data-link depth-first ("on the way in"), which is better for perf - and allows setting parent tags etc.
14281411
view = tag.tagCtx.view;
14291412
addDataBinding(undefined, tag._prv, view, linkInfo.id);
@@ -1634,11 +1617,11 @@ function addDataBinding(linkMarkup, node, currentView, boundTagId, isLink, data,
16341617
attr = tokens[1];
16351618
tagExpr = tokens[3];
16361619
while (linkExpressions[0] && linkExpressions[0][4] === "else") { // If this is {someTag...} and is followed by an {else...} add to tagExpr
1637-
tagExpr += "}{" + linkExpressions.shift()[3];
1620+
tagExpr += delimCloseChar1 + delimOpenChar0 + linkExpressions.shift()[3];
16381621
hasElse = true;
16391622
}
16401623
if (hasElse) { // If an {else} has been added, need also to add closing {{/someTag}}
1641-
tagExpr += "}{{/" + tokens[4] + "}";
1624+
tagExpr += delimCloseChar1 + delimOpenChar0 + delimOpenChar1 + "/" + tokens[4] + delimCloseChar0;
16421625
}
16431626
linkCtx = {
16441627
type: isLink ? "top" : "link",
@@ -1792,54 +1775,60 @@ function normalizeLinkTag(linkMarkup, twoway) {
17921775
// Methods for views and tags
17931776
//===========================
17941777

1795-
function callAfterLink(tag, eventArgs) {
1796-
var $linkedElem, linkedElem, radioButtons, val, l, linkedTag, oldTrig, newTrig,
1778+
function callAfterLink(tag, ev, eventArgs) {
1779+
var $linkedElem, linkedElem, radioButtons, val, l, linkedTag, oldTrig, newTrig, tagProps, propsExpr, linkedElemView, prop, propDef,
17971780
tagCtx = tag.tagCtx,
17981781
view = tagCtx.view,
17991782
props = tagCtx.props,
18001783
linkCtx = tag.linkCtx;
18011784

1785+
if (tag._.unlinked) { // First call to onAfterLink, or first call after onUpdate: updateContent. Initialize and call onBind and set properties
1786+
if (tag.linkedElement !== undefined) {
1787+
// linkedElement: - selector for identifying linked element in template/rendered content
1788+
tag.linkedElem = tag._.inline ? tag.contents(true, tag.linkedElement || "*").first() : $(linkCtx.elem);
1789+
}
1790+
if (tag.onBind) {
1791+
tag.onBind(tagCtx, linkCtx, tag.ctx, ev, eventArgs);
1792+
}
1793+
}
1794+
18021795
if (tag.onAfterLink) {
1803-
tag.onAfterLink(tagCtx, linkCtx, eventArgs);
1796+
tag.onAfterLink(tagCtx, linkCtx, tag.ctx, ev, eventArgs);
18041797
}
1798+
18051799
tag._.unlinked = undefined;
18061800
$linkedElem = tag.targetTag ? tag.targetTag.linkedElem : tag.linkedElem;
1807-
if (!tag.noVal && (linkedElem = $linkedElem && $linkedElem[0])) {
1808-
if (radioButtons = tag._.radio) {
1809-
$linkedElem = $linkedElem.find(RADIOINPUT);
1810-
}
1811-
if (radioButtons || !tag._.chging) {
1812-
val = tag.cvtArgs()[0];
1801+
if (linkedElem = $linkedElem && $linkedElem[0]) {
1802+
if (!tag.noVal) {
1803+
if (!tag._.chging) {
1804+
val = tag.cvtArgs()[0];
18131805

1814-
if (radioButtons || linkedElem !== linkCtx.elem) {
1815-
l = $linkedElem.length;
1816-
while (l--) {
1817-
linkedElem = $linkedElem[l];
1818-
linkedTag = linkedElem._jsvLkEl;
1819-
if (tag._.inline && (!linkedTag || linkedTag !== tag && linkedTag.targetTag !== tag)) {
1820-
// For data-linked tags, identify the linkedElem with the tag, for "to" binding
1821-
// (For data-linked elements, if not yet bound, we identify later when the linkCtx.elem is bound)
1822-
linkedElem._jsvLkEl = tag;
1823-
bindTo(bindingStore[tag._tgId], tag, linkedElem);
1824-
linkedElem._jsvBnd = "&" + tag._tgId + "+"; // Add a "+" for cloned binding - so removing
1825-
// elems with cloned bindings will not remove the 'parent' binding from the bindingStore.
1826-
}
1827-
if (radioButtons) {
1828-
// For radio button, set to if val === value. For others set val() to val, below
1829-
linkedElem[CHECKED] = val === linkedElem.value;
1806+
if (linkedElem !== linkCtx.elem) {
1807+
l = $linkedElem.length;
1808+
while (l--) {
1809+
linkedElem = $linkedElem[l];
1810+
linkedTag = linkedElem._jsvLkEl;
1811+
if (tag._.inline && (!linkedTag || linkedTag !== tag && linkedTag.targetTag !== tag)) {
1812+
// For data-linked tags, identify the linkedElem with the tag, for "to" binding
1813+
// (For data-linked elements, if not yet bound, we identify later when the linkCtx.elem is bound)
1814+
linkedElem._jsvLkEl = tag;
1815+
bindTo(bindingStore[tag._tgId], tag, linkedElem);
1816+
linkedElem._jsvBnd = "&" + tag._tgId + "+"; // Add a "+" for cloned binding - so removing
1817+
// elems with cloned bindings will not remove the 'parent' binding from the bindingStore.
1818+
}
18301819
}
1820+
linkCtx._val = val;
18311821
}
1832-
linkCtx._val = val;
1833-
}
1834-
if (val !== undefined) {
1835-
if (!radioButtons && linkedElem.value !== undefined) {
1836-
if (linkedElem.type === CHECKBOX) {
1837-
linkedElem[CHECKED] = val && val !== "false";
1838-
} else {
1839-
$linkedElem.val(val);
1822+
if (val !== undefined) {
1823+
if (linkedElem.value !== undefined) {
1824+
if (linkedElem.type === CHECKBOX) {
1825+
linkedElem[CHECKED] = val && val !== "false";
1826+
} else if (linkedElem.type === "text") {
1827+
linkedElem.value = val;
1828+
}
1829+
} else if (linkedElem.contentEditable === TRUE) {
1830+
linkedElem.innerHTML = val;
18401831
}
1841-
} else if (linkedElem.contentEditable === TRUE) {
1842-
linkedElem.innerHTML = val;
18431832
}
18441833
}
18451834
}
@@ -1851,7 +1840,14 @@ function callAfterLink(tag, eventArgs) {
18511840
$linkedElem.width(props.width);
18521841
}
18531842
}
1843+
if (props.title !== undefined) {
1844+
$linkedElem.attr("title", props.title);
1845+
}
18541846
if (props["class"]) {
1847+
// This code supports dynamic binding to class - where it adds the class if absent, and removes/adds if a previous value is present
1848+
if (eventArgs && $linkedElem.hasClass(eventArgs.oldValue)) {
1849+
$linkedElem.removeClass(eventArgs.oldValue);
1850+
}
18551851
$linkedElem.addClass(props["class"]);
18561852
}
18571853
if (props.id) {
@@ -1887,6 +1883,8 @@ function bindTo(binding, tag, linkedElem, cvtBk) {
18871883
source = linkCtx.data,
18881884
paths = linkCtx.fn.paths;
18891885

1886+
tag = tag || linkedElem._jsvLkEl;
1887+
18901888
if (binding && paths) {
18911889
oldTrig = linkedElem._jsvTr || false;
18921890
if (tag) {
@@ -2064,6 +2062,9 @@ function removeViewBinding(bindId, linkedElemTag, elem) {
20642062
}
20652063
$linkedElem = tag.linkedElem;
20662064

2065+
if (tag.onUnbind) {
2066+
tag.onUnbind(tag.tagCtx, linkCtx, tag.ctx, true);
2067+
}
20672068
if (tag.onDispose) {
20682069
tag.onDispose();
20692070
}
@@ -2615,6 +2616,7 @@ $converters.merge = function(val) {
26152616

26162617
$tags("on", {
26172618
attr: NONE,
2619+
noVal: true, // This tag control does not bind to arg[0] - no default binding to current #data context
26182620
init: function(tagCtx) {
26192621
var content,
26202622
tag = this,
@@ -2636,7 +2638,7 @@ $tags("on", {
26362638
},
26372639
render: function() {
26382640
var tagCtx = this.tagCtx;
2639-
return tagCtx.render(tagCtx.view, true); // no arg, so renders against parentView.data
2641+
return this._.inline && tagCtx.render(tagCtx.view, true); // no arg, so renders against parentView.data
26402642
},
26412643
onAfterLink: function(tagCtx, linkCtx) {
26422644
var handler, params, find, activeElem,
@@ -2659,17 +2661,18 @@ $tags("on", {
26592661
? (tag._sel = args[1] || "*", tag.parentElem)
26602662
// If inline, attach to child elements of tag parent element (filtered by selector argument if provided.
26612663
// (In handler we'll filter out events from sibling elements preceding or following tag.)
2664+
// This allows us to use the delegated pattern where the attached event works even for added elements satisfying the selector
26622665
: linkCtx.elem);
26632666

26642667
if (!contextOb) {
26652668
// Get the path for the preceding object (context object) of handler (which is the last arg), compile function
26662669
// to return that context object, and run compiled function against data
26672670
contextOb = /^(.*)[\.^][\w$]+$/.exec(tagCtx.params.args.slice(-params.length - 1)[0]);
2668-
contextOb = contextOb && $sub.tmplFn("{:" + contextOb[1] + "}", view.tmpl, true)(linkCtx.data, view);
2671+
contextOb = contextOb && $sub.tmplFn(delimOpenChar1 + ":" + contextOb[1] + delimCloseChar0, view.tmpl, true)(linkCtx.data, view);
26692672
}
26702673

26712674
if (tag._evs) {
2672-
tag.onDispose();
2675+
tag.onUnbind();
26732676
}
26742677

26752678
activeElem.on(
@@ -2706,7 +2709,7 @@ $tags("on", {
27062709
onUpdate: function() {
27072710
return false;
27082711
},
2709-
onDispose: function() {
2712+
onUnbind: function() {
27102713
var self = this;
27112714
if (self.activeElem) {
27122715
self.activeElem.off(self._evs, self._sel, self._hlr);
@@ -2717,11 +2720,11 @@ $tags("on", {
27172720
});
27182721

27192722
$extend($tags["for"], {
2720-
//onUpdate: function(ev, eventArgs, tagCtxs) {
2723+
//onUpdate: function(ev, eventArgs, newTagCtxs) {
27212724
//Consider adding filtering for perf optimization. However the below prevents update on some scenarios which _should_ update - namely when there is another array on which for also depends.
27222725
//var i, l, tci, prevArg;
27232726
//for (tci = 0; (prevArg = this.tagCtxs[tci]) && prevArg.args.length; tci++) {
2724-
// if (prevArg.args[0] !== tagCtxs[tci].args[0]) {
2727+
// if (prevArg.args[0] !== newTagCtxs[tci].args[0]) {
27252728
// return true;
27262729
// }
27272730
//}
@@ -2795,10 +2798,10 @@ $extend($tags["for"], {
27952798
});
27962799

27972800
$extend($tags["if"], {
2798-
onUpdate: function(ev, eventArgs, tagCtxs) {
2801+
onUpdate: function(ev, eventArgs, newTagCtxs) {
27992802
var tci, prevArg, different;
28002803
for (tci = 0; (prevArg = this.tagCtxs[tci]); tci++) {
2801-
different = prevArg.props.tmpl !== tagCtxs[tci].props.tmpl || prevArg.args.length && !(prevArg = prevArg.args[0]) !== !tagCtxs[tci].args[0];
2804+
different = prevArg.props.tmpl !== newTagCtxs[tci].props.tmpl || prevArg.args.length && !(prevArg = prevArg.args[0]) !== !newTagCtxs[tci].args[0];
28022805
if ((!this.convert && !!prevArg) || different) {
28032806
return different;
28042807
// If there is not a change of template, and there is no converter, and newArg and prevArg are both truthy, return false to cancel update.
@@ -2810,7 +2813,7 @@ $extend($tags["if"], {
28102813
// Boolean value of all args are unchanged (falsey), so return false to cancel update
28112814
return false;
28122815
},
2813-
onAfterLink: function(tagCtx, linkCtx, eventArgs) {
2816+
onAfterLink: function(tagCtx, linkCtx, ctx, ev, eventArgs) {
28142817
if (eventArgs) {
28152818
this.domChange(tagCtx, linkCtx, eventArgs);
28162819
}
@@ -3050,7 +3053,7 @@ $views.getCtx = function(param) { // Return value of ctx.foo, including for comp
30503053

30513054
$sub._cp = function(paramVal, params, view) { // Get compiled contextual parameters (or properties) ~foo=expr.
30523055
if (view.linked) { // In JsViews, returns [view, linkFn] where linkFn is compiled function for expression
3053-
params = "{:" + params + "}";
3056+
params = delimOpenChar1 + ":" + params + delimCloseChar0;
30543057
var tmpl = view.tmpl,
30553058
links = topView.tmpl.links, // Use topView links, as for compiled top-level linking expressions. To do - should this ever get disposed?
30563059
linkFn = links[params];

jquery.views.min.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jquery.views.min.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)