Binder

Binders are how we do something with an element, such as showing, hiding, looping, changing attributes, taking inputs etc. This is the reason for having a binding engine/tool/framework, to allow us to bring HTML to life.

What Actually ARE Binders?

Binders the way we make that HTML dance, turning your run of the mill HTML document into an application of sorts, showing, hiding, looping, changing and controlling your users view in a more UI-ifyable way. Basically the many things you can do manually to an element in your application using plain jain superbrain javascript. They abstract application logic to nice moduler code, linking it to your HTML via HTML attributes.

Tell me More Sir!

Binders are the basas of the binding engine, they all have them, we just seperate them in a more modular way, makes them easier to manage and update. Stripped into standalone classes and imported by razilobind-binder, the binders and their parent class allow you do do mcuh much more with HTML. Binders start with bind, then have the binder type, the attribute then contains resolvable data to work with or bind to, see resolver for resolving attribute data.

How do we Use Binders?

Each binder can accept all resolvers [] or certain types of resolver ['boolean', 'object', 'phantom'] to resolved the data in it's raziloBind attribute. Using a binder is as simple as adding an attribute to your HTML, in which you place data to be resolved, different binders do different things, all based around the resolvable data. This can be altered before using it (via alterers), or can be used in conjunction with other razilobind attributes.

Binders are injected into the core on load, either automatically using the daddy module razilobind or manually when using razilbind-core class directly. We use them as injectables to allow you the option to add the binders you wish, your own binders, a mix of both, or something not yet thought of. Yes thats right, you can add your own too, via the razilobind methods or via core injection.

Usage Example

Remember to compile to ES6 peeps, add a watcher if you are continually developing!

HTML Binding (Default: without prefix)

<!-- binding model data directly (two way) -->
<p bind-text="foobar"></p>

<!-- bind a literal (one way) -->
<p bind-show="true"></p>

<!-- binders producing phantom properties (two way) [configurable] -->
<ul>
  <li bind-for="object"><span bind-text="$key"></span> <span bind-text="$value"></span></li>
</ul>

<!-- one way binder (object) that gets re-evaluated like two way binding, by having an observed value inside it that changes -->
<p bind-attribute="{'disabled': something}"></p>

<!-- one way binder (method) that gets re-evaluated like two way binding, by having an observed value inside it that changes -->
<p bind-text="doSomething(foobar)"></p>

One Way and Two Way Binding

We mention one and two way binding, as raziloBind has both it is considered a two way binding engine. Well that's correct but you need to understand it a little more and why we have both. Also raziloBind differs a little from some typical binding engines.

One Way

One way binding is mainly down to literals, this means when you bind to a string literal you create in your HTML attribute (it doesn't exist in the model). Basically if it's not in the model, you can think of it as one way binding, meaning it is resolved and used once on load, that it, there are no changes to minitor as there is nothing in the model to monitor (the model being the JS object you bind to). It doesn't stop there though, we cannot two way bind on model methods either (functions you save to model properties for things like clicks). Sure if the method changed, this would be detected, but we cannot know how or when to re-run a method.

Two Way

Two way binding happens with any model property being changed, that includes array objects, methods etc. If you change the properties contents, the dom (your HTML) is updated too, to reflect the changes. If you where looping over an array in the dom, and you added a new value to the model array, the dom would add an extra element. So two way binding means when we change the model, either directly or from the dom (via, say, an input, checkbox etc.) the model is changed, then the dom is instructed to update. You can do this with any model property.

A Little of Both

Well we said we are a little different, we allow you to use bindable data for any razilobind attribute (configs, alterers, binds, specials). We also allow you to embed resolvable data in resolvable data. Simply put, whilst a method is one way binding, if you add a model property as an input variable on a method for say, a click, the method will become two way binding, bound to the change of the input property. This i sthe same for other types too, add a one time resolvable object in HTML and use a model property in it, a change to the property will evaluate the whole element again. In essence we can turn one time binding into two by using properties to flag changes, used anywhere on an element.

Show me the Binders

These are the default binders at present, they are loaded automatically via razilobind, if you are using the core directly, don't forget to import and inject them, see razilobind-core on how to do this.

Manipulate

Changing an element in one way or another.

text Add text to element

Adds text inside an element.

  • Accepts Resolvers: all resolver types
<span bind-text="foo"></span>

html Add html to element

Adds html inside an element.

  • Accepts Resolvers: string, property, phantom, method
<span bind-html="foo"></span>

show Show an element

Show an element and it's children only if resolved data is truthy. Use for constant showing and hiding during application use.

  • Accepts Resolvers: all resolver types
<span bind-show="foo"></span>

hide Hide an element

Hide an element and it's children only if resolved data is truthy. Use for constant showing and hiding during application use.

  • Accepts Resolvers: all resolver types
<span bind-hide="foo"></span>

if Use an element

Use an element and it's children in the dom if resolved data is truthy. Not to be mistaken for 'show'. Use for one time evaluation of wether element should be present on load.

  • Accepts Resolvers: property, phantom, boolean, method
<span bind-if="foo"></span>

else Don't use an element

Don't use an element and it's children in the dom if resolved data is truthy. Not to be mistaken for 'hide'. Use for one time evaluation of wether element should be present on load.

  • Accepts Resolvers: property, phantom, boolean, method
<span bind-else="foo"></span>

class Add class name/s to element

Add class name/s to an element, do this as a one time bind or on a changable basis (add/remove based on truthy).

  • Accepts Resolvers: property, phantom, object, array, string, method
<!-- basic add -->
<span bind-class="'classname'"></span>

<!-- add property value -->
<span bind-class="foobar"></span>

<!-- add method value -->
<span bind-class="foobarMethod()"></span>

<!-- add method value -->
<span bind-class="whatever(something, 'another')"></span>

<!-- add add/remove truthy -->
<span bind-class="{'something': foo.bar['baz']}"></span>

<!-- add collection of classes -->
<span bind-class="['test', foobar, whatever()]"></span>

attributes Add attributes to element

Add attributes to an element either as an attribute only based on truthy, or an attribute with data set on it.

  • Accepts Resolvers: property, phantom, object, array, string, method
<!-- basic add attribute only -->
<span bind-attribute="'disabled'"></span>

<!-- basic add attribute from property -->
<span bind-attribute="foo.bar['baz']"></span>

<!-- basic add attribute with data -->
<span bind-attribute="{'type': foobar, 'data-help': 'help me'}"></span>

<!-- add/remove with property as data, or if  -->
<span bind-attribute="{'disabled': truthy}"></span>

<!-- add property value -->
<span bind-attribute="{'something': some.color}"></span>

<!-- add method value -->
<span bind-attribute="{'something': someFunction()}"></span>

disabled Add disabled attribute to element

Add disabled attribute to an element, add/remove based on truthy.

  • Accepts Resolvers: property, phantom, object, array, string, method
<!-- basic add attribute only -->
<span bind-disabled="true"></span>

<!-- basic add attribute from property -->
<span bind-disabled="foo.bar['baz']"></span>

required Add required attribute to element

Add required attribute to an element, add/remove based on truthy.

  • Accepts Resolvers: property, phantom, object, array, string, method
<!-- basic add attribute only -->
<span bind-required="true"></span>

<!-- basic add attribute from property -->
<span bind-required="foo.bar['baz']"></span>

selected Add selected attribute to element

Add selected attribute to an element, add/remove based on truthy.

  • Accepts Resolvers: property, phantom, object, array, string, method
<!-- basic add attribute only -->
<span bind-selected="true"></span>

<!-- basic add attribute from property -->
<span bind-selected="foo.bar['baz']"></span>

href Add href attributes to element

Add href attribute and value to an element.

  • Accepts Resolvers: property, phantom, object, array, string, method
<!-- basic add attribute only -->
<a bind-href="'http://razilo.net'"></a>

<!-- basic add attribute from property -->
<a bind-href="foo.bar['baz']"></a>

src Add src attribute to element

Add src attribute and value to an element.

  • Accepts Resolvers: property, phantom, object, array, string, method
<!-- basic add attribute only -->
<img bind-src="'http://razilo.net.image.png'"/>

<!-- basic add attribute from property -->
<img bind-src="foo.bar['baz']"/>

style Add style to element

Add style to an element, css style names and values permitted.

  • Accepts Resolvers: property, phantom, object, array, string, method
  • Accepts Style: {'cssStyleName': 'cssStyleValue',...}
<!-- basic add -->
<span bind-style="{'display': 'none'}"></span>

<!-- add property value -->
<span bind-style="{'color': some.color}"></span>

<!-- add method value -->
<span bind-style="{'margin': someFunction()}"></span>

Itterate

Iterrating over an elements bindable data, to do things such as replication.

for Loop over element

Loop over an element, repeating it for each instance of resolved data. Iterates over objects ({}) or array objects ([])

Configure this binder with config-for="{'key': ..., 'value': ...}" to change phantom $key and $value names.

Order the loop by it's contents with order-for="{'property': 'asc/desc'}", to reorder based on iteration property.

Filter the loop by it's contents with filter-for="{'property': 'filterText'}", to remove loops. filterText accepts * as wildcard and arrays

Limit the loops shown with limit-for="XX" where XX = 10, shows ten loops.

Offset the loops shown with offset-for="XX" where XX = 100, skips the first 100 loops.

  • Accepts Resolvers: property, phantom, method, array, object
  • Accepts Config: object {'key': 'name', 'value': ''}
  • Accepts Order: object {'iterationProperty': 'direction as asc/desc'}
  • Accepts Filter: object {'key': 'name', 'value': 'name'}
  • Accepts Limit: number 10, 100, 234...
  • Accepts Offset: number 10, 100, 234...
<!-- basic for loop, access itteration using phantom property -->
<ul>
  <li bind-for="['a', 'b', 'c']">
    <span bind-text="$key"></span>
    <span bind-text="$value"></span>
  </li>
</ul>

<!-- loop from property object/array, access itteration using phantom property -->
<ul>
  <li bind-for="list">
    <span bind-text="$key"></span>
    <span bind-text="$value.name"></span>
  </li>
</ul>

<!-- set phantom names for key and value -->
<ul>
  <li bind-for="list" config-for="{'key': 'idx', 'value': 'data'}">
    <span bind-text="$idx"></span>
    <span bind-text="$data.name"></span>
  </li>
</ul>

<!-- more complex optionsm ordering on contents, filtering on contents, limits and offsets -->
<ul>
  <li bind-for="list" order-for="{'id': 'desc', 'title': 'asc'}" filter-for="{'title': '*wild*', 'foo': 'literal', 'bar': ['*', property, '*']}" limit-for="2" offset-for="2">
    <span bind-text="$key"></span>
    <span bind-text="$value.title"></span>
  </li>
</ul>

Control

Allowing HTML controls to update model properties.

value Two way bind to element value

Offers one way binding to option, two way binding to form controls such as inputs, select boxes, textareas, custom elements etc. Updating the controls will update the model property, model property updates will update the dom. Use `document.querySelector(element).value` to get and set element value.

  • Accepts Resolvers: property, phantom
<!-- basic value bind -->
<input type="text" bind-value="some.value"/>

<!-- basic value bind -->
<textarea bind-value="some.value"></textarea>

<!-- binding values in selects -->
<select bind-value="some.value">
   <option bind-for="list" bind-value="$key" bind-text="$value"></option>
</select>

model Two way bind to element model

Offers two way binding element, primarily for custom web components such as Razilo Components. Custom web components updating, will update the model property, model property updates will update the dom. Use `document.querySelector(element).model` to get and set element model.

  • Accepts Resolvers: property, phantom
<!-- basic model binds -->
<razilo-choose bind-model="an.object"></razilo-choose>

checked Two way bind to element checked value

Offers two way binding to form controls that use checked status such as radio buttons and check boxes. Updating the controls will update the model, model updates will update the dom.

  • Accepts Resolvers: property, phantom
<!-- radio buttons -->
<ul>
  <li bind-for="list">
    <input type="radio" name="test" bind-value="$key" bind-checked="some.value"/>
    <label bind-text="$value"></label>
  </li>
</ul>

<!-- check box -->
<input type="checkbox" name="boohoo" value="whatever" bind-checked="some.value"/>

event Multi-purpose event binder

Offers a simple way to bind any event to a method for an element. Accepts all js element event types without the 'on' bit.

Use this to add one or multiple events such as 'click' or 'mouseenter'.

  • Accepts Resolvers: object
  • Accepts Methods: {'click': methodName(), 'mouseenter': methodName('hello', foobar, $key)}
  • Accepts Methods Values: all resolver types after the event name.
<!-- button clicks -->
<button bind-event="{'click': someMethod()}"></button>
<button bind-event="{'mouseenter': someMethod($key, 'something else')}"></button>

Application

Application specific binding helpers.

init App initiation binder

Offers a simple way to bind any method/function to a dom ready event. Add to any element in your bound area (maybe the actual root element) to fire the method/function on dom ready. Great for kick starting tasks when your project is loaded for setting up an app, removing a cloak etc.

  • Accepts Resolvers: method
<!-- your app wrapper you sent into the bind function, or any element... -->
<div id="my-app" bind-init="run()">
  <p>...</p>
  <p>...</p>
  <p>...</p>
</div>

Making your own Binders

There are two ways to add your own binders to the library, by injecting them with the addBinders() method bundled with razilobind, or if you have decided to import the core and have extended it, you may inject them along with all the other binders in the same fashion.

First off you will need a new binder, you can start off by taking an existing binder and copying it, changing the necessary parts. Lets call this your-test.binder.js

import {RaziloBindBinder} from 'razilobind-binder'

/**
 * Test Binder
 * Do something to the element node based on resolved data
 *
 * Inherits
 *
 * properties: options, node, resolver, traverser, model, accepts
 * method: detect(node) { return bool }
 * method: build(model) { return binder }
 * method: update(newValue, oldValue) { }
 */
export default class ShowBinder extends Binder {
  constructor(options, traverser) {
    super();
    this.options = options; 		// prefix etc.
    this.traverser = traverser; 	// so you can re-traverse new elements for binds
    this.name = 'your-test'; 		// name of the resolver to search for
    this.accepts = []; 				// accept all resolvers
  }

  /**
   * bind()
   * Bind the resolved data by showing hiding the node
   * @param object oldValue The old value of the observed object
   */
  bind(oldValue, path, action, key) {
    // oldValue is the value before the new change, before it was re-evaluated or empty on load
    // path is the path to the model property if property bound
    // action is the action being performed in this evaluation, such as update or array-remove
    // key is the key of any objects or array values removed or added (to allow synching of elements such as looping)

    // this.resolver contains the element binds resolver (resolved is the data that has been resolved and after alterers applied)
    // this.node is the actual element node you are on
    // this.traverser is the traverser instance you can use to re-traverse new elements (garbage collection on removed nodes is automatic!)
    // this.model is the bound model at the root level

    // various things methods are automatically run on bind, such as detection, build and updates from observers (which fire this method)
    // all that is required is to complete the necessary changes for your element in this method

    // this is a sample of showing and hiding an element based in truthy data
    if (!!this.resolver.resolved) this.node.style.display = '';
    else this.node.style.display = 'none';
  }
}

You can now import this into your project logic along with razilobind, injecting YourTestBinder into razilobind by adding custom binder.

import RaziloBind from 'razilobind'
import YourTestBinder from './your-test.binder.js'

var model = {foo: 'foo', bar: 'bar'};

var rb = new RaziloBind();
rb.addBinders({YourTest: YourTestBinder});
rb.bind('#test', model);

Or if you have extended the core with your own class, you can add them as follows.

import {RaziloBindCore, RaziloBindCoreDetector} from 'razilobind-core'
import {RaziloBindTrimAlterer, ...} from 'razilobind-alterer'
import {RaziloBindForBinder, ...} from 'razilobind-binder'
import {RaziloBindBooleanResolver, ...} from 'razilobind-resolver'
import YourTestBinder from './your-test.binder.js'

export default class YourProjectBind extends RaziloBindCore {
  constructor(options) {
    super(options);

    // Inject injectables, pull in what you need!
    RaziloBindCoreDetector.defaultAlterers = {TrimAlterer: RaziloBindTrimAlterer, ...};
    RaziloBindCoreDetector.defaultBinders = {ForBinder: RaziloBindForBinder, ...};
    RaziloBindCoreDetector.defaultResolvers = {BooleanResolver: RaziloBindBooleanResolver, ...};

    // Inject custom injectables
    RaziloBindCoreDetector.customBinders = {YourTest: YourTestBinder, ...};
  }
}

Either way will inject custom binders, should you wish to replace all default binders with your own custom ones, substitute the default injectables with your custom ones. Default injectables will also be parsed first, followed by custom ones, you choose how to and what to inject.

Once your new binder is injected, you should be able to use it like so (don't forget strings are in quotes, miss the quotes and you will be sending a property in!).

<span bind-your-test="foo"></span>

Thats all there is to it folks!