Skip to content

Commit

Permalink
Commit 86 (v0.9.86 - Beta)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
BorisMoore committed Jun 17, 2017
1 parent 8a7db5b commit 3ee4dc6
Show file tree
Hide file tree
Showing 29 changed files with 39,690 additions and 27,199 deletions.
6 changes: 3 additions & 3 deletions demos/step-by-step/04_form-elements_if-binding.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ <h3>Ticket order form</h3>
<div class="box">
<div class="subhead">Choose Currency:</div>
{{for ~currencies ~details=#data}}
<input type="radio" name="currencyPicker" value="{{:#index}}" data-link="~details.selectedCurrency" />{{:label}}<br/>
<label><input type="radio" name="currencyPicker" value="{{:#index}}" data-link="~details.selectedCurrency" /> {{:label}}</label><br/>
{{/for}}
</div>
{{/if}}
Expand All @@ -72,8 +72,8 @@ <h3>Ticket order form</h3>
<script type="text/javascript">
var currencies = [
{ name:"US", label:"US Dollar", rate: 1.0, symbol: "$" },
{ name:"EUR", label:"Euro", rate: 0.95, symbol: "" },
{ name:"GB", label:"Pound", rate: 0.63, symbol: "£" }
{ name:"EUR", label:"Euro", rate: 0.95, symbol: "Euros: " },
{ name:"GB", label:"Pound", rate: 0.63, symbol: "Pounds: " }
],

orderDetails = {
Expand Down
94 changes: 57 additions & 37 deletions jquery.observable.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*! JsObservable v0.9.85 (Beta): http://jsviews.com/#jsobservable */
/*! JsObservable v0.9.86 (Beta): http://jsviews.com/#jsobservable */
/*
* Subcomponent of JsViews
* Data change events for data-linking
Expand All @@ -7,7 +7,7 @@
* Released under the MIT License.
*/

//jshint -W018, -W041
//jshint -W018, -W041, -W120

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

var versionNumber = "v0.9.85",
var versionNumber = "v0.9.86",
_ocp = "_ocp", // Observable contextual parameter
$observe, $observable,

$views = $.views =
Expand Down Expand Up @@ -128,16 +129,18 @@ if (!$.observe) {
: [paths]
: [];

var i, path,
object = root,
nextObj = object,
var i, path, object, rt,
nextObj = object = root,
l = paths && paths.length,
out = [];

for (i = 0; i < l; i++) {
path = paths[i];
if ($isFunction(path)) {
out = out.concat(dependsPaths(path.call(root, root, callback), root, callback));
rt = root._ocp
? root.view.data // observable contextual parameter
: root;
out = out.concat(dependsPaths(path.call(rt, rt, callback), root, callback));
continue;
} else if ("" + path !== path) {
root = nextObj = path;
Expand Down Expand Up @@ -401,6 +404,7 @@ if (!$.observe) {
};
}
$(boundObOrArr).on(namespace, null, evData, onDataChange);

if (cbBindings) {
// Add object to cbBindings
cbBindings[$data(object).obId || $data(object, "obId", observeObjKey++)] = object;
Expand All @@ -415,7 +419,7 @@ if (!$.observe) {
// 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.
// If it is an array, registers array binding
var origRt = root;
// Note: For jsviews/issues/292 ctxCb will need var ctxCb = contextCb || function(exprOb, origRt) {return exprOb._jsv(origRt);};
// Note: For jsviews/issues/292 ctxCb will need var ctxCb = contextCb || function(exprOb, origRt) {return exprOb._cpfn(origRt);};

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

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

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

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

if (unobserve && callback && !callback._cId) {
origRoots.shift();
return;
}

Expand Down Expand Up @@ -548,18 +553,13 @@ if (!$.observe) {
depth = 0;
for (i = 0; i < l; i++) {
path = paths[i];
if (path === "") {
if (path === "" || path === root) {
continue;
}
if (path && path._ar) {
allowArray += path._ar; // Switch on allowArray for depends paths, and off, afterwards.
continue;
}
if (path && path._cp) { // Contextual parameter
contextCb = $sub._gccb(path[0]); // getContextCb: Get context callback for the contextual view (where contextual param evaluated/assigned)
origRoot = root = path[0].data; // Contextual data
path = path[1];
}
object = root;
if ("" + path === path) {
// Consider support for computed paths: jsviews/issues/292
Expand All @@ -580,20 +580,24 @@ if (!$.observe) {
}
if (contextCb) {
items = contextCb(path, root, depth);
contextCb = innerContextCb;
}
if (items) {
// If the array of objects and paths returned by contextCb is non empty, insert them
// into the sequence, replacing the current item (path). Otherwise simply remove current item (path)
l += items.length - 1;
splice.apply(paths, [i--, 1].concat(items));
continue;
}
contextCb = innerContextCb;
parts = path.split(".");
} else if (path && path._cxp) { // contextual parameter
view = path.shift(); // Contextual data
if (_ocp in view) {
root = view;
contextCb = 0;
} else {
contextCb = $sub._gccb(view); // getContextCb: Get context callback for the contextual view (where contextual param evaluated/assigned)
root = view.data;
}
items = path;
items.push(origRoot);
} else {
if (!$isFunction(path)) {
if (path && path._jsv) {
// This is a compiled function for binding to an object returned by a helper/data function.
if (path && path._cpfn) {
// Path is an exprOb returned by a computed property - helper/data function (compiled expr function).
// Set current object on exprOb.ob, and get innerCb for updating the object
innerCb = unobserve ? path.cb : getInnerCb(path);
// 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
Expand All @@ -603,9 +607,12 @@ if (!$.observe) {
if (path.bnd || path.prm && path.prm.length || !path.sb) {
// 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()
// then observe changes on the object, or its parameters and sub-path
innerObserve([object], path.path, [origRoot], path.prm, innerCb, contextCb, unobserve);
innerObserve([object], path.path, [path.root||root], path.prm, innerCb, contextCb, unobserve);
}
if (path.sb) { // subPath
if (path.sb.prm) {
path.sb.root = root;
}
innerObserve([path.ob], path.sb, callback, contextCb, unobserve);
}
path = origRoot;
Expand All @@ -614,6 +621,14 @@ if (!$.observe) {
}
parts = [root = path];
}
if (items) {
// If the array of objects and paths returned by contextCb is non empty, insert them
// into the sequence, replacing the current item (path). Otherwise simply remove current item (path)
l += items.length - 1;
splice.apply(paths, [i--, 1].concat(items));
items = undefined;
continue;
}
while (object && (prop = parts.shift()) !== undefined) {
if (typeof object === OBJECT) {
if ("" + prop === prop) {
Expand Down Expand Up @@ -680,7 +695,7 @@ if (!$.observe) {
if (dep = prop.depends) {
// This is a computed observable. We will observe any declared dependencies.
// Pass {_ar: ...} objects to switch on allowArray, for depends paths, then return to contextual allowArray value
innerObserve([object], dependsPaths(dep, object, callback), callback, contextCb, unobserve);
innerObserve([object._ocp ? object.view.data : object], dependsPaths(dep, object, callback), callback, contextCb, unobserve);
}
break;
}
Expand All @@ -694,6 +709,7 @@ if (!$.observe) {
}

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

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

if (origRoot + "" === origRoot && allowArray) {
initialNs = origRoot; // The first arg is a namespace, since it is a string, and this call is not from observeAndBind
paths.shift();
origRoot = paths[0];
}

return innerObserve.apply(1, paths);
};

Expand Down Expand Up @@ -757,7 +773,7 @@ if (!$.observe) {
setProperty: function(path, value, nonStrict) {
path = path || "";
var key, pair, parts,
multi = path + "" !== path,
multi = path + "" !== path && !path._is, // Hash of paths, not view object
self = this,
object = self._data;

Expand All @@ -780,9 +796,13 @@ if (!$.observe) {
}
} else if (path !== $expando) {
// Simple single property case.
parts = path.split(/[.^]/);
while (object && parts.length > 1) {
object = object[parts.shift()];
if (path._is) {
parts = [path];
} else {
parts = path.split(/[.^]/);
while (object && parts.length > 1) {
object = object[parts.shift()];
}
}
if (object) {
self._setProperty(object, parts[0], value, nonStrict);
Expand All @@ -804,9 +824,9 @@ if (!$.observe) {
if ($isFunction(property)) {
if (property.set) {
// Case of property setter/getter - with convention that property is getter and property.set is setter
leaf = leaf._wrp // Case of JsViews 2-way data-linking to a helper function as getter, with a setter.
leaf = leaf._vw // Case of JsViews 2-way data-linking to an observable context parameter, with a setter.
// The view will be the this pointer for getter and setter. Note: this is the one scenario where path is "".
|| leaf;
|| leaf;
getter = property;
setter = getter.set === true ? getter : getter.set;
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'.
Expand Down
Loading

0 comments on commit 3ee4dc6

Please sign in to comment.