Сommon DOM data flow on proxies. http://helium.asmer.org.ua:10011

Ivan Asmer cbd1cccf52 closure fake selector 6 years ago
static cbd1cccf52 closure fake selector 6 years ago
README.md 4414d61985 closure fake selector 6 years ago
index.js c6587fb14b initial 7 years ago
package.json c6587fb14b initial 7 years ago

README.md

NanoBind - Templating and Data binding on DOM in JS

Sorry my poor English, my native language is Pascal.

Samples

here: http://nanobind.asmer.fe2.a-level.com.ua/static/

License

MIT

Idea

Declarative better than imperative

Instead of write loops in code and/or template, just give a structure and get a view.

Clean HTML

Good HTML declarative enough, to avoid using some template engine markers like {{ varname }}, {% control structure syntax %} and so on. Also, usually template loops requires some loopable data structure, passed into temlate engine to render.

Less better, than more

It's not jQuery, Angular or Meteor. It's just a way to put and get data from DOM Tree more transparent and declarative. And NanoBind does nothing to conflict with any other framework.

Requirements

Support of ES6 Proxy object.

It's a magic

Basic initialization:

<p id='p1'></p>
<p id='p2'></p>
<input type='text' value="some input value" name='sampleText'>
var $s = nbInit({p1: "first paragraph"});

$s.p2 = "second paragraph";

alert($s.sampleText);

NanoBind gives ability to read and write data into DOM tree, using usual JS data structures: strings, booleans, arrays and objects. Access to DOM tree works on ids, names, classes and CSS-Selectors

DOM Tree search priorities

When you accessing to some property in bound object ($s in this document), for example $s.input NanoBind tries to find DOM elements in next priority order:

  • Element with id="input", if not found, it tries to find,
  • Element(s) by CSS-Selector "input", e. g. some tag(s) input, if not found, it tries to find,
  • Element(s) with name="input". You can use it for radiobuttons, or, if not found,
  • Element(s) with one of classes, equals input.

special fake selectors

This selectors changes default nanobind behaviour about data structures (cloning, round-robin, object-to-class assignements, nested structures, and so)

|dom

some selector|dom says to nanobind than assigning structure isn't a subobject for nested tags, but dom object to assign to exactly this element(s).

|closure

some selector|closure says to nanobind than assigning array isn't for default array nanobind behavior, but function with params, which initializes some plugin at matching elements. Function runned by nanobind, and should return a closure function to read plugin status:

$s['somePluginContainer|closure'] = [function(value, options){
    this.value = value
    return function(){
        return this.value
    }
},
'someDefaultValueForInput']

....
alert($s['somePluginContainer|closure'])

When you set array to |closure selector, nanobind runs your function (first in array) with this set to matched element(s) (i. e. #somePluginContainer in sample above). When you read this selector, nanobind runs function, returned by passed function when set.

Anisotropic dataflow

Nanobind offers anisotropic behavior for data passed and read in DOM tree. I. e. when you set something to DOM and read this property, it's sometimes aren't same value.

<select id="select">
    <option value="">--</option>
</select>
//filling dropdown list
$s.select = {"": "--",
             M:  "Male",
             F:  "Female",
             X:  "Xenomorph"};
//reading dropdown value:
$s.select; //evaluates as "", because it's first value in list
//set it to Xenomorph:
$s.select = 'X'; 

In sample above we are populate select with options from associative array, than read value of select, and it's not associative array, it's value of select. Than we set other value for this select using option value.

Setting and templating

How to set some HTML values and properties, using NanoBind, and how data types and structures interpreted.

Strings and Numbers

Are used for setting DOM value or innerText:

<span></span>
<input type='number' placeholder="editable number"/>
$s.span = "Some text in span";
$s.input = "45";

Please note, than both tags will be found by CSS-Selector. NanoBind will change all matched tags found.

input[type='radio']

Radiogroups works as expected:

<label>
    <input type="radio" class="radio" name="sex" value="M">
    <span class="description">Male</span><br>
</label>
<label>
    <input type="radio" class="radio" name="sex" value="F">
    <span class="description">Female</span><br>
</label>
$s.sex = "M"; //set to male sex, or
//$s.sex = "F"; //set to female sex


$s.sex; //evaluated as 'M' or 'F', depending on current radio group value.

Boolean

Booleans has two roles in NanoBind

input[type='checkbox']

On checkboxes, booleans, naturally, turns check on or off:

    <input type='checkbox' id='check1'>
$s.check1 = true;
$s.check1 = false;

On other elements

it turns visibility on or off:

<div class="alert alert-danger">
  <strong>Error</strong>Some error happens
</div>
$s["alert-danger"] = !!errorMessage; //if errorMessage is empty, turn off that div.
$s["alert-danger"] =   errorMessage; //and set innerText to errorMessage value.

Arrays

Usually arrays means some repeating one type data values, should be represented as list or table in HTML:

round robin

<div id='menu'>
    <a href='#' style='color: red;'></a>
    <a href='#' style='color: green;'></a>
</div>
$s.menu = ["Main", "About Us", "Other links", "second green link"];

In sample above NanoBind create four elements a, using round robin, and fills them with data from array.

Arrays and many found elements

If NanoBind found more than one matched element with no children or count of elements equals array length, it set them one-to-one, w/o multiplication:

<label><input type='checkbox' class='check'><span class='description'></span><br/></label>
<label><input type='checkbox' class='check'><span class='description'></span><br/></label>
<label><input type='checkbox' class='check'><span class='description'></span><br/></label>
$s.check = [false,false,false]; //turn all checkboxes with class `check` to unchecked state

In other cases every matched element will be set with array (with multiplication of subnodes)

Arrays and recursion.

NanoBind can fill tables with array of arrays recursively:

<table>
    <tbody id="numberTable">
        <tr>
            <td><input>table template line 1</td>
            <td style='background-color: blue;'>table template line 1</td>
        </tr>
        <tr style='background-color: gray;'>
            <td>table template line 2</td>
            <td style='background-color: red;'>table template line 2</td>
        </tr>
    </tbody>
</table>
$s.numberTable = [[[1],2,[3,"input"],4,5,6],
                    [7,8,9,10,11,12],
                    [13,14,15,16,17,18],
                    [19,20,21,22,23,24]],

Due to input in some td, some values passed as one-element array to get one level deeper into input. As you can see, round-robin works recursively on tr and td levels. Also, you may set more than one element in deepest arrays like [1] and [3] and got third level of recurrent multiplication: many inputs in td.

Associative Arrays (Objects)

Objects used for set some paired values.

select > option, ul|ol > li and other simple cases.

<select id="select">
    <option value="">--</option>
</select>
//filling dropdown list
$s.select = {"": "--",
             M:  "Male",
             F:  "Female",
             X:  "Xenomorph"};

works as expected - multiplying option with object, using keys as value and values as innerText.

Setting element properties

Sometimes you need to set some set of DOM HTML Element properties, and you can use objects for this:

<input id='someInput' />
$s.someInput = {type: "number", placeholder: "percents", max: "100", min: "0", value: 50, onchange: function(){
    alert(this.value);
}}

Event handlers can be passed as HTML Element properties too.

Using key as class

To be free from HTML structure and key order in object, you can use object keys as class name:

<table>
    <thead>
        <th> age</th>
        <th> married</th>
        <th> surname</th>
        <th> name</th>
        <th> note</th>
    </thead>
    <tbody id="hashTable">
        <tr>
            <td class='age'></td>
            <td><input type='checkbox' class='married'></td>
            <td class='surname'></td>
            <td class='name'></td>
            <td><textarea class='note'></textarea></td>
            <td> just some another field, not bound to data</td>
        </tr>
    </tbody>
</table>
$s.hashTable = [{ name: "Ivan",
                  surname: "Ivanovv",
                  age: "57",
                  note: {value: "Buhaet", name: 'ivanovvsTextArea'},
                  married: true},
                { name: "Petr",
                  surname: "Petroff",
                  age: "17",
                  note: "Tyolki v golove",
                  married: false,
                  },
                { name: "Mary",
                  surname: "Tester",
                  married: true,
                  note: "Ovulyashka",
                  age: "27"} ];

Getting data

NanoBind attaches your data to DOM HTML Element when you set it as nbData property, and use it as "template" for read. But, in some cases it doesn't returns updated data, but other value.

Numbers

Can't be returned at all, because DOM doesn't use numbers even for number properties like value in li or value in input[type='number'].

Strings

Are usual result of any read for primitive values.

Boolean

Returned only when input[type='checkbox'] reading. You can't read visibility status as boolean.

Reading inputs

...like textarea, select, input works as expected - returns what user has selected or entered into inputs.

Complex data structures like arrays and objects

...reading recursively, as passed when set. But some collisions possible with nested inputs.

Tricks

Appendable Edit

simple sample of editable data set with minimal event handling

<table>
    <thead>
        <th> age</th>
        <th> married</th>
        <th> surname</th>
        <th> name</th>
        <th> note</th>
    </thead>
    <tbody id="hashTable">
        <tr>
            <td class='age'></td>
            <td><input type='checkbox' class='married'></td>
            <td class='surname'></td>
            <td class='name'></td>
            <td><textarea class='note'></textarea></td>
            <td><button class='btnDel'>x</button></td> 
        </tr>
    </tbody>
</table>
var persons = [{ name: "Ivan",
                  surname: "Ivanovv",
                  age: "57",
                  note: {value: "Buhaet", name: 'ivanovvsTextArea'},
                  married: true},
                { name: "Petr",
                  surname: "Petroff",
                  age: "17",
                  note: "Tyolki v golove",
                  married: false,
                  },
                { name: "Mary",
                  surname: "Tester",
                  married: true,
                  note: "Ovulyashka",
                  age: "27"} ];

$s.hashTable = persons;

function btnDel(){
    var thisLine = this.parentElement.parentElement;
    thisLine.remove();
    $s["#hashTable tr:first-child button"] = false; //turn off first delete button 
};

$s.btnDel = {onclick: btnDel};

$s.addPerson = {onclick: function(){
    var newLine = document.getElementById('hashTable').copy.children[0].cloneNode(true);
    newLine.nbData = Object.assign({},persons[0]);
    document.getElementById('hashTable').appendChild(newLine);
    $s.btnDel = {onclick: btnDel};
}}

Overall, I'm not sure than event handlers and other imperative things might be used this way with NanoBind. Maybe frameworks better for this.

TODO

Recursive HTML

For building nested structures from same HTML subtree like tree control or menu

Substructure Proxy

Now data passed only when you're rewrite some key in $s. Any changes like $s.hashTable[0].age = 19 has no effect, because object instance are same as before.

nbData collisions.

Now every element has one or zero nbData, but any element matches on many selectors, potentially with many nbData. So, nbData should be converted to associative array 'selector': data.