├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGES.md ├── LICENSE ├── README.md ├── bin └── msx ├── demo └── index.html ├── dist └── MSXTransformer.js ├── gulpfile.js ├── main.js ├── package.json ├── test ├── js │ ├── example.html │ ├── example.js │ └── test.js ├── jsx │ ├── example.jsx │ └── test.jsx ├── tests.js └── vendor │ └── mithril-0.2.0.js └── vendor ├── fbtransform ├── syntax.js ├── transforms │ ├── jsx.js │ ├── react.js │ └── reactDisplayName.js └── visitors.js └── inline-source-map.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | demo 2 | test 3 | .gitignore 4 | .travis.yml 5 | gulpfile.js 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## 0.4.1 / 2015-05-12 2 | 3 | * Fixed missing comma in bin script. 4 | 5 | ## 0.4.0 / 2015-05-11 6 | 7 | * Mithril 0.2 component support: 8 | * `` and `` tags are now treated as Mithril 9 | components - `` and `` tags are treated as 10 | regular tags. 11 | * Component attributes will be passed as the second argument to 12 | `m.component()`. 13 | * Component child contents will be wrapped in an array and passed as the third 14 | argument to `m.component()`. 15 | 16 | * Merged in changes since JSX Transformer 0.12.2 up to the last version before 17 | React's visitors were moved into jstransform itself. 18 | * Removed hardcoded list of known tags. Any lowercase name, allowing for 19 | hyphenation, is now accepted as an HTML tag. 20 | * New transforms: es6-call-spread, reserved-words 21 | 22 | ## 0.3.0 / 2015-01-27 23 | 24 | * Changed license back to MIT, now that React is licensed under BSD. 25 | 26 | * Merged in changes since JSX Transformer 0.10.0 up to the current release 27 | version, 0.12.2: 28 | * The `/* @jsx m */` pragma is no longer needed - Mithril's `m()` function is 29 | assumed to be in scope. 30 | * More [ES6 transforms](http://kangax.github.io/compat-table/es6/#jsx) are 31 | supported when using the `harmony` flag. 32 | * [JSX Spread Attributes](http://facebook.github.io/react/docs/jsx-spread.html) 33 | allow you to merge objects into a tag's attrs, but with MSX you must ensure 34 | `Object.assign()` is available in order to use them. 35 | * More HTML and SVG tag names are supported. 36 | * Inline sourcemaps can be generated by passing a `sourceMap: true` option (or 37 | `--source-map-inline` flag on the command line). 38 | 39 | ## 0.2.1 / 2015-01-26 40 | 41 | * Added a `no-precompile` option to the `msx` command. 42 | 43 | * Added a `precompile` option, defaulting to `true`. 44 | 45 | ## 0.2.0 / 2015-01-24 46 | 47 | * Known tag names are now precompiled to tag objects \[[liamcurry]\] 48 | * Unknown tag names will continue to generate `m()` calls 49 | 50 | ## 0.1.3 / 2014-03-26 51 | 52 | * Added `msx` command when installed globally 53 | 54 | ## 0.1.2 / 2014-03-21 55 | 56 | * Changed tag name string to use double quotes for consistency with other text 57 | content output 58 | 59 | ## 0.1.1 / 2014-03-21 60 | 61 | * Changed to Apache 2.0 License, as per React 62 | 63 | ## 0.1.0 / 2014-03-21 64 | 65 | * Initial release 66 | 67 | [liamcurry]: https://github.com/liamcurry -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jonathan Buchanan 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 | 23 | Based on React: 24 | 25 | BSD License 26 | 27 | For React software 28 | 29 | Copyright (c) 2013-2015, Facebook, Inc. 30 | All rights reserved. 31 | 32 | Redistribution and use in source and binary forms, with or without modification, 33 | are permitted provided that the following conditions are met: 34 | 35 | * Redistributions of source code must retain the above copyright notice, this 36 | list of conditions and the following disclaimer. 37 | 38 | * Redistributions in binary form must reproduce the above copyright notice, 39 | this list of conditions and the following disclaimer in the documentation 40 | and/or other materials provided with the distribution. 41 | 42 | * Neither the name Facebook nor the names of its contributors may be used to 43 | endorse or promote products derived from this software without specific 44 | prior written permission. 45 | 46 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 47 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 48 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 49 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 50 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 51 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 52 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 53 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 54 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 55 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | 3 | As of Mithril.js v1.0.0, you can use the `react-transform-jsx` Babel plugin for JSX. 4 | 5 | See [Mithril.js' JSX documentation](http://mithril.js.org/jsx.html) for details. 6 | 7 | --- 8 | 9 | # MSX [![Build Status](https://secure.travis-ci.org/insin/msx.png?branch=master)](http://travis-ci.org/insin/msx) 10 | 11 | *MSX is based on version 0.13.2 of React's JSX Transformer* 12 | 13 | MSX tweaks [React](http://facebook.github.io/react/)'s JSX Transformer to output 14 | contents compatible with [Mithril](http://lhorie.github.io/mithril/)'s 15 | `m.render()` function, allowing you to use HTML-like syntax in your Mithril 16 | view code, like this: 17 | 18 | ```html 19 | var todos = ctrl.list.map(function(task, index) { 20 | return
  • 21 |
    22 | 28 | 29 |
    31 | 32 |
  • 33 | }) 34 | ``` 35 | 36 | ## HTML tags and custom elements 37 | 38 | For tag names which look like HTML elements or custom elements (lowercase, 39 | optionally containing hyphens), raw virtual DOM objects - matching the 40 | [`VirtualElement` signature](http://lhorie.github.io/mithril/mithril.render.html#signature) 41 | accepted by `m.render()` - will be generated by default. 42 | 43 | _Input:_ 44 | 45 | ```html 46 |
    47 |

    Test

    48 | 49 |
    50 | ``` 51 | 52 | _Output:_ 53 | 54 | ```javascript 55 | {tag: "div", attrs: {id:"example"}, children: [ 56 | {tag: "h1", attrs: {}, children: ["Test"]}, 57 | {tag: "my-element", attrs: {name:"test"}} 58 | ]} 59 | ``` 60 | 61 | This effectively [precompiles](http://lhorie.github.io/mithril/optimizing-performance.html) 62 | your view code for a slight performance tweak. 63 | 64 | ## Mithril components 65 | 66 | Otherwise, it's assumed a tag name is a reference to an in-scope variable which 67 | is a [Mithril component](http://lhorie.github.io/mithril/components.html). 68 | 69 | Passing attributes or children to a component will generate a call to Mithril's 70 | [`m.component()`](http://lhorie.github.io/mithril/mithril.component.html) 71 | function, with children always being passed as an Array: 72 | 73 | _Input:_ 74 | 75 | ```html 76 |
    77 | {/* Bare component */} 78 | 79 | {/* Component with attributes */} 80 | 81 | {/* Component with attributes and children */} 82 | 83 | {ctrl.files().map(file => )} 84 | 85 | 86 | 87 | ``` 88 | 89 | _Output:_ 90 | 91 | ```javascript 92 | {tag: "form", attrs: {}, children: [ 93 | /* Bare component */ 94 | Uploader, 95 | /* Component with attributes */ 96 | m.component(Uploader, {onchange:ctrl.files}), 97 | /* Component with attributes and children */ 98 | m.component(Uploader, {onchange:ctrl.files}, [ 99 | ctrl.files().map(function(file) {return m.component(File, Object.assign({}, file));}) 100 | ]), 101 | {tag: "button", attrs: {type:"button", onclick:ctrl.save}, children: ["Upload"]} 102 | ]} 103 | ``` 104 | 105 | MSX assumes your component's (optional) `controller()` and (required) `view()` 106 | functions have the following signatures, where `attributes` is an `Object` and 107 | `children` is an `Array`: 108 | 109 | ```javascript 110 | controller([attributes[, children]]) 111 | view(ctrl[, attributes[, children]]) 112 | ``` 113 | 114 | As such, if a component has children but no attributes, an empty attributes 115 | object will still be passed: 116 | 117 | _Input:_ 118 | 119 | ```html 120 | 121 | 122 | 123 | ``` 124 | 125 | _Output:_ 126 | 127 | ```javascript 128 | m.component(Field, {}, [ 129 | {tag: "input", attrs: {onchange:m.withAttr('value', ctrl.description), value:ctrl.description()}} 130 | ]) 131 | ``` 132 | 133 | ## JSX spread attributes and `Object.assign()` 134 | 135 | If you make use of [JSX Spread Attributes](http://facebook.github.io/react/docs/jsx-spread.html), 136 | the resulting code will make use of `Object.assign()` to merge attributes - if 137 | your code needs to run in environments which don't implement `Object.assign()` 138 | natively, you're responsible for ensuring it's available via a 139 | [shim](https://github.com/ljharb/object.assign), or otherwise. 140 | 141 | Other than that, the rest of React's JSX documentation should still apply: 142 | 143 | * [JSX in Depth](http://facebook.github.io/react/docs/jsx-in-depth.html) 144 | * [JSX Spread Attributes](http://facebook.github.io/react/docs/jsx-spread.html) 145 | * [JSX Gotchas](http://facebook.github.io/react/docs/jsx-gotchas.html) - with 146 | the exception of `dangerouslySetInnerHTML`: use 147 | [`m.trust()`](http://lhorie.github.io/mithril/mithril.trust.html) on contents 148 | instead. 149 | * [If-Else in JSX](http://facebook.github.io/react/tips/if-else-in-JSX.html) 150 | 151 | ## In-browser JSX Transform 152 | 153 | For development and quick prototyping, an in-browser MSX transform is available. 154 | 155 | Download or use it directly from cdn.rawgit.com: 156 | 157 | * https://cdn.rawgit.com/insin/msx/master/dist/MSXTransformer.js 158 | 159 | Include a ` 170 | 171 |
    172 | 187 | ``` 188 | 189 | ## Command Line Usage 190 | 191 | ``` 192 | npm install -g msx 193 | ``` 194 | 195 | ``` 196 | msx --watch src/ build/ 197 | ``` 198 | 199 | To disable precompilation from the command line, pass a `--no-precompile` flag. 200 | 201 | Run `msx --help` for more information. 202 | 203 | ## Module Usage 204 | 205 | ``` 206 | npm install msx 207 | ``` 208 | 209 | ```javascript 210 | var msx = require('msx') 211 | ``` 212 | 213 | ### Module API 214 | 215 | #### `msx.transform(source: String[, options: Object])` 216 | 217 | Transforms XML-like syntax in the given source into object literals compatible 218 | with Mithril's `m.render()` function, or to function calls using Mithril's 219 | `m()` function, returning the transformed source. 220 | 221 | To enable [ES6 transforms supported by JSX Transformer](http://kangax.github.io/compat-table/es6/#jsx), 222 | pass a `harmony` option: 223 | 224 | ```javascript 225 | msx.transform(source, {harmony: true}) 226 | ``` 227 | 228 | To disable default precompilation and always output `m()` calls, pass a 229 | `precompile` option: 230 | 231 | ```javascript 232 | msx.transform(source, {precompile: false}) 233 | ``` 234 | 235 | ## Examples 236 | 237 | Example inputs (using some ES6 features) and outputs are in 238 | [test/jsx](https://github.com/insin/msx/tree/master/test/jsx) and 239 | [test/js](https://github.com/insin/msx/tree/master/test/js), respectively. 240 | 241 | An example [gulpfile.js](https://github.com/insin/msx/blob/master/gulpfile.js) 242 | is provided, which implements an `msxTransform()` step using `msx.transform()`. 243 | 244 | ## Related Modules 245 | 246 | * [gulp-msx](https://github.com/insin/gulp-msx) - gulp plugin. 247 | * [grunt-msx](https://github.com/hung-phan/grunt-msx) - grunt plugin. 248 | * [mithrilify](https://github.com/sectore/mithrilify) - browserify transform. 249 | * [msx-loader](https://github.com/sdemjanenko/msx-loader) - webpack loader. 250 | * [babel-plugin-mjsx](https://github.com/Naddiseo/babel-plugin-mjsx) - babel transform 251 | 252 | ## MIT Licensed 253 | -------------------------------------------------------------------------------- /bin/msx: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // -*- mode: js -*- 3 | 'use strict'; 4 | 5 | var transform = require('../main').transform; 6 | 7 | require('commoner').version( 8 | require('../package.json').version 9 | ).resolve(function(id) { 10 | return this.readModuleP(id); 11 | }).option( 12 | '--harmony', 13 | 'Turns on JS transformations such as ES6 Classes etc.' 14 | ).option( 15 | '--target [version]', 16 | 'Specify your target version of ECMAScript. Valid values are "es3" and ' + 17 | '"es5". The default is "es5". "es3" will avoid uses of defineProperty and ' + 18 | 'will quote reserved words. WARNING: "es5" is not properly supported, even ' + 19 | 'with the use of es5shim, es5sham. If you need to support IE8, use "es3".', 20 | 'es5' 21 | ).option( 22 | '--strip-types', 23 | 'Strips out type annotations.' 24 | ).option( 25 | '--es6module', 26 | 'Parses the file as a valid ES6 module. ' + 27 | '(Note that this means implicit strict mode)' 28 | ).option( 29 | '--non-strict-es6module', 30 | 'Parses the file as an ES6 module, except disables implicit strict-mode. ' + 31 | '(This is useful if you\'re porting non-ES6 modules to ES6, but haven\'t ' + 32 | 'yet verified that they are strict-mode safe yet)' 33 | ).option( 34 | '--source-map-inline', 35 | 'Embed inline sourcemap in transformed source' 36 | ).option( 37 | '--no-precompile', 38 | 'Disable precompilation to raw virtual DOM objects.' 39 | ).process(function(id, source) { 40 | // This is where JSX, ES6, etc. desugaring happens. 41 | // We don't do any pre-processing of options so that the command line and the 42 | // JS API both expose the same set of options. We do extract the options that 43 | // we care about from commoner though so we aren't passing too many things 44 | // along. 45 | var options = { 46 | harmony: this.options.harmony, 47 | sourceMap: this.options.sourceMapInline, 48 | stripTypes: this.options.stripTypes, 49 | precompile: this.options.precompile, 50 | es6module: this.options.es6module, 51 | nonStrictEs6module: this.options.nonStrictEs6module, 52 | target: this.options.target 53 | }; 54 | return transform(source, options); 55 | }); 56 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MSX Transformer 5 | 6 | 7 | 8 | 9 |
    10 | 112 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | 3 | var msx = require('./main') 4 | var through = require('through2') 5 | 6 | var plumber = require('gulp-plumber') 7 | var gutil = require('gulp-util') 8 | 9 | var testJSX = './test/jsx/*.jsx' 10 | 11 | function msxTransform(options) { 12 | return through.obj(function (file, enc, cb) { 13 | try { 14 | file.contents = new Buffer(msx.transform(file.contents.toString(), options)) 15 | file.path = gutil.replaceExtension(file.path, '.js') 16 | this.push(file) 17 | } 18 | catch (err) { 19 | err.fileName = file.path 20 | this.emit('error', new gutil.PluginError('msx', err)) 21 | } 22 | cb() 23 | }) 24 | } 25 | 26 | gulp.task('msx', function() { 27 | return gulp.src(testJSX) 28 | .pipe(plumber()) 29 | .pipe(msxTransform({harmony: true})) 30 | .on('error', function(e) { 31 | console.error(e.message + '\n in ' + e.fileName) 32 | }) 33 | .pipe(gulp.dest('./test/js/')) 34 | }) 35 | 36 | gulp.task('watch', function() { 37 | gulp.watch([testJSX], ['msx']) 38 | }) 39 | 40 | gulp.task('default', ['watch', 'msx']) 41 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | 'use strict'; 11 | /*eslint-disable no-undef*/ 12 | var visitors = require('./vendor/fbtransform/visitors'); 13 | var transform = require('jstransform').transform; 14 | var typesSyntax = require('jstransform/visitors/type-syntax'); 15 | var inlineSourceMap = require('./vendor/inline-source-map'); 16 | 17 | var visitReactTag = require('./vendor/fbtransform/transforms/react').visitorList[0]; 18 | 19 | var slice = Array.prototype.slice; 20 | 21 | function partial(func) { 22 | var partialArgs = slice.call(arguments, 1); 23 | return function() { 24 | return func.apply(this, partialArgs.concat(slice.call(arguments))); 25 | }; 26 | } 27 | 28 | function extend(dest, src) { 29 | if (src != null) { 30 | Object.keys(src).forEach(function(prop) { 31 | dest[prop] = src[prop]; 32 | }); 33 | } 34 | return dest; 35 | } 36 | 37 | module.exports = { 38 | transform: function(input, options) { 39 | options = processOptions(options); 40 | var output = innerTransform(input, options); 41 | var result = output.code; 42 | if (options.sourceMap) { 43 | var map = inlineSourceMap( 44 | output.sourceMap, 45 | input, 46 | options.filename 47 | ); 48 | result += '\n' + map; 49 | } 50 | return result; 51 | }, 52 | transformWithDetails: function(input, options) { 53 | options = processOptions(options); 54 | var output = innerTransform(input, options); 55 | var result = {}; 56 | result.code = output.code; 57 | if (options.sourceMap) { 58 | result.sourceMap = output.sourceMap.toJSON(); 59 | } 60 | if (options.filename) { 61 | result.sourceMap.sources = [options.filename]; 62 | } 63 | return result; 64 | } 65 | }; 66 | 67 | /** 68 | * Only copy the values that we need. We'll do some preprocessing to account for 69 | * converting command line flags to options that jstransform can actually use. 70 | */ 71 | function processOptions(opts) { 72 | opts = extend({precompile: true}, opts); 73 | var options = {}; 74 | 75 | options.harmony = opts.harmony; 76 | options.stripTypes = opts.stripTypes; 77 | options.sourceMap = opts.sourceMap; 78 | options.filename = opts.sourceFilename; 79 | options.precompile = opts.precompile; 80 | 81 | if (opts.es6module) { 82 | options.sourceType = 'module'; 83 | } 84 | if (opts.nonStrictEs6module) { 85 | options.sourceType = 'nonStrictModule'; 86 | } 87 | 88 | // Instead of doing any fancy validation, only look for 'es3'. If we have 89 | // that, then use it. Otherwise use 'es5'. 90 | options.es3 = opts.target === 'es3'; 91 | options.es5 = !options.es3; 92 | 93 | return options; 94 | } 95 | 96 | function innerTransform(input, options) { 97 | var visitorSets = ['react']; 98 | if (options.harmony) { 99 | visitorSets.push('harmony'); 100 | } 101 | 102 | if (options.es3) { 103 | visitorSets.push('es3'); 104 | } 105 | 106 | if (options.stripTypes) { 107 | // Stripping types needs to happen before the other transforms 108 | // unfortunately, due to bad interactions. For example, 109 | // es6-rest-param-visitors conflict with stripping rest param type 110 | // annotation 111 | input = transform(typesSyntax.visitorList, input, options).code; 112 | } 113 | 114 | var visitorList = visitors.getVisitorsBySet(visitorSets); 115 | 116 | // Patch the react tag visitor to preconfigure the MSX precompile argument 117 | for (var i = 0; i < visitorList.length ; i++) { 118 | var visitor = visitorList[i]; 119 | if (visitor === visitReactTag) { 120 | visitorList[i] = partial(visitor, options.precompile); 121 | visitorList[i].test = visitor.test; 122 | } 123 | } 124 | 125 | return transform(visitorList, input, options); 126 | } 127 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "msx", 3 | "version": "0.4.1", 4 | "description": "JSX for Mithril", 5 | "main": "main.js", 6 | "dependencies": { 7 | "commoner": "^0.10.0", 8 | "jstransform": "^10.1.0" 9 | }, 10 | "devDependencies": { 11 | "gulp": "^3.8.11", 12 | "gulp-plumber": "^1.0.0", 13 | "gulp-util": "^3.0.4", 14 | "tape": "^4.0.0", 15 | "through2": "^0.6.5" 16 | }, 17 | "keywords": [ 18 | "jsx", 19 | "mithril" 20 | ], 21 | "author": "Jonathan Buchanan (https://github.com/insin)", 22 | "repository": { 23 | "type": "git", 24 | "url": "git://github.com/insin/msx.git" 25 | }, 26 | "licenses": [ 27 | { 28 | "type": "Apache-2.0", 29 | "url": "http://www.apache.org/licenses/LICENSE-2.0" 30 | } 31 | ], 32 | "scripts": { 33 | "test": "node test/tests.js" 34 | }, 35 | "bin": { 36 | "msx": "./bin/msx" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/js/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/js/example.js: -------------------------------------------------------------------------------- 1 | // JSX version of the Mithril Getting Started documentation's TODO example. 2 | // http://lhorie.github.io/mithril/getting-started.html 3 | 4 | //this application only has one module: todo 5 | var todo = {}; 6 | 7 | //for simplicity, we use this module to namespace the model classes 8 | 9 | //the Todo class has two properties 10 | todo.Todo = function(data) { 11 | this.description = m.prop(data.description); 12 | this.done = m.prop(false); 13 | }; 14 | 15 | //the TodoList class is a list of Todo's 16 | todo.TodoList = Array; 17 | 18 | //the controller uses 3 model-level entities, of which one is a custom defined class: 19 | //`Todo` is the central class in this application 20 | //`list` is merely a generic array, with standard array methods 21 | //`description` is a temporary storage box that holds a string 22 | // 23 | //the `add` method simply adds a new todo to the list 24 | todo.controller = function() { 25 | this.list = new todo.TodoList(); 26 | this.description = m.prop(""); 27 | 28 | this.add = function(description) { 29 | if (description()) { 30 | this.list.push(new todo.Todo({description: description()})); 31 | this.description(""); 32 | } 33 | }; 34 | }; 35 | 36 | //here's the view 37 | todo.view = function(ctrl) { 38 | return {tag: "html", attrs: {}, children: [ 39 | {tag: "body", attrs: {}, children: [ 40 | {tag: "input", attrs: {onchange:m.withAttr("value", ctrl.description), value:ctrl.description()}}, 41 | {tag: "button", attrs: {onclick:ctrl.add.bind(ctrl, ctrl.description)}, children: ["Add"]}, 42 | {tag: "table", attrs: {}, children: [ 43 | ctrl.list.map(function(task, index) {return {tag: "tr", attrs: {}, children: [ 44 | {tag: "td", attrs: {}, children: [ 45 | {tag: "input", attrs: { 46 | type:"checkbox", 47 | onclick:m.withAttr("checked", task.done), 48 | checked:task.done()} 49 | } 50 | ]}, 51 | {tag: "td", attrs: {style:{textDecoration: task.done() ? "line-through" : "none"}}, children: [ 52 | task.description() 53 | ]} 54 | ]};}) 55 | ]} 56 | ]} 57 | ]} 58 | }; 59 | 60 | //initialize the application 61 | m.module(document, todo); -------------------------------------------------------------------------------- /test/js/test.js: -------------------------------------------------------------------------------- 1 | // JSX version of https://github.com/jpmonette/todomvc-mithril/blob/1d627f1d5dc0890d0a99cdcb16d09387f68f6df0/js/views/todo-view.js 2 | 3 | 'use strict'; 4 | 5 | function view(ctrl) { 6 | var clearCompleted 7 | if (ctrl.amountCompleted() != 0) { 8 | clearCompleted = {tag: "button", attrs: {id:"clear-completed", onclick:ctrl.clearCompleted.bind(ctrl)}, children: [ 9 | "Clear completed (", ctrl.amountCompleted(), ")" 10 | ]} 11 | } 12 | 13 | var todos = ctrl.list.map(function(task, index) {return {tag: "li", attrs: {className:task.completed() && 'completed'}, children: [ 14 | {tag: "div", attrs: {className:"view"}, children: [ 15 | {tag: "input", attrs: { 16 | className:"toggle", 17 | type:"checkbox", 18 | onclick:m.withAttr('checked', task.completed), 19 | checked:task.completed()} 20 | }, 21 | {tag: "label", attrs: {}, children: [task.title()]}, 22 | {tag: "button", attrs: {className:"destroy", onclick:ctrl.remove.bind(ctrl, index)}} 23 | ]}, 24 | {tag: "input", attrs: {className:"edit"}} 25 | ]};}) 26 | 27 | return {tag: "div", attrs: {id:"todoapp"}, children: [ 28 | {tag: "header", attrs: {id:"header"}, children: [ 29 | {tag: "h1", attrs: {}, children: ["todos"]}, 30 | {tag: "input", attrs: { 31 | id:"new-todo", 32 | placeholder:"What needs to be done?", 33 | onkeydown:function(e) { m.withAttr('value', ctrl.title)(e); ctrl.add(ctrl.title, e) }, 34 | value:ctrl.title()} 35 | } 36 | ]}, 37 | {tag: "section", attrs: {id:"main"}, children: [ 38 | {tag: "input", attrs: {id:"toggle-all", type:"checkbox"}}, 39 | {tag: "ul", attrs: {id:"todo-list"}, children: [ 40 | todos 41 | ]} 42 | ]}, 43 | {tag: "footer", attrs: {id:"footer"}, children: [ 44 | {tag: "span", attrs: {id:"todo-count"}, children: [ 45 | {tag: "strong", attrs: {}, children: [ctrl.list.length, " item", ctrl.list.length > 1 ? 's' : '', " left"]} 46 | ]}, 47 | {tag: "ul", attrs: {id:"filters"}, children: [ 48 | {tag: "li", attrs: {className:"selected"}, children: [ 49 | {tag: "a", attrs: {href:"#/"}, children: ["All"]} 50 | ]}, 51 | {tag: "li", attrs: {}, children: [ 52 | {tag: "a", attrs: {href:"#/active"}, children: ["Active"]} 53 | ]}, 54 | {tag: "li", attrs: {}, children: [ 55 | {tag: "a", attrs: {href:"#/completed"}, children: ["Completed"]} 56 | ]} 57 | ]}, 58 | clearCompleted 59 | ]} 60 | ]} 61 | } 62 | 63 | module.exports = view -------------------------------------------------------------------------------- /test/jsx/example.jsx: -------------------------------------------------------------------------------- 1 | // JSX version of the Mithril Getting Started documentation's TODO example. 2 | // http://lhorie.github.io/mithril/getting-started.html 3 | 4 | //this application only has one module: todo 5 | var todo = {}; 6 | 7 | //for simplicity, we use this module to namespace the model classes 8 | 9 | //the Todo class has two properties 10 | todo.Todo = function(data) { 11 | this.description = m.prop(data.description); 12 | this.done = m.prop(false); 13 | }; 14 | 15 | //the TodoList class is a list of Todo's 16 | todo.TodoList = Array; 17 | 18 | //the controller uses 3 model-level entities, of which one is a custom defined class: 19 | //`Todo` is the central class in this application 20 | //`list` is merely a generic array, with standard array methods 21 | //`description` is a temporary storage box that holds a string 22 | // 23 | //the `add` method simply adds a new todo to the list 24 | todo.controller = function() { 25 | this.list = new todo.TodoList(); 26 | this.description = m.prop(""); 27 | 28 | this.add = function(description) { 29 | if (description()) { 30 | this.list.push(new todo.Todo({description: description()})); 31 | this.description(""); 32 | } 33 | }; 34 | }; 35 | 36 | //here's the view 37 | todo.view = function(ctrl) { 38 | return 39 | 40 | 41 | 42 | 43 | {ctrl.list.map((task, index) => 44 | 51 | 54 | )} 55 |
    45 | 50 | 52 | {task.description()} 53 |
    56 | 57 | 58 | }; 59 | 60 | //initialize the application 61 | m.module(document, todo); -------------------------------------------------------------------------------- /test/jsx/test.jsx: -------------------------------------------------------------------------------- 1 | // JSX version of https://github.com/jpmonette/todomvc-mithril/blob/1d627f1d5dc0890d0a99cdcb16d09387f68f6df0/js/views/todo-view.js 2 | 3 | 'use strict'; 4 | 5 | function view(ctrl) { 6 | var clearCompleted 7 | if (ctrl.amountCompleted() != 0) { 8 | clearCompleted = 11 | } 12 | 13 | var todos = ctrl.list.map((task, index) =>
  • 14 |
    15 | 21 | 22 |
    24 | 25 |
  • ) 26 | 27 | return
    28 | 37 |
    38 | 39 |
      40 | {todos} 41 |
    42 |
    43 |
    44 | 45 | {ctrl.list.length} item{ctrl.list.length > 1 ? 's' : ''} left 46 | 47 | 58 | {clearCompleted} 59 |
    60 |
    61 | } 62 | 63 | module.exports = view -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | var test = require('tape').test 2 | 3 | var transform = require('../main').transform 4 | 5 | test('tag variations (default options)', function(t) { 6 | t.plan(20) 7 | var tagTests = { 8 | '
    ': '{tag: "br", attrs: {}}' 9 | , '
    ': '{tag: "div", attrs: {}}' 10 | , '
    ': '{tag: "div", attrs: {}}' 11 | , '
    X
    ': '{tag: "div", attrs: {}, children: ["X"]}' 12 | , '
    {X}
    ': '{tag: "div", attrs: {}, children: [X]}' 13 | , '
    ': '{tag: "div", attrs: {id:"test"}}' 14 | , '
    X
    ': '{tag: "div", attrs: {id:"test", className:"test"}, children: ["X"]}' 15 | , '
    X{X} X {X}
    ': '{tag: "div", attrs: {}, children: ["X", X, " X ", X]}' 16 | , '

    ': '{tag: "div", attrs: {}, children: [{tag: "p", attrs: {}}]}' 17 | , '

    X

    ': '{tag: "div", attrs: {}, children: [{tag: "p", attrs: {id:"test"}, children: ["X"]}]}' 18 | , 'X': '{tag: "unknowntag", attrs: {}, children: ["X"]}' 19 | , 'X': '{tag: "unknowntag", attrs: {id:"test"}, children: ["X"]}' 20 | , 'X': '{tag: "unknown-tag", attrs: {}, children: ["X"]}' 21 | , 'X': '{tag: "unknown-tag", attrs: {id:"test"}, children: ["X"]}' 22 | , '': 'Component' 23 | , '': 'Component' 24 | , 'X': 'm.component(Component, {}, ["X"])' 25 | , '': 'm.component(Component, {id:"test"})' 26 | , 'X': 'm.component(Component, {id:"test"}, ["X"])' 27 | , 'X{X} X {X}': 'm.component(Component, {id:"test"}, ["X", X, " X ", X])' 28 | } 29 | var tags = Object.keys(tagTests) 30 | tags.forEach(function(tag) { 31 | var result = transform(tag).split('\n').pop() 32 | t.equal(result, tagTests[tag], tag + ' -> ' + tagTests[tag]) 33 | }) 34 | }) 35 | 36 | test('tag variations (precompile: false)', function(t) { 37 | t.plan(14) 38 | var tagTests = { 39 | '
    ': 'm("br")' 40 | , '
    ': 'm("div")' 41 | , '
    ': 'm("div")' 42 | , '
    X
    ': 'm("div", ["X"])' 43 | , '
    {X}
    ': 'm("div", [X])' 44 | , '
    ': 'm("div", {id:"test"})' 45 | , '
    X
    ': 'm("div", {id:"test", className:"test"}, ["X"])' 46 | , '
    X{X} X {X}
    ': 'm("div", ["X", X, " X ", X])' 47 | , '

    ': 'm("div", [m("p")])' 48 | , '

    X

    ': 'm("div", [m("p", {id:"test"}, ["X"])])' 49 | , 'X': 'm("unknown", ["X"])' 50 | , 'X': 'm("unknown", {id:"test"}, ["X"])' 51 | , 'X': 'm("unknown-tag", ["X"])' 52 | , 'X': 'm("unknown-tag", {id:"test"}, ["X"])' 53 | } 54 | var tags = Object.keys(tagTests) 55 | tags.forEach(function(tag) { 56 | var result = transform(tag, {precompile: false}).split('\n').pop() 57 | t.equal(result, tagTests[tag], tag + ' -> ' + tagTests[tag]) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /test/vendor/mithril-0.2.0.js: -------------------------------------------------------------------------------- 1 | var m = (function app(window, undefined) { 2 | var OBJECT = "[object Object]", ARRAY = "[object Array]", STRING = "[object String]", FUNCTION = "function"; 3 | var type = {}.toString; 4 | var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/; 5 | var voidElements = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/; 6 | var noop = function() {} 7 | 8 | // caching commonly used variables 9 | var $document, $location, $requestAnimationFrame, $cancelAnimationFrame; 10 | 11 | // self invoking function needed because of the way mocks work 12 | function initialize(window){ 13 | $document = window.document; 14 | $location = window.location; 15 | $cancelAnimationFrame = window.cancelAnimationFrame || window.clearTimeout; 16 | $requestAnimationFrame = window.requestAnimationFrame || window.setTimeout; 17 | } 18 | 19 | initialize(window); 20 | 21 | 22 | /** 23 | * @typedef {String} Tag 24 | * A string that looks like -> div.classname#id[param=one][param2=two] 25 | * Which describes a DOM node 26 | */ 27 | 28 | /** 29 | * 30 | * @param {Tag} The DOM node tag 31 | * @param {Object=[]} optional key-value pairs to be mapped to DOM attrs 32 | * @param {...mNode=[]} Zero or more Mithril child nodes. Can be an array, or splat (optional) 33 | * 34 | */ 35 | function m() { 36 | var args = [].slice.call(arguments); 37 | var hasAttrs = args[1] != null && type.call(args[1]) === OBJECT && !("tag" in args[1] || "view" in args[1]) && !("subtree" in args[1]); 38 | var attrs = hasAttrs ? args[1] : {}; 39 | var classAttrName = "class" in attrs ? "class" : "className"; 40 | var cell = {tag: "div", attrs: {}}; 41 | var match, classes = []; 42 | if (type.call(args[0]) != STRING) throw new Error("selector in m(selector, attrs, children) should be a string") 43 | while (match = parser.exec(args[0])) { 44 | if (match[1] === "" && match[2]) cell.tag = match[2]; 45 | else if (match[1] === "#") cell.attrs.id = match[2]; 46 | else if (match[1] === ".") classes.push(match[2]); 47 | else if (match[3][0] === "[") { 48 | var pair = attrParser.exec(match[3]); 49 | cell.attrs[pair[1]] = pair[3] || (pair[2] ? "" :true) 50 | } 51 | } 52 | 53 | var children = hasAttrs ? args.slice(2) : args.slice(1); 54 | if (children.length === 1 && type.call(children[0]) === ARRAY) { 55 | cell.children = children[0] 56 | } 57 | else { 58 | cell.children = children 59 | } 60 | 61 | for (var attrName in attrs) { 62 | if (attrs.hasOwnProperty(attrName)) { 63 | if (attrName === classAttrName && attrs[attrName] != null && attrs[attrName] !== "") { 64 | classes.push(attrs[attrName]) 65 | cell.attrs[attrName] = "" //create key in correct iteration order 66 | } 67 | else cell.attrs[attrName] = attrs[attrName] 68 | } 69 | } 70 | if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" "); 71 | 72 | return cell 73 | } 74 | function build(parentElement, parentTag, parentCache, parentIndex, data, cached, shouldReattach, index, editable, namespace, configs) { 75 | //`build` is a recursive function that manages creation/diffing/removal of DOM elements based on comparison between `data` and `cached` 76 | //the diff algorithm can be summarized as this: 77 | //1 - compare `data` and `cached` 78 | //2 - if they are different, copy `data` to `cached` and update the DOM based on what the difference is 79 | //3 - recursively apply this algorithm for every array and for the children of every virtual element 80 | 81 | //the `cached` data structure is essentially the same as the previous redraw's `data` data structure, with a few additions: 82 | //- `cached` always has a property called `nodes`, which is a list of DOM elements that correspond to the data represented by the respective virtual element 83 | //- in order to support attaching `nodes` as a property of `cached`, `cached` is *always* a non-primitive object, i.e. if the data was a string, then cached is a String instance. If data was `null` or `undefined`, cached is `new String("")` 84 | //- `cached also has a `configContext` property, which is the state storage object exposed by config(element, isInitialized, context) 85 | //- when `cached` is an Object, it represents a virtual element; when it's an Array, it represents a list of elements; when it's a String, Number or Boolean, it represents a text node 86 | 87 | //`parentElement` is a DOM element used for W3C DOM API calls 88 | //`parentTag` is only used for handling a corner case for textarea values 89 | //`parentCache` is used to remove nodes in some multi-node cases 90 | //`parentIndex` and `index` are used to figure out the offset of nodes. They're artifacts from before arrays started being flattened and are likely refactorable 91 | //`data` and `cached` are, respectively, the new and old nodes being diffed 92 | //`shouldReattach` is a flag indicating whether a parent node was recreated (if so, and if this node is reused, then this node must reattach itself to the new parent) 93 | //`editable` is a flag that indicates whether an ancestor is contenteditable 94 | //`namespace` indicates the closest HTML namespace as it cascades down from an ancestor 95 | //`configs` is a list of config functions to run after the topmost `build` call finishes running 96 | 97 | //there's logic that relies on the assumption that null and undefined data are equivalent to empty strings 98 | //- this prevents lifecycle surprises from procedural helpers that mix implicit and explicit return statements (e.g. function foo() {if (cond) return m("div")} 99 | //- it simplifies diffing code 100 | //data.toString() might throw or return null if data is the return value of Console.log in Firefox (behavior depends on version) 101 | try {if (data == null || data.toString() == null) data = "";} catch (e) {data = ""} 102 | if (data.subtree === "retain") return cached; 103 | var cachedType = type.call(cached), dataType = type.call(data); 104 | if (cached == null || cachedType !== dataType) { 105 | if (cached != null) { 106 | if (parentCache && parentCache.nodes) { 107 | var offset = index - parentIndex; 108 | var end = offset + (dataType === ARRAY ? data : cached.nodes).length; 109 | clear(parentCache.nodes.slice(offset, end), parentCache.slice(offset, end)) 110 | } 111 | else if (cached.nodes) clear(cached.nodes, cached) 112 | } 113 | cached = new data.constructor; 114 | if (cached.tag) cached = {}; //if constructor creates a virtual dom element, use a blank object as the base cached node instead of copying the virtual el (#277) 115 | cached.nodes = [] 116 | } 117 | 118 | if (dataType === ARRAY) { 119 | //recursively flatten array 120 | for (var i = 0, len = data.length; i < len; i++) { 121 | if (type.call(data[i]) === ARRAY) { 122 | data = data.concat.apply([], data); 123 | i-- //check current index again and flatten until there are no more nested arrays at that index 124 | len = data.length 125 | } 126 | } 127 | 128 | var nodes = [], intact = cached.length === data.length, subArrayCount = 0; 129 | 130 | //keys algorithm: sort elements without recreating them if keys are present 131 | //1) create a map of all existing keys, and mark all for deletion 132 | //2) add new keys to map and mark them for addition 133 | //3) if key exists in new list, change action from deletion to a move 134 | //4) for each key, handle its corresponding action as marked in previous steps 135 | var DELETION = 1, INSERTION = 2 , MOVE = 3; 136 | var existing = {}, shouldMaintainIdentities = false; 137 | for (var i = 0; i < cached.length; i++) { 138 | if (cached[i] && cached[i].attrs && cached[i].attrs.key != null) { 139 | shouldMaintainIdentities = true; 140 | existing[cached[i].attrs.key] = {action: DELETION, index: i} 141 | } 142 | } 143 | 144 | var guid = 0 145 | for (var i = 0, len = data.length; i < len; i++) { 146 | if (data[i] && data[i].attrs && data[i].attrs.key != null) { 147 | for (var j = 0, len = data.length; j < len; j++) { 148 | if (data[j] && data[j].attrs && data[j].attrs.key == null) data[j].attrs.key = "__mithril__" + guid++ 149 | } 150 | break 151 | } 152 | } 153 | 154 | if (shouldMaintainIdentities) { 155 | var keysDiffer = false 156 | if (data.length != cached.length) keysDiffer = true 157 | else for (var i = 0, cachedCell, dataCell; cachedCell = cached[i], dataCell = data[i]; i++) { 158 | if (cachedCell.attrs && dataCell.attrs && cachedCell.attrs.key != dataCell.attrs.key) { 159 | keysDiffer = true 160 | break 161 | } 162 | } 163 | 164 | if (keysDiffer) { 165 | for (var i = 0, len = data.length; i < len; i++) { 166 | if (data[i] && data[i].attrs) { 167 | if (data[i].attrs.key != null) { 168 | var key = data[i].attrs.key; 169 | if (!existing[key]) existing[key] = {action: INSERTION, index: i}; 170 | else existing[key] = { 171 | action: MOVE, 172 | index: i, 173 | from: existing[key].index, 174 | element: cached.nodes[existing[key].index] || $document.createElement("div") 175 | } 176 | } 177 | } 178 | } 179 | var actions = [] 180 | for (var prop in existing) actions.push(existing[prop]) 181 | var changes = actions.sort(sortChanges); 182 | var newCached = new Array(cached.length) 183 | newCached.nodes = cached.nodes.slice() 184 | 185 | for (var i = 0, change; change = changes[i]; i++) { 186 | if (change.action === DELETION) { 187 | clear(cached[change.index].nodes, cached[change.index]); 188 | newCached.splice(change.index, 1) 189 | } 190 | if (change.action === INSERTION) { 191 | var dummy = $document.createElement("div"); 192 | dummy.key = data[change.index].attrs.key; 193 | parentElement.insertBefore(dummy, parentElement.childNodes[change.index] || null); 194 | newCached.splice(change.index, 0, {attrs: {key: data[change.index].attrs.key}, nodes: [dummy]}) 195 | newCached.nodes[change.index] = dummy 196 | } 197 | 198 | if (change.action === MOVE) { 199 | if (parentElement.childNodes[change.index] !== change.element && change.element !== null) { 200 | parentElement.insertBefore(change.element, parentElement.childNodes[change.index] || null) 201 | } 202 | newCached[change.index] = cached[change.from] 203 | newCached.nodes[change.index] = change.element 204 | } 205 | } 206 | cached = newCached; 207 | } 208 | } 209 | //end key algorithm 210 | 211 | for (var i = 0, cacheCount = 0, len = data.length; i < len; i++) { 212 | //diff each item in the array 213 | var item = build(parentElement, parentTag, cached, index, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace, configs); 214 | if (item === undefined) continue; 215 | if (!item.nodes.intact) intact = false; 216 | if (item.$trusted) { 217 | //fix offset of next element if item was a trusted string w/ more than one html element 218 | //the first clause in the regexp matches elements 219 | //the second clause (after the pipe) matches text nodes 220 | subArrayCount += (item.match(/<[^\/]|\>\s*[^<]/g) || [0]).length 221 | } 222 | else subArrayCount += type.call(item) === ARRAY ? item.length : 1; 223 | cached[cacheCount++] = item 224 | } 225 | if (!intact) { 226 | //diff the array itself 227 | 228 | //update the list of DOM nodes by collecting the nodes from each item 229 | for (var i = 0, len = data.length; i < len; i++) { 230 | if (cached[i] != null) nodes.push.apply(nodes, cached[i].nodes) 231 | } 232 | //remove items from the end of the array if the new array is shorter than the old one 233 | //if errors ever happen here, the issue is most likely a bug in the construction of the `cached` data structure somewhere earlier in the program 234 | for (var i = 0, node; node = cached.nodes[i]; i++) { 235 | if (node.parentNode != null && nodes.indexOf(node) < 0) clear([node], [cached[i]]) 236 | } 237 | if (data.length < cached.length) cached.length = data.length; 238 | cached.nodes = nodes 239 | } 240 | } 241 | else if (data != null && dataType === OBJECT) { 242 | var views = [], controllers = [] 243 | while (data.view) { 244 | var view = data.view.$original || data.view 245 | var controllerIndex = m.redraw.strategy() == "diff" && cached.views ? cached.views.indexOf(view) : -1 246 | var controller = controllerIndex > -1 ? cached.controllers[controllerIndex] : new (data.controller || noop) 247 | var key = data && data.attrs && data.attrs.key 248 | data = pendingRequests == 0 || (cached && cached.controllers && cached.controllers.indexOf(controller) > -1) ? data.view(controller) : {tag: "placeholder"} 249 | if (data.subtree === "retain") return cached; 250 | if (key) { 251 | if (!data.attrs) data.attrs = {} 252 | data.attrs.key = key 253 | } 254 | if (controller.onunload) unloaders.push({controller: controller, handler: controller.onunload}) 255 | views.push(view) 256 | controllers.push(controller) 257 | } 258 | if (!data.tag && controllers.length) throw new Error("Component template must return a virtual element, not an array, string, etc.") 259 | if (!data.attrs) data.attrs = {}; 260 | if (!cached.attrs) cached.attrs = {}; 261 | 262 | var dataAttrKeys = Object.keys(data.attrs) 263 | var hasKeys = dataAttrKeys.length > ("key" in data.attrs ? 1 : 0) 264 | //if an element is different enough from the one in cache, recreate it 265 | if (data.tag != cached.tag || dataAttrKeys.sort().join() != Object.keys(cached.attrs).sort().join() || data.attrs.id != cached.attrs.id || data.attrs.key != cached.attrs.key || (m.redraw.strategy() == "all" && (!cached.configContext || cached.configContext.retain !== true)) || (m.redraw.strategy() == "diff" && cached.configContext && cached.configContext.retain === false)) { 266 | if (cached.nodes.length) clear(cached.nodes); 267 | if (cached.configContext && typeof cached.configContext.onunload === FUNCTION) cached.configContext.onunload() 268 | if (cached.controllers) { 269 | for (var i = 0, controller; controller = cached.controllers[i]; i++) { 270 | if (typeof controller.onunload === FUNCTION) controller.onunload({preventDefault: noop}) 271 | } 272 | } 273 | } 274 | if (type.call(data.tag) != STRING) return; 275 | 276 | var node, isNew = cached.nodes.length === 0; 277 | if (data.attrs.xmlns) namespace = data.attrs.xmlns; 278 | else if (data.tag === "svg") namespace = "http://www.w3.org/2000/svg"; 279 | else if (data.tag === "math") namespace = "http://www.w3.org/1998/Math/MathML"; 280 | 281 | if (isNew) { 282 | if (data.attrs.is) node = namespace === undefined ? $document.createElement(data.tag, data.attrs.is) : $document.createElementNS(namespace, data.tag, data.attrs.is); 283 | else node = namespace === undefined ? $document.createElement(data.tag) : $document.createElementNS(namespace, data.tag); 284 | cached = { 285 | tag: data.tag, 286 | //set attributes first, then create children 287 | attrs: hasKeys ? setAttributes(node, data.tag, data.attrs, {}, namespace) : data.attrs, 288 | children: data.children != null && data.children.length > 0 ? 289 | build(node, data.tag, undefined, undefined, data.children, cached.children, true, 0, data.attrs.contenteditable ? node : editable, namespace, configs) : 290 | data.children, 291 | nodes: [node] 292 | }; 293 | if (controllers.length) { 294 | cached.views = views 295 | cached.controllers = controllers 296 | for (var i = 0, controller; controller = controllers[i]; i++) { 297 | if (controller.onunload && controller.onunload.$old) controller.onunload = controller.onunload.$old 298 | if (pendingRequests && controller.onunload) { 299 | var onunload = controller.onunload 300 | controller.onunload = noop 301 | controller.onunload.$old = onunload 302 | } 303 | } 304 | } 305 | 306 | if (cached.children && !cached.children.nodes) cached.children.nodes = []; 307 | //edge case: setting value on