Skip to content

Commit

Permalink
Commit 72 (v0.9.72 - Beta)
Browse files Browse the repository at this point in the history
- $.observe() improvements: New "**" wild card path for observing
  all changes under an object: "object.**" - for 'declarative observeAll'
  "http://jsviews.com/#observe@**"

- Improvements to computed observables: 'depends' can now include
  both "**" wild card, and functions that observe changes and handle
  a callback programmatically. "http://jsviews.com/#computed@depends"

- trigger=true now also accepts other truthy value e.g. trigger=1

- Improvements to <select> data-linking:
  It is no longer necessary to data-link the <option> 'selected' property.
  Multiselect - data-links to array of values.
  #330

- Improved syntax error messages:
  #332

- Improved tag parsing:
  #331

- Unlinked JsRender tags - {{}} - are supported within the attribute markup
  of an HTML element, without needing to set link=false on the tag.
  (link=false can still be used for JsRender tags in HTML element block
  contentfor, to make the tags 'render-only' with no data-linking and no
  inserted script-node data-link markers)

- Minor bug fixes, including:
  BorisMoore/jsrender#290

- Many documentation additions and improvements, including:
  Data-linked paths topic:
  http://jsviews.com/#linked-paths
  Computed observables topic:
  http://jsviews.com/#computed
  New computed observables samples:
  http://jsviews.com/#samples/computed
  Documentation for #index and #getIndex():
  "http://jsviews.com/#viewobject@getIndex"
  BorisMoore/jsrender#266

- CDN support: JsRender and JsViews are now both available on the cdnjs CDN
  at "http://cdnjs.com/libraries/jsrender"
  and "http://cdnjs.com/libraries/jsviews".
  • Loading branch information
BorisMoore committed Jan 28, 2016
1 parent 68d7330 commit 0072edc
Show file tree
Hide file tree
Showing 19 changed files with 989 additions and 475 deletions.
63 changes: 52 additions & 11 deletions README.md
Expand Up @@ -8,8 +8,19 @@

The content of this ***ReadMe*** is available also as a *[JsViews Quickstart](http://www.jsviews.com/#jsv-quickstart)*.

<h3>Installation</h3>

jsviews.js is available from [downloads](http://www.jsviews.com/#download) on the jsviews.com site.

CDN delivery is available from the ***[cdnjs](https://cdnjs.com)*** CDN at [cdnjs.com/libraries/jsviews](https://cdnjs.com/libraries/jsviews).

Alternatively:
- It can be installed with ***[Bower](http://bower.io/search/?q=jsviews)***, using `$ bower install jsviews`
- It can be loaded using an AMD script loader, such as RequireJS
- For installation using *Node.js* (*npm*) -- and browser loading using *Browserify* -- see *[JsRender Node.js Quickstart](http://www.jsviews.com/#jsr-node-quickstart)*

<h3>JsRender and JsViews</h3>
**JsRender** is used for data-driven rendering of templates to strings, ready for insertion in the DOM. (See [JsRender Quickstart](http://www.jsviews.com/#jsr-quickstart) and [JsRender GitHub repository](https://github.com/BorisMoore/jsrender)).
**JsRender** is used for data-driven rendering of templates to strings, ready for insertion in the DOM. (See *[JsRender Quickstart](http://www.jsviews.com/#jsr-quickstart)* and [JsRender GitHub repository](https://github.com/BorisMoore/jsrender)).

**[JsRender](https://github.com/BorisMoore/jsrender)** and **[JsViews](https://github.com/BorisMoore/jsviews)** together provide the next-generation implementation of the official jQuery plugins *[JQuery Templates](https://github.com/BorisMoore/jquery-tmpl)*, and *[JQuery Data Link](https://github.com/BorisMoore/jquery-datalink)* -- and supersede those libraries.

Expand All @@ -28,8 +39,8 @@ Any JsRender tag, `{{...}}` can be *data-linked* by writing `{^{...}}`, as in:

```html
<ul>
{^{for people}} <!-- List will update when people array changes -->
<li>{^{:name}}</li> <!-- Will update when name property changes -->
{^{for people}} <!--List will update when people array changes-->
<li>{^{:name}}</li> <!--Will update when name property changes-->
{{/for}}
</ul>
```
Expand All @@ -41,8 +52,8 @@ Any JsRender tag, `{{...}}` can be *data-linked* by writing `{^{...}}`, as in:
HTML elements within templates can be *data-linked* by adding a `data-link` attribute:

```html
<input data-link="name"/> <!-- Two-way data-binding to the name property -->
<span data-link="name"></span> <!-- Will update when name property changes -->
<input data-link="name"/> <!--Two-way data-binding to the name property-->
<span data-link="name"></span> <!--Will update when name property changes-->
```

HTML elements within 'top-level' page content can also be data-linked -- see [below](#jsv-quickstart@toplink).
Expand Down Expand Up @@ -127,12 +138,12 @@ Jim <input value="Jim" />

with the `name` property of `person` object data-linked to the `"Jim"` text node and *two-way* data-linked to the `<input />`

See: [Playing with JsViews](http://www.jsviews.com/#jsvplaying) for working samples, such as [this one](http://www.jsviews.com/#jsvplaying@twoway)
See: *[Playing with JsViews](http://www.jsviews.com/#jsvplaying)* for working samples, such as [this one](http://www.jsviews.com/#jsvplaying@twoway)

[Learn more...](http://www.jsviews.com/#jsvlinktmpl)

<h3 id="jsv-quickstart@toplink"><i>Top-level data-linking</i></h3>

You can use data-linking not only for templated content, but also to data-bind to top-level HTML content in your page:

```js
Expand Down Expand Up @@ -173,7 +184,7 @@ JsViews uses the *<a href="http://www.jsviews.com/#onpropchange">property change

**observe() and observeAll()**

The [$.observe()](http://www.jsviews.com/#observe) and [$.observable().observeAll()](http://www.jsviews.com/#observeAll) APIs make it very easy for you to register event handlers or listeners, so your code can listen to specific observable changes made to your data objects or view models:
The [`$.observe()`](http://www.jsviews.com/#observe) and [`$.observable().observeAll()`](http://www.jsviews.com/#observeAll) APIs make it very easy for you to register event handlers or listeners, so your code can listen to specific observable changes made to your data objects or view models:

```js
$.observe(person, "name", function(...) {
Expand Down Expand Up @@ -222,16 +233,46 @@ $(".changeBtn").on("click", function() {
// Get index of this 'item view'. (Equals index of person in people array)
var index = view.index;

// Change the person.name
$.observable(person).setProperty("name", person.name + " " + index);
// Change the person.name
$.observable(person).setProperty("name", person.name + " " + index);
});
```

[Learn more...](http://www.jsviews.com/#$view)


<h3><i>Data-linked paths</i></h3>

JsViews data-linked templates (and the `$.observe()` API) use the same [paths and expressions](http://www.jsviews.com/#paths) as JsRender templates, but in addition provide *'leaf'* data-binding -- such as:

```html
{^{:team.manager.name`}} <!--updates when name changes-->
<span data-link="team.manager.name"></span> <!--updates when name changes-->
<input data-link="team.manager.name" /> <!--two-way binding to name-->
```

But data-linked paths have additional support, such as linking deeper into paths:

```html
{^{:team^manager.name}} <!--updates when name, manager, or team changes-->
```

[Learn more...](http://www.jsviews.com/#linked-paths)

<h3><i>Computed observables</i></h3>

JsViews also allows you to data-bind to computed values, such as:

```html
{^{:shoppingCart.totalAmount()}} <!--updates when totalAmount() changes-->
<input data-link="person.fullName()" /> <!--two-way binding, computed fullName()-->
```

[Learn more...](http://www.jsviews.com/#computed)

<h3><i>Documentation and APIs</i></h3>

See the [www.jsviews.com](http://www.jsviews.com) site, including the [JsViews Quickstart](http://www.jsviews.com/#jsv-quickstart), [JsViews APIs](http://www.jsviews.com/#jsvapi) and [JsObservable APIs](http://www.jsviews.com/#jsoapi)topics.
See the [www.jsviews.com](http://www.jsviews.com) site, including the *[JsViews Quickstart](http://www.jsviews.com/#jsv-quickstart)*, [JsViews APIs](http://www.jsviews.com/#jsvapi) and [JsObservable APIs](http://www.jsviews.com/#jsoapi)topics.

<h3><i>Demos</i></h3>
Demos and samples can be found at [www.jsviews.com/#samples](http://www.jsviews.com/#samples), and throughout the [API documentation](http://www.jsviews.com/#jsvapi).
Expand Down
72 changes: 40 additions & 32 deletions jquery.observable.js
@@ -1,4 +1,4 @@
/*! JsObservable v0.9.71 (Beta): http://jsviews.com/#jsobservable */
/*! JsObservable v0.9.72 (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 = "v1.0.0-alpha",
var versionNumber = "v0.9.72",
$observe, $observable,

$views = $.views =
Expand Down Expand Up @@ -100,19 +100,23 @@ if (!$.observe) {
: data;
},

resolvePathObjects = function(paths, root) {
paths = $isArray(paths) ? paths : [paths];
resolvePathObjects = function(paths, root, callback) {
paths = paths
? $isArray(paths)
? paths
: [paths]
: [];

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

for (i = 0; i < l; i++) {
path = paths[i];
if ($isFunction(path)) {
out = out.concat(resolvePathObjects(path.call(root, root), root));
out = out.concat(resolvePathObjects(path.call(root, root, callback), root));
continue;
} else if ("" + path !== path) {
root = nextObj = path;
Expand Down Expand Up @@ -213,8 +217,8 @@ if (!$.observe) {
if (prop !== $expando) {
if (newObject = $observable._fltr(newAllPath, obj[prop], nextParentObs, filter)) {
newParentObs = nextParentObs.slice();
if (nestedArray && updatedTgt) {
newParentObs.unshift(updatedTgt); // For array change events need to add updated array to parentObs
if (nestedArray && updatedTgt && newParentObs[0] !== updatedTgt) {
newParentObs.unshift(updatedTgt); // For array change events when observing an array which is not the root, need to add updated array to parentObs
}
observeAll(namespace, newObject, cb, filter || (nestedArray ? undefined : 0), newParentObs, newAllPath, unobs, objMap);
// If nested array, need to observe the array too - so set filter to undefined
Expand Down Expand Up @@ -255,7 +259,7 @@ if (!$.observe) {
if (objMap && notRemoving && $hasData(object) && objMap[obId = $data(object).obId]) {
objMap[obId]++;
return; // This object has already being observed/unobserved by this observeAll/unobserveAll call (must be a cyclic object graph) so skip, to avoid
// stack overflow/multiple instances of listener. See https://github.com/BorisMoore/jsviews/pull/305
// stack overflow/multiple instances of listener. See jsviews/pull/305
// NOTE - WE DO NOT support ObserveAll on data with cyclic graphs which include DUPLICATE REFERENCES TO ARRAY PROPERTIES - such as data.children = data.descendants = []
}
if (!objMap) {
Expand Down Expand Up @@ -345,8 +349,6 @@ if (!$.observe) {
// Duplicate exists, so skip. (This can happen e.g. with {^{for people ~foo=people}})
// or for cases with cyclic objects - e.g. obj.children = obj.descendants = []
return;
} else if (pathStr === "*" && data.prop !== pathStr) {
$(object).off(namespace, onObservableChange);
}
}
}
Expand Down Expand Up @@ -397,7 +399,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 https://github.com/BorisMoore/jsviews/issues/292ctxCb 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._jsv(origRt);};

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

Expand Down Expand Up @@ -453,7 +455,7 @@ if (!$.observe) {
var i, p, skip, parts, prop, path, dep, unobserve, callback, cbId, el, data, events, contextCb, items, cbBindings, depth, innerCb, parentObs,
allPath, filter, initNsArr, initNsArrLen,
ns = observeStr,
paths = this != 1 // Using != for IE<10 bug- see https://github.com/BorisMoore/jsviews/issues/237
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'
// style - such as innerObserve([object], path.path, [origRoot], path.prm, innerCb, ...);
: slice.call(arguments), // Don't flatten - this is the first 'top-level call, to innerObserve.apply(1, paths)
Expand Down Expand Up @@ -511,7 +513,7 @@ if (!$.observe) {
}
object = root;
if ("" + path === path) {
// Consider support for computed paths: https://github.com/BorisMoore/jsviews/issues/292
// Consider support for computed paths: jsviews/issues/292
//if (/[\(\[\+]/.test(path)) {
// var b={links:{}}, t = $sub.tmplFn("{:"+path+"}", b, true), items = t.paths[0];
// l += items.length - 1;
Expand Down Expand Up @@ -576,14 +578,16 @@ if (!$.observe) {
skip = 0;
while (el--) { // Skip duplicates
data = events[el].data;
if (data && data.cb === callback && data.ns === initialNs) {
if (data.prop === prop || data.prop === "*") {
if (p = parts.join(".")) {
data.paths.push(p); // We will skip this binding, but if it is not a leaf binding,
// need to keep bindings for rest of path, ready for if the object gets swapped.
}
skip++;
if (data
&& data.cb._cId === callback._cId
&& data.ns === initialNs
&& !callback._inId // Don't skip if this is an innerCb for an object returned by a computed observable.
&& (data.prop === prop || data.prop === "*" || data.prop === "**")) {
if (p = parts.join(".")) {
data.paths.push(p); // We will skip this binding, but if it is not a leaf binding,
// need to keep bindings for rest of path, ready for if the object gets swapped.
}
skip++;
}
}
if (skip) {
Expand All @@ -592,17 +596,21 @@ if (!$.observe) {
continue;
}
}
if (prop === "*") {
if (prop === "*" || prop === "**") { // "*" => all properties. "**" => all properties and sub-properties (i.e. deep observeAll behavior)
if (!unobserve && events && events.length) {
// Remove existing bindings, since they will be duplicates with "*"
// Remove existing bindings, since they will be duplicates with "*" or "**"
observeOnOff(ns, "", false, true);
}
observeOnOff(ns, ""); // observe the object for any property change
for (p in object) {
// observing "*": So (in addition to listening to prop change, above) listen to arraychange on props of type array
if (p !== $expando) {
bindArray(object, unobserve, undefined, p);
if (prop === "*") {
observeOnOff(ns, ""); // observe the object for any property change
for (p in object) {
// observing "*": So (in addition to listening to prop change, above) listen to arraychange on props of type array
if (p !== $expando) {
bindArray(object, unobserve, undefined, p);
}
}
} else {
$.observable(object)[(unobserve ? "un" : "") + "observeAll"](callback); // observe or unobserve the object for any property change
}
break;
} else if (prop) {
Expand All @@ -617,7 +625,7 @@ if (!$.observe) {
if ($isFunction(prop)) {
if (dep = prop.depends) {
// This is a computed observable. We will observe any declared dependencies
innerObserve([object], resolvePathObjects(dep, object), callback, contextCb, unobserve);
innerObserve([object], resolvePathObjects(dep, object, callback), callback, contextCb, unobserve);
}
break;
}
Expand All @@ -638,7 +646,7 @@ if (!$.observe) {
var initialNs,
allowArray = this != false, // If this === false, this is a call from observeAndBind - doing binding of datalink expressions. We don't bind
// 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 != false, rather than this !== false because of IE<10 bug- see https://github.com/BorisMoore/jsviews/issues/237
// Note deliberately using this != false, rather than this !== false because of IE<10 bug- see jsviews/issues/237
paths = slice.call(arguments),
origRoot = paths[0];

Expand Down Expand Up @@ -755,8 +763,8 @@ if (!$.observe) {
// The view will be the this pointer for getter and setter. Note: this is the one scenario where path is "".
|| leaf;
getter = property;
setter = property.set === true ? property : property.set;
property = property.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'.
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

0 comments on commit 0072edc

Please sign in to comment.