Skip to content

Commit

Permalink
Commit 81 (v0.9.81 - Beta)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
BorisMoore committed Oct 2, 2016
1 parent 65112e9 commit fd836ea
Show file tree
Hide file tree
Showing 29 changed files with 1,154 additions and 997 deletions.
4 changes: 2 additions & 2 deletions jquery.observable.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*! JsObservable v0.9.80 (Beta): http://jsviews.com/#jsobservable */
/*! JsObservable v0.9.81 (Beta): http://jsviews.com/#jsobservable */
/*
* Subcomponent of JsViews
* Data change events for data-linking
Expand Down Expand Up @@ -44,7 +44,7 @@ if (!$ || !$.fn) {
throw "JsObservable requires jQuery"; // We require jQuery
}

var versionNumber = "v0.9.74",
var versionNumber = "v0.9.81",
$observe, $observable,

$views = $.views =
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.

153 changes: 78 additions & 75 deletions jquery.views.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*! jquery.views.js v0.9.80 (Beta): http://jsviews.com/ */
/*! jquery.views.js v0.9.81 (Beta): http://jsviews.com/ */
/*
* Interactive data-driven views using JsRender templates.
* Subcomponent of JsViews
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 = "v0.9.80",
var versionNumber = "v0.9.81",
requiresStr = "JsViews requires ";

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

if (tag.onUnbind) {
tag.onUnbind(tag.tagCtx, linkCtx, tag.ctx, ev, eventArgs);
}

sourceValue = tag.tagName === ":" // Call convertVal if it is a {{cvt:...}} - otherwise call renderTag
? $sub._cnvt(tag.cvt, view, sourceValue[0])
: $sub._tag(tag, view, view.tmpl, sourceValue, true, onError);
Expand All @@ -335,7 +336,7 @@ function propertyChangeHandler(ev, eventArgs, linkFn) {

if (tag) {
tag._er = hasError;
callAfterLink(tag, eventArgs);
callAfterLink(tag, ev, eventArgs);
}
}

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

if (tag) {
// Initialize the tag with element references
tag._.unlinked = true; // Set to unlinked, so initialization is triggered after re-rendering, e.g. for setting linkedElem, and calling onBind
tag.parentElem = tag.parentElem || (linkCtx.expr || tag._elCnt) ? target : targetParent;
prevNode = tag._prv;
nextNode = tag._nxt;
}
if (!renders) {
if (attr === HTML && tag && tag.onBeforeLink) {
tag.onBeforeLink();
}
return;
}

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

if (tag && tag.onBeforeLink) {
tag.onBeforeLink();
}
// Insert and link new content
promise = view.link(view.data, target, prevNode, nextNode, sourceValue, tag && {tag: tag._tgId, lazyLink: tag.tagCtx.props.lazyLink});
} else {
Expand All @@ -541,9 +537,6 @@ function updateContent(sourceValue, linkCtx, attr, tag) {
if (renders) {
$target.empty();
}
if (tag && tag.onBeforeLink) {
tag.onBeforeLink();
}
if (renders) {
promise = view.link(source, target, prevNode, nextNode, sourceValue, tag && {tag: tag._tgId});
}
Expand Down Expand Up @@ -800,7 +793,7 @@ function observeAndBind(linkCtx, source, target) { //TODO? linkFnArgs) {;

if (tag) {
// Use the 'depends' paths set on linkCtx.tag - which may have been set on declaration
// or in events: init, render, onBeforeLink, onAfterLink etc.
// or in events: init, render, onAfterLink etc.
depends = tag.depends || depends;
depends = $isFunction(depends) ? tag.depends(tag) : depends;
linkedElem = tag.linkedElem;
Expand Down Expand Up @@ -854,20 +847,13 @@ function observeAndBind(linkCtx, source, target) { //TODO? linkFnArgs) {;
bindTo(binding, tag, linkedElem && linkedElem[0] || target, cvtBk);
}
if (tag) {
if (tag.onAfterBind) {
tag.onAfterBind(binding);
}
if (!tag.flow && !tag._.inline) {
target.setAttribute(jsvAttrStr, (target.getAttribute(jsvAttrStr)||"") + "#" + bindId + "^/" + bindId + "^");
tag._tgId = "" + bindId;
}
}
}
if (linkedElem && linkedElem[0]) {
if (tag._.radio) {
linkedElem = linkedElem.find(RADIOINPUT);
}

l = linkedElem.length;
while (l--) {
linkedElem[l]._jsvBnd = linkedElem[l]._jsvBnd || (target._jsvBnd + "+");
Expand Down Expand Up @@ -1421,9 +1407,6 @@ function viewLink(outerData, parentNode, prevNode, nextNode, html, refresh, cont
tag._prv = elem;
}
tag._elCnt = linkInfo.elCnt;
if (tag.onBeforeLink) {
tag.onBeforeLink();
}
// We data-link depth-first ("on the way in"), which is better for perf - and allows setting parent tags etc.
view = tag.tagCtx.view;
addDataBinding(undefined, tag._prv, view, linkInfo.id);
Expand Down Expand Up @@ -1634,11 +1617,11 @@ function addDataBinding(linkMarkup, node, currentView, boundTagId, isLink, data,
attr = tokens[1];
tagExpr = tokens[3];
while (linkExpressions[0] && linkExpressions[0][4] === "else") { // If this is {someTag...} and is followed by an {else...} add to tagExpr
tagExpr += "}{" + linkExpressions.shift()[3];
tagExpr += delimCloseChar1 + delimOpenChar0 + linkExpressions.shift()[3];
hasElse = true;
}
if (hasElse) { // If an {else} has been added, need also to add closing {{/someTag}}
tagExpr += "}{{/" + tokens[4] + "}";
tagExpr += delimCloseChar1 + delimOpenChar0 + delimOpenChar1 + "/" + tokens[4] + delimCloseChar0;
}
linkCtx = {
type: isLink ? "top" : "link",
Expand Down Expand Up @@ -1792,54 +1775,60 @@ function normalizeLinkTag(linkMarkup, twoway) {
// Methods for views and tags
//===========================

function callAfterLink(tag, eventArgs) {
var $linkedElem, linkedElem, radioButtons, val, l, linkedTag, oldTrig, newTrig,
function callAfterLink(tag, ev, eventArgs) {
var $linkedElem, linkedElem, radioButtons, val, l, linkedTag, oldTrig, newTrig, tagProps, propsExpr, linkedElemView, prop, propDef,
tagCtx = tag.tagCtx,
view = tagCtx.view,
props = tagCtx.props,
linkCtx = tag.linkCtx;

if (tag._.unlinked) { // First call to onAfterLink, or first call after onUpdate: updateContent. Initialize and call onBind and set properties
if (tag.linkedElement !== undefined) {
// linkedElement: - selector for identifying linked element in template/rendered content
tag.linkedElem = tag._.inline ? tag.contents(true, tag.linkedElement || "*").first() : $(linkCtx.elem);
}
if (tag.onBind) {
tag.onBind(tagCtx, linkCtx, tag.ctx, ev, eventArgs);
}
}

if (tag.onAfterLink) {
tag.onAfterLink(tagCtx, linkCtx, eventArgs);
tag.onAfterLink(tagCtx, linkCtx, tag.ctx, ev, eventArgs);
}

tag._.unlinked = undefined;
$linkedElem = tag.targetTag ? tag.targetTag.linkedElem : tag.linkedElem;
if (!tag.noVal && (linkedElem = $linkedElem && $linkedElem[0])) {
if (radioButtons = tag._.radio) {
$linkedElem = $linkedElem.find(RADIOINPUT);
}
if (radioButtons || !tag._.chging) {
val = tag.cvtArgs()[0];
if (linkedElem = $linkedElem && $linkedElem[0]) {
if (!tag.noVal) {
if (!tag._.chging) {
val = tag.cvtArgs()[0];

if (radioButtons || linkedElem !== linkCtx.elem) {
l = $linkedElem.length;
while (l--) {
linkedElem = $linkedElem[l];
linkedTag = linkedElem._jsvLkEl;
if (tag._.inline && (!linkedTag || linkedTag !== tag && linkedTag.targetTag !== tag)) {
// For data-linked tags, identify the linkedElem with the tag, for "to" binding
// (For data-linked elements, if not yet bound, we identify later when the linkCtx.elem is bound)
linkedElem._jsvLkEl = tag;
bindTo(bindingStore[tag._tgId], tag, linkedElem);
linkedElem._jsvBnd = "&" + tag._tgId + "+"; // Add a "+" for cloned binding - so removing
// elems with cloned bindings will not remove the 'parent' binding from the bindingStore.
}
if (radioButtons) {
// For radio button, set to if val === value. For others set val() to val, below
linkedElem[CHECKED] = val === linkedElem.value;
if (linkedElem !== linkCtx.elem) {
l = $linkedElem.length;
while (l--) {
linkedElem = $linkedElem[l];
linkedTag = linkedElem._jsvLkEl;
if (tag._.inline && (!linkedTag || linkedTag !== tag && linkedTag.targetTag !== tag)) {
// For data-linked tags, identify the linkedElem with the tag, for "to" binding
// (For data-linked elements, if not yet bound, we identify later when the linkCtx.elem is bound)
linkedElem._jsvLkEl = tag;
bindTo(bindingStore[tag._tgId], tag, linkedElem);
linkedElem._jsvBnd = "&" + tag._tgId + "+"; // Add a "+" for cloned binding - so removing
// elems with cloned bindings will not remove the 'parent' binding from the bindingStore.
}
}
linkCtx._val = val;
}
linkCtx._val = val;
}
if (val !== undefined) {
if (!radioButtons && linkedElem.value !== undefined) {
if (linkedElem.type === CHECKBOX) {
linkedElem[CHECKED] = val && val !== "false";
} else {
$linkedElem.val(val);
if (val !== undefined) {
if (linkedElem.value !== undefined) {
if (linkedElem.type === CHECKBOX) {
linkedElem[CHECKED] = val && val !== "false";
} else if (linkedElem.type === "text") {
linkedElem.value = val;
}
} else if (linkedElem.contentEditable === TRUE) {
linkedElem.innerHTML = val;
}
} else if (linkedElem.contentEditable === TRUE) {
linkedElem.innerHTML = val;
}
}
}
Expand All @@ -1851,7 +1840,14 @@ function callAfterLink(tag, eventArgs) {
$linkedElem.width(props.width);
}
}
if (props.title !== undefined) {
$linkedElem.attr("title", props.title);
}
if (props["class"]) {
// This code supports dynamic binding to class - where it adds the class if absent, and removes/adds if a previous value is present
if (eventArgs && $linkedElem.hasClass(eventArgs.oldValue)) {
$linkedElem.removeClass(eventArgs.oldValue);
}
$linkedElem.addClass(props["class"]);
}
if (props.id) {
Expand Down Expand Up @@ -1887,6 +1883,8 @@ function bindTo(binding, tag, linkedElem, cvtBk) {
source = linkCtx.data,
paths = linkCtx.fn.paths;

tag = tag || linkedElem._jsvLkEl;

if (binding && paths) {
oldTrig = linkedElem._jsvTr || false;
if (tag) {
Expand Down Expand Up @@ -2064,6 +2062,9 @@ function removeViewBinding(bindId, linkedElemTag, elem) {
}
$linkedElem = tag.linkedElem;

if (tag.onUnbind) {
tag.onUnbind(tag.tagCtx, linkCtx, tag.ctx, true);
}
if (tag.onDispose) {
tag.onDispose();
}
Expand Down Expand Up @@ -2615,6 +2616,7 @@ $converters.merge = function(val) {

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

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

if (tag._evs) {
tag.onDispose();
tag.onUnbind();
}

activeElem.on(
Expand Down Expand Up @@ -2706,7 +2709,7 @@ $tags("on", {
onUpdate: function() {
return false;
},
onDispose: function() {
onUnbind: function() {
var self = this;
if (self.activeElem) {
self.activeElem.off(self._evs, self._sel, self._hlr);
Expand All @@ -2717,11 +2720,11 @@ $tags("on", {
});

$extend($tags["for"], {
//onUpdate: function(ev, eventArgs, tagCtxs) {
//onUpdate: function(ev, eventArgs, newTagCtxs) {
//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.
//var i, l, tci, prevArg;
//for (tci = 0; (prevArg = this.tagCtxs[tci]) && prevArg.args.length; tci++) {
// if (prevArg.args[0] !== tagCtxs[tci].args[0]) {
// if (prevArg.args[0] !== newTagCtxs[tci].args[0]) {
// return true;
// }
//}
Expand Down Expand Up @@ -2795,10 +2798,10 @@ $extend($tags["for"], {
});

$extend($tags["if"], {
onUpdate: function(ev, eventArgs, tagCtxs) {
onUpdate: function(ev, eventArgs, newTagCtxs) {
var tci, prevArg, different;
for (tci = 0; (prevArg = this.tagCtxs[tci]); tci++) {
different = prevArg.props.tmpl !== tagCtxs[tci].props.tmpl || prevArg.args.length && !(prevArg = prevArg.args[0]) !== !tagCtxs[tci].args[0];
different = prevArg.props.tmpl !== newTagCtxs[tci].props.tmpl || prevArg.args.length && !(prevArg = prevArg.args[0]) !== !newTagCtxs[tci].args[0];
if ((!this.convert && !!prevArg) || different) {
return different;
// 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.
Expand All @@ -2810,7 +2813,7 @@ $extend($tags["if"], {
// Boolean value of all args are unchanged (falsey), so return false to cancel update
return false;
},
onAfterLink: function(tagCtx, linkCtx, eventArgs) {
onAfterLink: function(tagCtx, linkCtx, ctx, ev, eventArgs) {
if (eventArgs) {
this.domChange(tagCtx, linkCtx, eventArgs);
}
Expand Down Expand Up @@ -3050,7 +3053,7 @@ $views.getCtx = function(param) { // Return value of ctx.foo, including for comp

$sub._cp = function(paramVal, params, view) { // Get compiled contextual parameters (or properties) ~foo=expr.
if (view.linked) { // In JsViews, returns [view, linkFn] where linkFn is compiled function for expression
params = "{:" + params + "}";
params = delimOpenChar1 + ":" + params + delimCloseChar0;
var tmpl = view.tmpl,
links = topView.tmpl.links, // Use topView links, as for compiled top-level linking expressions. To do - should this ever get disposed?
linkFn = links[params];
Expand Down
4 changes: 2 additions & 2 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 fd836ea

Please sign in to comment.