Reactive template engine for robust real-time rendering of model data changes.
With component:
$ component install component/reactive
With the stand-alone browser build:
<script src="reactive.js"></script>
Bind object to the given element with optional view object. When a view object is present it will be checked first for overrides, which otherwise delegate to the model object.
For example if you have the following HTML:
<h1data-text="name"></h1>And pass the following object as the second argument:
{name: 'Tobi'}The output will become:
<h1>Tobi</h1>However if you wish to manipulate the output or provided computed properties thae view object may be passed. For example an object of:
{first_name: "Tobi",last_name: "Ferret"}And a view of:
functionUserView(user){this.user=user;}UserView.prototype.name=function(){returnthis.user.first_name+' '+this.user.last_name;}Would produce:
<h1>Tobi Ferret</h1>Typically a view object wraps a model to provide additional functionality, this may look something like the following:
functionUserView(user){this.user=user;this.el=reactive(tmpl,user,this);}UserView.prototype.name=function(){ ... }Often a higher-level API is built on top of this pattern to keep things DRY but this is left to your application / other libraries.
Subscriptions allow reactive to know when an object's data has changed updating the DOM appropriately without re-rendering a static template. This means if you make manual DOM adjustments, append canvases etc they will remain intact.
By default reactive subscribes using .on("change <name>", callback) however it's easy to define your own subscription methods:
reactive.subscribe(function(obj,prop,fn){obj.bind(prop,fn);});reactive.unsubscribe(function(obj,prop,fn){obj.unbind(prop,fn);});You can make reactive compatible with your favorite framework by defining how reactive gets and sets the model.
By default reactive supports obj[prop] = val and obj[prop](val), but these can be changed with reactive.get(fn) and reactive.set(fn). Here's how to make reactive compatible with backbone:
reactive.get(function(obj,prop){returnobj.get(prop);});reactive.set(function(obj,prop,val){obj.set(prop,val);});Bindings may be applied via interoplation on attributes or text. For example here
is a simple use of this feature to react to changes of an article's .name property:
<article><h2>{name}</h2></article>Text interpolation may appear anywhere within the copy, and may contain complex JavaScript expressions for defaulting values or other operations.
<article><h2>{ name || 'Untitled' }</h2><p>Summary: { body.slice(0, 10) }</p></article>Reactive is smart enough to pick out multiple properties that may be used, and react to any of their changes:
<p>Welcome { first + ' ' + last }.</p>Interpolation works for attributes as well, reacting to changes as you'd expect:
<liclass="file-{id}"><h3>{filename}</h3><p><ahref="/files/{id}/download">Download {filename}</a></p><li>By default reactive supplies bindings for setting properties, listening to events, toggling visibility, appending and replacing elements. Most of these start with "data-*" however this is not required.
The data-text binding sets the text content of an element.
The data-html binding sets the inner html of an element.
The data-<attr> bindings allows you to set an attribute:
<adata-href="download_url">Download</a>The on-<event> bindings allow you to listen on an event:
<lidata-text="title"><aon-click="remove">x</a></li>The data-append binding allows you to append an existing element:
<divclass="photo" data-append="histogram"></div>The data-replace binding allows you to replace an existing element:
<divclass="photo" data-replace="histogram"></div>The data-show and data-hide bindings conditionally add "show" or "hide" classnames so that you may style an element as hidden or visible.
<pdata-show="hasDescription" data-text="truncatedDescription"></p>Toggles checkbox state:
<inputtype="checkbox" data-checked="agreed_to_terms">To author bindings simply call the reactive.bind(name, fn) method, passing the binding name and a callback which is invoked with the element itself and the value. For example here is a binding which removes an element when truthy:
reactive.bind('remove-if',function(el,name){el=$(el);varparent=el.parent();this.change(function(){if(this.value(name)){el.remove();}});});Reactive supports computed properties denoted with the < character. Here the fullname property does not exist on the model, it is a combination of both .first and .last, however you must tell Reactive about the real properties in order for it to react appropriately:
<h1data-text="fullname < first last"></h1>NOTE: in the future Reactive may support hinting of computed properties from outside Reactive itself, as your ORM-ish library may already have this information.
Some bindings such as data-text and data-<attr> support interpolation. These properties are automatically added to the subscription, and react to changes:
<adata-href="/download/{id}" data-text="Download {filename}"></a>Get creative! There's a lot of application-specific logic that can be converted to declarative Reactive bindings. For example here's a naive "auto-submit" form binding:
<divclass="login"><formaction="/user" method="post" autosubmit><inputtype="text" name="name" placeholder="Username" /><inputtype="password" name="pass" placeholder="Password" /><inputtype="submit" value="Login" /></form></div>varreactive=require('reactive');// bindvarview=reactive(document.querySelector('.login'));// custom binding available to this view onlyview.bind('autosubmit',function(el){el.onsubmit=function(e){e.preventDefault();varpath=el.getAttribute('action');varmethod=el.getAttribute('method').toUpperCase();console.log('submit to %s %s',method,path);}});For more examples view the ./examples directory.
MIT