Resolver

Resolvers are how we resolve the string data in a raziloBind attribute to something like a literal, an object or a model property etc. They allow us to express data as a string in our HTML and offer the resolved attribute data to the binder.

What Actually ARE Resolvers?

Binders would be nothing without somethig to bind to, some data to work with. They offer a way to connect data to any raziloBind attribute, such as binders, alterers, configs, and specials (attributes only for certain binders). Without resolvers, how would we know what to connect to our HTML elements? Resolvers provide one simple generic task, resolving a text based string to a concrete type. Why do we use resolvers? because eval is evil. This way we standardize how data is used in attributes accross the board, and only ever pass data in, in a fashion that is resolvable. If data is not resolvable, no further action is taken.

Tell me More Sir!

Resolvers are the way we resolve the data inside raziloBind attributes to something legible, such as a string, an object a method (function assigned to a model property). Whilst other frameworks use pipes, commas, colons etc. to create a complex syntax (remember this?... do i need quotes here or not hmmm), we do things a little different. We offer a single way to express data in raziloBind attributes accross the board. Losely based around JSON too, so you will be familiar, we specify data as though we are writing JSON with single quotes (not double as HTML uses double). Need to send more data into a binder, we use another attribute! We do not convolute the data string.

How do we Use Resolvers?

Well you dont directly use them, they are used by things like binders, alterers, configs... basically, any data sent in over a raziloBind attribute is resolved first to try and make sense of it. If the data cannot be resolved the action is ended. Resolvers can be 'literals', 432, ['array', 'data'], {'object': 'data'}, model.property['name'], methods() etc. For a full list of resolvers, please look below. Wehn you use data in this fashion, it is resolved to an actual value directly or from the model. Any resolvable data that can be watched for changes (two way binding, or one way binding with embedded two way binding values) will force the whole element to be re-evaluated on a change.

Whilst other frameworks can use regex (dangerous), we parse over resolvable data first, with a regex to try and dirty match it (quick and simple), if we get a match, well we inspect further. The end part of this inspection should yield data that has been resolved, which is past back to be altered (via alterers, if set), or it is used directly by the binder to do it's job. Resolvers are pretty awesome to, some resolvers use other resolvers, such as objects resolving the data within them, so we can embed any resolvable data inside resolvable data!

Usage Example

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

Resolving data in HTML attributes (binders)

Dont't forget you can use resolvable data with any raziloBind attribute, like alterers, configs etc.

<!-- literal resolvers such as string, number, boolean -->
<p bind-text="'Hellow World'"></p>
<p bind-text="12345"></p>
<p bind-show="true"></p>

<!-- array resolver with various resolvable types inside -->
<p bind-class="['a-string', 1234, foobar]"></p>
<!-- array resolver with nested arrays/objects -->
<p bind-for="[{'id': 1, data: [10, 11, 12]}, {'id': 3, data: [100, 110, 120]}, {'id': 3, data: [1000, 1100, 1200]}]"></p>

<!-- object resolver with various resolvable types inside -->
<p bind-class="{'name': 'a-string', 'value': 1234, 'something': foobar}"></p>
<!-- object resolver with nested arrays/objects -->
<p bind-for="{'names': ['dave', 'john'], 'places': {'somewhere': 10, 'somewhere else': 20}}"></p>

<!-- property resolver - 'foobar' property of the model -->
<p bind-text="foobar"></p>
<!-- property resolver - 'foobar.baz.boo' we can have dot or bracket notation -->
<p bind-text="foobar.baz['boo']"></p>
<!-- property resolver - 'foobar.baz.boo.namePropertyValue' we can also use property values as the key! This adds a another observer for 'foobar.name' -->
<p bind-text="foobar.baz['boo'][foobar.name]"></p>

<!-- method resolver - 'someMethod' method assign to model property -->
<p bind-text="someMethod()"></p>
<!-- method resolver - sending in variables, these can be any resolvable types (this will be re-run if foobar.baz changes!) -->
<p bind-text="someMethod('hello', foobar.baz)"></p>
<!-- method resolver - use them anyhow you like, change foo.disabled, re-run the method and alter the class! -->
<p bind-class="{'disabled': someMethod(foo.disabled)}"></p>

<!-- phantom binder - they resolve back to an object/array key and value, they start with a $, they can be renamed with some binders like 'for'! -->
<ul>
  <li bind-for="object"><span bind-text="$key"></span> <span bind-text="$value"></span></li>
</ul>

Show me the Resolvers

These are the default resolvers 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.

One Way Binding

Pure literals, such as strings, numbers booleans.

string Resolve as string

Resolves data to string literal.

  • Binding Type: One way on page load
  • Resolvable Data: 'Single quotes around data'
<span bind-???="'Hello World'"></span>

number Resolve as number

Resolves a string to number literal, with or without periods.

  • Binding Type: One way on page load
  • Resolvable Data: 123456
<span bind-???="123"></span>

boolean Resolve as bool

Resolves data to boolean literal. Ensure you inject this resolver before property resolver if injecting resolvers manually i.e. via core.

  • Binding Type: One way on page load
  • Resolvable Data: true or false
<span bind-???="true"></span>
<span bind-???="false"></span>

Updatable One Way Binding

Literals with embedded properties and non changing model methods with embedded properties/phantoms.

array Resolve as array

Resolves data to an array literal (well, array object to be precise).

  • Binding Type: One way on page load, two with embedded property/phantom
  • Resolvable Data: [123, 'hello', method(), ['a', 'b']]
<span bind-???="[123, 'hello', true, ['a', 'b']]"></span>
<span bind-???="[foobar, method(foo), {'list', [1, 2, 3]}]"></span>

object Resolve as object

Resolves data to an object literal.

  • Binding Type: One way on page load, two with embedded property/phantom
  • Resolvable Data: {'key': 123, 'title': 'hello'}
<span bind-???="{'key': 123, 'title': 'hello', 'other': {'a': 'aaa'}}"></span>
<span bind-???="{'phantom': $value.id, 'method': method({'test': 'test'})}"></span>

method Runs model method

Resolves data to a model property that is a method (function). This will run the method and return the any values as the resolved data.

When using a method resolver in conjunction with an event type binder, processing of the method is delated until the event is fired.

  • Binding Type: One way on page load, two with embedded property/phantom
  • Resolvable Data: some.method('something', 123, true, [1,2,3])
<span bind-???="someMethod('something', 123, true)"></span>
<span bind-???="someMethod([1,2,3], foo.bar, anotherMethod())"></span>
<span bind-event="{'click': delayedMethod($key)}"></span>

Two Way Binding

Properties or variations of properties such as phantom properties (properties being served with by an identifier).

property Resolve as model property

Resolves data to a model property by matching the name, specified using a mix of dot and bracket notation.

You can also embed properties in property names (inside brackets) such as foo.bar[bar.baz] to resolve property names based on values of other properties.

  • Binding Type: Two way, forces one way re-evaluation when embedded.
  • Resolvable Data: method or method.with.dot['and'].brackets
<span bind-???="foobar"></span>
<span bind-???="foobar.something['else']"></span>
<span bind-???="foobar[baz.bar['test']]"></span>

phantom Resolve as model property

Resolves data to a model property based on a parent binder.

Phantom data starts with a dollar sign, and basically means we are referencing a model property by another name.

Binders set phantom data, such as the for binder, which generates $key and $value, mapping to the specific loop instance, allowing you to grab data for each loop easily, using it in child elements.

Binders generating phantom variables also allow the names to be configured, such as for binders default $key and $value, to stop conflicts.

  • Binding Type: One way on page load, two with embedded property/phantom
  • Resolvable Data: $phantomName or $value.with['objectInIt']
<span bind-for="['a', 'b', 'c']" bind-???="$key" bind-???="$value"></span>
<span bind-for="['a': {'id': 1}, 'b': {'id': 1}]" bind-???="$key" bind-???="$value.id"></span>
<span bind-for="['a': {'id': 1}]" config-for="{'key': 'idx', 'value': 'data'}" bind-???="$idx" bind-???="$data.id"></span>

Making your own Resolvers

There are two ways to add your own resolvers to the library, by injecting them with the addResolvers() 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 resolvers in the same fashion.

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

  import {RaziloBindResolver} from 'razilobind-resolver'

  /**
   * Test Resolver
   * Resolves data as test
   *
   * Inherits
   *
   * property: data, node
   * method: detect(data) { return bool }
   */
  export default class TestResolver extends Resolver {
    constructor(node) {
      super();
      this.node = node;
      this.name = 'test';
      this.regex = TestResolver.regex();
    }

    /**
     * resolve()
     * Resolve data to a number, set any observables on data
     */
    resolve(object) {
      // object is the model to resolve against
      // this.node is the element you are currently working on (usefull if resolving iterations of a loop like phantom)
      // this.data is the data from the attribute to resolve

      var res = TestResolver.toTest(this.data); // resolve the data to a value and any observables
      this.resolved = res.resolved; // push the observables to this object
      this.observers = res.obeservers; // push the observables to this object
    }

    /**
     * static regex()
     * The pattern to test the data on, this should be clever enough to catch the data but not too in-depth, it will test attribute data
     * Created as a static function to allow for easy testing from other resolvers
     * @return object regex The regex used to validate if of type or not
     */
    static regex() {
      return /^\'.*\'$/; // checks for a string ala 'string'
    }

    /**
     * static toString()
     * turns data into an actual string literal, yours should convert the data into the type you want it to be
     * @param string data The data to resolve
     * @return object {resolved: ..., observers:...} The resolved data and any observers needed to track future changes
     */
    static toString(data) {
      // remove the quotes around the data (we know they are there because the regex must have them!)
      // do not set any obervers as literals do not have anything to watch
      // if you do set obervers, these should be dot notation only path to property (even if key has spaces or is a number)

      return {resolved: data.substring(1, data.length -1), observers: []};
    }
  }

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

import RaziloBind from 'razilobind'
import TestResolver from './test.resolver.js'

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

var rb = new RaziloBind();
rb.addResolvers({Test: TestResolver});
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 TestResolver from './test.resolver.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.customResolvers = {Test: TestBinder, ...};
    }
  }

Either way will inject custom resolvers, should you wish to replace all default resolvers 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 resolver 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-text="your-new-data-to-resolve"></span>

Thats all there is to it folks!