├── demo ├── todoapp.js ├── todoapp.tag ├── riotcontrol.js ├── todo.css ├── index.html ├── todostore.js ├── todo.js ├── todo.tag └── js │ ├── html5-shiv.js │ ├── riot.js │ └── es5-shim.js ├── routing_demo ├── item-detail.js ├── item-detail.tag ├── riotcontrol.js ├── todo.css ├── index.html ├── itemstore.js ├── item-app.js ├── item-app.tag └── js │ ├── html5-shiv.js │ └── riot.js ├── binary_demo ├── riotcontrol.js ├── calcstore.js ├── binary-app.tag ├── binary-app.js ├── index.html └── js │ ├── riot.min.js │ ├── html5-shiv.js │ └── es5-shim.js ├── bower.json ├── riotcontrol.js ├── package.json ├── LICENSE.txt └── README.md /demo/todoapp.js: -------------------------------------------------------------------------------- 1 | 2 | riot.tag('todoapp', '

RiotControl

A Simplistic Central Event Controller / Dispatcher For RiotJS, Inspired By Flux ', function(opts) { 3 | }) -------------------------------------------------------------------------------- /demo/todoapp.tag: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

RiotControl

5 | A Simplistic Central Event Controller / Dispatcher For RiotJS, Inspired By Flux 6 | 7 | 8 |
-------------------------------------------------------------------------------- /routing_demo/item-detail.js: -------------------------------------------------------------------------------- 1 | 2 | riot.tag('item-detail', '

Item Details

ID: { opts.item.id }
Name: { opts.item.title }
', function(opts) { 3 | }) 4 | -------------------------------------------------------------------------------- /routing_demo/item-detail.tag: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

Item Details

6 |
ID: { opts.item.id }
7 |
Name: { opts.item.title }
8 |
9 | 10 |
11 | -------------------------------------------------------------------------------- /demo/riotcontrol.js: -------------------------------------------------------------------------------- 1 | var _RiotControlApi = ['on','one','off','trigger'] 2 | var RiotControl = { 3 | _stores: [], 4 | addStore: function(store) { 5 | this._stores.push(store) 6 | } 7 | } 8 | _RiotControlApi.forEach(function(api){ 9 | RiotControl[api] = function() { 10 | var args = [].slice.call(arguments) 11 | this._stores.forEach(function(el){ 12 | el[api].apply(null, args) 13 | }) 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /binary_demo/riotcontrol.js: -------------------------------------------------------------------------------- 1 | var _RiotControlApi = ['on','one','off','trigger'] 2 | var RiotControl = { 3 | _stores: [], 4 | addStore: function(store) { 5 | this._stores.push(store) 6 | } 7 | } 8 | _RiotControlApi.forEach(function(api){ 9 | RiotControl[api] = function() { 10 | var args = [].slice.call(arguments) 11 | this._stores.forEach(function(el){ 12 | el[api].apply(null, args) 13 | }) 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /routing_demo/riotcontrol.js: -------------------------------------------------------------------------------- 1 | var _RiotControlApi = ['on','one','off','trigger'] 2 | var RiotControl = { 3 | _stores: [], 4 | addStore: function(store) { 5 | this._stores.push(store) 6 | } 7 | } 8 | _RiotControlApi.forEach(function(api){ 9 | RiotControl[api] = function() { 10 | var args = [].slice.call(arguments) 11 | this._stores.forEach(function(el){ 12 | el[api].apply(null, args) 13 | }) 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RiotControl", 3 | "version": "0.0.3", 4 | "homepage": "https://github.com/jimsparkman/RiotControl", 5 | "authors": [ 6 | "Jim Sparkman" 7 | ], 8 | "description": "Event Controller / Dispatcher For RiotJS, Inspired By Flux", 9 | "main": "riotcontrol.js", 10 | "moduleType": [ 11 | "node" 12 | ], 13 | "keywords": [ 14 | "riotjs", 15 | "flux" 16 | ], 17 | "license": "MIT", 18 | "ignore": [ 19 | "**/.*", 20 | "node_modules", 21 | "bower_components", 22 | "test", 23 | "tests" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /riotcontrol.js: -------------------------------------------------------------------------------- 1 | /* RiotControl v0.0.3, @license MIT */ 2 | var RiotControl = { 3 | _stores: [], 4 | addStore: function(store) { 5 | this._stores.push(store); 6 | }, 7 | reset: function() { 8 | this._stores = []; 9 | } 10 | }; 11 | 12 | ['on','one','off','trigger'].forEach(function(api){ 13 | RiotControl[api] = function() { 14 | var args = [].slice.call(arguments); 15 | this._stores.forEach(function(el){ 16 | el[api].apply(el, args); 17 | }); 18 | }; 19 | }); 20 | 21 | if (typeof(module) !== 'undefined') module.exports = RiotControl; 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "riotcontrol", 3 | "version": "0.0.3", 4 | "description": "Event Controller / Dispatcher For RiotJS, Inspired By Flux", 5 | "main": "riotcontrol.js", 6 | "files": [ 7 | "riotcontrol.js", 8 | "README.md", 9 | "LICENSE.txt" 10 | ], 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/jimsparkman/RiotControl" 17 | }, 18 | "keywords": [ 19 | "riotjs", 20 | "flux" 21 | ], 22 | "author": "Jim Sparkman", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/jimsparkman/RiotControl/issues" 26 | }, 27 | "homepage": "https://github.com/jimsparkman/RiotControl" 28 | } 29 | -------------------------------------------------------------------------------- /binary_demo/calcstore.js: -------------------------------------------------------------------------------- 1 | function CalculatorStore() { 2 | riot.observable(this) 3 | 4 | var self = this 5 | 6 | self.hex = '' 7 | self.max = 7 8 | self.bitlist = [] 9 | 10 | self.convert = function(num, currentRadix, resultRadix) { 11 | return parseInt(num, currentRadix).toString(resultRadix) 12 | } 13 | 14 | self.on('hex_changed', function(hexVal) { 15 | self.hex = hexVal 16 | var bNum = parseInt(hexVal,16) 17 | self.bitlist = [] 18 | for (var i = 0; i<=self.max; i++) { 19 | self.bitlist.push({ pos: self.max-i, value: ((1 << self.max-i) & bNum) > 0 ? 1 : 0}) 20 | } 21 | 22 | self.trigger('binary_changed', self.bitlist) 23 | }) 24 | 25 | self.on('bit_changed', function(bitPos) { 26 | var mask = (1 << bitPos) 27 | var newHex = parseInt(self.hex,16) ^ mask 28 | self.trigger('hex_forced_change', self.convert(newHex, 10, 16)) 29 | }) 30 | 31 | } 32 | -------------------------------------------------------------------------------- /binary_demo/binary-app.tag: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Binary Calculator

4 | Enter hex: 5 |
6 | 0x 7 | 8 | 9 | { value } 10 | 11 |
12 |
13 | { pos } 14 | 15 |
16 |
17 | 18 | var self = this 19 | self.bitlist = [] 20 | 21 | hexChanged(e) { 22 | e.target.value = e.target.value.toUpperCase() 23 | RiotControl.trigger('hex_changed', e.target.value) 24 | } 25 | 26 | toggle(e) { 27 | RiotControl.trigger('bit_changed', e.item.pos) 28 | } 29 | 30 | RiotControl.on('binary_changed', function(bitlist) { 31 | self.bitlist = bitlist 32 | riot.update() 33 | }) 34 | 35 | RiotControl.on('hex_forced_change', function(hexVal) { 36 | self.input.value = hexVal.toUpperCase() 37 | RiotControl.trigger('hex_changed', self.input.value) 38 | }) 39 | 40 |
-------------------------------------------------------------------------------- /binary_demo/binary-app.js: -------------------------------------------------------------------------------- 1 | riot.tag('binary-app', '

Binary Calculator

Enter hex:

0x { value }

{ pos }


', function(opts) { 2 | 3 | var self = this 4 | self.bitlist = [] 5 | 6 | this.hexChanged = function(e) { 7 | e.target.value = e.target.value.toUpperCase() 8 | RiotControl.trigger('hex_changed', e.target.value) 9 | }.bind(this); 10 | 11 | this.toggle = function(e) { 12 | RiotControl.trigger('bit_changed', e.item.pos) 13 | }.bind(this); 14 | 15 | RiotControl.on('binary_changed', function(bitlist) { 16 | self.bitlist = bitlist 17 | riot.update() 18 | }) 19 | 20 | RiotControl.on('hex_forced_change', function(hexVal) { 21 | self.input.value = hexVal.toUpperCase() 22 | RiotControl.trigger('hex_changed', self.input.value) 23 | }) 24 | 25 | 26 | }); -------------------------------------------------------------------------------- /routing_demo/todo.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | font-family: 'myriad pro', sans-serif; 4 | font-size: 20px; 5 | border: 0; 6 | } 7 | 8 | a { 9 | text-decoration: none; 10 | color: grey; 11 | } 12 | 13 | item-app { 14 | display: block; 15 | max-width: 400px; 16 | margin: 5% auto; 17 | } 18 | 19 | input { 20 | font-size: 100%; 21 | padding: .6em; 22 | border: 1px solid #ccc; 23 | border-radius: 3px; 24 | float: left; 25 | } 26 | 27 | button { 28 | background-color: #1FADC5; 29 | border: 1px solid rgba(0,0,0,.2); 30 | font-size: 100%; 31 | color: #fff; 32 | padding: .6em 1.2em; 33 | border-radius: 3em; 34 | cursor: pointer; 35 | margin: 0 .3em; 36 | outline: none; 37 | } 38 | 39 | button[disabled] { 40 | background-color: #ddd; 41 | color: #aaa; 42 | } 43 | 44 | ul { 45 | padding: 0; 46 | } 47 | 48 | li { 49 | list-style-type: none; 50 | padding: .2em 0; 51 | } 52 | 53 | .completed { 54 | text-decoration: line-through; 55 | color: #ccc; 56 | } 57 | 58 | label { 59 | cursor: pointer; 60 | } -------------------------------------------------------------------------------- /demo/todo.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | font-family: 'myriad pro', sans-serif; 4 | font-size: 20px; 5 | border: 0; 6 | } 7 | 8 | a { 9 | text-decoration: none; 10 | color: grey; 11 | display: block; 12 | max-width: 400px; 13 | margin: 5% auto; 14 | } 15 | 16 | todoapp { 17 | display: block; 18 | max-width: 400px; 19 | margin: 5% auto; 20 | } 21 | 22 | form input { 23 | font-size: 100%; 24 | padding: .6em; 25 | border: 1px solid #ccc; 26 | border-radius: 3px; 27 | } 28 | 29 | button { 30 | background-color: #1FADC5; 31 | border: 1px solid rgba(0,0,0,.2); 32 | font-size: 100%; 33 | color: #fff; 34 | padding: .6em 1.2em; 35 | border-radius: 3em; 36 | cursor: pointer; 37 | margin: 0 .3em; 38 | outline: none; 39 | } 40 | 41 | button[disabled] { 42 | background-color: #ddd; 43 | color: #aaa; 44 | } 45 | 46 | ul { 47 | padding: 0; 48 | } 49 | 50 | li { 51 | list-style-type: none; 52 | padding: .2em 0; 53 | } 54 | 55 | .completed { 56 | text-decoration: line-through; 57 | color: #ccc; 58 | } 59 | 60 | label { 61 | cursor: pointer; 62 | } -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | RiotControl Demo 7 | 8 | 9 | 10 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | View on GitHub 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jim Sparkman + contributors 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /demo/todostore.js: -------------------------------------------------------------------------------- 1 | // TodoStore definition. 2 | // Flux stores house application logic and state that relate to a specific domain. 3 | // In this case, a list of todo items. 4 | function TodoStore() { 5 | riot.observable(this) // Riot provides our event emitter. 6 | 7 | var self = this 8 | 9 | self.todos = [ 10 | { title: 'Task 1', done: false }, 11 | { title: 'Task 2', done: false } 12 | ] 13 | 14 | // Our store's event handlers / API. 15 | // This is where we would use AJAX calls to interface with the server. 16 | // Any number of views can emit actions/events without knowing the specifics of the back-end. 17 | // This store can easily be swapped for another, while the view components remain untouched. 18 | 19 | self.on('todo_add', function(newTodo) { 20 | self.todos.push(newTodo) 21 | self.trigger('todos_changed', self.todos) 22 | }) 23 | 24 | self.on('todo_remove', function() { 25 | self.todos.pop() 26 | self.trigger('todos_changed', self.todos) 27 | }) 28 | 29 | self.on('todo_init', function() { 30 | self.trigger('todos_changed', self.todos) 31 | }) 32 | 33 | // The store emits change events to any listening views, so that they may react and redraw themselves. 34 | 35 | } 36 | -------------------------------------------------------------------------------- /binary_demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | RiotControl Demo 7 | 8 | 9 | 14 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 | View on GitHub 35 | 36 | 37 | 38 | 39 | 40 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /demo/todo.js: -------------------------------------------------------------------------------- 1 | 2 | riot.tag('todo', '

{ opts.title }

', function(opts) { 3 | var self = this 4 | self.disabled = true 5 | self.items = [] 6 | 7 | self.on('mount', function() { 8 | RiotControl.trigger('todo_init') 9 | }) 10 | 11 | RiotControl.on('todos_changed', function(items) { 12 | self.items = items 13 | self.update() 14 | }) 15 | 16 | this.edit = function(e) { 17 | self.text = e.target.value 18 | }.bind(this) 19 | 20 | this.add = function(e) { 21 | if (self.text) { 22 | RiotControl.trigger('todo_add', { title: self.text }) 23 | self.text = self.input.value = '' 24 | } 25 | }.bind(this) 26 | 27 | this.toggle = function(e) { 28 | var item = e.item 29 | item.done = !item.done 30 | return true 31 | }.bind(this) 32 | 33 | this.remove = function(e) { 34 | RiotControl.trigger('todo_remove') 35 | }.bind(this) 36 | 37 | }) -------------------------------------------------------------------------------- /routing_demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | RiotControl Demo 7 | 8 | 9 | 10 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /demo/todo.tag: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

{ opts.title }

5 | 6 | 13 | 14 |
15 | 16 | 17 |
18 | 19 | 20 | var self = this 21 | self.disabled = true 22 | self.items = [] 23 | 24 | self.on('mount', function() { 25 | // Trigger init event when component is mounted to page. 26 | // Any store could respond to this. 27 | RiotControl.trigger('todo_init') 28 | }) 29 | 30 | // Register a listener for store change events. 31 | RiotControl.on('todos_changed', function(items) { 32 | self.items = items 33 | self.update() 34 | }) 35 | 36 | edit(e) { 37 | self.text = e.target.value 38 | } 39 | 40 | add(e) { 41 | if (self.text) { 42 | // Trigger event to all stores registered in central dispatch. 43 | // This allows loosely coupled stores/components to react to same events. 44 | RiotControl.trigger('todo_add', { title: self.text }) 45 | self.text = self.input.value = '' 46 | } 47 | } 48 | 49 | toggle(e) { 50 | var item = e.item 51 | item.done = !item.done 52 | return true 53 | } 54 | 55 | remove(e) { 56 | RiotControl.trigger('todo_remove') 57 | } 58 | 59 |
-------------------------------------------------------------------------------- /routing_demo/itemstore.js: -------------------------------------------------------------------------------- 1 | // Manages our item data locally, for now. 2 | function ItemStore() { 3 | riot.observable(this) 4 | 5 | var self = this 6 | 7 | // Could pull this from a server API. 8 | self.items = [ 9 | { id: 1, title: 'Foobar' }, 10 | { id: 2, title: 'Foobaz' }, 11 | { id: 3, title: 'Barbar' } 12 | ] 13 | 14 | // Init our list view. 15 | self.on('item_list_init', function() { 16 | self.trigger('item_list_changed', self.items) 17 | }) 18 | 19 | // Search our item collection. 20 | self.on('item_list_search', function(txt) { 21 | var list = self.items 22 | if (txt.length > 0) 23 | list = self.items.filter(function(el) { 24 | if (el.title.toLowerCase().search(new RegExp(txt.toLowerCase())) == -1) 25 | return false 26 | else 27 | return true 28 | }) 29 | 30 | self.trigger('item_list_changed', list) 31 | }) 32 | 33 | // Add to our item collection. 34 | // Could push this to a server API. 35 | self.on('item_detail_add', function(title) { 36 | self.items.push({ id: self.items.length+1, title: title }) 37 | self.trigger('item_list_changed', self.items) 38 | }) 39 | 40 | // Pull item for URL route. (id) 41 | self.on('route_item', function(id) { 42 | var item = null 43 | self.items.forEach(function(el) { 44 | if (el.id == id) 45 | item = el 46 | }) 47 | self.trigger('item_detail_changed', item) 48 | }) 49 | 50 | // Emit event for add item route. 51 | self.on('route_item_add', function() { 52 | self.trigger('item_detail_create') 53 | }) 54 | 55 | } -------------------------------------------------------------------------------- /routing_demo/item-app.js: -------------------------------------------------------------------------------- 1 | 2 | riot.tag('item-app', '

Gadget Browser (GitHub)

Notice the URL routing, back button works as expected.


Search:

Choose a product.


', function(opts) { 3 | var self = this 4 | 5 | self.items = [] 6 | self.txt = null 7 | self.detail = null 8 | self.edit = false 9 | 10 | this.search = function(e) { 11 | self.txt = e.target.value 12 | RiotControl.trigger('item_list_search', self.txt) 13 | }.bind(this) 14 | 15 | this.clear = function(e) { 16 | self.txt = '' 17 | this.input.value = '' 18 | RiotControl.trigger('item_list_search','') 19 | }.bind(this) 20 | 21 | this.add = function(e) { 22 | riot.route('add') 23 | }.bind(this) 24 | 25 | this.submit = function(e) { 26 | RiotControl.trigger('item_detail_add', this.title.value) 27 | this.title.value = '' 28 | this.edit = false 29 | riot.route('view') 30 | }.bind(this) 31 | 32 | this.cancel = function(e) { 33 | this.title.value = '' 34 | this.edit = false 35 | riot.route('view') 36 | }.bind(this) 37 | 38 | self.on('mount', function() { 39 | RiotControl.trigger('item_list_init') 40 | }) 41 | 42 | RiotControl.on('item_list_changed', function(items) { 43 | self.items = items 44 | self.update() 45 | }) 46 | 47 | RiotControl.on('item_detail_changed', function(item) { 48 | self.edit = false 49 | self.detail = item 50 | riot.update() 51 | }) 52 | 53 | RiotControl.on('item_detail_create', function() { 54 | self.edit = true 55 | self.update() 56 | }) 57 | 58 | }) -------------------------------------------------------------------------------- /routing_demo/item-app.tag: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Gadget Browser (GitHub)

5 |
Notice the URL routing, back button works as expected.
6 |
7 |
8 | Search: 9 |
10 | 11 |
12 | 13 |
14 | 19 | 20 | 21 |
22 | Choose a product. 23 |
24 |
25 |
26 | 27 |
28 |
29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 | 37 | // This is essentially the equivalent of the Flux view-controller. 38 | // Could be broken down further into more sub-componenets, if you wished to re-use views. 39 | 40 | var self = this 41 | 42 | self.items = [] 43 | self.txt = null 44 | self.detail = null 45 | self.edit = false 46 | 47 | search(e) { 48 | self.txt = e.target.value 49 | RiotControl.trigger('item_list_search', self.txt) 50 | } 51 | 52 | clear(e) { 53 | self.txt = '' 54 | this.input.value = '' 55 | RiotControl.trigger('item_list_search','') 56 | } 57 | 58 | add(e) { 59 | riot.route('add') 60 | } 61 | 62 | submit(e) { 63 | RiotControl.trigger('item_detail_add', this.title.value) 64 | this.title.value = '' 65 | this.edit = false 66 | riot.route('view') 67 | } 68 | 69 | cancel(e) { 70 | this.title.value = '' 71 | this.edit = false 72 | riot.route('view') 73 | } 74 | 75 | self.on('mount', function() { 76 | RiotControl.trigger('item_list_init') 77 | }) 78 | 79 | RiotControl.on('item_list_changed', function(items) { 80 | self.items = items 81 | self.update() 82 | }) 83 | 84 | RiotControl.on('item_detail_changed', function(item) { 85 | self.edit = false 86 | self.detail = item 87 | riot.update() 88 | }) 89 | 90 | RiotControl.on('item_detail_create', function() { 91 | self.edit = true 92 | self.update() 93 | }) 94 | 95 |
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RiotControl 2 | ============ 3 | 4 | __UPDATE:__ Experimental multi-threaded version under [dev branch](https://github.com/jimsparkman/RiotControl/tree/dev). 5 | 6 | ``` 7 | npm install riotcontrol 8 | ``` 9 | 10 | A Simplistic Central Event Controller / Dispatcher For [RiotJS](https://github.com/muut/riotjs), Inspired By Facebook's [Flux](https://github.com/facebook/flux) Architecture Pattern. 11 | 12 | RiotControl is, in the spirit of Riot itself, extremely lightweight. It forgoes elements of Flux, to favor small and simple applications. RiotControl passes events from views to stores, and back, relying heavily on Riot's observerable API. Stores can talk to many views, and views can talk to many stores. 13 | 14 | Example data flow 15 | ------- 16 | 17 | Given the following: 18 | 19 | - A TodoList view (Riot tag) 20 | - Triggers actions/events through RiotControl and listens for data change events. 21 | - A TodoStore (generic JS data store) 22 | - Mix of model manager/MVC-pattern controller that listens for actions/events, performs business logic, and dispatches data changed events. 23 | 24 | Possible data flow: 25 | 26 | 1. TodoList view triggers 'todo_remove' event to RiotControl. 27 | 2. RiotControl passes event along to stores. 28 | 3. TodoStore implements a 'todo_remove' event handler, talks to back-end server. 29 | 4. TodoStore triggers 'todos_changed' event, with new data. (new list with the todo removed) 30 | 5. TodoList view implements a 'todos_changed' event handler, receiving new data, and updating the UI. 31 | 32 | This encourages loosely coupled components. Stores house application logic and domain-specific data. Views comprise the user interface. Either side can be swapped out without interfering with the other. For example, a store that saves to local storage can be easily swapped for one that saves to a back-end service instead. 33 | 34 | Demos 35 | ============ 36 | 37 | [TodoList](http://jimsparkman.github.io/RiotControl/demo/) 38 | 39 | Reference demo/todostore.js and todo.tag to understand how this works. 40 | 41 | [URL Routing Example](http://jimsparkman.github.io/RiotControl/routing_demo/) 42 | 43 | Reference routing_demo/index.html, itemstore.js, and item-app.tag 44 | 45 | [Binary Calculator](http://jimsparkman.github.io/RiotControl/binary_demo/) 46 | 47 | Things People Have Built 48 | ============ 49 | 50 | [Flux Catalog](https://github.com/txchen/feplay/tree/gh-pages/riot_flux) 51 | 52 | RiotJS version of the flux-comparison catalog. 53 | 54 | [Where Da Movies At](https://github.com/derekr/wheredamoviesat) 55 | 56 | Map of all movies in a given location. 57 | 58 | Usage 59 | ============ 60 | 61 | Requires Riot 2.0+ 62 | 63 | Include riotcontrol.js, or it's few lines of code, in your project. 64 | 65 | API 66 | ============ 67 | 68 | Register the store in central dispatch, where store is a riot.observable(). Generally, all stores should be created and registered before the Riot app is mounted. 69 | 70 | ```javascript 71 | RiotControl.addStore(store) 72 | 73 | // Example, at start of application: 74 | var todoStore = new TodoStore() // Create a store instance. 75 | RiotControl.addStore(todoStore) // Register the store in central dispatch. 76 | ``` 77 | 78 | Trigger event on all stores registered in central dispatch. Essentially, a 'broadcast' version of Riot's el.trigger() API. 79 | 80 | ```javascript 81 | RiotControl.trigger(event) 82 | RiotControl.trigger(event, arg1 ... argN) 83 | 84 | // Example, inside Riot view (tag): 85 | RiotControl.trigger('todo_add', { title: self.text }) 86 | ``` 87 | 88 | Listen for event, and execute callback when it is triggered. This applies to all stores registered, so that you may receive the same event from multiple sources. 89 | 90 | ```javascript 91 | RiotControl.on(event, callback) 92 | 93 | // Example, inside Riot view (tag): 94 | RiotControl.on('todos_changed', function(items) { 95 | self.items = items 96 | self.update() 97 | }) 98 | ``` 99 | 100 | Remove event listener. 101 | 102 | ```javascript 103 | RiotControl.off(event) 104 | 105 | RiotControl.off(event, callback) 106 | ``` 107 | 108 | Same as RiotControl.on(), executes once. 109 | 110 | ```javascript 111 | RiotControl.one(event, callback) 112 | ``` 113 | -------------------------------------------------------------------------------- /binary_demo/js/riot.min.js: -------------------------------------------------------------------------------- 1 | /* Riot 2.0.7, @license MIT, (c) 2015 Muut Inc. + contributors */ 2 | (function(){var e={version:"v2.0.7"};"use strict";e.observable=function(e){e=e||{};var t={};e.on=function(n,r){if(typeof r=="function"){n.replace(/\S+/g,function(e,n){(t[e]=t[e]||[]).push(r);r.typed=n>0})}return e};e.off=function(n,r){if(n=="*")t={};else if(r){var i=t[n];for(var o=0,u;u=i&&i[o];++o){if(u==r){i.splice(o,1);o--}}}else{n.replace(/\S+/g,function(e){t[e]=[]})}return e};e.one=function(t,n){if(n)n.one=1;return e.on(t,n)};e.trigger=function(n){var r=[].slice.call(arguments,1),i=t[n]||[];for(var o=0,u;u=i[o];++o){if(!u.busy){u.busy=1;u.apply(e,u.typed?[n].concat(r):r);if(u.one){i.splice(o,1);o--}else if(i[o]!==u){o--}u.busy=0}}return e};return e};(function(e,t){if(!this.top)return;var n=location,r=e.observable(),i=u(),o=window;function u(){return n.hash.slice(1)}function f(e){return e.split("/")}function a(e){if(e.type)e=u();if(e!=i){r.trigger.apply(null,["H"].concat(f(e)));i=e}}var l=e.route=function(e){if(e[0]){n.hash=e;a(e)}else{r.on("H",e)}};l.exec=function(e){e.apply(null,f(u()))};l.parser=function(e){f=e};o.addEventListener?o.addEventListener(t,a,false):o.attachEvent("on"+t,a)})(e,"hashchange");e._tmpl=function(){var e={},t=/("|').+?[^\\]\1|\.\w*|\w*:|\b(?:this|true|false|null|undefined|new|typeof|Number|String|Object|Array|Math|Date|JSON)\b|([a-z_]\w*)/gi;return function(t,r){return t&&(e[t]=e[t]||n(t))(r)};function n(e,t){t=(e||"{}").replace(/\\{/g,"￰").replace(/\\}/g,"￱").split(/({[\s\S]*?})/);return new Function("d","return "+(!t[0]&&!t[2]?r(t[1]):"["+t.map(function(e,t){return t%2?r(e,1):'"'+e.replace(/\n/g,"\\n").replace(/"/g,'\\"')+'"'}).join(",")+'].join("")').replace(/\uFFF0/g,"{").replace(/\uFFF1/g,"}"))}function r(e,t){e=e.replace(/\n/g," ").replace(/^[{ ]+|[ }]+$|\/\*.+?\*\//g,"");return/^\s*[\w-"']+ *:/.test(e)?"["+e.replace(/\W*([\w-]+)\W*:([^,]+)/g,function(e,n,r){return r.replace(/\w[^,|& ]*/g,function(e){return i(e,t)})+'?"'+n+'":"",'})+'].join(" ")':i(e,t)}function i(e,n){return"(function(v){try{v="+(e.replace(t,function(e,t,n){return n?"d."+n:e})||"x")+"}finally{return "+(n?'!v&&v!==0?"":v':"v")+"}}).call(d)"}}();(function(e,t){if(!t)return;var n=e._tmpl,r=[],i={},o=document;function u(e,t){for(var n=0;n<(e||[]).length;n++){if(t(e[n],n)===false)n--}}function f(e,t){t&&Object.keys(t).map(function(n){e[n]=t[n]});return e}function a(e,t){return e.filter(function(e){return t.indexOf(e)<0})}function l(e,t){e=t(e)===false?e.nextSibling:e.firstChild;while(e){l(e,t);e=e.nextSibling}}function c(e){var t=e.trim().slice(1,3).toLowerCase(),n=/td|th/.test(t)?"tr":t=="tr"?"tbody":"div";el=o.createElement(n);el.innerHTML=e;return el}function s(e,t){t.trigger("update");u(e,function(e){var r=e.tag,i=e.dom;function o(e){i.removeAttribute(e)}if(e.loop){o("each");return v(e,t)}if(r)return r.update?r.update():e.tag=d({tmpl:r[0],fn:r[1],root:i,parent:t});var u=e.attr,f=n(e.expr,t);if(f==null)f="";if(e.value===f)return;e.value=f;if(!u)return i.nodeValue=f;if(!f&&e.bool||/obj|func/.test(typeof f))o(u);if(typeof f=="function"){i[u]=function(e){e=e||window.event;e.which=e.which||e.charCode||e.keyCode;e.target=e.target||e.srcElement;e.currentTarget=i;e.item=t.__item||t;if(f.call(t,e)!==true){e.preventDefault&&e.preventDefault();e.returnValue=false}t.update()}}else if(/^(show|hide|if)$/.test(u)){o(u);if(u=="hide")f=!f;i.style.display=f?"":"none"}else{if(e.bool){i[u]=f;if(!f)return;f=u}i.setAttribute(u,f)}});t.trigger("updated")}function p(e){var t={},n=[];l(e,function(e){var n=e.nodeType,o=e.nodeValue;if(n==3&&e.parentNode.tagName!="STYLE"){r(e,o)}else if(n==1){o=e.getAttribute("each");if(o){r(e,o,{loop:1});return false}var f=i[e.tagName.toLowerCase()];u(e.attributes,function(n){var i=n.name,o=n.value;if(/^(name|id)$/.test(i))t[o]=e;if(!f){var u=i.split("__")[1];r(e,o,{attr:u||i,bool:u});if(u){e.removeAttribute(i);return false}}});if(f)r(e,0,{tag:f})}});return{expr:n,elem:t};function r(e,t,r){if(t?t.indexOf("{")>=0:r){var i={dom:e,expr:t};n.push(f(i,r||{}))}}}function d(t){var i=t.opts||{},a=c(t.tmpl),l=t.root,d=t.parent,v=p(a),m={root:l,opts:i,parent:d,__item:t.item},g={};f(m,v.elem);u(l.attributes,function(e){g[e.name]=e.value});function h(){Object.keys(g).map(function(e){var t=i[e]=n(g[e],d||m);if(typeof t=="object")l.removeAttribute(e)})}h();if(!m.on){e.observable(m);delete m.off}if(t.fn)t.fn.call(m,i);m.update=function(e,t){if(d&&a&&!a.firstChild){l=d.root;a=null}if(t||o.body.contains(l)){f(m,e);f(m,m.__item);h();s(v.expr,m);!t&&m.__item&&d.update();return true}else{m.trigger("unmount")}};m.update(0,true);while(a.firstChild){if(t.before)l.insertBefore(a.firstChild,t.before);else l.appendChild(a.firstChild)}m.trigger("mount");r.push(m);return m}function v(e,t){if(e.done)return;e.done=true;var r=e.dom,i=r.previousSibling,o=r.parentNode,u=r.outerHTML,f=e.expr,l=f.split(/\s+in\s+/),c=[],s,p;if(l[1]){f="{ "+l[1];p=l[0].slice(1).trim().split(/,\s*/)}t.one("mount",function(){var e=r.parentNode;if(e){o=e;o.removeChild(r)}});function v(){return Array.prototype.indexOf.call(o.childNodes,i)+1}t.on("updated",function(){var e=n(f,t);is_array=Array.isArray(e);if(is_array)e=e.slice(0);else{if(!e)return;var r=JSON.stringify(e);if(r==s)return;s=r;e=Object.keys(e).map(function(t,n){var r={};r[p[0]]=t;r[p[1]]=e[t];return r})}a(c,e).map(function(e){var t=c.indexOf(e);o.removeChild(o.childNodes[v()+t]);c.splice(t,1)});a(e,c).map(function(n,r){var i=e.indexOf(n);if(p&&!s){var f={};f[p[0]]=n;f[p[1]]=i;n=f}var a=d({before:o.childNodes[v()+i],parent:t,tmpl:u,item:n,root:o});t.on("update",function(){a.update(0,true)})});c=e})}e.tag=function(e,t,n){n=n||noop,i[e]=[t,n]};e.mountTo=function(e,t,n){var r=i[t];return r&&d({tmpl:r[0],fn:r[1],root:e,opts:n})};e.mount=function(t,n){if(t=="*")t=Object.keys(i).join(", ");var r=[];u(o.querySelectorAll(t),function(t){if(t.riot)return;var i=t.tagName.toLowerCase(),o=e.mountTo(t,i,n);if(o){r.push(o);t.riot=1}});return r};e.update=function(){return r=r.filter(function(e){return!!e.update()})}})(e,this.top);if(typeof exports==="object")module.exports=e;else if(typeof define==="function"&&define.amd)define(function(){return e});else this.riot=e})(); -------------------------------------------------------------------------------- /demo/js/html5-shiv.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.2 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | ;(function(window, document) { 5 | /*jshint evil:true */ 6 | /** version */ 7 | var version = '3.7.2'; 8 | 9 | /** Preset options */ 10 | var options = window.html5 || {}; 11 | 12 | /** Used to skip problem elements */ 13 | var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; 14 | 15 | /** Not all elements can be cloned in IE **/ 16 | var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i; 17 | 18 | /** Detect whether the browser supports default html5 styles */ 19 | var supportsHtml5Styles; 20 | 21 | /** Name of the expando, to work with multiple documents or to re-shiv one document */ 22 | var expando = '_html5shiv'; 23 | 24 | /** The id for the the documents expando */ 25 | var expanID = 0; 26 | 27 | /** Cached data for each document */ 28 | var expandoData = {}; 29 | 30 | /** Detect whether the browser supports unknown elements */ 31 | var supportsUnknownElements; 32 | 33 | (function() { 34 | try { 35 | var a = document.createElement('a'); 36 | a.innerHTML = ''; 37 | //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles 38 | supportsHtml5Styles = ('hidden' in a); 39 | 40 | supportsUnknownElements = a.childNodes.length == 1 || (function() { 41 | // assign a false positive if unable to shiv 42 | (document.createElement)('a'); 43 | var frag = document.createDocumentFragment(); 44 | return ( 45 | typeof frag.cloneNode == 'undefined' || 46 | typeof frag.createDocumentFragment == 'undefined' || 47 | typeof frag.createElement == 'undefined' 48 | ); 49 | }()); 50 | } catch(e) { 51 | // assign a false positive if detection fails => unable to shiv 52 | supportsHtml5Styles = true; 53 | supportsUnknownElements = true; 54 | } 55 | 56 | }()); 57 | 58 | /*--------------------------------------------------------------------------*/ 59 | 60 | /** 61 | * Creates a style sheet with the given CSS text and adds it to the document. 62 | * @private 63 | * @param {Document} ownerDocument The document. 64 | * @param {String} cssText The CSS text. 65 | * @returns {StyleSheet} The style element. 66 | */ 67 | function addStyleSheet(ownerDocument, cssText) { 68 | var p = ownerDocument.createElement('p'), 69 | parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement; 70 | 71 | p.innerHTML = 'x'; 72 | return parent.insertBefore(p.lastChild, parent.firstChild); 73 | } 74 | 75 | /** 76 | * Returns the value of `html5.elements` as an array. 77 | * @private 78 | * @returns {Array} An array of shived element node names. 79 | */ 80 | function getElements() { 81 | var elements = html5.elements; 82 | return typeof elements == 'string' ? elements.split(' ') : elements; 83 | } 84 | 85 | /** 86 | * Extends the built-in list of html5 elements 87 | * @memberOf html5 88 | * @param {String|Array} newElements whitespace separated list or array of new element names to shiv 89 | * @param {Document} ownerDocument The context document. 90 | */ 91 | function addElements(newElements, ownerDocument) { 92 | var elements = html5.elements; 93 | if(typeof elements != 'string'){ 94 | elements = elements.join(' '); 95 | } 96 | if(typeof newElements != 'string'){ 97 | newElements = newElements.join(' '); 98 | } 99 | html5.elements = elements +' '+ newElements; 100 | shivDocument(ownerDocument); 101 | } 102 | 103 | /** 104 | * Returns the data associated to the given document 105 | * @private 106 | * @param {Document} ownerDocument The document. 107 | * @returns {Object} An object of data. 108 | */ 109 | function getExpandoData(ownerDocument) { 110 | var data = expandoData[ownerDocument[expando]]; 111 | if (!data) { 112 | data = {}; 113 | expanID++; 114 | ownerDocument[expando] = expanID; 115 | expandoData[expanID] = data; 116 | } 117 | return data; 118 | } 119 | 120 | /** 121 | * returns a shived element for the given nodeName and document 122 | * @memberOf html5 123 | * @param {String} nodeName name of the element 124 | * @param {Document} ownerDocument The context document. 125 | * @returns {Object} The shived element. 126 | */ 127 | function createElement(nodeName, ownerDocument, data){ 128 | if (!ownerDocument) { 129 | ownerDocument = document; 130 | } 131 | if(supportsUnknownElements){ 132 | return ownerDocument.createElement(nodeName); 133 | } 134 | if (!data) { 135 | data = getExpandoData(ownerDocument); 136 | } 137 | var node; 138 | 139 | if (data.cache[nodeName]) { 140 | node = data.cache[nodeName].cloneNode(); 141 | } else if (saveClones.test(nodeName)) { 142 | node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode(); 143 | } else { 144 | node = data.createElem(nodeName); 145 | } 146 | 147 | // Avoid adding some elements to fragments in IE < 9 because 148 | // * Attributes like `name` or `type` cannot be set/changed once an element 149 | // is inserted into a document/fragment 150 | // * Link elements with `src` attributes that are inaccessible, as with 151 | // a 403 response, will cause the tab/window to crash 152 | // * Script elements appended to fragments will execute when their `src` 153 | // or `text` property is set 154 | return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node; 155 | } 156 | 157 | /** 158 | * returns a shived DocumentFragment for the given document 159 | * @memberOf html5 160 | * @param {Document} ownerDocument The context document. 161 | * @returns {Object} The shived DocumentFragment. 162 | */ 163 | function createDocumentFragment(ownerDocument, data){ 164 | if (!ownerDocument) { 165 | ownerDocument = document; 166 | } 167 | if(supportsUnknownElements){ 168 | return ownerDocument.createDocumentFragment(); 169 | } 170 | data = data || getExpandoData(ownerDocument); 171 | var clone = data.frag.cloneNode(), 172 | i = 0, 173 | elems = getElements(), 174 | l = elems.length; 175 | for(;i