# NanoBind - Templating and Data binding on DOM in JS Sorry my poor English, my native language is Pascal. ## Samples here: http://helium.asmer.org.ua:10011 ## 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 <p id='p1'></p> <p id='p2'></p> <input type='text' value="some input value" name='sampleText'> ``` ```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`. ## 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 <select id="select"> <option value="">--</option> </select> ``` ```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 <span></span> <input type='number' placeholder="editable number"/> ``` ```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 <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> ``` ```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 <input type='checkbox' id='check1'> ``` ```javascript $s.check1 = true; $s.check1 = false; ``` #### On other elements it turns visibility on or off: ```html <div class="alert alert-danger"> <strong>Error</strong>Some error happens </div> ``` ```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 <div id='menu'> <a href='#' style='color: red;'></a> <a href='#' style='color: green;'></a> </div> ``` ```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 <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> ``` ```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> <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> ``` ```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 <select id="select"> <option value="">--</option> </select> ``` ```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 <input id='someInput' /> ``` ```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 <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> ``` ```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 <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> ``` ```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.