Skip to content

Commit 0eb921e

Browse files
committed
v1.0.10 release
Minor bug fixes: See #451 - Binding to a computed function with no getter, such as <input data-link="name()"/> with name.set undefined leads to an exception: "name is not a function". - Tag two-way binding to a computed observable, such as {^{mytag name()/}} can trigger onBind callback twice.2
1 parent 51c1947 commit 0eb921e

29 files changed

+438
-263
lines changed

jquery.observable.js

+12-10
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
/*! JsObservable v1.0.9: http://jsviews.com/#jsobservable */
1+
/*! JsObservable v1.0.10: http://jsviews.com/#jsobservable */
22
/*
33
* Subcomponent of JsViews
44
* Data change events for data-linking
55
*
6-
* Copyright 2020, Boris Moore
6+
* Copyright 2021, Boris Moore
77
* Released under the MIT License.
88
*/
99

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

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

@@ -897,7 +897,7 @@ if (!$.observe) {
897897
return this._data;
898898
},
899899

900-
setProperty: function(path, value, nonStrict) {
900+
setProperty: function(path, value, nonStrict, isCpfn) {
901901
path = path || "";
902902
var key, pair, parts, tempBatch,
903903
multi = path + "" !== path, // Hash of paths
@@ -937,7 +937,7 @@ if (!$.observe) {
937937
object = object[parts.shift()];
938938
}
939939
if (object) {
940-
self._setProperty(object, parts[0], value, nonStrict);
940+
self._setProperty(object, parts[0], value, nonStrict, isCpfn);
941941
}
942942
}
943943
}
@@ -949,12 +949,13 @@ if (!$.observe) {
949949
return this;
950950
},
951951

952-
_setProperty: function(leaf, path, value, nonStrict) {
952+
_setProperty: function(leaf, path, value, nonStrict, isCpfn) {
953953
var setter, getter, removeProp, eventArgs, view,
954954
property = path ? leaf[path] : leaf;
955-
956-
if (property !== value || nonStrict && property != value) {
957-
if ($isFunction(property) && property.set) {
955+
if ($isFunction(property) && !$isFunction(value)) {
956+
if (isCpfn && !property.set) {
957+
return; // getter function with no setter defined. So will not trigger update
958+
} else if (property.set) {
958959
// Case of property setter/getter - with convention that property is getter and property.set is setter
959960
view = leaf._vw // Case of JsViews 2-way data-linking to an observable context parameter, with a setter.
960961
// The view will be the this pointer for getter and setter. Note: this is the one scenario where path is "".
@@ -964,7 +965,8 @@ if (!$.observe) {
964965
property = getter.call(view); // get - only treated as getter if also a setter. Otherwise it is simply a property of type function.
965966
// See unit tests 'Can observe properties of type function'.
966967
}
967-
968+
}
969+
if (property !== value || nonStrict && property != value) {
968970
// Optional non-strict equality, since serializeArray, and form-based editors can map numbers to strings, etc.
969971
// Date objects don't support != comparison. Treat as special case.
970972
if (!(property instanceof Date && value instanceof Date) || property > value || property < value) {

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

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*! jquery.views.js v1.0.9: http://jsviews.com/ */
1+
/*! jquery.views.js v1.0.10: 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 2020, Boris Moore
10+
* Copyright 2021, 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.9",
47+
var versionNumber = "v1.0.10",
4848
requiresStr = "jquery.views.js requires ";
4949

5050
if (!$ || !$.fn) {
@@ -294,7 +294,7 @@ function updateValues(sourceValues, tagElse, async, bindId, ev) {
294294
exprOb = exprOb.sb;
295295
}
296296
}
297-
$observable(target, async).setProperty(to[1], sourceValue); // 2way binding change event - observably updating bound object
297+
$observable(target, async).setProperty(to[1], sourceValue, undefined, to.isCpfn); // 2way binding change event - observably updating bound object
298298
}
299299
}
300300
}
@@ -2156,7 +2156,7 @@ function defineBindToDataTargets(binding, tag, cvtBk) {
21562156
// we bind to the path on the returned object, exprOb.ob, as target. Otherwise our target is the first path, paths[0], which we will convert
21572157
// with contextCb() for paths like ~a.b.c or #x.y.z
21582158

2159-
var pathIndex, path, lastPath, bindtoOb, to, bindTo, paths, k, obsCtxPrm, linkedCtxParam, contextCb, targetPaths, bindTos, fromIndex,
2159+
var pathIndex, path, lastPath, bindtoOb, to, bindTo, paths, k, obsCtxPrm, linkedCtxParam, contextCb, targetPaths, bindTos, fromIndex, isCpfn,
21602160
tagElse = 1,
21612161
tos = [],
21622162
linkCtx = binding.linkCtx,
@@ -2194,11 +2194,12 @@ function defineBindToDataTargets(binding, tag, cvtBk) {
21942194
path = lastPath = lastPath.sb;
21952195
}
21962196
path = lastPath.sb || path && path.path;
2197+
isCpfn = lastPath._cpfn && !lastPath.sb; // leaf binding to computed property/function "a.b.c()"
21972198
lastPath = path ? path.slice(1) : bindtoOb.path;
21982199
}
21992200
to = path
22002201
? [bindtoOb, // 'exprOb' for this expression and view-binding. So bindtoOb.ob is current object returned by expression.
2201-
lastPath]
2202+
lastPath]
22022203
: resolveDataTargetPath(lastPath, source, contextCb); // Get 'to' for target path: lastPath
22032204
} else {
22042205
// Contextual parameter ~foo with no external binding - has ctx.foo = [{_ocp: xxx}] and binds to ctx.foo._ocp
@@ -2214,6 +2215,7 @@ function defineBindToDataTargets(binding, tag, cvtBk) {
22142215
// This is a binding for a tag contextual parameter (e.g. <input data-link="~wd"/> within a tag block content
22152216
to = obsCtxPrm;
22162217
}
2218+
to.isCpfn = isCpfn;
22172219
bindTos.unshift(to);
22182220
}
22192221
}
@@ -2761,7 +2763,7 @@ function addLinkMethods(tagOrView) { // tagOrView is View prototype or tag insta
27612763
indexFrom = indexFrom || 0;
27622764
tagElse = tagElse || 0;
27632765

2764-
var linkedElem, linkedEl, linkedCtxParam, linkedCtxPrmKey, indexTo, linkedElems, newVal,
2766+
var linkedElem, linkedEl, linkedCtxParam, indexTo, linkedElems, newVal,
27652767
tagCtx = theTag.tagCtxs[tagElse];
27662768

27672769
if (tagCtx._bdArgs && (eventArgs || val !== undefined) && tagCtx._bdArgs[indexFrom]===val
@@ -2782,12 +2784,10 @@ function addLinkMethods(tagOrView) { // tagOrView is View prototype or tag insta
27822784
}
27832785
}
27842786

2785-
if (val !== undefined && (linkedCtxParam = theTag.linkedCtxParam) && linkedCtxParam[indexFrom]
2787+
if (val !== undefined && (linkedCtxParam = theTag.linkedCtxParam) && linkedCtxParam[indexFrom]) {
27862788
// If this setValue call corresponds to a tag contextual parameter and the tag has a converter, then we need to set the
27872789
// value of this contextual parameter (since it is not directly bound to the tag argument/property when there is a converter).
2788-
&& (linkedCtxPrmKey = linkedCtxParam[indexFrom])
2789-
) {
2790-
tagCtx.ctxPrm(linkedCtxPrmKey, val);
2790+
tagCtx.ctxPrm(linkedCtxParam[indexFrom], val);
27912791
}
27922792
indexTo = theTag._.toIndex[indexFrom];
27932793
if (indexTo !== undefined) {

jquery.views.min.js

+3-3
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

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
/*! JsRender v1.0.9: http://jsviews.com/#jsrender */
1+
/*! JsRender v1.0.10: 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.
55
* Does not require jQuery, or HTML DOM
66
* Integrates with JsViews (http://jsviews.com/#jsviews)
77
*
8-
* Copyright 2020, Boris Moore
8+
* Copyright 2021, Boris Moore
99
* Released under the MIT License.
1010
*/
1111

@@ -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 = "v1.0.9",
47+
var versionNumber = "v1.0.10",
4848
jsvStoreName, rTag, rTmplString, topView, $views, $expando,
4949
_ocp = "_ocp", // Observable contextual parameter
5050

@@ -2231,7 +2231,8 @@ function parseParams(params, pathBindings, tmpl, isLinkExpr) {
22312231
}
22322232
if (rtPrnDot && bindings) {
22332233
// This is a binding to a path in which an object is returned by a helper/data function/expression, e.g. foo()^x.y or (a?b:c)^x.y
2234-
// We create a compiled function to get the object instance (which will be called when the dependent data of the subexpression changes, to return the new object, and trigger re-binding of the subsequent path)
2234+
// We create a compiled function to get the object instance (which will be called when the dependent data of the subexpression changes,
2235+
// to return the new object, and trigger re-binding of the subsequent path)
22352236
expr = pathStart[fnDp-1];
22362237
if (full.length - 1 > ind - (expr || 0)) { // We need to compile a subexpression
22372238
expr = $.trim(full.slice(expr, ind + all.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.

jsviews.js

+24-21
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
/*! jsviews.js v1.0.9 single-file version: http://jsviews.com/ */
1+
/*! jsviews.js v1.0.10 single-file version: http://jsviews.com/ */
22
/*! includes JsRender, JsObservable and JsViews - see: http://jsviews.com/#download */
33

44
/* Interactive data-driven views using JsRender templates */
55

66
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< JsRender >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
77
/* JsRender:
88
* See http://jsviews.com/#jsrender and http://github.com/BorisMoore/jsrender
9-
* Copyright 2020, Boris Moore
9+
* Copyright 2021, Boris Moore
1010
* Released under the MIT License.
1111
*/
1212

@@ -47,7 +47,7 @@ if (!$ || !$.fn) {
4747
throw "JsViews requires jQuery"; // We require jQuery
4848
}
4949

50-
var versionNumber = "v1.0.9",
50+
var versionNumber = "v1.0.10",
5151

5252
jsvStoreName, rTag, rTmplString, topView, $views, $observe, $observable, $expando,
5353
_ocp = "_ocp", // Observable contextual parameter
@@ -2235,7 +2235,8 @@ function parseParams(params, pathBindings, tmpl, isLinkExpr) {
22352235
}
22362236
if (rtPrnDot && bindings) {
22372237
// This is a binding to a path in which an object is returned by a helper/data function/expression, e.g. foo()^x.y or (a?b:c)^x.y
2238-
// We create a compiled function to get the object instance (which will be called when the dependent data of the subexpression changes, to return the new object, and trigger re-binding of the subsequent path)
2238+
// We create a compiled function to get the object instance (which will be called when the dependent data of the subexpression changes,
2239+
// to return the new object, and trigger re-binding of the subsequent path)
22392240
expr = pathStart[fnDp-1];
22402241
if (full.length - 1 > ind - (expr || 0)) { // We need to compile a subexpression
22412242
expr = $.trim(full.slice(expr, ind + all.length));
@@ -3021,7 +3022,7 @@ if (jsrToJq) { // Moving from jsrender namespace to jQuery namepace - copy over
30213022
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< JsObservable >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
30223023
/* JsObservable:
30233024
* See https://www.jsviews.com/#jsobservable and http://github.com/borismoore/jsviews
3024-
* Copyright 2020, Boris Moore
3025+
* Copyright 2021, Boris Moore
30253026
* Released under the MIT License.
30263027
*/
30273028

@@ -3847,7 +3848,7 @@ if (!$.observe) {
38473848
return this._data;
38483849
},
38493850

3850-
setProperty: function(path, value, nonStrict) {
3851+
setProperty: function(path, value, nonStrict, isCpfn) {
38513852
path = path || "";
38523853
var key, pair, parts, tempBatch,
38533854
multi = path + "" !== path, // Hash of paths
@@ -3887,7 +3888,7 @@ if (!$.observe) {
38873888
object = object[parts.shift()];
38883889
}
38893890
if (object) {
3890-
self._setProperty(object, parts[0], value, nonStrict);
3891+
self._setProperty(object, parts[0], value, nonStrict, isCpfn);
38913892
}
38923893
}
38933894
}
@@ -3899,12 +3900,13 @@ if (!$.observe) {
38993900
return this;
39003901
},
39013902

3902-
_setProperty: function(leaf, path, value, nonStrict) {
3903+
_setProperty: function(leaf, path, value, nonStrict, isCpfn) {
39033904
var setter, getter, removeProp, eventArgs, view,
39043905
property = path ? leaf[path] : leaf;
3905-
3906-
if (property !== value || nonStrict && property != value) {
3907-
if ($isFunction(property) && property.set) {
3906+
if ($isFunction(property) && !$isFunction(value)) {
3907+
if (isCpfn && !property.set) {
3908+
return; // getter function with no setter defined. So will not trigger update
3909+
} else if (property.set) {
39083910
// Case of property setter/getter - with convention that property is getter and property.set is setter
39093911
view = leaf._vw // Case of JsViews 2-way data-linking to an observable context parameter, with a setter.
39103912
// The view will be the this pointer for getter and setter. Note: this is the one scenario where path is "".
@@ -3914,7 +3916,8 @@ if (!$.observe) {
39143916
property = getter.call(view); // get - only treated as getter if also a setter. Otherwise it is simply a property of type function.
39153917
// See unit tests 'Can observe properties of type function'.
39163918
}
3917-
3919+
}
3920+
if (property !== value || nonStrict && property != value) {
39183921
// Optional non-strict equality, since serializeArray, and form-based editors can map numbers to strings, etc.
39193922
// Date objects don't support != comparison. Treat as special case.
39203923
if (!(property instanceof Date && value instanceof Date) || property > value || property < value) {
@@ -4308,7 +4311,7 @@ if (!$.observe) {
43084311
/* JsViews:
43094312
* Interactive data-driven views using templates and data-linking.
43104313
* See https://www.jsviews.com/#jsviews and http://github.com/BorisMoore/jsviews
4311-
* Copyright 2020, Boris Moore
4314+
* Copyright 2021, Boris Moore
43124315
* Released under the MIT License.
43134316
*/
43144317

@@ -4519,7 +4522,7 @@ function updateValues(sourceValues, tagElse, async, bindId, ev) {
45194522
exprOb = exprOb.sb;
45204523
}
45214524
}
4522-
$observable(target, async).setProperty(to[1], sourceValue); // 2way binding change event - observably updating bound object
4525+
$observable(target, async).setProperty(to[1], sourceValue, undefined, to.isCpfn); // 2way binding change event - observably updating bound object
45234526
}
45244527
}
45254528
}
@@ -6381,7 +6384,7 @@ function defineBindToDataTargets(binding, tag, cvtBk) {
63816384
// we bind to the path on the returned object, exprOb.ob, as target. Otherwise our target is the first path, paths[0], which we will convert
63826385
// with contextCb() for paths like ~a.b.c or #x.y.z
63836386

6384-
var pathIndex, path, lastPath, bindtoOb, to, bindTo, paths, k, obsCtxPrm, linkedCtxParam, contextCb, targetPaths, bindTos, fromIndex,
6387+
var pathIndex, path, lastPath, bindtoOb, to, bindTo, paths, k, obsCtxPrm, linkedCtxParam, contextCb, targetPaths, bindTos, fromIndex, isCpfn,
63856388
tagElse = 1,
63866389
tos = [],
63876390
linkCtx = binding.linkCtx,
@@ -6419,11 +6422,12 @@ function defineBindToDataTargets(binding, tag, cvtBk) {
64196422
path = lastPath = lastPath.sb;
64206423
}
64216424
path = lastPath.sb || path && path.path;
6425+
isCpfn = lastPath._cpfn && !lastPath.sb; // leaf binding to computed property/function "a.b.c()"
64226426
lastPath = path ? path.slice(1) : bindtoOb.path;
64236427
}
64246428
to = path
64256429
? [bindtoOb, // 'exprOb' for this expression and view-binding. So bindtoOb.ob is current object returned by expression.
6426-
lastPath]
6430+
lastPath]
64276431
: resolveDataTargetPath(lastPath, source, contextCb); // Get 'to' for target path: lastPath
64286432
} else {
64296433
// Contextual parameter ~foo with no external binding - has ctx.foo = [{_ocp: xxx}] and binds to ctx.foo._ocp
@@ -6439,6 +6443,7 @@ function defineBindToDataTargets(binding, tag, cvtBk) {
64396443
// This is a binding for a tag contextual parameter (e.g. <input data-link="~wd"/> within a tag block content
64406444
to = obsCtxPrm;
64416445
}
6446+
to.isCpfn = isCpfn;
64426447
bindTos.unshift(to);
64436448
}
64446449
}
@@ -6983,7 +6988,7 @@ function addLinkMethods(tagOrView) { // tagOrView is View prototype or tag insta
69836988
indexFrom = indexFrom || 0;
69846989
tagElse = tagElse || 0;
69856990

6986-
var linkedElem, linkedEl, linkedCtxParam, linkedCtxPrmKey, indexTo, linkedElems, newVal,
6991+
var linkedElem, linkedEl, linkedCtxParam, indexTo, linkedElems, newVal,
69876992
tagCtx = theTag.tagCtxs[tagElse];
69886993

69896994
if (tagCtx._bdArgs && (eventArgs || val !== undefined) && tagCtx._bdArgs[indexFrom]===val
@@ -7004,12 +7009,10 @@ function addLinkMethods(tagOrView) { // tagOrView is View prototype or tag insta
70047009
}
70057010
}
70067011

7007-
if (val !== undefined && (linkedCtxParam = theTag.linkedCtxParam) && linkedCtxParam[indexFrom]
7012+
if (val !== undefined && (linkedCtxParam = theTag.linkedCtxParam) && linkedCtxParam[indexFrom]) {
70087013
// If this setValue call corresponds to a tag contextual parameter and the tag has a converter, then we need to set the
70097014
// value of this contextual parameter (since it is not directly bound to the tag argument/property when there is a converter).
7010-
&& (linkedCtxPrmKey = linkedCtxParam[indexFrom])
7011-
) {
7012-
tagCtx.ctxPrm(linkedCtxPrmKey, val);
7015+
tagCtx.ctxPrm(linkedCtxParam[indexFrom], val);
70137016
}
70147017
indexTo = theTag._.toIndex[indexFrom];
70157018
if (indexTo !== undefined) {

jsviews.min.js

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

jsviews.min.js.map

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

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "jsviews",
3-
"version": "v1.0.9",
3+
"version": "v1.0.10",
44
"description": "Next-generation MVVM and MVP framework - built on top of JsRender templates. Bringing templates to life...",
55
"main": "./jsviews.js",
66
"author": {
@@ -35,7 +35,7 @@
3535
"browserify": "^11.0.1",
3636
"glob-stream": "^5.0.0",
3737
"gulp": "^3.9.0",
38-
"jsrender": "^1.0.9",
38+
"jsrender": "^1.0.10",
3939
"qunit": "^0.7.6"
4040
},
4141
"dependencies": {

test/browserify/bundles/1-bundle.js

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

0 commit comments

Comments
 (0)