├── .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 | [](https://travis-ci.org/bredele/legoh)
4 | [](https://www.npmjs.com/package/legoh)
5 | [](http://npm-stat.com/charts.html?package=legoh)
6 | [](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 | [](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 |
--------------------------------------------------------------------------------