├── LICENSE.txt ├── README.md ├── binary_demo ├── binary-app.js ├── binary-app.tag ├── calcstore.js ├── index.html ├── js │ ├── es5-shim.js │ ├── html5-shiv.js │ └── riot.min.js └── riotcontrol.js ├── bower.json ├── demo ├── index.html ├── js │ ├── es5-shim.js │ ├── html5-shiv.js │ └── riot.js ├── riotcontrol.js ├── todo.css ├── todo.js ├── todo.tag ├── todoapp.js ├── todoapp.tag └── todostore.js ├── package.json ├── riotcontrol.js └── routing_demo ├── index.html ├── item-app.js ├── item-app.tag ├── item-detail.js ├── item-detail.tag ├── itemstore.js ├── js ├── es5-shim.js ├── html5-shiv.js └── riot.js ├── riotcontrol.js └── todo.css /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 | -------------------------------------------------------------------------------- /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/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 | }); -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /binary_demo/js/es5-shim.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * https://github.com/es-shims/es5-shim 3 | * @license es5-shim Copyright 2009-2014 by contributors, MIT License 4 | * see https://github.com/es-shims/es5-shim/blob/master/LICENSE 5 | */ 6 | 7 | // vim: ts=4 sts=4 sw=4 expandtab 8 | 9 | //Add semicolon to prevent IIFE from being passed as argument to concatenated code. 10 | ; 11 | 12 | // UMD (Universal Module Definition) 13 | // see https://github.com/umdjs/umd/blob/master/returnExports.js 14 | (function (root, factory) { 15 | if (typeof define === 'function' && define.amd) { 16 | // AMD. Register as an anonymous module. 17 | define(factory); 18 | } else if (typeof exports === 'object') { 19 | // Node. Does not work with strict CommonJS, but 20 | // only CommonJS-like enviroments that support module.exports, 21 | // like Node. 22 | module.exports = factory(); 23 | } else { 24 | // Browser globals (root is window) 25 | root.returnExports = factory(); 26 | } 27 | }(this, function () { 28 | 29 | /** 30 | * Brings an environment as close to ECMAScript 5 compliance 31 | * as is possible with the facilities of erstwhile engines. 32 | * 33 | * Annotated ES5: http://es5.github.com/ (specific links below) 34 | * ES5 Spec: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf 35 | * Required reading: http://javascriptweblog.wordpress.com/2011/12/05/extending-javascript-natives/ 36 | */ 37 | 38 | // Shortcut to an often accessed properties, in order to avoid multiple 39 | // dereference that costs universally. 40 | var ArrayPrototype = Array.prototype; 41 | var ObjectPrototype = Object.prototype; 42 | var FunctionPrototype = Function.prototype; 43 | var StringPrototype = String.prototype; 44 | var NumberPrototype = Number.prototype; 45 | var array_slice = ArrayPrototype.slice; 46 | var array_splice = ArrayPrototype.splice; 47 | var array_push = ArrayPrototype.push; 48 | var array_unshift = ArrayPrototype.unshift; 49 | var call = FunctionPrototype.call; 50 | 51 | // Having a toString local variable name breaks in Opera so use _toString. 52 | var _toString = ObjectPrototype.toString; 53 | 54 | var isFunction = function (val) { 55 | return ObjectPrototype.toString.call(val) === '[object Function]'; 56 | }; 57 | var isRegex = function (val) { 58 | return ObjectPrototype.toString.call(val) === '[object RegExp]'; 59 | }; 60 | var isArray = function isArray(obj) { 61 | return _toString.call(obj) === '[object Array]'; 62 | }; 63 | var isString = function isString(obj) { 64 | return _toString.call(obj) === '[object String]'; 65 | }; 66 | var isArguments = function isArguments(value) { 67 | var str = _toString.call(value); 68 | var isArgs = str === '[object Arguments]'; 69 | if (!isArgs) { 70 | isArgs = !isArray(value) 71 | && value !== null 72 | && typeof value === 'object' 73 | && typeof value.length === 'number' 74 | && value.length >= 0 75 | && isFunction(value.callee); 76 | } 77 | return isArgs; 78 | }; 79 | 80 | var supportsDescriptors = Object.defineProperty && (function () { 81 | try { 82 | Object.defineProperty({}, 'x', {}); 83 | return true; 84 | } catch (e) { /* this is ES3 */ 85 | return false; 86 | } 87 | }()); 88 | 89 | // Define configurable, writable and non-enumerable props 90 | // if they don't exist. 91 | var defineProperty; 92 | if (supportsDescriptors) { 93 | defineProperty = function (object, name, method, forceAssign) { 94 | if (!forceAssign && (name in object)) { return; } 95 | Object.defineProperty(object, name, { 96 | configurable: true, 97 | enumerable: false, 98 | writable: true, 99 | value: method 100 | }); 101 | }; 102 | } else { 103 | defineProperty = function (object, name, method, forceAssign) { 104 | if (!forceAssign && (name in object)) { return; } 105 | object[name] = method; 106 | }; 107 | } 108 | var defineProperties = function (object, map, forceAssign) { 109 | for (var name in map) { 110 | if (ObjectPrototype.hasOwnProperty.call(map, name)) { 111 | defineProperty(object, name, map[name], forceAssign); 112 | } 113 | } 114 | }; 115 | 116 | // 117 | // Util 118 | // ====== 119 | // 120 | 121 | // ES5 9.4 122 | // http://es5.github.com/#x9.4 123 | // http://jsperf.com/to-integer 124 | 125 | function toInteger(num) { 126 | var n = +num; 127 | if (n !== n) { // isNaN 128 | n = 0; 129 | } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) { 130 | n = (n > 0 || -1) * Math.floor(Math.abs(n)); 131 | } 132 | return n; 133 | } 134 | 135 | function isPrimitive(input) { 136 | var type = typeof input; 137 | return ( 138 | input === null || 139 | type === 'undefined' || 140 | type === 'boolean' || 141 | type === 'number' || 142 | type === 'string' 143 | ); 144 | } 145 | 146 | function toPrimitive(input) { 147 | var val, valueOf, toStr; 148 | if (isPrimitive(input)) { 149 | return input; 150 | } 151 | valueOf = input.valueOf; 152 | if (isFunction(valueOf)) { 153 | val = valueOf.call(input); 154 | if (isPrimitive(val)) { 155 | return val; 156 | } 157 | } 158 | toStr = input.toString; 159 | if (isFunction(toStr)) { 160 | val = toStr.call(input); 161 | if (isPrimitive(val)) { 162 | return val; 163 | } 164 | } 165 | throw new TypeError(); 166 | } 167 | 168 | // ES5 9.9 169 | // http://es5.github.com/#x9.9 170 | var toObject = function (o) { 171 | if (o == null) { // this matches both null and undefined 172 | throw new TypeError("can't convert " + o + ' to object'); 173 | } 174 | return Object(o); 175 | }; 176 | 177 | var ToUint32 = function ToUint32(x) { 178 | return x >>> 0; 179 | }; 180 | 181 | // 182 | // Function 183 | // ======== 184 | // 185 | 186 | // ES-5 15.3.4.5 187 | // http://es5.github.com/#x15.3.4.5 188 | 189 | function Empty() {} 190 | 191 | defineProperties(FunctionPrototype, { 192 | bind: function bind(that) { // .length is 1 193 | // 1. Let Target be the this value. 194 | var target = this; 195 | // 2. If IsCallable(Target) is false, throw a TypeError exception. 196 | if (!isFunction(target)) { 197 | throw new TypeError('Function.prototype.bind called on incompatible ' + target); 198 | } 199 | // 3. Let A be a new (possibly empty) internal list of all of the 200 | // argument values provided after thisArg (arg1, arg2 etc), in order. 201 | // XXX slicedArgs will stand in for "A" if used 202 | var args = array_slice.call(arguments, 1); // for normal call 203 | // 4. Let F be a new native ECMAScript object. 204 | // 11. Set the [[Prototype]] internal property of F to the standard 205 | // built-in Function prototype object as specified in 15.3.3.1. 206 | // 12. Set the [[Call]] internal property of F as described in 207 | // 15.3.4.5.1. 208 | // 13. Set the [[Construct]] internal property of F as described in 209 | // 15.3.4.5.2. 210 | // 14. Set the [[HasInstance]] internal property of F as described in 211 | // 15.3.4.5.3. 212 | var binder = function () { 213 | 214 | if (this instanceof bound) { 215 | // 15.3.4.5.2 [[Construct]] 216 | // When the [[Construct]] internal method of a function object, 217 | // F that was created using the bind function is called with a 218 | // list of arguments ExtraArgs, the following steps are taken: 219 | // 1. Let target be the value of F's [[TargetFunction]] 220 | // internal property. 221 | // 2. If target has no [[Construct]] internal method, a 222 | // TypeError exception is thrown. 223 | // 3. Let boundArgs be the value of F's [[BoundArgs]] internal 224 | // property. 225 | // 4. Let args be a new list containing the same values as the 226 | // list boundArgs in the same order followed by the same 227 | // values as the list ExtraArgs in the same order. 228 | // 5. Return the result of calling the [[Construct]] internal 229 | // method of target providing args as the arguments. 230 | 231 | var result = target.apply( 232 | this, 233 | args.concat(array_slice.call(arguments)) 234 | ); 235 | if (Object(result) === result) { 236 | return result; 237 | } 238 | return this; 239 | 240 | } else { 241 | // 15.3.4.5.1 [[Call]] 242 | // When the [[Call]] internal method of a function object, F, 243 | // which was created using the bind function is called with a 244 | // this value and a list of arguments ExtraArgs, the following 245 | // steps are taken: 246 | // 1. Let boundArgs be the value of F's [[BoundArgs]] internal 247 | // property. 248 | // 2. Let boundThis be the value of F's [[BoundThis]] internal 249 | // property. 250 | // 3. Let target be the value of F's [[TargetFunction]] internal 251 | // property. 252 | // 4. Let args be a new list containing the same values as the 253 | // list boundArgs in the same order followed by the same 254 | // values as the list ExtraArgs in the same order. 255 | // 5. Return the result of calling the [[Call]] internal method 256 | // of target providing boundThis as the this value and 257 | // providing args as the arguments. 258 | 259 | // equiv: target.call(this, ...boundArgs, ...args) 260 | return target.apply( 261 | that, 262 | args.concat(array_slice.call(arguments)) 263 | ); 264 | 265 | } 266 | 267 | }; 268 | 269 | // 15. If the [[Class]] internal property of Target is "Function", then 270 | // a. Let L be the length property of Target minus the length of A. 271 | // b. Set the length own property of F to either 0 or L, whichever is 272 | // larger. 273 | // 16. Else set the length own property of F to 0. 274 | 275 | var boundLength = Math.max(0, target.length - args.length); 276 | 277 | // 17. Set the attributes of the length own property of F to the values 278 | // specified in 15.3.5.1. 279 | var boundArgs = []; 280 | for (var i = 0; i < boundLength; i++) { 281 | boundArgs.push('$' + i); 282 | } 283 | 284 | // XXX Build a dynamic function with desired amount of arguments is the only 285 | // way to set the length property of a function. 286 | // In environments where Content Security Policies enabled (Chrome extensions, 287 | // for ex.) all use of eval or Function costructor throws an exception. 288 | // However in all of these environments Function.prototype.bind exists 289 | // and so this code will never be executed. 290 | var bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder); 291 | 292 | if (target.prototype) { 293 | Empty.prototype = target.prototype; 294 | bound.prototype = new Empty(); 295 | // Clean up dangling references. 296 | Empty.prototype = null; 297 | } 298 | 299 | // TODO 300 | // 18. Set the [[Extensible]] internal property of F to true. 301 | 302 | // TODO 303 | // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3). 304 | // 20. Call the [[DefineOwnProperty]] internal method of F with 305 | // arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]: 306 | // thrower, [[Enumerable]]: false, [[Configurable]]: false}, and 307 | // false. 308 | // 21. Call the [[DefineOwnProperty]] internal method of F with 309 | // arguments "arguments", PropertyDescriptor {[[Get]]: thrower, 310 | // [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false}, 311 | // and false. 312 | 313 | // TODO 314 | // NOTE Function objects created using Function.prototype.bind do not 315 | // have a prototype property or the [[Code]], [[FormalParameters]], and 316 | // [[Scope]] internal properties. 317 | // XXX can't delete prototype in pure-js. 318 | 319 | // 22. Return F. 320 | return bound; 321 | } 322 | }); 323 | 324 | // _Please note: Shortcuts are defined after `Function.prototype.bind` as we 325 | // us it in defining shortcuts. 326 | var owns = call.bind(ObjectPrototype.hasOwnProperty); 327 | 328 | // If JS engine supports accessors creating shortcuts. 329 | var defineGetter; 330 | var defineSetter; 331 | var lookupGetter; 332 | var lookupSetter; 333 | var supportsAccessors; 334 | if ((supportsAccessors = owns(ObjectPrototype, '__defineGetter__'))) { 335 | defineGetter = call.bind(ObjectPrototype.__defineGetter__); 336 | defineSetter = call.bind(ObjectPrototype.__defineSetter__); 337 | lookupGetter = call.bind(ObjectPrototype.__lookupGetter__); 338 | lookupSetter = call.bind(ObjectPrototype.__lookupSetter__); 339 | } 340 | 341 | // 342 | // Array 343 | // ===== 344 | // 345 | 346 | // ES5 15.4.4.12 347 | // http://es5.github.com/#x15.4.4.12 348 | var spliceNoopReturnsEmptyArray = (function () { 349 | var a = [1, 2]; 350 | var result = a.splice(); 351 | return a.length === 2 && isArray(result) && result.length === 0; 352 | }()); 353 | defineProperties(ArrayPrototype, { 354 | // Safari 5.0 bug where .splice() returns undefined 355 | splice: function splice(start, deleteCount) { 356 | if (arguments.length === 0) { 357 | return []; 358 | } else { 359 | return array_splice.apply(this, arguments); 360 | } 361 | } 362 | }, spliceNoopReturnsEmptyArray); 363 | 364 | var spliceWorksWithEmptyObject = (function () { 365 | var obj = {}; 366 | ArrayPrototype.splice.call(obj, 0, 0, 1); 367 | return obj.length === 1; 368 | }()); 369 | defineProperties(ArrayPrototype, { 370 | splice: function splice(start, deleteCount) { 371 | if (arguments.length === 0) { return []; } 372 | var args = arguments; 373 | this.length = Math.max(toInteger(this.length), 0); 374 | if (arguments.length > 0 && typeof deleteCount !== 'number') { 375 | args = array_slice.call(arguments); 376 | if (args.length < 2) { 377 | args.push(this.length - start); 378 | } else { 379 | args[1] = toInteger(deleteCount); 380 | } 381 | } 382 | return array_splice.apply(this, args); 383 | } 384 | }, !spliceWorksWithEmptyObject); 385 | 386 | // ES5 15.4.4.12 387 | // http://es5.github.com/#x15.4.4.13 388 | // Return len+argCount. 389 | // [bugfix, ielt8] 390 | // IE < 8 bug: [].unshift(0) === undefined but should be "1" 391 | var hasUnshiftReturnValueBug = [].unshift(0) !== 1; 392 | defineProperties(ArrayPrototype, { 393 | unshift: function () { 394 | array_unshift.apply(this, arguments); 395 | return this.length; 396 | } 397 | }, hasUnshiftReturnValueBug); 398 | 399 | // ES5 15.4.3.2 400 | // http://es5.github.com/#x15.4.3.2 401 | // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray 402 | defineProperties(Array, { isArray: isArray }); 403 | 404 | // The IsCallable() check in the Array functions 405 | // has been replaced with a strict check on the 406 | // internal class of the object to trap cases where 407 | // the provided function was actually a regular 408 | // expression literal, which in V8 and 409 | // JavaScriptCore is a typeof "function". Only in 410 | // V8 are regular expression literals permitted as 411 | // reduce parameters, so it is desirable in the 412 | // general case for the shim to match the more 413 | // strict and common behavior of rejecting regular 414 | // expressions. 415 | 416 | // ES5 15.4.4.18 417 | // http://es5.github.com/#x15.4.4.18 418 | // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/forEach 419 | 420 | // Check failure of by-index access of string characters (IE < 9) 421 | // and failure of `0 in boxedString` (Rhino) 422 | var boxedString = Object('a'); 423 | var splitString = boxedString[0] !== 'a' || !(0 in boxedString); 424 | 425 | var properlyBoxesContext = function properlyBoxed(method) { 426 | // Check node 0.6.21 bug where third parameter is not boxed 427 | var properlyBoxesNonStrict = true; 428 | var properlyBoxesStrict = true; 429 | if (method) { 430 | method.call('foo', function (_, __, context) { 431 | if (typeof context !== 'object') { properlyBoxesNonStrict = false; } 432 | }); 433 | 434 | method.call([1], function () { 435 | 'use strict'; 436 | properlyBoxesStrict = typeof this === 'string'; 437 | }, 'x'); 438 | } 439 | return !!method && properlyBoxesNonStrict && properlyBoxesStrict; 440 | }; 441 | 442 | defineProperties(ArrayPrototype, { 443 | forEach: function forEach(fun /*, thisp*/) { 444 | var object = toObject(this), 445 | self = splitString && isString(this) ? this.split('') : object, 446 | thisp = arguments[1], 447 | i = -1, 448 | length = self.length >>> 0; 449 | 450 | // If no callback function or if callback is not a callable function 451 | if (!isFunction(fun)) { 452 | throw new TypeError(); // TODO message 453 | } 454 | 455 | while (++i < length) { 456 | if (i in self) { 457 | // Invoke the callback function with call, passing arguments: 458 | // context, property value, property key, thisArg object 459 | // context 460 | fun.call(thisp, self[i], i, object); 461 | } 462 | } 463 | } 464 | }, !properlyBoxesContext(ArrayPrototype.forEach)); 465 | 466 | // ES5 15.4.4.19 467 | // http://es5.github.com/#x15.4.4.19 468 | // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map 469 | defineProperties(ArrayPrototype, { 470 | map: function map(fun /*, thisp*/) { 471 | var object = toObject(this), 472 | self = splitString && isString(this) ? this.split('') : object, 473 | length = self.length >>> 0, 474 | result = Array(length), 475 | thisp = arguments[1]; 476 | 477 | // If no callback function or if callback is not a callable function 478 | if (!isFunction(fun)) { 479 | throw new TypeError(fun + ' is not a function'); 480 | } 481 | 482 | for (var i = 0; i < length; i++) { 483 | if (i in self) { 484 | result[i] = fun.call(thisp, self[i], i, object); 485 | } 486 | } 487 | return result; 488 | } 489 | }, !properlyBoxesContext(ArrayPrototype.map)); 490 | 491 | // ES5 15.4.4.20 492 | // http://es5.github.com/#x15.4.4.20 493 | // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/filter 494 | defineProperties(ArrayPrototype, { 495 | filter: function filter(fun /*, thisp */) { 496 | var object = toObject(this), 497 | self = splitString && isString(this) ? this.split('') : object, 498 | length = self.length >>> 0, 499 | result = [], 500 | value, 501 | thisp = arguments[1]; 502 | 503 | // If no callback function or if callback is not a callable function 504 | if (!isFunction(fun)) { 505 | throw new TypeError(fun + ' is not a function'); 506 | } 507 | 508 | for (var i = 0; i < length; i++) { 509 | if (i in self) { 510 | value = self[i]; 511 | if (fun.call(thisp, value, i, object)) { 512 | result.push(value); 513 | } 514 | } 515 | } 516 | return result; 517 | } 518 | }, !properlyBoxesContext(ArrayPrototype.filter)); 519 | 520 | // ES5 15.4.4.16 521 | // http://es5.github.com/#x15.4.4.16 522 | // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/every 523 | defineProperties(ArrayPrototype, { 524 | every: function every(fun /*, thisp */) { 525 | var object = toObject(this), 526 | self = splitString && isString(this) ? this.split('') : object, 527 | length = self.length >>> 0, 528 | thisp = arguments[1]; 529 | 530 | // If no callback function or if callback is not a callable function 531 | if (!isFunction(fun)) { 532 | throw new TypeError(fun + ' is not a function'); 533 | } 534 | 535 | for (var i = 0; i < length; i++) { 536 | if (i in self && !fun.call(thisp, self[i], i, object)) { 537 | return false; 538 | } 539 | } 540 | return true; 541 | } 542 | }, !properlyBoxesContext(ArrayPrototype.every)); 543 | 544 | // ES5 15.4.4.17 545 | // http://es5.github.com/#x15.4.4.17 546 | // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some 547 | defineProperties(ArrayPrototype, { 548 | some: function some(fun /*, thisp */) { 549 | var object = toObject(this), 550 | self = splitString && isString(this) ? this.split('') : object, 551 | length = self.length >>> 0, 552 | thisp = arguments[1]; 553 | 554 | // If no callback function or if callback is not a callable function 555 | if (!isFunction(fun)) { 556 | throw new TypeError(fun + ' is not a function'); 557 | } 558 | 559 | for (var i = 0; i < length; i++) { 560 | if (i in self && fun.call(thisp, self[i], i, object)) { 561 | return true; 562 | } 563 | } 564 | return false; 565 | } 566 | }, !properlyBoxesContext(ArrayPrototype.some)); 567 | 568 | // ES5 15.4.4.21 569 | // http://es5.github.com/#x15.4.4.21 570 | // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce 571 | var reduceCoercesToObject = false; 572 | if (ArrayPrototype.reduce) { 573 | reduceCoercesToObject = typeof ArrayPrototype.reduce.call('es5', function (_, __, ___, list) { return list; }) === 'object'; 574 | } 575 | defineProperties(ArrayPrototype, { 576 | reduce: function reduce(fun /*, initial*/) { 577 | var object = toObject(this), 578 | self = splitString && isString(this) ? this.split('') : object, 579 | length = self.length >>> 0; 580 | 581 | // If no callback function or if callback is not a callable function 582 | if (!isFunction(fun)) { 583 | throw new TypeError(fun + ' is not a function'); 584 | } 585 | 586 | // no value to return if no initial value and an empty array 587 | if (!length && arguments.length === 1) { 588 | throw new TypeError('reduce of empty array with no initial value'); 589 | } 590 | 591 | var i = 0; 592 | var result; 593 | if (arguments.length >= 2) { 594 | result = arguments[1]; 595 | } else { 596 | do { 597 | if (i in self) { 598 | result = self[i++]; 599 | break; 600 | } 601 | 602 | // if array contains no values, no initial value to return 603 | if (++i >= length) { 604 | throw new TypeError('reduce of empty array with no initial value'); 605 | } 606 | } while (true); 607 | } 608 | 609 | for (; i < length; i++) { 610 | if (i in self) { 611 | result = fun.call(void 0, result, self[i], i, object); 612 | } 613 | } 614 | 615 | return result; 616 | } 617 | }, !reduceCoercesToObject); 618 | 619 | // ES5 15.4.4.22 620 | // http://es5.github.com/#x15.4.4.22 621 | // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduceRight 622 | var reduceRightCoercesToObject = false; 623 | if (ArrayPrototype.reduceRight) { 624 | reduceRightCoercesToObject = typeof ArrayPrototype.reduceRight.call('es5', function (_, __, ___, list) { return list; }) === 'object'; 625 | } 626 | defineProperties(ArrayPrototype, { 627 | reduceRight: function reduceRight(fun /*, initial*/) { 628 | var object = toObject(this), 629 | self = splitString && isString(this) ? this.split('') : object, 630 | length = self.length >>> 0; 631 | 632 | // If no callback function or if callback is not a callable function 633 | if (!isFunction(fun)) { 634 | throw new TypeError(fun + ' is not a function'); 635 | } 636 | 637 | // no value to return if no initial value, empty array 638 | if (!length && arguments.length === 1) { 639 | throw new TypeError('reduceRight of empty array with no initial value'); 640 | } 641 | 642 | var result, i = length - 1; 643 | if (arguments.length >= 2) { 644 | result = arguments[1]; 645 | } else { 646 | do { 647 | if (i in self) { 648 | result = self[i--]; 649 | break; 650 | } 651 | 652 | // if array contains no values, no initial value to return 653 | if (--i < 0) { 654 | throw new TypeError('reduceRight of empty array with no initial value'); 655 | } 656 | } while (true); 657 | } 658 | 659 | if (i < 0) { 660 | return result; 661 | } 662 | 663 | do { 664 | if (i in self) { 665 | result = fun.call(void 0, result, self[i], i, object); 666 | } 667 | } while (i--); 668 | 669 | return result; 670 | } 671 | }, !reduceRightCoercesToObject); 672 | 673 | // ES5 15.4.4.14 674 | // http://es5.github.com/#x15.4.4.14 675 | // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf 676 | var hasFirefox2IndexOfBug = Array.prototype.indexOf && [0, 1].indexOf(1, 2) !== -1; 677 | defineProperties(ArrayPrototype, { 678 | indexOf: function indexOf(sought /*, fromIndex */ ) { 679 | var self = splitString && isString(this) ? this.split('') : toObject(this), 680 | length = self.length >>> 0; 681 | 682 | if (!length) { 683 | return -1; 684 | } 685 | 686 | var i = 0; 687 | if (arguments.length > 1) { 688 | i = toInteger(arguments[1]); 689 | } 690 | 691 | // handle negative indices 692 | i = i >= 0 ? i : Math.max(0, length + i); 693 | for (; i < length; i++) { 694 | if (i in self && self[i] === sought) { 695 | return i; 696 | } 697 | } 698 | return -1; 699 | } 700 | }, hasFirefox2IndexOfBug); 701 | 702 | // ES5 15.4.4.15 703 | // http://es5.github.com/#x15.4.4.15 704 | // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/lastIndexOf 705 | var hasFirefox2LastIndexOfBug = Array.prototype.lastIndexOf && [0, 1].lastIndexOf(0, -3) !== -1; 706 | defineProperties(ArrayPrototype, { 707 | lastIndexOf: function lastIndexOf(sought /*, fromIndex */) { 708 | var self = splitString && isString(this) ? this.split('') : toObject(this), 709 | length = self.length >>> 0; 710 | 711 | if (!length) { 712 | return -1; 713 | } 714 | var i = length - 1; 715 | if (arguments.length > 1) { 716 | i = Math.min(i, toInteger(arguments[1])); 717 | } 718 | // handle negative indices 719 | i = i >= 0 ? i : length - Math.abs(i); 720 | for (; i >= 0; i--) { 721 | if (i in self && sought === self[i]) { 722 | return i; 723 | } 724 | } 725 | return -1; 726 | } 727 | }, hasFirefox2LastIndexOfBug); 728 | 729 | // 730 | // Object 731 | // ====== 732 | // 733 | 734 | // ES5 15.2.3.14 735 | // http://es5.github.com/#x15.2.3.14 736 | 737 | // http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation 738 | var hasDontEnumBug = !({'toString': null}).propertyIsEnumerable('toString'), 739 | hasProtoEnumBug = (function () {}).propertyIsEnumerable('prototype'), 740 | dontEnums = [ 741 | 'toString', 742 | 'toLocaleString', 743 | 'valueOf', 744 | 'hasOwnProperty', 745 | 'isPrototypeOf', 746 | 'propertyIsEnumerable', 747 | 'constructor' 748 | ], 749 | dontEnumsLength = dontEnums.length; 750 | 751 | defineProperties(Object, { 752 | keys: function keys(object) { 753 | var isFn = isFunction(object), 754 | isArgs = isArguments(object), 755 | isObject = object !== null && typeof object === 'object', 756 | isStr = isObject && isString(object); 757 | 758 | if (!isObject && !isFn && !isArgs) { 759 | throw new TypeError('Object.keys called on a non-object'); 760 | } 761 | 762 | var theKeys = []; 763 | var skipProto = hasProtoEnumBug && isFn; 764 | if (isStr || isArgs) { 765 | for (var i = 0; i < object.length; ++i) { 766 | theKeys.push(String(i)); 767 | } 768 | } else { 769 | for (var name in object) { 770 | if (!(skipProto && name === 'prototype') && owns(object, name)) { 771 | theKeys.push(String(name)); 772 | } 773 | } 774 | } 775 | 776 | if (hasDontEnumBug) { 777 | var ctor = object.constructor, 778 | skipConstructor = ctor && ctor.prototype === object; 779 | for (var j = 0; j < dontEnumsLength; j++) { 780 | var dontEnum = dontEnums[j]; 781 | if (!(skipConstructor && dontEnum === 'constructor') && owns(object, dontEnum)) { 782 | theKeys.push(dontEnum); 783 | } 784 | } 785 | } 786 | return theKeys; 787 | } 788 | }); 789 | 790 | var keysWorksWithArguments = Object.keys && (function () { 791 | // Safari 5.0 bug 792 | return Object.keys(arguments).length === 2; 793 | }(1, 2)); 794 | var originalKeys = Object.keys; 795 | defineProperties(Object, { 796 | keys: function keys(object) { 797 | if (isArguments(object)) { 798 | return originalKeys(ArrayPrototype.slice.call(object)); 799 | } else { 800 | return originalKeys(object); 801 | } 802 | } 803 | }, !keysWorksWithArguments); 804 | 805 | // 806 | // Date 807 | // ==== 808 | // 809 | 810 | // ES5 15.9.5.43 811 | // http://es5.github.com/#x15.9.5.43 812 | // This function returns a String value represent the instance in time 813 | // represented by this Date object. The format of the String is the Date Time 814 | // string format defined in 15.9.1.15. All fields are present in the String. 815 | // The time zone is always UTC, denoted by the suffix Z. If the time value of 816 | // this object is not a finite Number a RangeError exception is thrown. 817 | var negativeDate = -62198755200000; 818 | var negativeYearString = '-000001'; 819 | var hasNegativeDateBug = Date.prototype.toISOString && new Date(negativeDate).toISOString().indexOf(negativeYearString) === -1; 820 | 821 | defineProperties(Date.prototype, { 822 | toISOString: function toISOString() { 823 | var result, length, value, year, month; 824 | if (!isFinite(this)) { 825 | throw new RangeError('Date.prototype.toISOString called on non-finite value.'); 826 | } 827 | 828 | year = this.getUTCFullYear(); 829 | 830 | month = this.getUTCMonth(); 831 | // see https://github.com/es-shims/es5-shim/issues/111 832 | year += Math.floor(month / 12); 833 | month = (month % 12 + 12) % 12; 834 | 835 | // the date time string format is specified in 15.9.1.15. 836 | result = [month + 1, this.getUTCDate(), this.getUTCHours(), this.getUTCMinutes(), this.getUTCSeconds()]; 837 | year = ( 838 | (year < 0 ? '-' : (year > 9999 ? '+' : '')) + 839 | ('00000' + Math.abs(year)).slice(0 <= year && year <= 9999 ? -4 : -6) 840 | ); 841 | 842 | length = result.length; 843 | while (length--) { 844 | value = result[length]; 845 | // pad months, days, hours, minutes, and seconds to have two 846 | // digits. 847 | if (value < 10) { 848 | result[length] = '0' + value; 849 | } 850 | } 851 | // pad milliseconds to have three digits. 852 | return ( 853 | year + '-' + result.slice(0, 2).join('-') + 854 | 'T' + result.slice(2).join(':') + '.' + 855 | ('000' + this.getUTCMilliseconds()).slice(-3) + 'Z' 856 | ); 857 | } 858 | }, hasNegativeDateBug); 859 | 860 | 861 | // ES5 15.9.5.44 862 | // http://es5.github.com/#x15.9.5.44 863 | // This function provides a String representation of a Date object for use by 864 | // JSON.stringify (15.12.3). 865 | var dateToJSONIsSupported = false; 866 | try { 867 | dateToJSONIsSupported = ( 868 | Date.prototype.toJSON && 869 | new Date(NaN).toJSON() === null && 870 | new Date(negativeDate).toJSON().indexOf(negativeYearString) !== -1 && 871 | Date.prototype.toJSON.call({ // generic 872 | toISOString: function () { 873 | return true; 874 | } 875 | }) 876 | ); 877 | } catch (e) { 878 | } 879 | if (!dateToJSONIsSupported) { 880 | Date.prototype.toJSON = function toJSON(key) { 881 | // When the toJSON method is called with argument key, the following 882 | // steps are taken: 883 | 884 | // 1. Let O be the result of calling ToObject, giving it the this 885 | // value as its argument. 886 | // 2. Let tv be toPrimitive(O, hint Number). 887 | var o = Object(this), 888 | tv = toPrimitive(o), 889 | toISO; 890 | // 3. If tv is a Number and is not finite, return null. 891 | if (typeof tv === 'number' && !isFinite(tv)) { 892 | return null; 893 | } 894 | // 4. Let toISO be the result of calling the [[Get]] internal method of 895 | // O with argument "toISOString". 896 | toISO = o.toISOString; 897 | // 5. If IsCallable(toISO) is false, throw a TypeError exception. 898 | if (typeof toISO !== 'function') { 899 | throw new TypeError('toISOString property is not callable'); 900 | } 901 | // 6. Return the result of calling the [[Call]] internal method of 902 | // toISO with O as the this value and an empty argument list. 903 | return toISO.call(o); 904 | 905 | // NOTE 1 The argument is ignored. 906 | 907 | // NOTE 2 The toJSON function is intentionally generic; it does not 908 | // require that its this value be a Date object. Therefore, it can be 909 | // transferred to other kinds of objects for use as a method. However, 910 | // it does require that any such object have a toISOString method. An 911 | // object is free to use the argument key to filter its 912 | // stringification. 913 | }; 914 | } 915 | 916 | // ES5 15.9.4.2 917 | // http://es5.github.com/#x15.9.4.2 918 | // based on work shared by Daniel Friesen (dantman) 919 | // http://gist.github.com/303249 920 | var supportsExtendedYears = Date.parse('+033658-09-27T01:46:40.000Z') === 1e15; 921 | var acceptsInvalidDates = !isNaN(Date.parse('2012-04-04T24:00:00.500Z')) || !isNaN(Date.parse('2012-11-31T23:59:59.000Z')); 922 | var doesNotParseY2KNewYear = isNaN(Date.parse('2000-01-01T00:00:00.000Z')); 923 | if (!Date.parse || doesNotParseY2KNewYear || acceptsInvalidDates || !supportsExtendedYears) { 924 | // XXX global assignment won't work in embeddings that use 925 | // an alternate object for the context. 926 | Date = (function (NativeDate) { 927 | 928 | // Date.length === 7 929 | function Date(Y, M, D, h, m, s, ms) { 930 | var length = arguments.length; 931 | if (this instanceof NativeDate) { 932 | var date = length === 1 && String(Y) === Y ? // isString(Y) 933 | // We explicitly pass it through parse: 934 | new NativeDate(Date.parse(Y)) : 935 | // We have to manually make calls depending on argument 936 | // length here 937 | length >= 7 ? new NativeDate(Y, M, D, h, m, s, ms) : 938 | length >= 6 ? new NativeDate(Y, M, D, h, m, s) : 939 | length >= 5 ? new NativeDate(Y, M, D, h, m) : 940 | length >= 4 ? new NativeDate(Y, M, D, h) : 941 | length >= 3 ? new NativeDate(Y, M, D) : 942 | length >= 2 ? new NativeDate(Y, M) : 943 | length >= 1 ? new NativeDate(Y) : 944 | new NativeDate(); 945 | // Prevent mixups with unfixed Date object 946 | date.constructor = Date; 947 | return date; 948 | } 949 | return NativeDate.apply(this, arguments); 950 | } 951 | 952 | // 15.9.1.15 Date Time String Format. 953 | var isoDateExpression = new RegExp('^' + 954 | '(\\d{4}|[\+\-]\\d{6})' + // four-digit year capture or sign + 955 | // 6-digit extended year 956 | '(?:-(\\d{2})' + // optional month capture 957 | '(?:-(\\d{2})' + // optional day capture 958 | '(?:' + // capture hours:minutes:seconds.milliseconds 959 | 'T(\\d{2})' + // hours capture 960 | ':(\\d{2})' + // minutes capture 961 | '(?:' + // optional :seconds.milliseconds 962 | ':(\\d{2})' + // seconds capture 963 | '(?:(\\.\\d{1,}))?' + // milliseconds capture 964 | ')?' + 965 | '(' + // capture UTC offset component 966 | 'Z|' + // UTC capture 967 | '(?:' + // offset specifier +/-hours:minutes 968 | '([-+])' + // sign capture 969 | '(\\d{2})' + // hours offset capture 970 | ':(\\d{2})' + // minutes offset capture 971 | ')' + 972 | ')?)?)?)?' + 973 | '$'); 974 | 975 | var months = [ 976 | 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 977 | ]; 978 | 979 | function dayFromMonth(year, month) { 980 | var t = month > 1 ? 1 : 0; 981 | return ( 982 | months[month] + 983 | Math.floor((year - 1969 + t) / 4) - 984 | Math.floor((year - 1901 + t) / 100) + 985 | Math.floor((year - 1601 + t) / 400) + 986 | 365 * (year - 1970) 987 | ); 988 | } 989 | 990 | function toUTC(t) { 991 | return Number(new NativeDate(1970, 0, 1, 0, 0, 0, t)); 992 | } 993 | 994 | // Copy any custom methods a 3rd party library may have added 995 | for (var key in NativeDate) { 996 | Date[key] = NativeDate[key]; 997 | } 998 | 999 | // Copy "native" methods explicitly; they may be non-enumerable 1000 | Date.now = NativeDate.now; 1001 | Date.UTC = NativeDate.UTC; 1002 | Date.prototype = NativeDate.prototype; 1003 | Date.prototype.constructor = Date; 1004 | 1005 | // Upgrade Date.parse to handle simplified ISO 8601 strings 1006 | Date.parse = function parse(string) { 1007 | var match = isoDateExpression.exec(string); 1008 | if (match) { 1009 | // parse months, days, hours, minutes, seconds, and milliseconds 1010 | // provide default values if necessary 1011 | // parse the UTC offset component 1012 | var year = Number(match[1]), 1013 | month = Number(match[2] || 1) - 1, 1014 | day = Number(match[3] || 1) - 1, 1015 | hour = Number(match[4] || 0), 1016 | minute = Number(match[5] || 0), 1017 | second = Number(match[6] || 0), 1018 | millisecond = Math.floor(Number(match[7] || 0) * 1000), 1019 | // When time zone is missed, local offset should be used 1020 | // (ES 5.1 bug) 1021 | // see https://bugs.ecmascript.org/show_bug.cgi?id=112 1022 | isLocalTime = Boolean(match[4] && !match[8]), 1023 | signOffset = match[9] === '-' ? 1 : -1, 1024 | hourOffset = Number(match[10] || 0), 1025 | minuteOffset = Number(match[11] || 0), 1026 | result; 1027 | if ( 1028 | hour < ( 1029 | minute > 0 || second > 0 || millisecond > 0 ? 1030 | 24 : 25 1031 | ) && 1032 | minute < 60 && second < 60 && millisecond < 1000 && 1033 | month > -1 && month < 12 && hourOffset < 24 && 1034 | minuteOffset < 60 && // detect invalid offsets 1035 | day > -1 && 1036 | day < ( 1037 | dayFromMonth(year, month + 1) - 1038 | dayFromMonth(year, month) 1039 | ) 1040 | ) { 1041 | result = ( 1042 | (dayFromMonth(year, month) + day) * 24 + 1043 | hour + 1044 | hourOffset * signOffset 1045 | ) * 60; 1046 | result = ( 1047 | (result + minute + minuteOffset * signOffset) * 60 + 1048 | second 1049 | ) * 1000 + millisecond; 1050 | if (isLocalTime) { 1051 | result = toUTC(result); 1052 | } 1053 | if (-8.64e15 <= result && result <= 8.64e15) { 1054 | return result; 1055 | } 1056 | } 1057 | return NaN; 1058 | } 1059 | return NativeDate.parse.apply(this, arguments); 1060 | }; 1061 | 1062 | return Date; 1063 | })(Date); 1064 | } 1065 | 1066 | // ES5 15.9.4.4 1067 | // http://es5.github.com/#x15.9.4.4 1068 | if (!Date.now) { 1069 | Date.now = function now() { 1070 | return new Date().getTime(); 1071 | }; 1072 | } 1073 | 1074 | 1075 | // 1076 | // Number 1077 | // ====== 1078 | // 1079 | 1080 | // ES5.1 15.7.4.5 1081 | // http://es5.github.com/#x15.7.4.5 1082 | var hasToFixedBugs = NumberPrototype.toFixed && ( 1083 | (0.00008).toFixed(3) !== '0.000' 1084 | || (0.9).toFixed(0) !== '1' 1085 | || (1.255).toFixed(2) !== '1.25' 1086 | || (1000000000000000128).toFixed(0) !== '1000000000000000128' 1087 | ); 1088 | 1089 | var toFixedHelpers = { 1090 | base: 1e7, 1091 | size: 6, 1092 | data: [0, 0, 0, 0, 0, 0], 1093 | multiply: function multiply(n, c) { 1094 | var i = -1; 1095 | while (++i < toFixedHelpers.size) { 1096 | c += n * toFixedHelpers.data[i]; 1097 | toFixedHelpers.data[i] = c % toFixedHelpers.base; 1098 | c = Math.floor(c / toFixedHelpers.base); 1099 | } 1100 | }, 1101 | divide: function divide(n) { 1102 | var i = toFixedHelpers.size, c = 0; 1103 | while (--i >= 0) { 1104 | c += toFixedHelpers.data[i]; 1105 | toFixedHelpers.data[i] = Math.floor(c / n); 1106 | c = (c % n) * toFixedHelpers.base; 1107 | } 1108 | }, 1109 | numToString: function numToString() { 1110 | var i = toFixedHelpers.size; 1111 | var s = ''; 1112 | while (--i >= 0) { 1113 | if (s !== '' || i === 0 || toFixedHelpers.data[i] !== 0) { 1114 | var t = String(toFixedHelpers.data[i]); 1115 | if (s === '') { 1116 | s = t; 1117 | } else { 1118 | s += '0000000'.slice(0, 7 - t.length) + t; 1119 | } 1120 | } 1121 | } 1122 | return s; 1123 | }, 1124 | pow: function pow(x, n, acc) { 1125 | return (n === 0 ? acc : (n % 2 === 1 ? pow(x, n - 1, acc * x) : pow(x * x, n / 2, acc))); 1126 | }, 1127 | log: function log(x) { 1128 | var n = 0; 1129 | while (x >= 4096) { 1130 | n += 12; 1131 | x /= 4096; 1132 | } 1133 | while (x >= 2) { 1134 | n += 1; 1135 | x /= 2; 1136 | } 1137 | return n; 1138 | } 1139 | }; 1140 | 1141 | defineProperties(NumberPrototype, { 1142 | toFixed: function toFixed(fractionDigits) { 1143 | var f, x, s, m, e, z, j, k; 1144 | 1145 | // Test for NaN and round fractionDigits down 1146 | f = Number(fractionDigits); 1147 | f = f !== f ? 0 : Math.floor(f); 1148 | 1149 | if (f < 0 || f > 20) { 1150 | throw new RangeError('Number.toFixed called with invalid number of decimals'); 1151 | } 1152 | 1153 | x = Number(this); 1154 | 1155 | // Test for NaN 1156 | if (x !== x) { 1157 | return 'NaN'; 1158 | } 1159 | 1160 | // If it is too big or small, return the string value of the number 1161 | if (x <= -1e21 || x >= 1e21) { 1162 | return String(x); 1163 | } 1164 | 1165 | s = ''; 1166 | 1167 | if (x < 0) { 1168 | s = '-'; 1169 | x = -x; 1170 | } 1171 | 1172 | m = '0'; 1173 | 1174 | if (x > 1e-21) { 1175 | // 1e-21 < x < 1e21 1176 | // -70 < log2(x) < 70 1177 | e = toFixedHelpers.log(x * toFixedHelpers.pow(2, 69, 1)) - 69; 1178 | z = (e < 0 ? x * toFixedHelpers.pow(2, -e, 1) : x / toFixedHelpers.pow(2, e, 1)); 1179 | z *= 0x10000000000000; // Math.pow(2, 52); 1180 | e = 52 - e; 1181 | 1182 | // -18 < e < 122 1183 | // x = z / 2 ^ e 1184 | if (e > 0) { 1185 | toFixedHelpers.multiply(0, z); 1186 | j = f; 1187 | 1188 | while (j >= 7) { 1189 | toFixedHelpers.multiply(1e7, 0); 1190 | j -= 7; 1191 | } 1192 | 1193 | toFixedHelpers.multiply(toFixedHelpers.pow(10, j, 1), 0); 1194 | j = e - 1; 1195 | 1196 | while (j >= 23) { 1197 | toFixedHelpers.divide(1 << 23); 1198 | j -= 23; 1199 | } 1200 | 1201 | toFixedHelpers.divide(1 << j); 1202 | toFixedHelpers.multiply(1, 1); 1203 | toFixedHelpers.divide(2); 1204 | m = toFixedHelpers.numToString(); 1205 | } else { 1206 | toFixedHelpers.multiply(0, z); 1207 | toFixedHelpers.multiply(1 << (-e), 0); 1208 | m = toFixedHelpers.numToString() + '0.00000000000000000000'.slice(2, 2 + f); 1209 | } 1210 | } 1211 | 1212 | if (f > 0) { 1213 | k = m.length; 1214 | 1215 | if (k <= f) { 1216 | m = s + '0.0000000000000000000'.slice(0, f - k + 2) + m; 1217 | } else { 1218 | m = s + m.slice(0, k - f) + '.' + m.slice(k - f); 1219 | } 1220 | } else { 1221 | m = s + m; 1222 | } 1223 | 1224 | return m; 1225 | } 1226 | }, hasToFixedBugs); 1227 | 1228 | 1229 | // 1230 | // String 1231 | // ====== 1232 | // 1233 | 1234 | // ES5 15.5.4.14 1235 | // http://es5.github.com/#x15.5.4.14 1236 | 1237 | // [bugfix, IE lt 9, firefox 4, Konqueror, Opera, obscure browsers] 1238 | // Many browsers do not split properly with regular expressions or they 1239 | // do not perform the split correctly under obscure conditions. 1240 | // See http://blog.stevenlevithan.com/archives/cross-browser-split 1241 | // I've tested in many browsers and this seems to cover the deviant ones: 1242 | // 'ab'.split(/(?:ab)*/) should be ["", ""], not [""] 1243 | // '.'.split(/(.?)(.?)/) should be ["", ".", "", ""], not ["", ""] 1244 | // 'tesst'.split(/(s)*/) should be ["t", undefined, "e", "s", "t"], not 1245 | // [undefined, "t", undefined, "e", ...] 1246 | // ''.split(/.?/) should be [], not [""] 1247 | // '.'.split(/()()/) should be ["."], not ["", "", "."] 1248 | 1249 | var string_split = StringPrototype.split; 1250 | if ( 1251 | 'ab'.split(/(?:ab)*/).length !== 2 || 1252 | '.'.split(/(.?)(.?)/).length !== 4 || 1253 | 'tesst'.split(/(s)*/)[1] === 't' || 1254 | 'test'.split(/(?:)/, -1).length !== 4 || 1255 | ''.split(/.?/).length || 1256 | '.'.split(/()()/).length > 1 1257 | ) { 1258 | (function () { 1259 | var compliantExecNpcg = /()??/.exec('')[1] === void 0; // NPCG: nonparticipating capturing group 1260 | 1261 | StringPrototype.split = function (separator, limit) { 1262 | var string = this; 1263 | if (separator === void 0 && limit === 0) { 1264 | return []; 1265 | } 1266 | 1267 | // If `separator` is not a regex, use native split 1268 | if (_toString.call(separator) !== '[object RegExp]') { 1269 | return string_split.call(this, separator, limit); 1270 | } 1271 | 1272 | var output = [], 1273 | flags = (separator.ignoreCase ? 'i' : '') + 1274 | (separator.multiline ? 'm' : '') + 1275 | (separator.extended ? 'x' : '') + // Proposed for ES6 1276 | (separator.sticky ? 'y' : ''), // Firefox 3+ 1277 | lastLastIndex = 0, 1278 | // Make `global` and avoid `lastIndex` issues by working with a copy 1279 | separator2, match, lastIndex, lastLength; 1280 | separator = new RegExp(separator.source, flags + 'g'); 1281 | string += ''; // Type-convert 1282 | if (!compliantExecNpcg) { 1283 | // Doesn't need flags gy, but they don't hurt 1284 | separator2 = new RegExp('^' + separator.source + '$(?!\\s)', flags); 1285 | } 1286 | /* Values for `limit`, per the spec: 1287 | * If undefined: 4294967295 // Math.pow(2, 32) - 1 1288 | * If 0, Infinity, or NaN: 0 1289 | * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296; 1290 | * If negative number: 4294967296 - Math.floor(Math.abs(limit)) 1291 | * If other: Type-convert, then use the above rules 1292 | */ 1293 | limit = limit === void 0 ? 1294 | -1 >>> 0 : // Math.pow(2, 32) - 1 1295 | ToUint32(limit); 1296 | while (match = separator.exec(string)) { 1297 | // `separator.lastIndex` is not reliable cross-browser 1298 | lastIndex = match.index + match[0].length; 1299 | if (lastIndex > lastLastIndex) { 1300 | output.push(string.slice(lastLastIndex, match.index)); 1301 | // Fix browsers whose `exec` methods don't consistently return `undefined` for 1302 | // nonparticipating capturing groups 1303 | if (!compliantExecNpcg && match.length > 1) { 1304 | match[0].replace(separator2, function () { 1305 | for (var i = 1; i < arguments.length - 2; i++) { 1306 | if (arguments[i] === void 0) { 1307 | match[i] = void 0; 1308 | } 1309 | } 1310 | }); 1311 | } 1312 | if (match.length > 1 && match.index < string.length) { 1313 | ArrayPrototype.push.apply(output, match.slice(1)); 1314 | } 1315 | lastLength = match[0].length; 1316 | lastLastIndex = lastIndex; 1317 | if (output.length >= limit) { 1318 | break; 1319 | } 1320 | } 1321 | if (separator.lastIndex === match.index) { 1322 | separator.lastIndex++; // Avoid an infinite loop 1323 | } 1324 | } 1325 | if (lastLastIndex === string.length) { 1326 | if (lastLength || !separator.test('')) { 1327 | output.push(''); 1328 | } 1329 | } else { 1330 | output.push(string.slice(lastLastIndex)); 1331 | } 1332 | return output.length > limit ? output.slice(0, limit) : output; 1333 | }; 1334 | }()); 1335 | 1336 | // [bugfix, chrome] 1337 | // If separator is undefined, then the result array contains just one String, 1338 | // which is the this value (converted to a String). If limit is not undefined, 1339 | // then the output array is truncated so that it contains no more than limit 1340 | // elements. 1341 | // "0".split(undefined, 0) -> [] 1342 | } else if ('0'.split(void 0, 0).length) { 1343 | StringPrototype.split = function split(separator, limit) { 1344 | if (separator === void 0 && limit === 0) { return []; } 1345 | return string_split.call(this, separator, limit); 1346 | }; 1347 | } 1348 | 1349 | var str_replace = StringPrototype.replace; 1350 | var replaceReportsGroupsCorrectly = (function () { 1351 | var groups = []; 1352 | 'x'.replace(/x(.)?/g, function (match, group) { 1353 | groups.push(group); 1354 | }); 1355 | return groups.length === 1 && typeof groups[0] === 'undefined'; 1356 | }()); 1357 | 1358 | if (!replaceReportsGroupsCorrectly) { 1359 | StringPrototype.replace = function replace(searchValue, replaceValue) { 1360 | var isFn = isFunction(replaceValue); 1361 | var hasCapturingGroups = isRegex(searchValue) && (/\)[*?]/).test(searchValue.source); 1362 | if (!isFn || !hasCapturingGroups) { 1363 | return str_replace.call(this, searchValue, replaceValue); 1364 | } else { 1365 | var wrappedReplaceValue = function (match) { 1366 | var length = arguments.length; 1367 | var originalLastIndex = searchValue.lastIndex; 1368 | searchValue.lastIndex = 0; 1369 | var args = searchValue.exec(match) || []; 1370 | searchValue.lastIndex = originalLastIndex; 1371 | args.push(arguments[length - 2], arguments[length - 1]); 1372 | return replaceValue.apply(this, args); 1373 | }; 1374 | return str_replace.call(this, searchValue, wrappedReplaceValue); 1375 | } 1376 | }; 1377 | } 1378 | 1379 | // ECMA-262, 3rd B.2.3 1380 | // Not an ECMAScript standard, although ECMAScript 3rd Edition has a 1381 | // non-normative section suggesting uniform semantics and it should be 1382 | // normalized across all browsers 1383 | // [bugfix, IE lt 9] IE < 9 substr() with negative value not working in IE 1384 | var string_substr = StringPrototype.substr; 1385 | var hasNegativeSubstrBug = ''.substr && '0b'.substr(-1) !== 'b'; 1386 | defineProperties(StringPrototype, { 1387 | substr: function substr(start, length) { 1388 | return string_substr.call( 1389 | this, 1390 | start < 0 ? ((start = this.length + start) < 0 ? 0 : start) : start, 1391 | length 1392 | ); 1393 | } 1394 | }, hasNegativeSubstrBug); 1395 | 1396 | // ES5 15.5.4.20 1397 | // whitespace from: http://es5.github.io/#x15.5.4.20 1398 | var ws = '\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003' + 1399 | '\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028' + 1400 | '\u2029\uFEFF'; 1401 | var zeroWidth = '\u200b'; 1402 | var wsRegexChars = '[' + ws + ']'; 1403 | var trimBeginRegexp = new RegExp('^' + wsRegexChars + wsRegexChars + '*'); 1404 | var trimEndRegexp = new RegExp(wsRegexChars + wsRegexChars + '*$'); 1405 | var hasTrimWhitespaceBug = StringPrototype.trim && (ws.trim() || !zeroWidth.trim()); 1406 | defineProperties(StringPrototype, { 1407 | // http://blog.stevenlevithan.com/archives/faster-trim-javascript 1408 | // http://perfectionkills.com/whitespace-deviations/ 1409 | trim: function trim() { 1410 | if (this === void 0 || this === null) { 1411 | throw new TypeError("can't convert " + this + ' to object'); 1412 | } 1413 | return String(this).replace(trimBeginRegexp, '').replace(trimEndRegexp, ''); 1414 | } 1415 | }, hasTrimWhitespaceBug); 1416 | 1417 | // ES-5 15.1.2.2 1418 | if (parseInt(ws + '08') !== 8 || parseInt(ws + '0x16') !== 22) { 1419 | parseInt = (function (origParseInt) { 1420 | var hexRegex = /^0[xX]/; 1421 | return function parseIntES5(str, radix) { 1422 | str = String(str).trim(); 1423 | if (!Number(radix)) { 1424 | radix = hexRegex.test(str) ? 16 : 10; 1425 | } 1426 | return origParseInt(str, radix); 1427 | }; 1428 | }(parseInt)); 1429 | } 1430 | 1431 | })); 1432 | -------------------------------------------------------------------------------- /binary_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(;i0})}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})(); -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /demo/js/es5-shim.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * https://github.com/es-shims/es5-shim 3 | * @license es5-shim Copyright 2009-2014 by contributors, MIT License 4 | * see https://github.com/es-shims/es5-shim/blob/master/LICENSE 5 | */ 6 | 7 | // vim: ts=4 sts=4 sw=4 expandtab 8 | 9 | //Add semicolon to prevent IIFE from being passed as argument to concatenated code. 10 | ; 11 | 12 | // UMD (Universal Module Definition) 13 | // see https://github.com/umdjs/umd/blob/master/returnExports.js 14 | (function (root, factory) { 15 | if (typeof define === 'function' && define.amd) { 16 | // AMD. Register as an anonymous module. 17 | define(factory); 18 | } else if (typeof exports === 'object') { 19 | // Node. Does not work with strict CommonJS, but 20 | // only CommonJS-like enviroments that support module.exports, 21 | // like Node. 22 | module.exports = factory(); 23 | } else { 24 | // Browser globals (root is window) 25 | root.returnExports = factory(); 26 | } 27 | }(this, function () { 28 | 29 | /** 30 | * Brings an environment as close to ECMAScript 5 compliance 31 | * as is possible with the facilities of erstwhile engines. 32 | * 33 | * Annotated ES5: http://es5.github.com/ (specific links below) 34 | * ES5 Spec: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf 35 | * Required reading: http://javascriptweblog.wordpress.com/2011/12/05/extending-javascript-natives/ 36 | */ 37 | 38 | // Shortcut to an often accessed properties, in order to avoid multiple 39 | // dereference that costs universally. 40 | var ArrayPrototype = Array.prototype; 41 | var ObjectPrototype = Object.prototype; 42 | var FunctionPrototype = Function.prototype; 43 | var StringPrototype = String.prototype; 44 | var NumberPrototype = Number.prototype; 45 | var array_slice = ArrayPrototype.slice; 46 | var array_splice = ArrayPrototype.splice; 47 | var array_push = ArrayPrototype.push; 48 | var array_unshift = ArrayPrototype.unshift; 49 | var call = FunctionPrototype.call; 50 | 51 | // Having a toString local variable name breaks in Opera so use _toString. 52 | var _toString = ObjectPrototype.toString; 53 | 54 | var isFunction = function (val) { 55 | return ObjectPrototype.toString.call(val) === '[object Function]'; 56 | }; 57 | var isRegex = function (val) { 58 | return ObjectPrototype.toString.call(val) === '[object RegExp]'; 59 | }; 60 | var isArray = function isArray(obj) { 61 | return _toString.call(obj) === '[object Array]'; 62 | }; 63 | var isString = function isString(obj) { 64 | return _toString.call(obj) === '[object String]'; 65 | }; 66 | var isArguments = function isArguments(value) { 67 | var str = _toString.call(value); 68 | var isArgs = str === '[object Arguments]'; 69 | if (!isArgs) { 70 | isArgs = !isArray(value) 71 | && value !== null 72 | && typeof value === 'object' 73 | && typeof value.length === 'number' 74 | && value.length >= 0 75 | && isFunction(value.callee); 76 | } 77 | return isArgs; 78 | }; 79 | 80 | var supportsDescriptors = Object.defineProperty && (function () { 81 | try { 82 | Object.defineProperty({}, 'x', {}); 83 | return true; 84 | } catch (e) { /* this is ES3 */ 85 | return false; 86 | } 87 | }()); 88 | 89 | // Define configurable, writable and non-enumerable props 90 | // if they don't exist. 91 | var defineProperty; 92 | if (supportsDescriptors) { 93 | defineProperty = function (object, name, method, forceAssign) { 94 | if (!forceAssign && (name in object)) { return; } 95 | Object.defineProperty(object, name, { 96 | configurable: true, 97 | enumerable: false, 98 | writable: true, 99 | value: method 100 | }); 101 | }; 102 | } else { 103 | defineProperty = function (object, name, method, forceAssign) { 104 | if (!forceAssign && (name in object)) { return; } 105 | object[name] = method; 106 | }; 107 | } 108 | var defineProperties = function (object, map, forceAssign) { 109 | for (var name in map) { 110 | if (ObjectPrototype.hasOwnProperty.call(map, name)) { 111 | defineProperty(object, name, map[name], forceAssign); 112 | } 113 | } 114 | }; 115 | 116 | // 117 | // Util 118 | // ====== 119 | // 120 | 121 | // ES5 9.4 122 | // http://es5.github.com/#x9.4 123 | // http://jsperf.com/to-integer 124 | 125 | function toInteger(num) { 126 | var n = +num; 127 | if (n !== n) { // isNaN 128 | n = 0; 129 | } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) { 130 | n = (n > 0 || -1) * Math.floor(Math.abs(n)); 131 | } 132 | return n; 133 | } 134 | 135 | function isPrimitive(input) { 136 | var type = typeof input; 137 | return ( 138 | input === null || 139 | type === 'undefined' || 140 | type === 'boolean' || 141 | type === 'number' || 142 | type === 'string' 143 | ); 144 | } 145 | 146 | function toPrimitive(input) { 147 | var val, valueOf, toStr; 148 | if (isPrimitive(input)) { 149 | return input; 150 | } 151 | valueOf = input.valueOf; 152 | if (isFunction(valueOf)) { 153 | val = valueOf.call(input); 154 | if (isPrimitive(val)) { 155 | return val; 156 | } 157 | } 158 | toStr = input.toString; 159 | if (isFunction(toStr)) { 160 | val = toStr.call(input); 161 | if (isPrimitive(val)) { 162 | return val; 163 | } 164 | } 165 | throw new TypeError(); 166 | } 167 | 168 | // ES5 9.9 169 | // http://es5.github.com/#x9.9 170 | var toObject = function (o) { 171 | if (o == null) { // this matches both null and undefined 172 | throw new TypeError("can't convert " + o + ' to object'); 173 | } 174 | return Object(o); 175 | }; 176 | 177 | var ToUint32 = function ToUint32(x) { 178 | return x >>> 0; 179 | }; 180 | 181 | // 182 | // Function 183 | // ======== 184 | // 185 | 186 | // ES-5 15.3.4.5 187 | // http://es5.github.com/#x15.3.4.5 188 | 189 | function Empty() {} 190 | 191 | defineProperties(FunctionPrototype, { 192 | bind: function bind(that) { // .length is 1 193 | // 1. Let Target be the this value. 194 | var target = this; 195 | // 2. If IsCallable(Target) is false, throw a TypeError exception. 196 | if (!isFunction(target)) { 197 | throw new TypeError('Function.prototype.bind called on incompatible ' + target); 198 | } 199 | // 3. Let A be a new (possibly empty) internal list of all of the 200 | // argument values provided after thisArg (arg1, arg2 etc), in order. 201 | // XXX slicedArgs will stand in for "A" if used 202 | var args = array_slice.call(arguments, 1); // for normal call 203 | // 4. Let F be a new native ECMAScript object. 204 | // 11. Set the [[Prototype]] internal property of F to the standard 205 | // built-in Function prototype object as specified in 15.3.3.1. 206 | // 12. Set the [[Call]] internal property of F as described in 207 | // 15.3.4.5.1. 208 | // 13. Set the [[Construct]] internal property of F as described in 209 | // 15.3.4.5.2. 210 | // 14. Set the [[HasInstance]] internal property of F as described in 211 | // 15.3.4.5.3. 212 | var binder = function () { 213 | 214 | if (this instanceof bound) { 215 | // 15.3.4.5.2 [[Construct]] 216 | // When the [[Construct]] internal method of a function object, 217 | // F that was created using the bind function is called with a 218 | // list of arguments ExtraArgs, the following steps are taken: 219 | // 1. Let target be the value of F's [[TargetFunction]] 220 | // internal property. 221 | // 2. If target has no [[Construct]] internal method, a 222 | // TypeError exception is thrown. 223 | // 3. Let boundArgs be the value of F's [[BoundArgs]] internal 224 | // property. 225 | // 4. Let args be a new list containing the same values as the 226 | // list boundArgs in the same order followed by the same 227 | // values as the list ExtraArgs in the same order. 228 | // 5. Return the result of calling the [[Construct]] internal 229 | // method of target providing args as the arguments. 230 | 231 | var result = target.apply( 232 | this, 233 | args.concat(array_slice.call(arguments)) 234 | ); 235 | if (Object(result) === result) { 236 | return result; 237 | } 238 | return this; 239 | 240 | } else { 241 | // 15.3.4.5.1 [[Call]] 242 | // When the [[Call]] internal method of a function object, F, 243 | // which was created using the bind function is called with a 244 | // this value and a list of arguments ExtraArgs, the following 245 | // steps are taken: 246 | // 1. Let boundArgs be the value of F's [[BoundArgs]] internal 247 | // property. 248 | // 2. Let boundThis be the value of F's [[BoundThis]] internal 249 | // property. 250 | // 3. Let target be the value of F's [[TargetFunction]] internal 251 | // property. 252 | // 4. Let args be a new list containing the same values as the 253 | // list boundArgs in the same order followed by the same 254 | // values as the list ExtraArgs in the same order. 255 | // 5. Return the result of calling the [[Call]] internal method 256 | // of target providing boundThis as the this value and 257 | // providing args as the arguments. 258 | 259 | // equiv: target.call(this, ...boundArgs, ...args) 260 | return target.apply( 261 | that, 262 | args.concat(array_slice.call(arguments)) 263 | ); 264 | 265 | } 266 | 267 | }; 268 | 269 | // 15. If the [[Class]] internal property of Target is "Function", then 270 | // a. Let L be the length property of Target minus the length of A. 271 | // b. Set the length own property of F to either 0 or L, whichever is 272 | // larger. 273 | // 16. Else set the length own property of F to 0. 274 | 275 | var boundLength = Math.max(0, target.length - args.length); 276 | 277 | // 17. Set the attributes of the length own property of F to the values 278 | // specified in 15.3.5.1. 279 | var boundArgs = []; 280 | for (var i = 0; i < boundLength; i++) { 281 | boundArgs.push('$' + i); 282 | } 283 | 284 | // XXX Build a dynamic function with desired amount of arguments is the only 285 | // way to set the length property of a function. 286 | // In environments where Content Security Policies enabled (Chrome extensions, 287 | // for ex.) all use of eval or Function costructor throws an exception. 288 | // However in all of these environments Function.prototype.bind exists 289 | // and so this code will never be executed. 290 | var bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder); 291 | 292 | if (target.prototype) { 293 | Empty.prototype = target.prototype; 294 | bound.prototype = new Empty(); 295 | // Clean up dangling references. 296 | Empty.prototype = null; 297 | } 298 | 299 | // TODO 300 | // 18. Set the [[Extensible]] internal property of F to true. 301 | 302 | // TODO 303 | // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3). 304 | // 20. Call the [[DefineOwnProperty]] internal method of F with 305 | // arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]: 306 | // thrower, [[Enumerable]]: false, [[Configurable]]: false}, and 307 | // false. 308 | // 21. Call the [[DefineOwnProperty]] internal method of F with 309 | // arguments "arguments", PropertyDescriptor {[[Get]]: thrower, 310 | // [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false}, 311 | // and false. 312 | 313 | // TODO 314 | // NOTE Function objects created using Function.prototype.bind do not 315 | // have a prototype property or the [[Code]], [[FormalParameters]], and 316 | // [[Scope]] internal properties. 317 | // XXX can't delete prototype in pure-js. 318 | 319 | // 22. Return F. 320 | return bound; 321 | } 322 | }); 323 | 324 | // _Please note: Shortcuts are defined after `Function.prototype.bind` as we 325 | // us it in defining shortcuts. 326 | var owns = call.bind(ObjectPrototype.hasOwnProperty); 327 | 328 | // If JS engine supports accessors creating shortcuts. 329 | var defineGetter; 330 | var defineSetter; 331 | var lookupGetter; 332 | var lookupSetter; 333 | var supportsAccessors; 334 | if ((supportsAccessors = owns(ObjectPrototype, '__defineGetter__'))) { 335 | defineGetter = call.bind(ObjectPrototype.__defineGetter__); 336 | defineSetter = call.bind(ObjectPrototype.__defineSetter__); 337 | lookupGetter = call.bind(ObjectPrototype.__lookupGetter__); 338 | lookupSetter = call.bind(ObjectPrototype.__lookupSetter__); 339 | } 340 | 341 | // 342 | // Array 343 | // ===== 344 | // 345 | 346 | // ES5 15.4.4.12 347 | // http://es5.github.com/#x15.4.4.12 348 | var spliceNoopReturnsEmptyArray = (function () { 349 | var a = [1, 2]; 350 | var result = a.splice(); 351 | return a.length === 2 && isArray(result) && result.length === 0; 352 | }()); 353 | defineProperties(ArrayPrototype, { 354 | // Safari 5.0 bug where .splice() returns undefined 355 | splice: function splice(start, deleteCount) { 356 | if (arguments.length === 0) { 357 | return []; 358 | } else { 359 | return array_splice.apply(this, arguments); 360 | } 361 | } 362 | }, spliceNoopReturnsEmptyArray); 363 | 364 | var spliceWorksWithEmptyObject = (function () { 365 | var obj = {}; 366 | ArrayPrototype.splice.call(obj, 0, 0, 1); 367 | return obj.length === 1; 368 | }()); 369 | defineProperties(ArrayPrototype, { 370 | splice: function splice(start, deleteCount) { 371 | if (arguments.length === 0) { return []; } 372 | var args = arguments; 373 | this.length = Math.max(toInteger(this.length), 0); 374 | if (arguments.length > 0 && typeof deleteCount !== 'number') { 375 | args = array_slice.call(arguments); 376 | if (args.length < 2) { 377 | args.push(this.length - start); 378 | } else { 379 | args[1] = toInteger(deleteCount); 380 | } 381 | } 382 | return array_splice.apply(this, args); 383 | } 384 | }, !spliceWorksWithEmptyObject); 385 | 386 | // ES5 15.4.4.12 387 | // http://es5.github.com/#x15.4.4.13 388 | // Return len+argCount. 389 | // [bugfix, ielt8] 390 | // IE < 8 bug: [].unshift(0) === undefined but should be "1" 391 | var hasUnshiftReturnValueBug = [].unshift(0) !== 1; 392 | defineProperties(ArrayPrototype, { 393 | unshift: function () { 394 | array_unshift.apply(this, arguments); 395 | return this.length; 396 | } 397 | }, hasUnshiftReturnValueBug); 398 | 399 | // ES5 15.4.3.2 400 | // http://es5.github.com/#x15.4.3.2 401 | // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray 402 | defineProperties(Array, { isArray: isArray }); 403 | 404 | // The IsCallable() check in the Array functions 405 | // has been replaced with a strict check on the 406 | // internal class of the object to trap cases where 407 | // the provided function was actually a regular 408 | // expression literal, which in V8 and 409 | // JavaScriptCore is a typeof "function". Only in 410 | // V8 are regular expression literals permitted as 411 | // reduce parameters, so it is desirable in the 412 | // general case for the shim to match the more 413 | // strict and common behavior of rejecting regular 414 | // expressions. 415 | 416 | // ES5 15.4.4.18 417 | // http://es5.github.com/#x15.4.4.18 418 | // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/forEach 419 | 420 | // Check failure of by-index access of string characters (IE < 9) 421 | // and failure of `0 in boxedString` (Rhino) 422 | var boxedString = Object('a'); 423 | var splitString = boxedString[0] !== 'a' || !(0 in boxedString); 424 | 425 | var properlyBoxesContext = function properlyBoxed(method) { 426 | // Check node 0.6.21 bug where third parameter is not boxed 427 | var properlyBoxesNonStrict = true; 428 | var properlyBoxesStrict = true; 429 | if (method) { 430 | method.call('foo', function (_, __, context) { 431 | if (typeof context !== 'object') { properlyBoxesNonStrict = false; } 432 | }); 433 | 434 | method.call([1], function () { 435 | 'use strict'; 436 | properlyBoxesStrict = typeof this === 'string'; 437 | }, 'x'); 438 | } 439 | return !!method && properlyBoxesNonStrict && properlyBoxesStrict; 440 | }; 441 | 442 | defineProperties(ArrayPrototype, { 443 | forEach: function forEach(fun /*, thisp*/) { 444 | var object = toObject(this), 445 | self = splitString && isString(this) ? this.split('') : object, 446 | thisp = arguments[1], 447 | i = -1, 448 | length = self.length >>> 0; 449 | 450 | // If no callback function or if callback is not a callable function 451 | if (!isFunction(fun)) { 452 | throw new TypeError(); // TODO message 453 | } 454 | 455 | while (++i < length) { 456 | if (i in self) { 457 | // Invoke the callback function with call, passing arguments: 458 | // context, property value, property key, thisArg object 459 | // context 460 | fun.call(thisp, self[i], i, object); 461 | } 462 | } 463 | } 464 | }, !properlyBoxesContext(ArrayPrototype.forEach)); 465 | 466 | // ES5 15.4.4.19 467 | // http://es5.github.com/#x15.4.4.19 468 | // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map 469 | defineProperties(ArrayPrototype, { 470 | map: function map(fun /*, thisp*/) { 471 | var object = toObject(this), 472 | self = splitString && isString(this) ? this.split('') : object, 473 | length = self.length >>> 0, 474 | result = Array(length), 475 | thisp = arguments[1]; 476 | 477 | // If no callback function or if callback is not a callable function 478 | if (!isFunction(fun)) { 479 | throw new TypeError(fun + ' is not a function'); 480 | } 481 | 482 | for (var i = 0; i < length; i++) { 483 | if (i in self) { 484 | result[i] = fun.call(thisp, self[i], i, object); 485 | } 486 | } 487 | return result; 488 | } 489 | }, !properlyBoxesContext(ArrayPrototype.map)); 490 | 491 | // ES5 15.4.4.20 492 | // http://es5.github.com/#x15.4.4.20 493 | // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/filter 494 | defineProperties(ArrayPrototype, { 495 | filter: function filter(fun /*, thisp */) { 496 | var object = toObject(this), 497 | self = splitString && isString(this) ? this.split('') : object, 498 | length = self.length >>> 0, 499 | result = [], 500 | value, 501 | thisp = arguments[1]; 502 | 503 | // If no callback function or if callback is not a callable function 504 | if (!isFunction(fun)) { 505 | throw new TypeError(fun + ' is not a function'); 506 | } 507 | 508 | for (var i = 0; i < length; i++) { 509 | if (i in self) { 510 | value = self[i]; 511 | if (fun.call(thisp, value, i, object)) { 512 | result.push(value); 513 | } 514 | } 515 | } 516 | return result; 517 | } 518 | }, !properlyBoxesContext(ArrayPrototype.filter)); 519 | 520 | // ES5 15.4.4.16 521 | // http://es5.github.com/#x15.4.4.16 522 | // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/every 523 | defineProperties(ArrayPrototype, { 524 | every: function every(fun /*, thisp */) { 525 | var object = toObject(this), 526 | self = splitString && isString(this) ? this.split('') : object, 527 | length = self.length >>> 0, 528 | thisp = arguments[1]; 529 | 530 | // If no callback function or if callback is not a callable function 531 | if (!isFunction(fun)) { 532 | throw new TypeError(fun + ' is not a function'); 533 | } 534 | 535 | for (var i = 0; i < length; i++) { 536 | if (i in self && !fun.call(thisp, self[i], i, object)) { 537 | return false; 538 | } 539 | } 540 | return true; 541 | } 542 | }, !properlyBoxesContext(ArrayPrototype.every)); 543 | 544 | // ES5 15.4.4.17 545 | // http://es5.github.com/#x15.4.4.17 546 | // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some 547 | defineProperties(ArrayPrototype, { 548 | some: function some(fun /*, thisp */) { 549 | var object = toObject(this), 550 | self = splitString && isString(this) ? this.split('') : object, 551 | length = self.length >>> 0, 552 | thisp = arguments[1]; 553 | 554 | // If no callback function or if callback is not a callable function 555 | if (!isFunction(fun)) { 556 | throw new TypeError(fun + ' is not a function'); 557 | } 558 | 559 | for (var i = 0; i < length; i++) { 560 | if (i in self && fun.call(thisp, self[i], i, object)) { 561 | return true; 562 | } 563 | } 564 | return false; 565 | } 566 | }, !properlyBoxesContext(ArrayPrototype.some)); 567 | 568 | // ES5 15.4.4.21 569 | // http://es5.github.com/#x15.4.4.21 570 | // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce 571 | var reduceCoercesToObject = false; 572 | if (ArrayPrototype.reduce) { 573 | reduceCoercesToObject = typeof ArrayPrototype.reduce.call('es5', function (_, __, ___, list) { return list; }) === 'object'; 574 | } 575 | defineProperties(ArrayPrototype, { 576 | reduce: function reduce(fun /*, initial*/) { 577 | var object = toObject(this), 578 | self = splitString && isString(this) ? this.split('') : object, 579 | length = self.length >>> 0; 580 | 581 | // If no callback function or if callback is not a callable function 582 | if (!isFunction(fun)) { 583 | throw new TypeError(fun + ' is not a function'); 584 | } 585 | 586 | // no value to return if no initial value and an empty array 587 | if (!length && arguments.length === 1) { 588 | throw new TypeError('reduce of empty array with no initial value'); 589 | } 590 | 591 | var i = 0; 592 | var result; 593 | if (arguments.length >= 2) { 594 | result = arguments[1]; 595 | } else { 596 | do { 597 | if (i in self) { 598 | result = self[i++]; 599 | break; 600 | } 601 | 602 | // if array contains no values, no initial value to return 603 | if (++i >= length) { 604 | throw new TypeError('reduce of empty array with no initial value'); 605 | } 606 | } while (true); 607 | } 608 | 609 | for (; i < length; i++) { 610 | if (i in self) { 611 | result = fun.call(void 0, result, self[i], i, object); 612 | } 613 | } 614 | 615 | return result; 616 | } 617 | }, !reduceCoercesToObject); 618 | 619 | // ES5 15.4.4.22 620 | // http://es5.github.com/#x15.4.4.22 621 | // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduceRight 622 | var reduceRightCoercesToObject = false; 623 | if (ArrayPrototype.reduceRight) { 624 | reduceRightCoercesToObject = typeof ArrayPrototype.reduceRight.call('es5', function (_, __, ___, list) { return list; }) === 'object'; 625 | } 626 | defineProperties(ArrayPrototype, { 627 | reduceRight: function reduceRight(fun /*, initial*/) { 628 | var object = toObject(this), 629 | self = splitString && isString(this) ? this.split('') : object, 630 | length = self.length >>> 0; 631 | 632 | // If no callback function or if callback is not a callable function 633 | if (!isFunction(fun)) { 634 | throw new TypeError(fun + ' is not a function'); 635 | } 636 | 637 | // no value to return if no initial value, empty array 638 | if (!length && arguments.length === 1) { 639 | throw new TypeError('reduceRight of empty array with no initial value'); 640 | } 641 | 642 | var result, i = length - 1; 643 | if (arguments.length >= 2) { 644 | result = arguments[1]; 645 | } else { 646 | do { 647 | if (i in self) { 648 | result = self[i--]; 649 | break; 650 | } 651 | 652 | // if array contains no values, no initial value to return 653 | if (--i < 0) { 654 | throw new TypeError('reduceRight of empty array with no initial value'); 655 | } 656 | } while (true); 657 | } 658 | 659 | if (i < 0) { 660 | return result; 661 | } 662 | 663 | do { 664 | if (i in self) { 665 | result = fun.call(void 0, result, self[i], i, object); 666 | } 667 | } while (i--); 668 | 669 | return result; 670 | } 671 | }, !reduceRightCoercesToObject); 672 | 673 | // ES5 15.4.4.14 674 | // http://es5.github.com/#x15.4.4.14 675 | // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf 676 | var hasFirefox2IndexOfBug = Array.prototype.indexOf && [0, 1].indexOf(1, 2) !== -1; 677 | defineProperties(ArrayPrototype, { 678 | indexOf: function indexOf(sought /*, fromIndex */ ) { 679 | var self = splitString && isString(this) ? this.split('') : toObject(this), 680 | length = self.length >>> 0; 681 | 682 | if (!length) { 683 | return -1; 684 | } 685 | 686 | var i = 0; 687 | if (arguments.length > 1) { 688 | i = toInteger(arguments[1]); 689 | } 690 | 691 | // handle negative indices 692 | i = i >= 0 ? i : Math.max(0, length + i); 693 | for (; i < length; i++) { 694 | if (i in self && self[i] === sought) { 695 | return i; 696 | } 697 | } 698 | return -1; 699 | } 700 | }, hasFirefox2IndexOfBug); 701 | 702 | // ES5 15.4.4.15 703 | // http://es5.github.com/#x15.4.4.15 704 | // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/lastIndexOf 705 | var hasFirefox2LastIndexOfBug = Array.prototype.lastIndexOf && [0, 1].lastIndexOf(0, -3) !== -1; 706 | defineProperties(ArrayPrototype, { 707 | lastIndexOf: function lastIndexOf(sought /*, fromIndex */) { 708 | var self = splitString && isString(this) ? this.split('') : toObject(this), 709 | length = self.length >>> 0; 710 | 711 | if (!length) { 712 | return -1; 713 | } 714 | var i = length - 1; 715 | if (arguments.length > 1) { 716 | i = Math.min(i, toInteger(arguments[1])); 717 | } 718 | // handle negative indices 719 | i = i >= 0 ? i : length - Math.abs(i); 720 | for (; i >= 0; i--) { 721 | if (i in self && sought === self[i]) { 722 | return i; 723 | } 724 | } 725 | return -1; 726 | } 727 | }, hasFirefox2LastIndexOfBug); 728 | 729 | // 730 | // Object 731 | // ====== 732 | // 733 | 734 | // ES5 15.2.3.14 735 | // http://es5.github.com/#x15.2.3.14 736 | 737 | // http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation 738 | var hasDontEnumBug = !({'toString': null}).propertyIsEnumerable('toString'), 739 | hasProtoEnumBug = (function () {}).propertyIsEnumerable('prototype'), 740 | dontEnums = [ 741 | 'toString', 742 | 'toLocaleString', 743 | 'valueOf', 744 | 'hasOwnProperty', 745 | 'isPrototypeOf', 746 | 'propertyIsEnumerable', 747 | 'constructor' 748 | ], 749 | dontEnumsLength = dontEnums.length; 750 | 751 | defineProperties(Object, { 752 | keys: function keys(object) { 753 | var isFn = isFunction(object), 754 | isArgs = isArguments(object), 755 | isObject = object !== null && typeof object === 'object', 756 | isStr = isObject && isString(object); 757 | 758 | if (!isObject && !isFn && !isArgs) { 759 | throw new TypeError('Object.keys called on a non-object'); 760 | } 761 | 762 | var theKeys = []; 763 | var skipProto = hasProtoEnumBug && isFn; 764 | if (isStr || isArgs) { 765 | for (var i = 0; i < object.length; ++i) { 766 | theKeys.push(String(i)); 767 | } 768 | } else { 769 | for (var name in object) { 770 | if (!(skipProto && name === 'prototype') && owns(object, name)) { 771 | theKeys.push(String(name)); 772 | } 773 | } 774 | } 775 | 776 | if (hasDontEnumBug) { 777 | var ctor = object.constructor, 778 | skipConstructor = ctor && ctor.prototype === object; 779 | for (var j = 0; j < dontEnumsLength; j++) { 780 | var dontEnum = dontEnums[j]; 781 | if (!(skipConstructor && dontEnum === 'constructor') && owns(object, dontEnum)) { 782 | theKeys.push(dontEnum); 783 | } 784 | } 785 | } 786 | return theKeys; 787 | } 788 | }); 789 | 790 | var keysWorksWithArguments = Object.keys && (function () { 791 | // Safari 5.0 bug 792 | return Object.keys(arguments).length === 2; 793 | }(1, 2)); 794 | var originalKeys = Object.keys; 795 | defineProperties(Object, { 796 | keys: function keys(object) { 797 | if (isArguments(object)) { 798 | return originalKeys(ArrayPrototype.slice.call(object)); 799 | } else { 800 | return originalKeys(object); 801 | } 802 | } 803 | }, !keysWorksWithArguments); 804 | 805 | // 806 | // Date 807 | // ==== 808 | // 809 | 810 | // ES5 15.9.5.43 811 | // http://es5.github.com/#x15.9.5.43 812 | // This function returns a String value represent the instance in time 813 | // represented by this Date object. The format of the String is the Date Time 814 | // string format defined in 15.9.1.15. All fields are present in the String. 815 | // The time zone is always UTC, denoted by the suffix Z. If the time value of 816 | // this object is not a finite Number a RangeError exception is thrown. 817 | var negativeDate = -62198755200000; 818 | var negativeYearString = '-000001'; 819 | var hasNegativeDateBug = Date.prototype.toISOString && new Date(negativeDate).toISOString().indexOf(negativeYearString) === -1; 820 | 821 | defineProperties(Date.prototype, { 822 | toISOString: function toISOString() { 823 | var result, length, value, year, month; 824 | if (!isFinite(this)) { 825 | throw new RangeError('Date.prototype.toISOString called on non-finite value.'); 826 | } 827 | 828 | year = this.getUTCFullYear(); 829 | 830 | month = this.getUTCMonth(); 831 | // see https://github.com/es-shims/es5-shim/issues/111 832 | year += Math.floor(month / 12); 833 | month = (month % 12 + 12) % 12; 834 | 835 | // the date time string format is specified in 15.9.1.15. 836 | result = [month + 1, this.getUTCDate(), this.getUTCHours(), this.getUTCMinutes(), this.getUTCSeconds()]; 837 | year = ( 838 | (year < 0 ? '-' : (year > 9999 ? '+' : '')) + 839 | ('00000' + Math.abs(year)).slice(0 <= year && year <= 9999 ? -4 : -6) 840 | ); 841 | 842 | length = result.length; 843 | while (length--) { 844 | value = result[length]; 845 | // pad months, days, hours, minutes, and seconds to have two 846 | // digits. 847 | if (value < 10) { 848 | result[length] = '0' + value; 849 | } 850 | } 851 | // pad milliseconds to have three digits. 852 | return ( 853 | year + '-' + result.slice(0, 2).join('-') + 854 | 'T' + result.slice(2).join(':') + '.' + 855 | ('000' + this.getUTCMilliseconds()).slice(-3) + 'Z' 856 | ); 857 | } 858 | }, hasNegativeDateBug); 859 | 860 | 861 | // ES5 15.9.5.44 862 | // http://es5.github.com/#x15.9.5.44 863 | // This function provides a String representation of a Date object for use by 864 | // JSON.stringify (15.12.3). 865 | var dateToJSONIsSupported = false; 866 | try { 867 | dateToJSONIsSupported = ( 868 | Date.prototype.toJSON && 869 | new Date(NaN).toJSON() === null && 870 | new Date(negativeDate).toJSON().indexOf(negativeYearString) !== -1 && 871 | Date.prototype.toJSON.call({ // generic 872 | toISOString: function () { 873 | return true; 874 | } 875 | }) 876 | ); 877 | } catch (e) { 878 | } 879 | if (!dateToJSONIsSupported) { 880 | Date.prototype.toJSON = function toJSON(key) { 881 | // When the toJSON method is called with argument key, the following 882 | // steps are taken: 883 | 884 | // 1. Let O be the result of calling ToObject, giving it the this 885 | // value as its argument. 886 | // 2. Let tv be toPrimitive(O, hint Number). 887 | var o = Object(this), 888 | tv = toPrimitive(o), 889 | toISO; 890 | // 3. If tv is a Number and is not finite, return null. 891 | if (typeof tv === 'number' && !isFinite(tv)) { 892 | return null; 893 | } 894 | // 4. Let toISO be the result of calling the [[Get]] internal method of 895 | // O with argument "toISOString". 896 | toISO = o.toISOString; 897 | // 5. If IsCallable(toISO) is false, throw a TypeError exception. 898 | if (typeof toISO !== 'function') { 899 | throw new TypeError('toISOString property is not callable'); 900 | } 901 | // 6. Return the result of calling the [[Call]] internal method of 902 | // toISO with O as the this value and an empty argument list. 903 | return toISO.call(o); 904 | 905 | // NOTE 1 The argument is ignored. 906 | 907 | // NOTE 2 The toJSON function is intentionally generic; it does not 908 | // require that its this value be a Date object. Therefore, it can be 909 | // transferred to other kinds of objects for use as a method. However, 910 | // it does require that any such object have a toISOString method. An 911 | // object is free to use the argument key to filter its 912 | // stringification. 913 | }; 914 | } 915 | 916 | // ES5 15.9.4.2 917 | // http://es5.github.com/#x15.9.4.2 918 | // based on work shared by Daniel Friesen (dantman) 919 | // http://gist.github.com/303249 920 | var supportsExtendedYears = Date.parse('+033658-09-27T01:46:40.000Z') === 1e15; 921 | var acceptsInvalidDates = !isNaN(Date.parse('2012-04-04T24:00:00.500Z')) || !isNaN(Date.parse('2012-11-31T23:59:59.000Z')); 922 | var doesNotParseY2KNewYear = isNaN(Date.parse('2000-01-01T00:00:00.000Z')); 923 | if (!Date.parse || doesNotParseY2KNewYear || acceptsInvalidDates || !supportsExtendedYears) { 924 | // XXX global assignment won't work in embeddings that use 925 | // an alternate object for the context. 926 | Date = (function (NativeDate) { 927 | 928 | // Date.length === 7 929 | function Date(Y, M, D, h, m, s, ms) { 930 | var length = arguments.length; 931 | if (this instanceof NativeDate) { 932 | var date = length === 1 && String(Y) === Y ? // isString(Y) 933 | // We explicitly pass it through parse: 934 | new NativeDate(Date.parse(Y)) : 935 | // We have to manually make calls depending on argument 936 | // length here 937 | length >= 7 ? new NativeDate(Y, M, D, h, m, s, ms) : 938 | length >= 6 ? new NativeDate(Y, M, D, h, m, s) : 939 | length >= 5 ? new NativeDate(Y, M, D, h, m) : 940 | length >= 4 ? new NativeDate(Y, M, D, h) : 941 | length >= 3 ? new NativeDate(Y, M, D) : 942 | length >= 2 ? new NativeDate(Y, M) : 943 | length >= 1 ? new NativeDate(Y) : 944 | new NativeDate(); 945 | // Prevent mixups with unfixed Date object 946 | date.constructor = Date; 947 | return date; 948 | } 949 | return NativeDate.apply(this, arguments); 950 | } 951 | 952 | // 15.9.1.15 Date Time String Format. 953 | var isoDateExpression = new RegExp('^' + 954 | '(\\d{4}|[\+\-]\\d{6})' + // four-digit year capture or sign + 955 | // 6-digit extended year 956 | '(?:-(\\d{2})' + // optional month capture 957 | '(?:-(\\d{2})' + // optional day capture 958 | '(?:' + // capture hours:minutes:seconds.milliseconds 959 | 'T(\\d{2})' + // hours capture 960 | ':(\\d{2})' + // minutes capture 961 | '(?:' + // optional :seconds.milliseconds 962 | ':(\\d{2})' + // seconds capture 963 | '(?:(\\.\\d{1,}))?' + // milliseconds capture 964 | ')?' + 965 | '(' + // capture UTC offset component 966 | 'Z|' + // UTC capture 967 | '(?:' + // offset specifier +/-hours:minutes 968 | '([-+])' + // sign capture 969 | '(\\d{2})' + // hours offset capture 970 | ':(\\d{2})' + // minutes offset capture 971 | ')' + 972 | ')?)?)?)?' + 973 | '$'); 974 | 975 | var months = [ 976 | 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 977 | ]; 978 | 979 | function dayFromMonth(year, month) { 980 | var t = month > 1 ? 1 : 0; 981 | return ( 982 | months[month] + 983 | Math.floor((year - 1969 + t) / 4) - 984 | Math.floor((year - 1901 + t) / 100) + 985 | Math.floor((year - 1601 + t) / 400) + 986 | 365 * (year - 1970) 987 | ); 988 | } 989 | 990 | function toUTC(t) { 991 | return Number(new NativeDate(1970, 0, 1, 0, 0, 0, t)); 992 | } 993 | 994 | // Copy any custom methods a 3rd party library may have added 995 | for (var key in NativeDate) { 996 | Date[key] = NativeDate[key]; 997 | } 998 | 999 | // Copy "native" methods explicitly; they may be non-enumerable 1000 | Date.now = NativeDate.now; 1001 | Date.UTC = NativeDate.UTC; 1002 | Date.prototype = NativeDate.prototype; 1003 | Date.prototype.constructor = Date; 1004 | 1005 | // Upgrade Date.parse to handle simplified ISO 8601 strings 1006 | Date.parse = function parse(string) { 1007 | var match = isoDateExpression.exec(string); 1008 | if (match) { 1009 | // parse months, days, hours, minutes, seconds, and milliseconds 1010 | // provide default values if necessary 1011 | // parse the UTC offset component 1012 | var year = Number(match[1]), 1013 | month = Number(match[2] || 1) - 1, 1014 | day = Number(match[3] || 1) - 1, 1015 | hour = Number(match[4] || 0), 1016 | minute = Number(match[5] || 0), 1017 | second = Number(match[6] || 0), 1018 | millisecond = Math.floor(Number(match[7] || 0) * 1000), 1019 | // When time zone is missed, local offset should be used 1020 | // (ES 5.1 bug) 1021 | // see https://bugs.ecmascript.org/show_bug.cgi?id=112 1022 | isLocalTime = Boolean(match[4] && !match[8]), 1023 | signOffset = match[9] === '-' ? 1 : -1, 1024 | hourOffset = Number(match[10] || 0), 1025 | minuteOffset = Number(match[11] || 0), 1026 | result; 1027 | if ( 1028 | hour < ( 1029 | minute > 0 || second > 0 || millisecond > 0 ? 1030 | 24 : 25 1031 | ) && 1032 | minute < 60 && second < 60 && millisecond < 1000 && 1033 | month > -1 && month < 12 && hourOffset < 24 && 1034 | minuteOffset < 60 && // detect invalid offsets 1035 | day > -1 && 1036 | day < ( 1037 | dayFromMonth(year, month + 1) - 1038 | dayFromMonth(year, month) 1039 | ) 1040 | ) { 1041 | result = ( 1042 | (dayFromMonth(year, month) + day) * 24 + 1043 | hour + 1044 | hourOffset * signOffset 1045 | ) * 60; 1046 | result = ( 1047 | (result + minute + minuteOffset * signOffset) * 60 + 1048 | second 1049 | ) * 1000 + millisecond; 1050 | if (isLocalTime) { 1051 | result = toUTC(result); 1052 | } 1053 | if (-8.64e15 <= result && result <= 8.64e15) { 1054 | return result; 1055 | } 1056 | } 1057 | return NaN; 1058 | } 1059 | return NativeDate.parse.apply(this, arguments); 1060 | }; 1061 | 1062 | return Date; 1063 | })(Date); 1064 | } 1065 | 1066 | // ES5 15.9.4.4 1067 | // http://es5.github.com/#x15.9.4.4 1068 | if (!Date.now) { 1069 | Date.now = function now() { 1070 | return new Date().getTime(); 1071 | }; 1072 | } 1073 | 1074 | 1075 | // 1076 | // Number 1077 | // ====== 1078 | // 1079 | 1080 | // ES5.1 15.7.4.5 1081 | // http://es5.github.com/#x15.7.4.5 1082 | var hasToFixedBugs = NumberPrototype.toFixed && ( 1083 | (0.00008).toFixed(3) !== '0.000' 1084 | || (0.9).toFixed(0) !== '1' 1085 | || (1.255).toFixed(2) !== '1.25' 1086 | || (1000000000000000128).toFixed(0) !== '1000000000000000128' 1087 | ); 1088 | 1089 | var toFixedHelpers = { 1090 | base: 1e7, 1091 | size: 6, 1092 | data: [0, 0, 0, 0, 0, 0], 1093 | multiply: function multiply(n, c) { 1094 | var i = -1; 1095 | while (++i < toFixedHelpers.size) { 1096 | c += n * toFixedHelpers.data[i]; 1097 | toFixedHelpers.data[i] = c % toFixedHelpers.base; 1098 | c = Math.floor(c / toFixedHelpers.base); 1099 | } 1100 | }, 1101 | divide: function divide(n) { 1102 | var i = toFixedHelpers.size, c = 0; 1103 | while (--i >= 0) { 1104 | c += toFixedHelpers.data[i]; 1105 | toFixedHelpers.data[i] = Math.floor(c / n); 1106 | c = (c % n) * toFixedHelpers.base; 1107 | } 1108 | }, 1109 | numToString: function numToString() { 1110 | var i = toFixedHelpers.size; 1111 | var s = ''; 1112 | while (--i >= 0) { 1113 | if (s !== '' || i === 0 || toFixedHelpers.data[i] !== 0) { 1114 | var t = String(toFixedHelpers.data[i]); 1115 | if (s === '') { 1116 | s = t; 1117 | } else { 1118 | s += '0000000'.slice(0, 7 - t.length) + t; 1119 | } 1120 | } 1121 | } 1122 | return s; 1123 | }, 1124 | pow: function pow(x, n, acc) { 1125 | return (n === 0 ? acc : (n % 2 === 1 ? pow(x, n - 1, acc * x) : pow(x * x, n / 2, acc))); 1126 | }, 1127 | log: function log(x) { 1128 | var n = 0; 1129 | while (x >= 4096) { 1130 | n += 12; 1131 | x /= 4096; 1132 | } 1133 | while (x >= 2) { 1134 | n += 1; 1135 | x /= 2; 1136 | } 1137 | return n; 1138 | } 1139 | }; 1140 | 1141 | defineProperties(NumberPrototype, { 1142 | toFixed: function toFixed(fractionDigits) { 1143 | var f, x, s, m, e, z, j, k; 1144 | 1145 | // Test for NaN and round fractionDigits down 1146 | f = Number(fractionDigits); 1147 | f = f !== f ? 0 : Math.floor(f); 1148 | 1149 | if (f < 0 || f > 20) { 1150 | throw new RangeError('Number.toFixed called with invalid number of decimals'); 1151 | } 1152 | 1153 | x = Number(this); 1154 | 1155 | // Test for NaN 1156 | if (x !== x) { 1157 | return 'NaN'; 1158 | } 1159 | 1160 | // If it is too big or small, return the string value of the number 1161 | if (x <= -1e21 || x >= 1e21) { 1162 | return String(x); 1163 | } 1164 | 1165 | s = ''; 1166 | 1167 | if (x < 0) { 1168 | s = '-'; 1169 | x = -x; 1170 | } 1171 | 1172 | m = '0'; 1173 | 1174 | if (x > 1e-21) { 1175 | // 1e-21 < x < 1e21 1176 | // -70 < log2(x) < 70 1177 | e = toFixedHelpers.log(x * toFixedHelpers.pow(2, 69, 1)) - 69; 1178 | z = (e < 0 ? x * toFixedHelpers.pow(2, -e, 1) : x / toFixedHelpers.pow(2, e, 1)); 1179 | z *= 0x10000000000000; // Math.pow(2, 52); 1180 | e = 52 - e; 1181 | 1182 | // -18 < e < 122 1183 | // x = z / 2 ^ e 1184 | if (e > 0) { 1185 | toFixedHelpers.multiply(0, z); 1186 | j = f; 1187 | 1188 | while (j >= 7) { 1189 | toFixedHelpers.multiply(1e7, 0); 1190 | j -= 7; 1191 | } 1192 | 1193 | toFixedHelpers.multiply(toFixedHelpers.pow(10, j, 1), 0); 1194 | j = e - 1; 1195 | 1196 | while (j >= 23) { 1197 | toFixedHelpers.divide(1 << 23); 1198 | j -= 23; 1199 | } 1200 | 1201 | toFixedHelpers.divide(1 << j); 1202 | toFixedHelpers.multiply(1, 1); 1203 | toFixedHelpers.divide(2); 1204 | m = toFixedHelpers.numToString(); 1205 | } else { 1206 | toFixedHelpers.multiply(0, z); 1207 | toFixedHelpers.multiply(1 << (-e), 0); 1208 | m = toFixedHelpers.numToString() + '0.00000000000000000000'.slice(2, 2 + f); 1209 | } 1210 | } 1211 | 1212 | if (f > 0) { 1213 | k = m.length; 1214 | 1215 | if (k <= f) { 1216 | m = s + '0.0000000000000000000'.slice(0, f - k + 2) + m; 1217 | } else { 1218 | m = s + m.slice(0, k - f) + '.' + m.slice(k - f); 1219 | } 1220 | } else { 1221 | m = s + m; 1222 | } 1223 | 1224 | return m; 1225 | } 1226 | }, hasToFixedBugs); 1227 | 1228 | 1229 | // 1230 | // String 1231 | // ====== 1232 | // 1233 | 1234 | // ES5 15.5.4.14 1235 | // http://es5.github.com/#x15.5.4.14 1236 | 1237 | // [bugfix, IE lt 9, firefox 4, Konqueror, Opera, obscure browsers] 1238 | // Many browsers do not split properly with regular expressions or they 1239 | // do not perform the split correctly under obscure conditions. 1240 | // See http://blog.stevenlevithan.com/archives/cross-browser-split 1241 | // I've tested in many browsers and this seems to cover the deviant ones: 1242 | // 'ab'.split(/(?:ab)*/) should be ["", ""], not [""] 1243 | // '.'.split(/(.?)(.?)/) should be ["", ".", "", ""], not ["", ""] 1244 | // 'tesst'.split(/(s)*/) should be ["t", undefined, "e", "s", "t"], not 1245 | // [undefined, "t", undefined, "e", ...] 1246 | // ''.split(/.?/) should be [], not [""] 1247 | // '.'.split(/()()/) should be ["."], not ["", "", "."] 1248 | 1249 | var string_split = StringPrototype.split; 1250 | if ( 1251 | 'ab'.split(/(?:ab)*/).length !== 2 || 1252 | '.'.split(/(.?)(.?)/).length !== 4 || 1253 | 'tesst'.split(/(s)*/)[1] === 't' || 1254 | 'test'.split(/(?:)/, -1).length !== 4 || 1255 | ''.split(/.?/).length || 1256 | '.'.split(/()()/).length > 1 1257 | ) { 1258 | (function () { 1259 | var compliantExecNpcg = /()??/.exec('')[1] === void 0; // NPCG: nonparticipating capturing group 1260 | 1261 | StringPrototype.split = function (separator, limit) { 1262 | var string = this; 1263 | if (separator === void 0 && limit === 0) { 1264 | return []; 1265 | } 1266 | 1267 | // If `separator` is not a regex, use native split 1268 | if (_toString.call(separator) !== '[object RegExp]') { 1269 | return string_split.call(this, separator, limit); 1270 | } 1271 | 1272 | var output = [], 1273 | flags = (separator.ignoreCase ? 'i' : '') + 1274 | (separator.multiline ? 'm' : '') + 1275 | (separator.extended ? 'x' : '') + // Proposed for ES6 1276 | (separator.sticky ? 'y' : ''), // Firefox 3+ 1277 | lastLastIndex = 0, 1278 | // Make `global` and avoid `lastIndex` issues by working with a copy 1279 | separator2, match, lastIndex, lastLength; 1280 | separator = new RegExp(separator.source, flags + 'g'); 1281 | string += ''; // Type-convert 1282 | if (!compliantExecNpcg) { 1283 | // Doesn't need flags gy, but they don't hurt 1284 | separator2 = new RegExp('^' + separator.source + '$(?!\\s)', flags); 1285 | } 1286 | /* Values for `limit`, per the spec: 1287 | * If undefined: 4294967295 // Math.pow(2, 32) - 1 1288 | * If 0, Infinity, or NaN: 0 1289 | * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296; 1290 | * If negative number: 4294967296 - Math.floor(Math.abs(limit)) 1291 | * If other: Type-convert, then use the above rules 1292 | */ 1293 | limit = limit === void 0 ? 1294 | -1 >>> 0 : // Math.pow(2, 32) - 1 1295 | ToUint32(limit); 1296 | while (match = separator.exec(string)) { 1297 | // `separator.lastIndex` is not reliable cross-browser 1298 | lastIndex = match.index + match[0].length; 1299 | if (lastIndex > lastLastIndex) { 1300 | output.push(string.slice(lastLastIndex, match.index)); 1301 | // Fix browsers whose `exec` methods don't consistently return `undefined` for 1302 | // nonparticipating capturing groups 1303 | if (!compliantExecNpcg && match.length > 1) { 1304 | match[0].replace(separator2, function () { 1305 | for (var i = 1; i < arguments.length - 2; i++) { 1306 | if (arguments[i] === void 0) { 1307 | match[i] = void 0; 1308 | } 1309 | } 1310 | }); 1311 | } 1312 | if (match.length > 1 && match.index < string.length) { 1313 | ArrayPrototype.push.apply(output, match.slice(1)); 1314 | } 1315 | lastLength = match[0].length; 1316 | lastLastIndex = lastIndex; 1317 | if (output.length >= limit) { 1318 | break; 1319 | } 1320 | } 1321 | if (separator.lastIndex === match.index) { 1322 | separator.lastIndex++; // Avoid an infinite loop 1323 | } 1324 | } 1325 | if (lastLastIndex === string.length) { 1326 | if (lastLength || !separator.test('')) { 1327 | output.push(''); 1328 | } 1329 | } else { 1330 | output.push(string.slice(lastLastIndex)); 1331 | } 1332 | return output.length > limit ? output.slice(0, limit) : output; 1333 | }; 1334 | }()); 1335 | 1336 | // [bugfix, chrome] 1337 | // If separator is undefined, then the result array contains just one String, 1338 | // which is the this value (converted to a String). If limit is not undefined, 1339 | // then the output array is truncated so that it contains no more than limit 1340 | // elements. 1341 | // "0".split(undefined, 0) -> [] 1342 | } else if ('0'.split(void 0, 0).length) { 1343 | StringPrototype.split = function split(separator, limit) { 1344 | if (separator === void 0 && limit === 0) { return []; } 1345 | return string_split.call(this, separator, limit); 1346 | }; 1347 | } 1348 | 1349 | var str_replace = StringPrototype.replace; 1350 | var replaceReportsGroupsCorrectly = (function () { 1351 | var groups = []; 1352 | 'x'.replace(/x(.)?/g, function (match, group) { 1353 | groups.push(group); 1354 | }); 1355 | return groups.length === 1 && typeof groups[0] === 'undefined'; 1356 | }()); 1357 | 1358 | if (!replaceReportsGroupsCorrectly) { 1359 | StringPrototype.replace = function replace(searchValue, replaceValue) { 1360 | var isFn = isFunction(replaceValue); 1361 | var hasCapturingGroups = isRegex(searchValue) && (/\)[*?]/).test(searchValue.source); 1362 | if (!isFn || !hasCapturingGroups) { 1363 | return str_replace.call(this, searchValue, replaceValue); 1364 | } else { 1365 | var wrappedReplaceValue = function (match) { 1366 | var length = arguments.length; 1367 | var originalLastIndex = searchValue.lastIndex; 1368 | searchValue.lastIndex = 0; 1369 | var args = searchValue.exec(match) || []; 1370 | searchValue.lastIndex = originalLastIndex; 1371 | args.push(arguments[length - 2], arguments[length - 1]); 1372 | return replaceValue.apply(this, args); 1373 | }; 1374 | return str_replace.call(this, searchValue, wrappedReplaceValue); 1375 | } 1376 | }; 1377 | } 1378 | 1379 | // ECMA-262, 3rd B.2.3 1380 | // Not an ECMAScript standard, although ECMAScript 3rd Edition has a 1381 | // non-normative section suggesting uniform semantics and it should be 1382 | // normalized across all browsers 1383 | // [bugfix, IE lt 9] IE < 9 substr() with negative value not working in IE 1384 | var string_substr = StringPrototype.substr; 1385 | var hasNegativeSubstrBug = ''.substr && '0b'.substr(-1) !== 'b'; 1386 | defineProperties(StringPrototype, { 1387 | substr: function substr(start, length) { 1388 | return string_substr.call( 1389 | this, 1390 | start < 0 ? ((start = this.length + start) < 0 ? 0 : start) : start, 1391 | length 1392 | ); 1393 | } 1394 | }, hasNegativeSubstrBug); 1395 | 1396 | // ES5 15.5.4.20 1397 | // whitespace from: http://es5.github.io/#x15.5.4.20 1398 | var ws = '\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003' + 1399 | '\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028' + 1400 | '\u2029\uFEFF'; 1401 | var zeroWidth = '\u200b'; 1402 | var wsRegexChars = '[' + ws + ']'; 1403 | var trimBeginRegexp = new RegExp('^' + wsRegexChars + wsRegexChars + '*'); 1404 | var trimEndRegexp = new RegExp(wsRegexChars + wsRegexChars + '*$'); 1405 | var hasTrimWhitespaceBug = StringPrototype.trim && (ws.trim() || !zeroWidth.trim()); 1406 | defineProperties(StringPrototype, { 1407 | // http://blog.stevenlevithan.com/archives/faster-trim-javascript 1408 | // http://perfectionkills.com/whitespace-deviations/ 1409 | trim: function trim() { 1410 | if (this === void 0 || this === null) { 1411 | throw new TypeError("can't convert " + this + ' to object'); 1412 | } 1413 | return String(this).replace(trimBeginRegexp, '').replace(trimEndRegexp, ''); 1414 | } 1415 | }, hasTrimWhitespaceBug); 1416 | 1417 | // ES-5 15.1.2.2 1418 | if (parseInt(ws + '08') !== 8 || parseInt(ws + '0x16') !== 22) { 1419 | parseInt = (function (origParseInt) { 1420 | var hexRegex = /^0[xX]/; 1421 | return function parseIntES5(str, radix) { 1422 | str = String(str).trim(); 1423 | if (!Number(radix)) { 1424 | radix = hexRegex.test(str) ? 16 : 10; 1425 | } 1426 | return origParseInt(str, radix); 1427 | }; 1428 | }(parseInt)); 1429 | } 1430 | 1431 | })); 1432 | -------------------------------------------------------------------------------- /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 0 12 | }) 13 | } 14 | return el 15 | } 16 | 17 | el.off = function(events, fn) { 18 | if (events == '*') callbacks = {} 19 | else if (fn) { 20 | var arr = callbacks[events] 21 | for (var i = 0, cb; (cb = arr && arr[i]); ++i) { 22 | if (cb == fn) { arr.splice(i, 1); i-- } 23 | } 24 | } else { 25 | events.replace(/\S+/g, function(name) { 26 | callbacks[name] = [] 27 | }) 28 | } 29 | return el 30 | } 31 | 32 | // only single event supported 33 | el.one = function(name, fn) { 34 | if (fn) fn.one = 1 35 | return el.on(name, fn) 36 | } 37 | 38 | el.trigger = function(name) { 39 | var args = [].slice.call(arguments, 1), 40 | fns = callbacks[name] || [] 41 | 42 | for (var i = 0, fn; (fn = fns[i]); ++i) { 43 | if (!fn.busy) { 44 | fn.busy = 1 45 | fn.apply(el, fn.typed ? [name].concat(args) : args) 46 | if (fn.one) { fns.splice(i, 1); i-- } 47 | fn.busy = 0 48 | } 49 | } 50 | 51 | return el 52 | } 53 | 54 | return el 55 | 56 | } 57 | ;(function(riot, evt) { 58 | 59 | // browsers only 60 | if (!this.top) return 61 | 62 | var loc = location, 63 | fns = riot.observable({}), 64 | current = hash(), 65 | win = window 66 | 67 | function hash() { 68 | return loc.hash.slice(1) 69 | } 70 | 71 | function emit(path) { 72 | if (path.type) path = hash() 73 | 74 | if (path != current) { 75 | fns.trigger.apply(null, ['H'].concat(path.split('/'))) 76 | current = path 77 | } 78 | } 79 | 80 | var r = riot.route = function(arg) { 81 | // string 82 | if (arg[0]) { 83 | loc.hash = arg 84 | emit(arg) 85 | 86 | // function 87 | } else { 88 | fns.on('H', arg) 89 | } 90 | } 91 | 92 | r.exec = function(fn) { 93 | fn.apply(null, hash().split('/')) 94 | } 95 | 96 | win.addEventListener ? win.addEventListener(evt, emit, false) : win.attachEvent('on' + evt, emit) 97 | 98 | })(riot, 'hashchange') 99 | /* 100 | 101 | //// How it works? 102 | 103 | 104 | Three ways: 105 | 106 | 1. Expressions: tmpl('{ value }', data). 107 | Returns the result of evaluated expression as a raw object. 108 | 109 | 2. Templates: tmpl('Hi { name } { surname }', data). 110 | Returns a string with evaluated expressions. 111 | 112 | 3. Filters: tmpl('{ show: !done, highlight: active }', data). 113 | Returns a space separated list of trueish keys (mainly 114 | used for setting html classes), e.g. "show highlight". 115 | 116 | 117 | // Template examples 118 | 119 | tmpl('{ title || "Untitled" }', data) 120 | tmpl('Results are { results ? "ready" : "loading" }', data) 121 | tmpl('Today is { new Date() }', data) 122 | tmpl('{ message.length > 140 && "Message is too long" }', data) 123 | tmpl('This item got { Math.round(rating) } stars', data) 124 | tmpl('

{ title }

{ body }', data) 125 | 126 | 127 | // Falsy expressions in templates 128 | 129 | In templates (as opposed to single expressions) all falsy values 130 | except zero (undefined/null/false) will default to empty string: 131 | 132 | tmpl('{ undefined } - { false } - { null } - { 0 }', {}) 133 | // will return: " - - - 0" 134 | 135 | */ 136 | 137 | riot._tmpl = (function() { 138 | 139 | var cache = {}, 140 | 141 | // find variable names 142 | re_vars = /("|').+?[^\\]\1|\.\w*|\w*:|\b(?:this|true|false|null|new|typeof|Number|String|Object|Array|Math|Date)\b|([a-z_]\w*)/gi 143 | // [ 1 ][ 2 ][ 3 ][ 4 ][ 5 ] 144 | // 1. skip quoted strings: "a b", 'a b', 'a \'b\'' 145 | // 2. skip object properties: .name 146 | // 3. skip object literals: name: 147 | // 4. skip reserved words 148 | // 5. match var name 149 | 150 | // build a template (or get it from cache), render with data 151 | 152 | return function(str, data) { 153 | return str && (cache[str] = cache[str] || tmpl(str))(data) 154 | } 155 | 156 | 157 | // create a template instance 158 | 159 | function tmpl(s, p) { 160 | p = (s || '{}') 161 | 162 | // temporarily convert \{ and \} to a non-character 163 | .replace(/\\{/g, '\uFFF0') 164 | .replace(/\\}/g, '\uFFF1') 165 | 166 | // split string to expression and non-expresion parts 167 | .split(/({[\s\S]*?})/) 168 | 169 | return new Function('d', 'return ' + ( 170 | 171 | // is it a single expression or a template? i.e. {x} or {x} 172 | !p[0] && !p[2] 173 | 174 | // if expression, evaluate it 175 | ? expr(p[1]) 176 | 177 | // if template, evaluate all expressions in it 178 | : '[' + p.map(function(s, i) { 179 | 180 | // is it an expression or a string (every second part is an expression) 181 | return i % 2 182 | 183 | // evaluate the expressions 184 | ? expr(s, 1) 185 | 186 | // process string parts of the template: 187 | : '"' + s 188 | 189 | // preserve new lines 190 | .replace(/\n/g, '\\n') 191 | 192 | // escape quotes 193 | .replace(/"/g, '\\"') 194 | 195 | + '"' 196 | 197 | }).join(',') + '].join("")' 198 | ) 199 | 200 | // bring escaped { and } back 201 | .replace(/\uFFF0/g, '{') 202 | .replace(/\uFFF1/g, '}') 203 | 204 | ) 205 | 206 | } 207 | 208 | 209 | // parse { ... } expression 210 | 211 | function expr(s, n) { 212 | s = s 213 | 214 | // convert new lines to spaces 215 | .replace(/\n/g, ' ') 216 | 217 | // trim whitespace, curly brackets, strip comments 218 | .replace(/^[{ ]+|[ }]+$|\/\*.+?\*\//g, '') 219 | 220 | // is it an object literal? i.e. { key : value } 221 | return /^\s*[\w-"']+ *:/.test(s) 222 | 223 | // if object literal, return trueish keys 224 | // e.g.: { show: isOpen(), done: item.done } -> "show done" 225 | ? '[' + s.replace(/\W*([\w-]+)\W*:([^,]+)/g, function(_, k, v) { 226 | 227 | // safely execute vars to prevent undefined value errors 228 | return v.replace(/\w[^,|& ]*/g, function(v) { return wrap(v, n) }) + '?"' + k + '":"",' 229 | 230 | }) + '].join(" ")' 231 | 232 | // if js expression, evaluate as javascript 233 | : wrap(s, n) 234 | 235 | } 236 | 237 | 238 | // execute js w/o breaking on errors or undefined vars 239 | 240 | function wrap(s, nonull) { 241 | return '(function(v){try{v=' 242 | 243 | // prefix vars (name => data.name) 244 | + (s.replace(re_vars, function(s, _, v) { return v ? 'd.' + v : s }) 245 | 246 | // break the expression if its empty (resulting in undefined value) 247 | || 'x') 248 | 249 | + '}finally{return ' 250 | 251 | // default to empty string for falsy values except zero 252 | + (nonull ? '!v&&v!==0?"":v' : 'v') 253 | 254 | + '}}).call(d)' 255 | } 256 | 257 | })() 258 | ;(function(riot, doc) { 259 | 260 | var tmpl = riot._tmpl, 261 | all_tags = [], 262 | tag_impl = {} 263 | 264 | function each(nodes, fn) { 265 | for (var i = 0; i < (nodes || []).length; i++) { 266 | if (fn(nodes[i], i) === false) i-- 267 | } 268 | } 269 | 270 | function extend(obj, from) { 271 | from && Object.keys(from).map(function(key) { 272 | obj[key] = from[key] 273 | }) 274 | return obj 275 | } 276 | 277 | function diff(arr1, arr2) { 278 | return arr1.filter(function(el) { 279 | return arr2.indexOf(el) < 0 280 | }) 281 | } 282 | 283 | function walk(dom, fn) { 284 | dom = fn(dom) === false ? dom.nextSibling : dom.firstChild 285 | 286 | while (dom) { 287 | walk(dom, fn) 288 | dom = dom.nextSibling 289 | } 290 | } 291 | 292 | 293 | function mkdom(tmpl) { 294 | var el = doc.createElement('div') 295 | el.innerHTML = tmpl 296 | return el 297 | } 298 | 299 | 300 | function update(expressions, instance) { 301 | 302 | // allow recalculation of context data 303 | instance.trigger('update') 304 | 305 | each(expressions, function(expr) { 306 | var tag = expr.tag, 307 | dom = expr.dom 308 | 309 | function remAttr(name) { 310 | dom.removeAttribute(name) 311 | } 312 | 313 | // loops first: TODO remove from expressions arr 314 | if (expr.loop) { 315 | remAttr('each') 316 | return loop(expr, instance) 317 | } 318 | 319 | // custom tag 320 | if (tag) return tag.update ? tag.update() : 321 | expr.tag = createTag({ tmpl: tag[0], fn: tag[1], root: dom, parent: instance }) 322 | 323 | 324 | var attr_name = expr.attr, 325 | value = tmpl(expr.expr, instance) 326 | 327 | if (value == null) value = '' 328 | 329 | // no change 330 | if (expr.value === value) return 331 | expr.value = value 332 | 333 | 334 | // text node 335 | if (!attr_name) return dom.nodeValue = value 336 | 337 | // attribute 338 | if (!value && expr.bool || /obj|func/.test(typeof value)) remAttr(attr_name) 339 | 340 | // event handler 341 | if (typeof value == 'function') { 342 | dom[attr_name] = function(e) { 343 | 344 | // cross browser event fix 345 | e = e || window.event 346 | e.which = e.which || e.charCode || e.keyCode 347 | e.target = e.target || e.srcElement 348 | e.currentTarget = dom 349 | 350 | // currently looped item 351 | e.item = instance.__item || instance 352 | 353 | // prevent default behaviour (by default) 354 | if (value.call(instance, e) !== true) { 355 | e.preventDefault && e.preventDefault() 356 | e.returnValue = false 357 | } 358 | 359 | instance.update() 360 | } 361 | 362 | // show / hide / if 363 | } else if (/^(show|hide|if)$/.test(attr_name)) { 364 | remAttr(attr_name) 365 | if (attr_name == 'hide') value = !value 366 | dom.style.display = value ? '' : 'none' 367 | 368 | // normal attribute 369 | } else { 370 | if (expr.bool) { 371 | if (!value) return 372 | value = attr_name 373 | } 374 | 375 | dom.setAttribute(attr_name, value) 376 | } 377 | 378 | }) 379 | 380 | instance.trigger('updated') 381 | 382 | } 383 | 384 | function parse(root) { 385 | 386 | var named_elements = {}, 387 | expressions = [] 388 | 389 | walk(root, function(dom) { 390 | 391 | var type = dom.nodeType, 392 | value = dom.nodeValue 393 | 394 | function addExpr(value, data) { 395 | if (value ? value.indexOf('{') >= 0 : data) { 396 | var expr = { dom: dom, expr: value } 397 | expressions.push(extend(expr, data || {})) 398 | } 399 | } 400 | 401 | // text node 402 | if (type == 3 && dom.parentNode.tagName != 'STYLE') { 403 | addExpr(value) 404 | 405 | // element 406 | } else if (type == 1) { 407 | 408 | // loop? 409 | value = dom.getAttribute('each') 410 | 411 | if (value) { 412 | addExpr(value, { loop: 1 }) 413 | return false 414 | } 415 | 416 | // custom tag? 417 | var tag = tag_impl[dom.tagName.toLowerCase()] 418 | 419 | // attributes 420 | each(dom.attributes, function(attr) { 421 | var name = attr.name, 422 | value = attr.value 423 | 424 | // named elements 425 | if (/^(name|id)$/.test(name)) named_elements[value] = dom 426 | 427 | // expressions 428 | if (!tag) { 429 | var bool = name.split('__')[1] 430 | addExpr(value, { attr: bool || name, bool: bool }) 431 | if (bool) { 432 | dom.removeAttribute(name) 433 | return false 434 | } 435 | } 436 | 437 | }) 438 | 439 | if (tag) addExpr(0, { tag: tag }) 440 | 441 | } 442 | 443 | }) 444 | 445 | return { expr: expressions, elem: named_elements } 446 | 447 | } 448 | 449 | 450 | 451 | // create new custom tag (component) 452 | function createTag(conf) { 453 | 454 | var opts = conf.opts || {}, 455 | dom = mkdom(conf.tmpl), 456 | mountNode = conf.root, 457 | parent = conf.parent, 458 | ast = parse(dom), 459 | tag = { root: mountNode, opts: opts, parent: parent, __item: conf.item }, 460 | attributes = {} 461 | 462 | // named elements 463 | extend(tag, ast.elem) 464 | 465 | // attributes 466 | each(mountNode.attributes, function(attr) { 467 | attributes[attr.name] = attr.value 468 | }) 469 | 470 | function updateOpts() { 471 | Object.keys(attributes).map(function(name) { 472 | var val = opts[name] = tmpl(attributes[name], parent || tag) 473 | if (typeof val == 'object') mountNode.removeAttribute(name) 474 | }) 475 | } 476 | 477 | updateOpts() 478 | 479 | if (!tag.on) { 480 | riot.observable(tag) 481 | delete tag.off // off method not needed 482 | } 483 | 484 | if (conf.fn) conf.fn.call(tag, opts) 485 | 486 | 487 | tag.update = function(data, _system) { 488 | 489 | /* 490 | If loop is defined on the root of the HTML template 491 | the original parent is a temporary
by mkdom() 492 | */ 493 | if (parent && dom && !dom.firstChild) { 494 | mountNode = parent.root 495 | dom = null 496 | } 497 | 498 | if (_system || doc.body.contains(mountNode)) { 499 | extend(tag, data) 500 | extend(tag, tag.__item) 501 | updateOpts() 502 | update(ast.expr, tag) 503 | 504 | // update parent 505 | !_system && tag.__item && parent.update() 506 | return true 507 | 508 | } else { 509 | tag.trigger('unmount') 510 | } 511 | 512 | } 513 | 514 | tag.update(0, true) 515 | 516 | // append to root 517 | while (dom.firstChild) { 518 | if (conf.before) mountNode.insertBefore(dom.firstChild, conf.before) 519 | else mountNode.appendChild(dom.firstChild) 520 | } 521 | 522 | 523 | tag.trigger('mount') 524 | 525 | all_tags.push(tag) 526 | 527 | return tag 528 | } 529 | 530 | 531 | function loop(expr, instance) { 532 | 533 | // initialize once 534 | if (expr.done) return 535 | expr.done = true 536 | 537 | var dom = expr.dom, 538 | prev = dom.previousSibling, 539 | root = dom.parentNode, 540 | template = dom.outerHTML, 541 | val = expr.expr, 542 | els = val.split(/\s+in\s+/), 543 | rendered = [], 544 | checksum, 545 | root, 546 | keys 547 | 548 | 549 | if (els[1]) { 550 | val = '{ ' + els[1] 551 | keys = els[0].slice(1).trim().split(/,\s*/) 552 | } 553 | 554 | // clean template code 555 | instance.one('mount', function() { 556 | var p = dom.parentNode 557 | if (p) { 558 | root = p 559 | root.removeChild(dom) 560 | } 561 | }) 562 | 563 | function startPos() { 564 | return Array.prototype.indexOf.call(root.childNodes, prev) + 1 565 | } 566 | 567 | instance.on('updated', function() { 568 | 569 | var items = tmpl(val, instance) 570 | is_array = Array.isArray(items) 571 | 572 | if (is_array) items = items.slice(0) 573 | 574 | else { 575 | 576 | if (!items) return // some IE8 issue 577 | 578 | // detect Object changes 579 | var testsum = JSON.stringify(items) 580 | if (testsum == checksum) return 581 | checksum = testsum 582 | 583 | items = Object.keys(items).map(function(key, i) { 584 | var item = {} 585 | item[keys[0]] = key 586 | item[keys[1]] = items[key] 587 | return item 588 | }) 589 | 590 | } 591 | 592 | // remove redundant 593 | diff(rendered, items).map(function(item) { 594 | var pos = rendered.indexOf(item) 595 | root.removeChild(root.childNodes[startPos() + pos]) 596 | rendered.splice(pos, 1) 597 | }) 598 | 599 | // add new 600 | diff(items, rendered).map(function(item, i) { 601 | var pos = items.indexOf(item) 602 | 603 | // string array 604 | if (keys && !checksum) { 605 | var obj = {} 606 | obj[keys[0]] = item 607 | obj[keys[1]] = i 608 | item = obj 609 | } 610 | 611 | var tag = createTag({ 612 | before: root.childNodes[startPos() + pos], 613 | parent: instance, 614 | tmpl: template, 615 | item: item, 616 | root: root 617 | }) 618 | 619 | instance.on('update', function() { 620 | tag.update(0, true) 621 | }) 622 | 623 | }) 624 | 625 | // assign rendered 626 | rendered = items 627 | 628 | }) 629 | 630 | } 631 | 632 | riot.tag = function(name, tmpl, fn) { 633 | fn = fn || noop, 634 | tag_impl[name] = [tmpl, fn] 635 | } 636 | 637 | riot.mountTo = function(node, tagName, opts) { 638 | var tag = tag_impl[tagName] 639 | return tag && createTag({ tmpl: tag[0], fn: tag[1], root: node, opts: opts }) 640 | } 641 | 642 | riot.mount = function(selector, opts) { 643 | if (selector == '*') selector = Object.keys(tag_impl).join(', ') 644 | 645 | var instances = [] 646 | 647 | each(doc.querySelectorAll(selector), function(node) { 648 | if (node.riot) return 649 | 650 | var tagName = node.tagName.toLowerCase(), 651 | instance = riot.mountTo(node, tagName, opts) 652 | 653 | if (instance) { 654 | instances.push(instance) 655 | node.riot = 1 656 | } 657 | }) 658 | 659 | return instances 660 | } 661 | 662 | // update everything 663 | riot.update = function() { 664 | return all_tags = all_tags.filter(function(tag) { 665 | return !!tag.update() 666 | }) 667 | } 668 | 669 | })(riot, document) 670 | 671 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | }) -------------------------------------------------------------------------------- /demo/todo.tag: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

{ opts.title }

5 | 6 |
    7 |
  • 8 | 11 |
  • 12 |
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 |
-------------------------------------------------------------------------------- /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 |
-------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 |
-------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 0 12 | }) 13 | } 14 | return el 15 | } 16 | 17 | el.off = function(events, fn) { 18 | if (events == '*') callbacks = {} 19 | else if (fn) { 20 | var arr = callbacks[events] 21 | for (var i = 0, cb; (cb = arr && arr[i]); ++i) { 22 | if (cb == fn) { arr.splice(i, 1); i-- } 23 | } 24 | } else { 25 | events.replace(/\S+/g, function(name) { 26 | callbacks[name] = [] 27 | }) 28 | } 29 | return el 30 | } 31 | 32 | // only single event supported 33 | el.one = function(name, fn) { 34 | if (fn) fn.one = 1 35 | return el.on(name, fn) 36 | } 37 | 38 | el.trigger = function(name) { 39 | var args = [].slice.call(arguments, 1), 40 | fns = callbacks[name] || [] 41 | 42 | for (var i = 0, fn; (fn = fns[i]); ++i) { 43 | if (!fn.busy) { 44 | fn.busy = 1 45 | fn.apply(el, fn.typed ? [name].concat(args) : args) 46 | if (fn.one) { fns.splice(i, 1); i-- } 47 | fn.busy = 0 48 | } 49 | } 50 | 51 | return el 52 | } 53 | 54 | return el 55 | 56 | } 57 | ;(function(riot, evt) { 58 | 59 | // browsers only 60 | if (!this.top) return 61 | 62 | var loc = location, 63 | fns = riot.observable({}), 64 | current = hash(), 65 | win = window 66 | 67 | function hash() { 68 | return loc.hash.slice(1) 69 | } 70 | 71 | function emit(path) { 72 | if (path.type) path = hash() 73 | 74 | if (path != current) { 75 | fns.trigger.apply(null, ['H'].concat(path.split('/'))) 76 | current = path 77 | } 78 | } 79 | 80 | var r = riot.route = function(arg) { 81 | // string 82 | if (arg[0]) { 83 | loc.hash = arg 84 | emit(arg) 85 | 86 | // function 87 | } else { 88 | fns.on('H', arg) 89 | } 90 | } 91 | 92 | r.exec = function(fn) { 93 | fn.apply(null, hash().split('/')) 94 | } 95 | 96 | win.addEventListener ? win.addEventListener(evt, emit, false) : win.attachEvent('on' + evt, emit) 97 | 98 | })(riot, 'hashchange') 99 | /* 100 | 101 | //// How it works? 102 | 103 | 104 | Three ways: 105 | 106 | 1. Expressions: tmpl('{ value }', data). 107 | Returns the result of evaluated expression as a raw object. 108 | 109 | 2. Templates: tmpl('Hi { name } { surname }', data). 110 | Returns a string with evaluated expressions. 111 | 112 | 3. Filters: tmpl('{ show: !done, highlight: active }', data). 113 | Returns a space separated list of trueish keys (mainly 114 | used for setting html classes), e.g. "show highlight". 115 | 116 | 117 | // Template examples 118 | 119 | tmpl('{ title || "Untitled" }', data) 120 | tmpl('Results are { results ? "ready" : "loading" }', data) 121 | tmpl('Today is { new Date() }', data) 122 | tmpl('{ message.length > 140 && "Message is too long" }', data) 123 | tmpl('This item got { Math.round(rating) } stars', data) 124 | tmpl('

{ title }

{ body }', data) 125 | 126 | 127 | // Falsy expressions in templates 128 | 129 | In templates (as opposed to single expressions) all falsy values 130 | except zero (undefined/null/false) will default to empty string: 131 | 132 | tmpl('{ undefined } - { false } - { null } - { 0 }', {}) 133 | // will return: " - - - 0" 134 | 135 | */ 136 | 137 | riot._tmpl = (function() { 138 | 139 | var cache = {}, 140 | 141 | // find variable names 142 | re_vars = /("|').+?[^\\]\1|\.\w*|\w*:|\b(?:this|true|false|null|new|typeof|Number|String|Object|Array|Math|Date)\b|([a-z_]\w*)/gi 143 | // [ 1 ][ 2 ][ 3 ][ 4 ][ 5 ] 144 | // 1. skip quoted strings: "a b", 'a b', 'a \'b\'' 145 | // 2. skip object properties: .name 146 | // 3. skip object literals: name: 147 | // 4. skip reserved words 148 | // 5. match var name 149 | 150 | // build a template (or get it from cache), render with data 151 | 152 | return function(str, data) { 153 | return str && (cache[str] = cache[str] || tmpl(str))(data) 154 | } 155 | 156 | 157 | // create a template instance 158 | 159 | function tmpl(s, p) { 160 | p = (s || '{}') 161 | 162 | // temporarily convert \{ and \} to a non-character 163 | .replace(/\\{/g, '\uFFF0') 164 | .replace(/\\}/g, '\uFFF1') 165 | 166 | // split string to expression and non-expresion parts 167 | .split(/({[\s\S]*?})/) 168 | 169 | return new Function('d', 'return ' + ( 170 | 171 | // is it a single expression or a template? i.e. {x} or {x} 172 | !p[0] && !p[2] 173 | 174 | // if expression, evaluate it 175 | ? expr(p[1]) 176 | 177 | // if template, evaluate all expressions in it 178 | : '[' + p.map(function(s, i) { 179 | 180 | // is it an expression or a string (every second part is an expression) 181 | return i % 2 182 | 183 | // evaluate the expressions 184 | ? expr(s, 1) 185 | 186 | // process string parts of the template: 187 | : '"' + s 188 | 189 | // preserve new lines 190 | .replace(/\n/g, '\\n') 191 | 192 | // escape quotes 193 | .replace(/"/g, '\\"') 194 | 195 | + '"' 196 | 197 | }).join(',') + '].join("")' 198 | ) 199 | 200 | // bring escaped { and } back 201 | .replace(/\uFFF0/g, '{') 202 | .replace(/\uFFF1/g, '}') 203 | 204 | ) 205 | 206 | } 207 | 208 | 209 | // parse { ... } expression 210 | 211 | function expr(s, n) { 212 | s = s 213 | 214 | // convert new lines to spaces 215 | .replace(/\n/g, ' ') 216 | 217 | // trim whitespace, curly brackets, strip comments 218 | .replace(/^[{ ]+|[ }]+$|\/\*.+?\*\//g, '') 219 | 220 | // is it an object literal? i.e. { key : value } 221 | return /^\s*[\w-"']+ *:/.test(s) 222 | 223 | // if object literal, return trueish keys 224 | // e.g.: { show: isOpen(), done: item.done } -> "show done" 225 | ? '[' + s.replace(/\W*([\w-]+)\W*:([^,]+)/g, function(_, k, v) { 226 | 227 | // safely execute vars to prevent undefined value errors 228 | return v.replace(/\w[^,|& ]*/g, function(v) { return wrap(v, n) }) + '?"' + k + '":"",' 229 | 230 | }) + '].join(" ")' 231 | 232 | // if js expression, evaluate as javascript 233 | : wrap(s, n) 234 | 235 | } 236 | 237 | 238 | // execute js w/o breaking on errors or undefined vars 239 | 240 | function wrap(s, nonull) { 241 | return '(function(v){try{v=' 242 | 243 | // prefix vars (name => data.name) 244 | + (s.replace(re_vars, function(s, _, v) { return v ? 'd.' + v : s }) 245 | 246 | // break the expression if its empty (resulting in undefined value) 247 | || 'x') 248 | 249 | + '}finally{return ' 250 | 251 | // default to empty string for falsy values except zero 252 | + (nonull ? '!v&&v!==0?"":v' : 'v') 253 | 254 | + '}}).call(d)' 255 | } 256 | 257 | })() 258 | ;(function(riot, doc) { 259 | 260 | var tmpl = riot._tmpl, 261 | all_tags = [], 262 | tag_impl = {} 263 | 264 | function each(nodes, fn) { 265 | for (var i = 0; i < (nodes || []).length; i++) { 266 | if (fn(nodes[i], i) === false) i-- 267 | } 268 | } 269 | 270 | function extend(obj, from) { 271 | from && Object.keys(from).map(function(key) { 272 | obj[key] = from[key] 273 | }) 274 | return obj 275 | } 276 | 277 | function diff(arr1, arr2) { 278 | return arr1.filter(function(el) { 279 | return arr2.indexOf(el) < 0 280 | }) 281 | } 282 | 283 | function walk(dom, fn) { 284 | dom = fn(dom) === false ? dom.nextSibling : dom.firstChild 285 | 286 | while (dom) { 287 | walk(dom, fn) 288 | dom = dom.nextSibling 289 | } 290 | } 291 | 292 | 293 | function mkdom(tmpl) { 294 | var el = doc.createElement('div') 295 | el.innerHTML = tmpl 296 | return el 297 | } 298 | 299 | 300 | function update(expressions, instance) { 301 | 302 | // allow recalculation of context data 303 | instance.trigger('update') 304 | 305 | each(expressions, function(expr) { 306 | var tag = expr.tag, 307 | dom = expr.dom 308 | 309 | function remAttr(name) { 310 | dom.removeAttribute(name) 311 | } 312 | 313 | // loops first: TODO remove from expressions arr 314 | if (expr.loop) { 315 | remAttr('each') 316 | return loop(expr, instance) 317 | } 318 | 319 | // custom tag 320 | if (tag) return tag.update ? tag.update() : 321 | expr.tag = createTag({ tmpl: tag[0], fn: tag[1], root: dom, parent: instance }) 322 | 323 | 324 | var attr_name = expr.attr, 325 | value = tmpl(expr.expr, instance) 326 | 327 | if (value == null) value = '' 328 | 329 | // no change 330 | if (expr.value === value) return 331 | expr.value = value 332 | 333 | 334 | // text node 335 | if (!attr_name) return dom.nodeValue = value 336 | 337 | // attribute 338 | if (!value && expr.bool || /obj|func/.test(typeof value)) remAttr(attr_name) 339 | 340 | // event handler 341 | if (typeof value == 'function') { 342 | dom[attr_name] = function(e) { 343 | 344 | // cross browser event fix 345 | e = e || window.event 346 | e.which = e.which || e.charCode || e.keyCode 347 | e.target = e.target || e.srcElement 348 | e.currentTarget = dom 349 | 350 | // currently looped item 351 | e.item = instance.__item || instance 352 | 353 | // prevent default behaviour (by default) 354 | if (value.call(instance, e) !== true) { 355 | e.preventDefault && e.preventDefault() 356 | e.returnValue = false 357 | } 358 | 359 | instance.update() 360 | } 361 | 362 | // show / hide / if 363 | } else if (/^(show|hide|if)$/.test(attr_name)) { 364 | remAttr(attr_name) 365 | if (attr_name == 'hide') value = !value 366 | dom.style.display = value ? '' : 'none' 367 | 368 | // normal attribute 369 | } else { 370 | if (expr.bool) { 371 | if (!value) return 372 | value = attr_name 373 | } 374 | 375 | dom.setAttribute(attr_name, value) 376 | } 377 | 378 | }) 379 | 380 | instance.trigger('updated') 381 | 382 | } 383 | 384 | function parse(root) { 385 | 386 | var named_elements = {}, 387 | expressions = [] 388 | 389 | walk(root, function(dom) { 390 | 391 | var type = dom.nodeType, 392 | value = dom.nodeValue 393 | 394 | function addExpr(value, data) { 395 | if (value ? value.indexOf('{') >= 0 : data) { 396 | var expr = { dom: dom, expr: value } 397 | expressions.push(extend(expr, data || {})) 398 | } 399 | } 400 | 401 | // text node 402 | if (type == 3 && dom.parentNode.tagName != 'STYLE') { 403 | addExpr(value) 404 | 405 | // element 406 | } else if (type == 1) { 407 | 408 | // loop? 409 | value = dom.getAttribute('each') 410 | 411 | if (value) { 412 | addExpr(value, { loop: 1 }) 413 | return false 414 | } 415 | 416 | // custom tag? 417 | var tag = tag_impl[dom.tagName.toLowerCase()] 418 | 419 | // attributes 420 | each(dom.attributes, function(attr) { 421 | var name = attr.name, 422 | value = attr.value 423 | 424 | // named elements 425 | if (/^(name|id)$/.test(name)) named_elements[value] = dom 426 | 427 | // expressions 428 | if (!tag) { 429 | var bool = name.split('__')[1] 430 | addExpr(value, { attr: bool || name, bool: bool }) 431 | if (bool) { 432 | dom.removeAttribute(name) 433 | return false 434 | } 435 | } 436 | 437 | }) 438 | 439 | if (tag) addExpr(0, { tag: tag }) 440 | 441 | } 442 | 443 | }) 444 | 445 | return { expr: expressions, elem: named_elements } 446 | 447 | } 448 | 449 | 450 | 451 | // create new custom tag (component) 452 | function createTag(conf) { 453 | 454 | var opts = conf.opts || {}, 455 | dom = mkdom(conf.tmpl), 456 | mountNode = conf.root, 457 | parent = conf.parent, 458 | ast = parse(dom), 459 | tag = { root: mountNode, opts: opts, parent: parent, __item: conf.item }, 460 | attributes = {} 461 | 462 | // named elements 463 | extend(tag, ast.elem) 464 | 465 | // attributes 466 | each(mountNode.attributes, function(attr) { 467 | attributes[attr.name] = attr.value 468 | }) 469 | 470 | function updateOpts() { 471 | Object.keys(attributes).map(function(name) { 472 | var val = opts[name] = tmpl(attributes[name], parent || tag) 473 | if (typeof val == 'object') mountNode.removeAttribute(name) 474 | }) 475 | } 476 | 477 | updateOpts() 478 | 479 | if (!tag.on) { 480 | riot.observable(tag) 481 | delete tag.off // off method not needed 482 | } 483 | 484 | if (conf.fn) conf.fn.call(tag, opts) 485 | 486 | 487 | tag.update = function(data, _system) { 488 | 489 | /* 490 | If loop is defined on the root of the HTML template 491 | the original parent is a temporary
by mkdom() 492 | */ 493 | if (parent && dom && !dom.firstChild) { 494 | mountNode = parent.root 495 | dom = null 496 | } 497 | 498 | if (_system || doc.body.contains(mountNode)) { 499 | extend(tag, data) 500 | extend(tag, tag.__item) 501 | updateOpts() 502 | update(ast.expr, tag) 503 | 504 | // update parent 505 | !_system && tag.__item && parent.update() 506 | return true 507 | 508 | } else { 509 | tag.trigger('unmount') 510 | } 511 | 512 | } 513 | 514 | tag.update(0, true) 515 | 516 | // append to root 517 | while (dom.firstChild) { 518 | if (conf.before) mountNode.insertBefore(dom.firstChild, conf.before) 519 | else mountNode.appendChild(dom.firstChild) 520 | } 521 | 522 | 523 | tag.trigger('mount') 524 | 525 | all_tags.push(tag) 526 | 527 | return tag 528 | } 529 | 530 | 531 | function loop(expr, instance) { 532 | 533 | // initialize once 534 | if (expr.done) return 535 | expr.done = true 536 | 537 | var dom = expr.dom, 538 | prev = dom.previousSibling, 539 | root = dom.parentNode, 540 | template = dom.outerHTML, 541 | val = expr.expr, 542 | els = val.split(/\s+in\s+/), 543 | rendered = [], 544 | checksum, 545 | root, 546 | keys 547 | 548 | 549 | if (els[1]) { 550 | val = '{ ' + els[1] 551 | keys = els[0].slice(1).trim().split(/,\s*/) 552 | } 553 | 554 | // clean template code 555 | instance.one('mount', function() { 556 | var p = dom.parentNode 557 | if (p) { 558 | root = p 559 | root.removeChild(dom) 560 | } 561 | }) 562 | 563 | function startPos() { 564 | return Array.prototype.indexOf.call(root.childNodes, prev) + 1 565 | } 566 | 567 | instance.on('updated', function() { 568 | 569 | var items = tmpl(val, instance) 570 | is_array = Array.isArray(items) 571 | 572 | if (is_array) items = items.slice(0) 573 | 574 | else { 575 | 576 | if (!items) return // some IE8 issue 577 | 578 | // detect Object changes 579 | var testsum = JSON.stringify(items) 580 | if (testsum == checksum) return 581 | checksum = testsum 582 | 583 | items = Object.keys(items).map(function(key, i) { 584 | var item = {} 585 | item[keys[0]] = key 586 | item[keys[1]] = items[key] 587 | return item 588 | }) 589 | 590 | } 591 | 592 | // remove redundant 593 | diff(rendered, items).map(function(item) { 594 | var pos = rendered.indexOf(item) 595 | root.removeChild(root.childNodes[startPos() + pos]) 596 | rendered.splice(pos, 1) 597 | }) 598 | 599 | // add new 600 | diff(items, rendered).map(function(item, i) { 601 | var pos = items.indexOf(item) 602 | 603 | // string array 604 | if (keys && !checksum) { 605 | var obj = {} 606 | obj[keys[0]] = item 607 | obj[keys[1]] = i 608 | item = obj 609 | } 610 | 611 | var tag = createTag({ 612 | before: root.childNodes[startPos() + pos], 613 | parent: instance, 614 | tmpl: template, 615 | item: item, 616 | root: root 617 | }) 618 | 619 | instance.on('update', function() { 620 | tag.update(0, true) 621 | }) 622 | 623 | }) 624 | 625 | // assign rendered 626 | rendered = items 627 | 628 | }) 629 | 630 | } 631 | 632 | riot.tag = function(name, tmpl, fn) { 633 | fn = fn || noop, 634 | tag_impl[name] = [tmpl, fn] 635 | } 636 | 637 | riot.mountTo = function(node, tagName, opts) { 638 | var tag = tag_impl[tagName] 639 | return tag && createTag({ tmpl: tag[0], fn: tag[1], root: node, opts: opts }) 640 | } 641 | 642 | riot.mount = function(selector, opts) { 643 | if (selector == '*') selector = Object.keys(tag_impl).join(', ') 644 | 645 | var instances = [] 646 | 647 | each(doc.querySelectorAll(selector), function(node) { 648 | if (node.riot) return 649 | 650 | var tagName = node.tagName.toLowerCase(), 651 | instance = riot.mountTo(node, tagName, opts) 652 | 653 | if (instance) { 654 | instances.push(instance) 655 | node.riot = 1 656 | } 657 | }) 658 | 659 | return instances 660 | } 661 | 662 | // update everything 663 | riot.update = function() { 664 | return all_tags = all_tags.filter(function(tag) { 665 | return !!tag.update() 666 | }) 667 | } 668 | 669 | })(riot, document) 670 | 671 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } --------------------------------------------------------------------------------