# 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: ```html

``` ```javascript 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: ```javascript $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. ```html ``` ```javascript //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`: ```html ``` ```javascript $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: ```html ``` ```javascript $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: ```html ``` ```javascript $s.check1 = true; $s.check1 = false; ``` #### On other elements it turns visibility on or off: ```html
ErrorSome error happens
``` ```javascript $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` ```html ``` ```javascript $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: ```html ``` ```javascript $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: ```html
table template line 1 table template line 1
table template line 2 table template line 2
``` ```javascript $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 `input`s in `td`. ### Associative Arrays (Objects) `Objects` used for set some paired values. #### `select > option`, `ul|ol > li` and other simple cases. ```html ``` ```javascript //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: ```html ``` ```javascript $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: ```html
age married surname name note
just some another field, not bound to data
``` ```javascript $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 ```html
age married surname name note
``` ```javascript 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.