├── LICENSE ├── README.md └── uncle.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Fedor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Uncle.js 2 | Uncle is a tiny (1.3k minzipped) virtual DOM library. Ya know React, right? 3 | 4 | What's different: 5 | * Should be easy to understand because of it's small code base 6 | * HTML templates - no JSX 7 | * Templates can be precompiled for production (easy, TBD) 8 | 9 | [Live Demo](http://jsfiddle.net/asrsrqhr/) on JSFiddle 10 | 11 | ## Example 12 | ```javascript 13 | var TodoApp = { 14 | items: [ 15 | {text: 'Buy milk', complete: false}, 16 | {text: 'Rob a bank', complete: true} 17 | ], 18 | toggle: function(index) { 19 | this.items[index].complete = !this.items[index].complete; 20 | this.update(); 21 | }, 22 | // render each item in context of TodoApp 23 | render: uncle.render(''), 24 | // mount our UL as a child of BODY 25 | update: uncle.update(document.body) 26 | }; 27 | 28 | var TodoItem = uncle.render(`
  • {{text}}
  • `); 29 | 30 | TodoApp.update(); 31 | ``` 32 | ## Templates 33 | Use double curly braces (mustaches) to embed Javascript one-liners. 34 | ```html 35 |
  • 36 | Item #{{$index}}: {{text}} 37 |
  • 38 | ``` 39 | Special attributes (or "directives"): 40 | * `key="unique{{id}}"` - enables efficient reuse of DOM elements (as seen in React and other libraries) 41 | * `onclick="this.method(event, arg1)"` - DOM 1 event listeners where `this` is your render context (not a DOM element) 42 | * `html="raw {{html}}"` - same as `innerHTML` 43 | 44 | ## API 45 | ### uncle.render(html) 46 | Converts your template to a Javascript function, which renders virtual DOM. 47 | ```javascript 48 | var HelloMessage = { 49 | name: "Mr. Spock", 50 | render: uncle.render("
    Hello {{ this.name }}
    ") 51 | }; 52 | // HelloMessage.render() == {tag: "div", attrs:{}, children:[ "Hello ", HelloMessage.name ]} 53 | ``` 54 | Resulting function accepts two *optional* arguments: `render(some_value, index)`. 55 | This can be used with native Array methods like `Array#map()`. In template's context both arguments are avaliable as `$value` and `$index` accordingly. If `some_value` is an object, it's properties are made avaliable as regular variables for convenience. 56 | 57 | ### uncle.update(containerElement) 58 | Allows any "renderable" component to update the real DOM. When you call `HelloMessage.update()`, it runs `this.render()`, computes a diff between two virtual DOMs and patches the real one if needed. 59 | ```javascript 60 | HelloMessage.update = uncle.update(document.body); 61 | HelloMessage.update(); 62 | // OR 63 | var updateBody = uncle.update(document.body).bind(HelloMessage); 64 | updateBody(); 65 | ``` 66 | One can call `update()` on-demand or put it in a RAF loop like this: 67 | ```javascript 68 | function updateDOM() { 69 | HelloMessage.update(); 70 | window.requestAnimationFrame(updateDOM); 71 | } 72 | updateDOM(); 73 | ``` 74 | 75 | -------------------------------------------------------------------------------- /uncle.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Uncle.js is a tiny virtual DOM library 3 | * https://github.com/Fedia/unclejs 4 | * v0.1.0 5 | */ 6 | 7 | window.uncle = (function() { 8 | 9 | function vdom(html) { 10 | var render = Function('_c,$value,$index', 'with(_c)return' + precompile(html)); 11 | return function(val, i) { 12 | var ctx = typeof val === 'object'? val : {}; 13 | return render.call(this, ctx, val, i)[0]; 14 | }; 15 | } 16 | 17 | function precompile(html) { 18 | var tagrx = /(<\/?)([\w\-]+)((?:\s+[\w\-]+(?:\s*=\s*(?:".*?"|'.*?'|[^'">\s]+))?)+\s*|\s*)\/?>/g, 19 | voidrx = /^(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i, 20 | exprx = /{{([^}]+)}}/g; 21 | var tokens = html.split(tagrx); 22 | var js = '[', tok = '', tag = null, first_child = true; 23 | for (var i = 0, l = tokens.length; i < l; i++) { 24 | tok = tokens[i]; 25 | switch(tok) { 26 | case '<': 27 | tag = tokens[++i].toLowerCase(); 28 | js += (first_child? '' : ',') + '{"tag":"' + tag + '","attrs":{' + 29 | tokens[++i].replace(/\s([^on][\w\-]+)\s*=\s*(?:"(.*?)"|'(.*?)')/g, ',"$1":"$2"'). 30 | replace(/\s(on\w+)\s*=\s*(?:"(.*?)"|'(.*?)')/g, ',"$1":(function(){$2}).bind(this)'). 31 | replace(exprx, '"+($1)+"').substr(1) + '},"children":[].concat('; 32 | if (voidrx.test(tag)) { 33 | js += ')}'; 34 | } else { 35 | first_child = true; 36 | } 37 | break; 38 | case '