├── .gitignore ├── .travis.yml ├── .zuul.yml ├── Readme.md ├── index.js ├── lib └── bind.js ├── package.json └── test └── legoh.js /.gitignore: -------------------------------------------------------------------------------- 1 | components 2 | node_modules 3 | build 4 | backup -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | env: 5 | global: 6 | - secure: nQuikGRYucx6RHkG/4oSg7CNmdKHx3+vDWr8YzKBAn+X8lLI8z75vW++gznfXsglGR/9jUjX0tkZ9JADg21IQ8hOpc7+8OhERbnLlDa4LzOO+BVeHIRzy9p7LmYDY18mUWQ5zSiae//J9dvABTYPJegiNIKbalIvJfzqHesYdks= 7 | - secure: HzsxyUQPOQBJRfjQRjxuw2ss5vJ4/9L1JAW3eKvWa56kLAX+LUrpSmOFAETrOe4EggvwONBS29XuLYreZsi/7E4LC66bPhR9182+DfayDXctTMHeBaEtlFzoYx1eUGz8ml4tk0KVI5HX7uW/kfzmYMEgCvpbljVmYAgLcTwi5E0= 8 | -------------------------------------------------------------------------------- /.zuul.yml: -------------------------------------------------------------------------------- 1 | ui: mocha-bdd 2 | browsers: 3 | - name: chrome 4 | version: latest 5 | - name: firefox 6 | version: latest 7 | - name: opera 8 | version: latest 9 | - name: safari 10 | version: oldest..latest 11 | - name: iphone 12 | version: latest 13 | - name: android 14 | version: latest 15 | - name: ie 16 | version: 8..latest -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Legoh 2 | 3 | [![Build Status](https://travis-ci.org/bredele/legoh.svg?branch=master)](https://travis-ci.org/bredele/legoh) 4 | [![NPM](https://img.shields.io/npm/v/legoh.svg)](https://www.npmjs.com/package/legoh) 5 | [![Downloads](https://img.shields.io/npm/dm/legoh.svg)](http://npm-stat.com/charts.html?package=legoh) 6 | [![pledge](https://bredele.github.io/contributing-guide/community-pledge.svg)](https://github.com/bredele/contributing-guide/blob/master/community.md) 7 | 8 | **DEPRECATED** Please use [brickjs branch](https://github.com/bredele/lego/tree/brickjs) or even better, use React or Vue. 9 | 10 | ## Usage 11 | 12 | ```js 13 | var lego = require('legoh') 14 | 15 | ``` 16 | 17 | Check out [examples](/examples) and [docs](/docs) for more information. 18 | 19 | ## Installation 20 | 21 | ```shell 22 | npm install legoh --save 23 | ``` 24 | 25 | [![NPM](https://nodei.co/npm/legoh.png)](https://nodei.co/npm/legoh/) 26 | 27 | ## Question 28 | 29 | For questions and feedback please use our [twitter account](https://twitter.com/bredeleca). For support, bug reports and or feature requests please make sure to read our 30 | community guideline and use the issue list of this repo and make sure it's not present yet in our reporting checklist. 31 | 32 | 33 | ## Contribution 34 | 35 | Legoh is an open source project and would not exist without its community. If you want to participate please make sure to read our guideline before making a pull request. If you have any legoh-related project, component or other let everyone know in our wiki. 36 | 37 | ## License 38 | 39 | The MIT License (MIT) 40 | 41 | Copyright (c) 2016 Olivier Wietrich 42 | 43 | Permission is hereby granted, free of charge, to any person obtaining a copy 44 | of this software and associated documentation files (the "Software"), to deal 45 | in the Software without restriction, including without limitation the rights 46 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 47 | copies of the Software, and to permit persons to whom the Software is 48 | furnished to do so, subject to the following conditions: 49 | 50 | The above copyright notice and this permission notice shall be included in all 51 | copies or substantial portions of the Software. 52 | 53 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 54 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 55 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 56 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 57 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 58 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 59 | SOFTWARE. 60 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dependencies 3 | */ 4 | 5 | var store = require('datastore').factory 6 | var walk = require('domwalk') 7 | var bind = require('./lib/bind') 8 | 9 | 10 | /** 11 | * Create a Legoh brick. 12 | * A brick is a DOM element that updates itself with data changes. 13 | * 14 | * @param {String | Element} tmpl 15 | * @param {Object} data 16 | * @api public 17 | */ 18 | 19 | module.exports = function(tmpl, data) { 20 | 21 | data = data || {} 22 | 23 | var el = domify(tmpl) 24 | 25 | var brick = store(function() { 26 | return el 27 | }, data) 28 | 29 | walk(el, function(node) { 30 | if(node.nodeType == 1) { 31 | // @note attributes and node type should be handled by bind 32 | attribute(brick, node, data) 33 | } else bind(brick, node, data) 34 | }) 35 | 36 | return brick 37 | } 38 | 39 | function attribute(brick, node, data) { 40 | var attrs = node.attributes 41 | for(var i = 0, l = attrs.length; i < l; i++) { 42 | var attr = attrs[i] 43 | //text(brick, attr, data) 44 | var name = attr.nodeName 45 | if(name.substring(0,2) == 'on') { 46 | var content = attr.nodeValue 47 | attr.nodeValue = '' 48 | listen(brick, node, name.substring(2), content) 49 | } 50 | } 51 | } 52 | 53 | 54 | /** 55 | * Delegate DOM event to the datastore emitter. 56 | * 57 | * @note we should also register the listener handler 58 | * to a removed event (with mutation observer). 59 | * 60 | * @param {Datastore} brick 61 | * @param {Element} node 62 | * @param {String} type 63 | * @param {String} topic 64 | * @api private 65 | */ 66 | 67 | function listen(brick, node, type, topic) { 68 | node.addEventListener(type, function(event) { 69 | brick.emit(type + (topic ? ' ' + topic : '')) 70 | }) 71 | } 72 | 73 | /** 74 | * Transform template into a DOM element. 75 | * 76 | * @param {String | Element} tmpl 77 | * @return {Element} 78 | * @api private 79 | */ 80 | 81 | function domify(tmpl) { 82 | var div = document.createElement('div') 83 | div.innerHTML = tmpl 84 | return div.children[0] 85 | } 86 | -------------------------------------------------------------------------------- /lib/bind.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Expressions to parse and compile placeholders from string. 4 | * A placeholder is an expression enclosed into `${}` or `#{}` 5 | */ 6 | 7 | var expressions =/(\$|\#)\{([^{}]*)\}/g 8 | var parser = /\.\w+|"[^"]*"|'[^']*'|\/([^/]+)\/|[a-zA-Z_]\w*/g 9 | var forbidden = ['"', '.', "'"]; 10 | 11 | 12 | /** 13 | * Bind node with lego data. 14 | * 15 | * @api private 16 | */ 17 | 18 | module.exports = function(brick, node, data) { 19 | var parent = node.parentElement 20 | var str = node.nodeValue 21 | var idx = 0 22 | var fragment 23 | str.replace(expressions, function(_, type, expr, i) { 24 | var child = document.createTextNode(str.substring(idx, i)) 25 | fragment = fragment || document.createDocumentFragment() 26 | fragment.appendChild(child) 27 | update(brick, child, expr, type == '$', data) 28 | idx = i + _.length 29 | }) 30 | if(fragment) parent.replaceChild(fragment, node) 31 | } 32 | 33 | 34 | 35 | /** 36 | * Update node whenever model data changes. 37 | * 38 | * @note the node value of a text node when the data changes 39 | * should be transformed (stream, promises, etc) 40 | * 41 | * @param {Node} node 42 | * @param {Store} model 43 | * @param {String} expr 44 | * @param {Boolean} bool 45 | * @api private 46 | */ 47 | 48 | function update(brick, node, expr, bool, data) { 49 | var list = [] 50 | var cb = compile(expr, list) 51 | // we should use regurgitate 52 | node.nodeValue = cb(data) 53 | if(bool) list.map(function(name) { 54 | brick.on('changed ' + name, function() { 55 | node.nodeValue = cb(data) 56 | }) 57 | }) 58 | } 59 | 60 | 61 | /** 62 | * Compile expression by replacing identifiers. 63 | * 64 | * Examples: 65 | * 66 | * compile('name + last'); 67 | * // => model.name + model.last 68 | * 69 | * compile('name[0]'); 70 | * // => model.name[0] 71 | * 72 | * @param {String} str 73 | * @param {Array} arr 74 | * @return {String} 75 | * @api private 76 | */ 77 | 78 | function compile(str, arr) { 79 | return new Function('model', 'return ' + str.replace(parser, function(expr) { 80 | if(forbidden.indexOf(expr[0]) > -1) return expr 81 | if(!~arr.indexOf(expr)) arr.push(expr) 82 | return '(model.' + expr + ' || "")' 83 | })) 84 | } 85 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "legoh", 3 | "version": "0.0.0", 4 | "description": "Build web applications brick by brick as a child's play", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "browserify test/*.js | tape-run" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/bredele/legoh.git" 12 | }, 13 | "keywords": [ 14 | "MVVM", 15 | "brick", 16 | "UI", 17 | "MVC", 18 | "store", 19 | "model", 20 | "binding" 21 | ], 22 | "author": "Olivier Wietrich (http://github.com/bredele)", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/bredele/legoh/issues" 26 | }, 27 | "dependencies": { 28 | "datastore": "^1.8.2", 29 | "domwalk": "^1.1.0" 30 | }, 31 | "devDependencies": { 32 | "browserify": "^13.1.1", 33 | "tape": "^4.6.2", 34 | "tape-run": "^2.1.4" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/legoh.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests dependencies 3 | */ 4 | 5 | var test = require('tape') 6 | var lego = require('..') 7 | 8 | 9 | test('should be a datastore', assert => { 10 | assert.plan(1) 11 | var brick = lego('') 12 | brick.set('name', 'olivier') 13 | assert.equal(brick.get('name'), 'olivier') 14 | }) 15 | 16 | 17 | test('should initialize datastore with data', assert => { 18 | assert.plan(1) 19 | var brick = lego('', { 20 | name : 'olivier' 21 | }) 22 | assert.equal(brick.get('name'), 'olivier') 23 | }) 24 | 25 | 26 | test('should create a DOM element from a string', assert => { 27 | assert.plan(1) 28 | var btn = lego('') 29 | assert.equal(btn().outerHTML, '') 30 | }) 31 | 32 | 33 | test('should listen DOM events and trigger event in emitter', assert => { 34 | assert.plan(1) 35 | var btn = lego('hello') 36 | btn.on('click something', function() { 37 | assert.pass('button has been clicked') 38 | }) 39 | btn().click() 40 | }) 41 | 42 | 43 | test('should bind DOM element with simple data expression', assert => { 44 | assert.plan(2) 45 | var btn = lego('') 46 | assert.equal(btn().outerHTML, '') 47 | btn.set('name', 'olivier') 48 | assert.equal(btn().outerHTML, '') 49 | }) 50 | 51 | 52 | test('should bind once DOM element with simple data expression', assert => { 53 | assert.plan(2) 54 | var btn = lego('') 55 | assert.equal(btn().outerHTML, '') 56 | btn.set('name', 'olivier') 57 | assert.equal(btn().outerHTML, '') 58 | }) 59 | 60 | 61 | test('should bind DOM element with complex expression', assert => { 62 | assert.plan(2) 63 | var btn = lego('') 64 | btn.set('first', 'olivier') 65 | assert.equal(btn().outerHTML, '') 66 | btn.set('last', 'wietrich') 67 | assert.equal(btn().outerHTML, '') 68 | }) 69 | 70 | 71 | // test('should bind DOM attribute with simple data expression', assert => { 72 | // assert.plan(2) 73 | // var link = lego('link') 74 | // link.set('domain', 'http://www.google.com') 75 | // assert.equal(link().getAttribute('href'), 'http://www.google.com') 76 | // link.set('domain', 'http://github.com/bredele') 77 | // assert.equal(link().getAttribute('href'), 'http://github.com/bredele') 78 | // }) 79 | --------------------------------------------------------------------------------