Skip to content

Commit 8559dcf

Browse files
committed
Commit 87 (v0.9.87 - Beta)
Feature improvements: - Some improvements and minor bug fixes on lifecycle events Minor breaking change: - onBeforeChange event handler behavior has been simplified and improved. It is called only when a tag is being updated as a result of observable changes. It is no longer call when a tag with two-way binding is about to call updateValues(). In the latter scenario, a new onBeforeUpdateVal event is instead called. Bug fixes: - Several minor bug fixes Unit tests: - Several additional unit tests Documentation: - New documentation added for view.ctxPrm() API: http://www.jsviews.com/#jsvviewobject@ctxprm
1 parent 3ee4dc6 commit 8559dcf

28 files changed

+2337
-1050
lines changed

jquery.observable.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*! JsObservable v0.9.86 (Beta): http://jsviews.com/#jsobservable */
1+
/*! JsObservable v0.9.87 (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.86",
47+
var versionNumber = "v0.9.87",
4848
_ocp = "_ocp", // Observable contextual parameter
4949
$observe, $observable,
5050

@@ -829,7 +829,8 @@ if (!$.observe) {
829829
|| leaf;
830830
getter = property;
831831
setter = getter.set === true ? getter : getter.set;
832-
property = getter.call(leaf); // get - only treated as getter if also a setter. Otherwise it is simply a property of type function. See unit tests 'Can observe properties of type function'.
832+
property = getter.call(leaf); // get - only treated as getter if also a setter. Otherwise it is simply a property of type function.
833+
// See unit tests 'Can observe properties of type function'.
833834
}
834835
}
835836

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

+35-40
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*! jquery.views.js v0.9.86 (Beta): http://jsviews.com/ */
1+
/*! jquery.views.js v0.9.87 (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.86",
47+
var versionNumber = "v0.9.87",
4848
requiresStr = "JsViews requires ";
4949

5050
if (!$ || !$.fn) {
@@ -158,8 +158,8 @@ function updateValues(sourceValues, tagElse, bindId, ev) {
158158
// Observably update data values targeted by bindTo
159159
// Called when linkedElem changes: called as updateValues(sourceValues, tagElse, bindId, ev) - this: undefined
160160
// Called directly as tag.updateValues(val1, val2, val3, ...) - this: tag
161-
var cancel, linkCtx, cvtBack, cnvtName, target, view, binding, sourceValue, origVals, sourceElem, sourceEl,
162-
oldLinkCtx, onBeforeChange, onAfterChange, tos, to, tcpTag, eventArgs, exprOb, contextCb, l, srcVals, tag;
161+
var linkCtx, cvtBack, cnvtName, target, view, binding, sourceValue, origVals, sourceElem, sourceEl,
162+
oldLinkCtx, tos, to, tcpTag, exprOb, contextCb, l, tag;
163163

164164
if (bindId && bindId._tgId) {
165165
tag = bindId;
@@ -172,6 +172,7 @@ function updateValues(sourceValues, tagElse, bindId, ev) {
172172
// The binding has a 'to' field, which is of the form [tosForElse0, tosForElse1, ...]
173173
// where tosForElseX is of the form [[[targetObject, toPath], [targetObject, toPath], ...], cvtBack]
174174
linkCtx = binding.linkCtx;
175+
linkCtx.ev = ev;
175176
sourceElem = linkCtx.elem;
176177
view = linkCtx.view;
177178
tag = linkCtx.tag;
@@ -181,8 +182,6 @@ function updateValues(sourceValues, tagElse, bindId, ev) {
181182
sourceValues = [];
182183
sourceValues[tos._cxp.ind] = sourceValue;
183184
}
184-
onBeforeChange = changeHandler(view, onBeforeChangeStr);
185-
onAfterChange = changeHandler(view, onAfterChangeStr);
186185

187186
if (cnvtName = tag && tag.convertBack) {
188187
if ($isFunction(cnvtName)) {
@@ -202,7 +201,7 @@ function updateValues(sourceValues, tagElse, bindId, ev) {
202201
origVals = sourceValues;
203202
if (cvtBack) {
204203
sourceValues = cvtBack.apply(tag, sourceValues);
205-
sourceValues = tos.length>1 ? sourceValues : [sourceValues];
204+
sourceValues = tos.length>1 ? sourceValues || []: [sourceValues];
206205
}
207206

208207
// Set linkCtx on view, dynamically, just during this handler call
@@ -220,17 +219,16 @@ function updateValues(sourceValues, tagElse, bindId, ev) {
220219
// binding to tag.ctx.foo._ocp - and we use original values, without applying cvtBack converter
221220
: sourceValues // Otherwise use the converted value
222221
)[l];
223-
eventArgs = {
224-
change: "change",
225-
data: tcpTag || target, // For tag contextual parameter, this is the tag
226-
path: to[1] || to.ind, // For tag contextual parameter, this is the index of the parameter in tag.bindTo
227-
value: sourceValue
228-
};
229-
if ((!onBeforeChange || !(cancel = onBeforeChange.call(linkCtx, ev, eventArgs) === false)) &&
230-
(!tag || !tag.onBeforeChange || !(cancel = tag.onBeforeChange(ev, eventArgs) === false))) {
231-
232-
if (tcpTag) {
233-
tcpTag.updateValue(sourceValue, to.ind, to.tagElse);
222+
if (sourceValue !== undefined && (!ev || !tag || !tag.onBeforeUpdateVal || tag.onBeforeUpdateVal(ev, {
223+
change: "change",
224+
data: target,
225+
path: to[1],
226+
index: l,
227+
tagElse: tagElse,
228+
value: sourceValue
229+
}) !== false)) {
230+
if (tcpTag) { // We are modifying a tag contextual parameter ~foo (e.g. from within block) so update 'owner' tag: tcpTag
231+
tcpTag.updateValue(sourceValue, to.ind, to.tagElse, undefined, ev);
234232
if (tcpTag.setValue) {
235233
tcpTag.setValue(sourceValue, to.ind, to.tagElse);
236234
}
@@ -253,17 +251,11 @@ function updateValues(sourceValues, tagElse, bindId, ev) {
253251
}
254252
}
255253
$observable(target).setProperty(to[1], sourceValue); // 2way binding change event - observably updating bound object
256-
if (onAfterChange) {
257-
onAfterChange.call(linkCtx, ev, eventArgs);
258-
}
259-
}
260-
if (tag && tag.onAfterChange) {
261-
tag.onAfterChange(ev, eventArgs);
262254
}
263255
}
264256
}
265257
}
266-
sourceElem._jsvChg = undefined; // Clear marker
258+
sourceElem._jsvChg = linkCtx.ev = undefined; // Clear marker
267259
view.linkCtx = oldLinkCtx;
268260
}
269261
}
@@ -353,11 +345,11 @@ function onDataLinkedTagChange(ev, eventArgs) {
353345
parentElem = target.parentNode,
354346
view = linkCtx.view,
355347
oldLinkCtx = view.linkCtx,
356-
onEvent = changeHandler(view, onBeforeChangeStr);
348+
onEvent = eventArgs && changeHandler(view, onBeforeChangeStr, tag);
357349

358350
// Set linkCtx on view, dynamically, just during this handler call
359351
view.linkCtx = linkCtx;
360-
if (parentElem && (!onEvent || !(eventArgs && onEvent.call(linkCtx, ev, eventArgs) === false))
352+
if (parentElem && (!onEvent || onEvent.call(linkCtx, ev, eventArgs) !== false)
361353
// If data changed, the ev.data is set to be the path. Use that to filter the handler action...
362354
&& (!eventArgs || ev.data.prop === "*" || ev.data.prop === eventArgs.path)) {
363355

@@ -409,6 +401,9 @@ function onDataLinkedTagChange(ev, eventArgs) {
409401
observeAndBind(linkCtx, source, target);
410402
}
411403
view.linkCtx = oldLinkCtx;
404+
if (eventArgs && (onEvent = changeHandler(view, onAfterChangeStr, tag))) {
405+
onEvent.call(linkCtx, ev, eventArgs);
406+
}
412407
return;
413408
}
414409

@@ -436,9 +431,7 @@ function onDataLinkedTagChange(ev, eventArgs) {
436431
// will trigger the parent tags before the child tags.
437432
observeAndBind(linkCtx, source, target);
438433
}
439-
if (updateContent(sourceValue, linkCtx, attr, tag) && eventArgs && (onEvent = changeHandler(view, onAfterChangeStr))) {
440-
onEvent.call(linkCtx, ev, eventArgs);
441-
}
434+
updateContent(sourceValue, linkCtx, attr, tag);
442435
linkCtx._noUpd = 0; // For data-link="^{...}" remove _noUpd flag so updates on subsequent calls
443436

444437
if (tag) {
@@ -453,6 +446,9 @@ function onDataLinkedTagChange(ev, eventArgs) {
453446
observeAndBind(linkCtx, source, target);
454447
}
455448

449+
if (eventArgs && (onEvent = changeHandler(view, onAfterChangeStr, tag))) {
450+
onEvent.call(linkCtx, ev, eventArgs);
451+
}
456452
// Remove dynamically added linkCtx from view
457453
view.linkCtx = oldLinkCtx;
458454
}
@@ -639,8 +635,8 @@ function updateContent(sourceValue, linkCtx, attr, tag) {
639635

640636
function arrayChangeHandler(ev, eventArgs) {
641637
var self = this,
642-
onBeforeChange = changeHandler(self, onBeforeChangeStr),
643-
onAfterChange = changeHandler(self, onAfterChangeStr);
638+
onBeforeChange = changeHandler(self, onBeforeChangeStr, self.tag),
639+
onAfterChange = changeHandler(self, onAfterChangeStr, self.tag);
644640
if (!onBeforeChange || onBeforeChange.call(self, ev, eventArgs) !== false) {
645641
if (eventArgs) {
646642
// This is an observable action (not a trigger/handler call from pushValues, or similar, for which eventArgs will be null)
@@ -2089,9 +2085,7 @@ function resolveDataTargetPath(targetPath, source, contextCb) {
20892085
targetPath = to[1];
20902086
}
20912087
to = to || [source, path];
2092-
if (obsCtxPrm) {
2093-
to._cxp = obsCtxPrm;
2094-
}
2088+
to._cxp = topCp;
20952089
return to;
20962090
}
20972091

@@ -2270,9 +2264,9 @@ function inputAttrib(elem) {
22702264
return elem.type === CHECKBOX ? elem[CHECKED] : elem.value;
22712265
}
22722266

2273-
function changeHandler(view, name) {
2267+
function changeHandler(view, name, tag) {
22742268
// Get onBeforeChange, onAfterChange, onAfterCreate handler - if there is one;
2275-
return view.ctx[name] || $views.helpers[name];
2269+
return tag && tag[name] || view.ctx[name] && view.ctxPrm(name) || $views.helpers[name];
22762270
}
22772271

22782272
//========================== Initialize ==========================
@@ -3447,7 +3441,7 @@ $sub._gccb = function(view) { // Return a callback for accessing the context of
34473441
return function(path, object, depth) {
34483442
// TODO consider only calling the contextCb on the initial token in path '~a.b.c' and not calling again on
34493443
// the individual tokens, 'a', 'b', 'c'... Currently it is called multiple times
3450-
var tokens, tag, items, helper, last, nextPath, l, obsCtxPrm, addedTagCpDep, key;
3444+
var tokens, tag, items, helper, last, nextPath, l, obsCtxPrm, addedTagCpDep, key, bindTo;
34513445
if (view && path) {
34523446
if (path._cpfn) {
34533447
return path._cpfn.call(view.tmpl, object, view, $sub); // exprOb for computed property
@@ -3486,10 +3480,11 @@ $sub._gccb = function(view) { // Return a callback for accessing the context of
34863480
items = [helper]; // Contextual parameter
34873481
if ((tag = obsCtxPrm.tag) && tag.convert) {
34883482
// If there is a converter, it might mix inputs, so tag contextual param needs to depends on all bound args/props.
3489-
l = tag.bindTo.length;
3483+
bindTo = tag.bindTo || [0];
3484+
l = bindTo.length;
34903485
while (l--) {
34913486
if (depth !== undefined && l !== obsCtxPrm.ind) {
3492-
key = tag.bindTo[l];
3487+
key = bindTo[l];
34933488
addedTagCpDep = [helper[0], tag.tagCtx.params[+key === key ? "args" : "props"]];
34943489
addedTagCpDep._cxp = obsCtxPrm;
34953490
items.push(addedTagCpDep); // Added dependency for tag contextual parameter

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.

jsrender.js

+32-36
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*! JsRender v0.9.86 (Beta): http://jsviews.com/#jsrender */
1+
/*! JsRender v0.9.87 (Beta): http://jsviews.com/#jsrender */
22
/*! **VERSION FOR WEB** (For NODE.JS see http://jsviews.com/download/jsrender-node.js) */
33
/*
44
* Best-of-breed templating in browser or on Node.js.
@@ -44,7 +44,7 @@ var setGlobals = $ === false; // Only set globals if script block in browser (no
4444

4545
$ = $ && $.fn ? $ : global.jQuery; // $ is jQuery passed in by CommonJS loader (Browserify), or global jQuery.
4646

47-
var versionNumber = "v0.9.86",
47+
var versionNumber = "v0.9.87",
4848
jsvStoreName, rTag, rTmplString, topView, $views, $expando,
4949
_ocp = "_ocp", // Observable contextual parameter
5050

@@ -194,7 +194,7 @@ function getMethod(baseMethod, method) {
194194

195195
function tagHandlersFromProps(tag, tagCtx) {
196196
for (var prop in tagCtx.props) {
197-
if (rHasHandlers.test(prop) && !(tag[prop] && tag[prop].fix)) { // Don't override handlers with fix expando
197+
if (rHasHandlers.test(prop) && !(tag[prop] && tag[prop].fix)) { // Don't override handlers with fix expando (used in datepicker and spinner)
198198
tag[prop] = getMethod(tag.constructor.prototype[prop], tagCtx.props[prop]);
199199
// Copy over the onFoo props, convert and convertBack from tagCtx.props to tag (overrides values in tagDef).
200200
// Note: unsupported scenario: if handlers are dynamically added ^onFoo=expression this will work, but dynamically removing will not work.
@@ -228,10 +228,12 @@ function JsViewsError(message) {
228228
}
229229

230230
function $extend(target, source) {
231-
for (var name in source) {
232-
target[name] = source[name];
231+
if (target) {
232+
for (var name in source) {
233+
target[name] = source[name];
234+
}
235+
return target;
233236
}
234-
return target;
235237
}
236238

237239
(JsViewsError.prototype = new Error()).constructor = JsViewsError;
@@ -354,33 +356,18 @@ function contextParameter(key, value, isContextCb) {
354356

355357
if (key in store || key in (store = $helpers)) {
356358
res = store && store[key];
357-
if (res && $isFunction(res)) {
358-
// If a helper is of type function, and not already wrapped, we will wrap it, so if called with no this pointer it will be called with the
359-
// view as 'this' context. If the helper ~foo() was in a data-link expression, the view will have a 'temporary' linkCtx property too.
360-
// Note that helper functions on deeper paths will have specific this pointers, from the preceding path.
361-
// For example, ~util.foo() will have the ~util object as 'this' pointer
362-
wrapped = function() {
363-
return res.apply((!this || this === global) ? storeView : this, arguments);
364-
};
365-
$extend(wrapped, res); // Attach same expandos (if any) to the wrapped function
366-
wrapped._vw = storeView;
367-
return wrapped;
368-
}
369359
if (key === "tag" || key === "root" || key === "parentTags" || storeView._.it === key ) {
370360
return res;
371361
}
372362
} else {
373363
store = undefined;
374364
}
375-
if (storeView.linked || storeView.tagCtx) { // Data-linked view, or tag instance
365+
if (!res || !$isFunction(res) && storeView.linked || storeView.tagCtx) { // Data-linked view, or tag instance
376366
if (!res || !res._cxp) {
377367
// Not a contextual parameter
378368
if (store !== $helpers) {
379-
if (res === undefined || (storeView.tagCtx ? (storeView = storeView.tagCtx.view) : storeView).root.ctx[key] !== res) {
380-
// This is not an instance parameter (passed in with tmpl.link() call)
381-
// Set storeView to tag (if this is a tag.ctxPrm() call) or to root view (view under top view)
382-
storeView = storeView.ctx && storeView.ctx.tag || storeView.root;
383-
}
369+
// Set storeView to tag (if this is a tag.ctxPrm() call) or to root view (view under top view)
370+
storeView = storeView.ctx && storeView.ctx.tag || storeView.root;
384371
store = storeView._ocps;
385372
res = store && store[key] || res;
386373
}
@@ -406,7 +393,18 @@ function contextParameter(key, value, isContextCb) {
406393
: res[0]._ocp; // Observable contextual parameter (uninitialized, or initialized as static expression, so no path dependencies)
407394
}
408395
}
409-
return res;
396+
if (res && $isFunction(res)) {
397+
// If a helper is of type function, and not already wrapped, we will wrap it, so if called with no this pointer it will be called with the
398+
// view as 'this' context. If the helper ~foo() was in a data-link expression, the view will have a 'temporary' linkCtx property too.
399+
// Note that helper functions on deeper paths will have specific this pointers, from the preceding path.
400+
// For example, ~util.foo() will have the ~util object as 'this' pointer
401+
wrapped = function() {
402+
return res.apply((!this || this === global) ? storeView : this, arguments);
403+
};
404+
$extend(wrapped, res); // Attach same expandos (if any) to the wrapped function
405+
wrapped._vw = storeView;
406+
}
407+
return wrapped || res;
410408
}
411409

412410
function getTemplate(tmpl) {
@@ -514,9 +512,7 @@ function convertArgs(converter, bound, tagElse) { // tag.cvtArgs()
514512
bindTo = bindTo || [0];
515513
converter = converter.apply(tag, boundArgs || args);
516514
l = bindTo.length;
517-
if (l < 2) {
518-
converter = [converter];
519-
}
515+
converter = l < 2 ? [converter] : converter || [];
520516
if (bound) { // Call to bndArgs convertBoundArgs() - so apply converter to all boundArgs
521517
args = converter; // The array of values returned from the converter
522518
} else { // Call to cvtArgs()
@@ -558,7 +554,7 @@ function getResource(resourceType, itemName) {
558554
function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) {
559555
parentView = parentView || topView;
560556
var tag, tag_, tagDef, template, tags, attr, parentTag, l, m, n, itemRet, tagCtx, tagCtxCtx, ctxPrm, bindTo,
561-
content, callInit, mapDef, thisMap, args, props, initialTmpl, tagDataMap, contentCtx, key,
557+
content, callInit, mapDef, thisMap, args, props, tagDataMap, contentCtx, key,
562558
i = 0,
563559
ret = "",
564560
linkCtx = parentView.linkCtx || 0,
@@ -663,7 +659,6 @@ function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) {
663659

664660
if (!i) {
665661
if (callInit) {
666-
initialTmpl = tag.template;
667662
tag.init(tagCtx, linkCtx, tag.ctx);
668663
callInit = undefined;
669664
}
@@ -707,12 +702,13 @@ function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) {
707702
// to provide a contentView for the tag, which will correctly dispose bindings if deleted. The 'tmpl' for this view will
708703
// be a dumbed down template which will always return the itemRet string (no matter what the data is). The itemRet string
709704
// is not compiled as template markup, so can include "{{" or "}}" without triggering syntax errors
710-
itemRet = renderWithViews({
711-
fn: function() { // 'Dumbed down' template which always renders 'static' itemRet string
712-
return itemRet;
713-
},
714-
links: []},
715-
parentView.data, undefined, true, parentView, undefined, undefined, tag);
705+
tmpl = { // 'Dumbed down' template which always renders 'static' itemRet string
706+
links: []
707+
};
708+
tmpl.render = tmpl.fn = function() {
709+
return itemRet;
710+
};
711+
itemRet = renderWithViews(tmpl, parentView.data, undefined, true, parentView, undefined, undefined, tag);
716712
}
717713
}
718714
if (!args.length) {

jsrender.min.js

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

jsrender.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)