Skip to content

Commit 3ee4dc6

Browse files
committed
Commit 86 (v0.9.86 - Beta)
BREAKING CHANGES: - tag.update() method renamed to tag.updateValue() (for consistency with tag.setValue() and new tag.updateValues() and tag.setValues() APIs - see below) - $.views.getCtx(tagCtx.ctx.foo) API introduced in commit 80 has been removed. Use view.ctxPrm("foo") instead. Feature improvements: Many new and improved features, particularly related to custom tag scenarios, as shown below. Documentation to follow on each of these improvemnts to provide more information and specifics... - Contextual parameters now support 2-way data-binding: <input data-link="~foo" /> - New APIs view.ctxPrm() and tag.ctxPrm(): Programmatically get/set contextual parameters: var fooValue = view.ctxPrm("foo"); // Get value of ~foo view.ctxPrm("foo", newValue); // Set (update observably) value of ~foo Similarly: var fooValue = tag.ctxPrm("foo"); // Get value of ~foo tag.ctxPrm("foo", newValue); // Set value of ~foo - tagCtx now has a tagCtx.contentView property, which is a view object wrapping the contents of the tag (or of the {{else}} block for the tag) - whether content rendered by the tag (using a tag render() method or template), or block content wrapped by the tag. - The APIS: tagCtx.contents() tagCtx.nodes() and tagCtx.childTags() all return contents of the block - and are equivalent to: tagCtx.contentView.contents() etc. (And similarly for the APIs tag.contents(), tag.nodes() and tag.childTags()...) - Improvements to APIs: tag.cvtArgs(), tagCtx.cvtArgs(), tag.bndArgs(), tagCtx.bndArgs() - Views have a new property: view.root, which provides access to the "root ancestor view" (the uppermost view under the top view). - Improvements to APIs: tag.setValue(), tag.setValues(), tag.updateValue(), tag.updateValues() and tagCtx.setValues(). (Details and documentation to follow). - Improvements to linkedElems APIS: linkedElems are now supported on tags with one or more {{else}} blocks. Each {{else} block can have its own linkedElem bindings. - New support for <input type="number"/> Bug fixes: - #380 Support for <input type="number"/> - #382 Cannot set property '_prv' of undefined - Several minor bug fixes Unit tests: - Several additional unit tests
1 parent 8a7db5b commit 3ee4dc6

29 files changed

+39690
-27199
lines changed

demos/step-by-step/04_form-elements_if-binding.html

+3-3
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ <h3>Ticket order form</h3>
6060
<div class="box">
6161
<div class="subhead">Choose Currency:</div>
6262
{{for ~currencies ~details=#data}}
63-
<input type="radio" name="currencyPicker" value="{{:#index}}" data-link="~details.selectedCurrency" />{{:label}}<br/>
63+
<label><input type="radio" name="currencyPicker" value="{{:#index}}" data-link="~details.selectedCurrency" /> {{:label}}</label><br/>
6464
{{/for}}
6565
</div>
6666
{{/if}}
@@ -72,8 +72,8 @@ <h3>Ticket order form</h3>
7272
<script type="text/javascript">
7373
var currencies = [
7474
{ name:"US", label:"US Dollar", rate: 1.0, symbol: "$" },
75-
{ name:"EUR", label:"Euro", rate: 0.95, symbol: "" },
76-
{ name:"GB", label:"Pound", rate: 0.63, symbol: "£" }
75+
{ name:"EUR", label:"Euro", rate: 0.95, symbol: "Euros: " },
76+
{ name:"GB", label:"Pound", rate: 0.63, symbol: "Pounds: " }
7777
],
7878

7979
orderDetails = {

jquery.observable.js

+57-37
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*! JsObservable v0.9.85 (Beta): http://jsviews.com/#jsobservable */
1+
/*! JsObservable v0.9.86 (Beta): http://jsviews.com/#jsobservable */
22
/*
33
* Subcomponent of JsViews
44
* Data change events for data-linking
@@ -7,7 +7,7 @@
77
* Released under the MIT License.
88
*/
99

10-
//jshint -W018, -W041
10+
//jshint -W018, -W041, -W120
1111

1212
(function(factory, global) {
1313
// global var is the this object, which is window when running in the usual browser environment
@@ -44,7 +44,8 @@ if (!$ || !$.fn) {
4444
throw "JsObservable requires jQuery"; // We require jQuery
4545
}
4646

47-
var versionNumber = "v0.9.85",
47+
var versionNumber = "v0.9.86",
48+
_ocp = "_ocp", // Observable contextual parameter
4849
$observe, $observable,
4950

5051
$views = $.views =
@@ -128,16 +129,18 @@ if (!$.observe) {
128129
: [paths]
129130
: [];
130131

131-
var i, path,
132-
object = root,
133-
nextObj = object,
132+
var i, path, object, rt,
133+
nextObj = object = root,
134134
l = paths && paths.length,
135135
out = [];
136136

137137
for (i = 0; i < l; i++) {
138138
path = paths[i];
139139
if ($isFunction(path)) {
140-
out = out.concat(dependsPaths(path.call(root, root, callback), root, callback));
140+
rt = root._ocp
141+
? root.view.data // observable contextual parameter
142+
: root;
143+
out = out.concat(dependsPaths(path.call(rt, rt, callback), root, callback));
141144
continue;
142145
} else if ("" + path !== path) {
143146
root = nextObj = path;
@@ -401,6 +404,7 @@ if (!$.observe) {
401404
};
402405
}
403406
$(boundObOrArr).on(namespace, null, evData, onDataChange);
407+
404408
if (cbBindings) {
405409
// Add object to cbBindings
406410
cbBindings[$data(object).obId || $data(object, "obId", observeObjKey++)] = object;
@@ -415,7 +419,7 @@ if (!$.observe) {
415419
// Uses the contextCb callback to execute the compiled exprOb template in the context of the view/data etc. to get the returned value, typically an object or array.
416420
// If it is an array, registers array binding
417421
var origRt = root;
418-
// Note: For jsviews/issues/292 ctxCb will need var ctxCb = contextCb || function(exprOb, origRt) {return exprOb._jsv(origRt);};
422+
// Note: For jsviews/issues/292 ctxCb will need var ctxCb = contextCb || function(exprOb, origRt) {return exprOb._cpfn(origRt);};
419423

420424
exprOb.ob = contextCb(exprOb, origRt); // Initialize object
421425

@@ -435,8 +439,7 @@ if (!$.observe) {
435439
// Put the updated object instance onto the exprOb in the paths array, so subsequent string paths are relative to this object
436440
if (typeof newObj === OBJECT) {
437441
bindArray(newObj);
438-
if (sub || allowArray && $isArray(newObj)) {
439-
// Register array binding
442+
if (sub || allowArray && $isArray(newObj)) { // observe on new object
440443
innerObserve([newObj], sub, callback, contextCb);
441444
}
442445
}
@@ -470,7 +473,7 @@ if (!$.observe) {
470473
}
471474

472475
var i, p, skip, parts, prop, path, dep, unobserve, callback, cbId, inId, el, data, events, contextCb, innerContextCb,
473-
items, cbBindings, depth, innerCb, parentObs, allPath, filter, initNsArr, initNsArrLen,
476+
items, cbBindings, depth, innerCb, parentObs, allPath, filter, initNsArr, initNsArrLen, view,
474477
ns = observeStr,
475478
paths = this != 1 // Using != for IE<10 bug- see jsviews/issues/237
476479
? concat.apply([], arguments) // Flatten the arguments - this is a 'recursive call' with params using the 'wrapped array'
@@ -481,6 +484,7 @@ if (!$.observe) {
481484
object = root,
482485
l = paths.length;
483486

487+
origRoots.unshift(root);
484488
if (lastArg + "" === lastArg) { // If last arg is a string then this observe call is part of an observeAll call,
485489
allPath = lastArg; // and the last three args are the parentObs array, the filter, and the allPath string.
486490
parentObs = paths.pop();
@@ -505,6 +509,7 @@ if (!$.observe) {
505509
}
506510

507511
if (unobserve && callback && !callback._cId) {
512+
origRoots.shift();
508513
return;
509514
}
510515

@@ -548,18 +553,13 @@ if (!$.observe) {
548553
depth = 0;
549554
for (i = 0; i < l; i++) {
550555
path = paths[i];
551-
if (path === "") {
556+
if (path === "" || path === root) {
552557
continue;
553558
}
554559
if (path && path._ar) {
555560
allowArray += path._ar; // Switch on allowArray for depends paths, and off, afterwards.
556561
continue;
557562
}
558-
if (path && path._cp) { // Contextual parameter
559-
contextCb = $sub._gccb(path[0]); // getContextCb: Get context callback for the contextual view (where contextual param evaluated/assigned)
560-
origRoot = root = path[0].data; // Contextual data
561-
path = path[1];
562-
}
563563
object = root;
564564
if ("" + path === path) {
565565
// Consider support for computed paths: jsviews/issues/292
@@ -580,20 +580,24 @@ if (!$.observe) {
580580
}
581581
if (contextCb) {
582582
items = contextCb(path, root, depth);
583-
contextCb = innerContextCb;
584-
}
585-
if (items) {
586-
// If the array of objects and paths returned by contextCb is non empty, insert them
587-
// into the sequence, replacing the current item (path). Otherwise simply remove current item (path)
588-
l += items.length - 1;
589-
splice.apply(paths, [i--, 1].concat(items));
590-
continue;
591583
}
584+
contextCb = innerContextCb;
592585
parts = path.split(".");
586+
} else if (path && path._cxp) { // contextual parameter
587+
view = path.shift(); // Contextual data
588+
if (_ocp in view) {
589+
root = view;
590+
contextCb = 0;
591+
} else {
592+
contextCb = $sub._gccb(view); // getContextCb: Get context callback for the contextual view (where contextual param evaluated/assigned)
593+
root = view.data;
594+
}
595+
items = path;
596+
items.push(origRoot);
593597
} else {
594598
if (!$isFunction(path)) {
595-
if (path && path._jsv) {
596-
// This is a compiled function for binding to an object returned by a helper/data function.
599+
if (path && path._cpfn) {
600+
// Path is an exprOb returned by a computed property - helper/data function (compiled expr function).
597601
// Set current object on exprOb.ob, and get innerCb for updating the object
598602
innerCb = unobserve ? path.cb : getInnerCb(path);
599603
// innerCb._ctx = callback._ctx; Could pass context (e.g. linkCtx) for use in a depends = function() {} call, so depends is different for different linkCtx's
@@ -603,9 +607,12 @@ if (!$.observe) {
603607
if (path.bnd || path.prm && path.prm.length || !path.sb) {
604608
// If the exprOb is bound e.g. foo()^sub.path, or has parameters e.g. foo(bar) or is a leaf object (so no sub path) e.g. foo()
605609
// then observe changes on the object, or its parameters and sub-path
606-
innerObserve([object], path.path, [origRoot], path.prm, innerCb, contextCb, unobserve);
610+
innerObserve([object], path.path, [path.root||root], path.prm, innerCb, contextCb, unobserve);
607611
}
608612
if (path.sb) { // subPath
613+
if (path.sb.prm) {
614+
path.sb.root = root;
615+
}
609616
innerObserve([path.ob], path.sb, callback, contextCb, unobserve);
610617
}
611618
path = origRoot;
@@ -614,6 +621,14 @@ if (!$.observe) {
614621
}
615622
parts = [root = path];
616623
}
624+
if (items) {
625+
// If the array of objects and paths returned by contextCb is non empty, insert them
626+
// into the sequence, replacing the current item (path). Otherwise simply remove current item (path)
627+
l += items.length - 1;
628+
splice.apply(paths, [i--, 1].concat(items));
629+
items = undefined;
630+
continue;
631+
}
617632
while (object && (prop = parts.shift()) !== undefined) {
618633
if (typeof object === OBJECT) {
619634
if ("" + prop === prop) {
@@ -680,7 +695,7 @@ if (!$.observe) {
680695
if (dep = prop.depends) {
681696
// This is a computed observable. We will observe any declared dependencies.
682697
// Pass {_ar: ...} objects to switch on allowArray, for depends paths, then return to contextual allowArray value
683-
innerObserve([object], dependsPaths(dep, object, callback), callback, contextCb, unobserve);
698+
innerObserve([object._ocp ? object.view.data : object], dependsPaths(dep, object, callback), callback, contextCb, unobserve);
684699
}
685700
break;
686701
}
@@ -694,6 +709,7 @@ if (!$.observe) {
694709
}
695710

696711
// Return the cbBindings to the top-level caller, along with the cbId
712+
origRoots.shift();
697713
return {cbId: cbId, bnd: cbBindings, s: cbBindingsStore};
698714
}
699715

@@ -702,14 +718,14 @@ if (!$.observe) {
702718
// arrayChange events in this scenario. Instead, {^{for}} and similar do specific arrayChange binding to the tagCtx.args[0] value, in onAfterLink.
703719
// Note deliberately using this == 1, rather than this === 1 because of IE<10 bug- see jsviews/issues/237
704720
paths = slice.call(arguments),
705-
origRoot = paths[0];
721+
origRoot = paths[0],
722+
origRoots = [origRoot];
706723

707724
if (origRoot + "" === origRoot && allowArray) {
708725
initialNs = origRoot; // The first arg is a namespace, since it is a string, and this call is not from observeAndBind
709726
paths.shift();
710727
origRoot = paths[0];
711728
}
712-
713729
return innerObserve.apply(1, paths);
714730
};
715731

@@ -757,7 +773,7 @@ if (!$.observe) {
757773
setProperty: function(path, value, nonStrict) {
758774
path = path || "";
759775
var key, pair, parts,
760-
multi = path + "" !== path,
776+
multi = path + "" !== path && !path._is, // Hash of paths, not view object
761777
self = this,
762778
object = self._data;
763779

@@ -780,9 +796,13 @@ if (!$.observe) {
780796
}
781797
} else if (path !== $expando) {
782798
// Simple single property case.
783-
parts = path.split(/[.^]/);
784-
while (object && parts.length > 1) {
785-
object = object[parts.shift()];
799+
if (path._is) {
800+
parts = [path];
801+
} else {
802+
parts = path.split(/[.^]/);
803+
while (object && parts.length > 1) {
804+
object = object[parts.shift()];
805+
}
786806
}
787807
if (object) {
788808
self._setProperty(object, parts[0], value, nonStrict);
@@ -804,9 +824,9 @@ if (!$.observe) {
804824
if ($isFunction(property)) {
805825
if (property.set) {
806826
// Case of property setter/getter - with convention that property is getter and property.set is setter
807-
leaf = leaf._wrp // Case of JsViews 2-way data-linking to a helper function as getter, with a setter.
827+
leaf = leaf._vw // Case of JsViews 2-way data-linking to an observable context parameter, with a setter.
808828
// The view will be the this pointer for getter and setter. Note: this is the one scenario where path is "".
809-
|| leaf;
829+
|| leaf;
810830
getter = property;
811831
setter = getter.set === true ? getter : getter.set;
812832
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'.

0 commit comments

Comments
 (0)