├── .codeclimate.yml ├── .coveralls.yml ├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── avalon.cookbook.mobi ├── avalon1与avalon2的异同.md ├── build.js ├── buildIE6.js ├── buildIE6Sauce.js ├── buildIE6Test.js ├── buildIE9.js ├── buildIE9Test.js ├── buildTap.js ├── components ├── button │ └── index.js ├── grid.html ├── list │ └── index.html ├── loading │ └── 外面包一个专门loading的组件.html ├── logo.png ├── pager │ ├── component形式.html │ └── 非component形式.html ├── panel │ ├── index.js │ └── template.html ├── router │ ├── README.md │ ├── first.html │ ├── firstVm.js │ ├── heredoc.js │ ├── index.html │ ├── main.js │ ├── mmRouter.js │ ├── pagination.js │ ├── second.html │ ├── secondVm.js │ └── third.html └── select │ └── index.html ├── dist ├── arthur.js ├── avalon.js └── avalon.modern.js ├── index2.html ├── issue_template.md ├── karma.conf.js ├── karma.sauce.js ├── package.json ├── perf ├── ENV.js ├── _index.js ├── app.js ├── archur.html ├── component │ ├── onViewChange的触发.html │ ├── router │ │ ├── grid.template.html │ │ ├── ms-grid.css │ │ ├── ms-grid.js │ │ ├── tab1.html │ │ ├── tab1.js │ │ ├── tab2.html │ │ └── tab3.html │ ├── simplegrid.html │ ├── soleSlot的使用.html │ ├── 不带ms-的自定义标签.html │ ├── 与duplex联动.html │ ├── 使用slot做切换卡.html │ ├── 单个按钮.html │ ├── 多个slot.html │ ├── 弹出层(组件套组件).html │ ├── 循环生成必须加id.html │ ├── 按钮组件的不同声明方式.html │ ├── 树(组件形式).html │ ├── 树.html │ ├── 树2.html │ ├── 生命周期.html │ ├── 组件容器与组件模板的属性的优先级.html │ ├── 组件的VM.html │ └── 路由组件.html ├── controller │ ├── controller.html │ └── important.html ├── duplex │ ├── duplex.html │ ├── duplex2.html │ └── duplex3.html ├── effect │ ├── stagger.html │ └── 动画系统.html ├── event │ ├── on.html │ ├── on2.html │ └── swipe.html ├── for │ └── index.html ├── index-ko.html ├── index-ng.html ├── index-react.html ├── index-vue.html ├── index.html ├── index1.4.html ├── memory-stats.js ├── mithril.js ├── monitor.js ├── repaint.html ├── repeat │ ├── for.html │ ├── for2.html │ ├── for3.html │ ├── for4.html │ ├── for5.html │ ├── for_duplex.html │ └── 商品促销截止期变化例子.html ├── style.css ├── widget │ ├── index0.html │ ├── index1.html │ ├── index10.html │ ├── index11.html │ ├── index12.html │ ├── index13.html │ ├── index2.html │ ├── index3.html │ ├── index4.html │ ├── index5.html │ ├── index6.html │ ├── index7.html │ ├── index8.html │ └── index9.html └── 重点问题.html ├── router.build.js ├── src ├── REAMD.md ├── avalon.js ├── avalon.modern.js ├── avalon.tap.js ├── component │ └── index.js ├── directives │ ├── attr.compact.js │ ├── attr.modern.js │ ├── class.hover.active.js │ ├── compact.js │ ├── controller.js │ ├── css.js │ ├── duplex │ │ ├── compact.js │ │ ├── modern.js │ │ ├── option.js │ │ ├── share.js │ │ ├── updateDataActions.js │ │ ├── updateDataEvents.compact.js │ │ ├── updateDataEvents.modern.js │ │ └── updateDataHandle.js │ ├── expr.js │ ├── for.js │ ├── html.js │ ├── if.js │ ├── important.js │ ├── modern.js │ ├── on.js │ ├── rules.js │ ├── skip.js │ ├── text.js │ ├── validate.js │ └── visible.js ├── dom │ ├── attr │ │ ├── compact.js │ │ ├── isVML.js │ │ ├── modern.js │ │ ├── parseJSON.compact.js │ │ └── propMap.js │ ├── class │ │ ├── compact.js │ │ └── modern.js │ ├── compact.js │ ├── css │ │ ├── compact.js │ │ ├── modern.js │ │ └── share.js │ ├── event │ │ ├── canBubbleUp.js │ │ ├── compact.js │ │ ├── modern.js │ │ └── share.js │ ├── html │ │ └── index.js │ ├── modern.js │ ├── rcheckedType.js │ ├── ready │ │ ├── compact.js │ │ └── modern.js │ ├── shim │ │ ├── compact.js │ │ ├── fixClone.js │ │ ├── fixContains.js │ │ └── modern.js │ └── val │ │ ├── compact.js │ │ ├── getDuplexType.js │ │ ├── modern.js │ │ └── option.compact.js ├── effect │ ├── detect.js │ └── index.js ├── filters │ ├── array.js │ ├── date.js │ ├── escape.js │ ├── event.js │ ├── index.js │ ├── number.js │ └── sanitize.js ├── gesture │ ├── drag.js │ ├── pinch.js │ ├── press.js │ ├── readme.md │ ├── recognizer.js │ ├── rotate.js │ ├── swipe.js │ └── tap.js ├── pager.js ├── parser │ ├── attributes.js │ ├── index.js │ └── interpolate.js ├── renders │ ├── Directive.js │ ├── domRender.js │ ├── serverRender.js │ └── share.js ├── routergrid.js ├── seed │ ├── browser.js │ ├── cache.js │ ├── core.js │ ├── directive.js │ ├── lang.compact.js │ ├── lang.fix.js │ └── lang.modern.js ├── vdom │ ├── VComment.js │ ├── VElement.js │ ├── VElement.modern.js │ ├── VFragment.js │ ├── VText.js │ ├── compact.js │ └── modern.js ├── vmodel │ ├── Action.js │ ├── Computed.js │ ├── Mutation.js │ ├── ProxyArray.js │ ├── compact.js │ ├── modern.js │ ├── proxy.js │ ├── reserved.js │ ├── share.js │ └── transaction.js └── vtree │ ├── clearString.js │ ├── fromDOM.js │ ├── fromString.js │ ├── makeOrphan.js │ ├── makeTbody.js │ ├── orphanTag.js │ ├── validateDOMNesting.js │ └── voidTag.js ├── structure.jpg ├── test ├── beforeIt.js ├── directives │ ├── active.spec.js │ ├── attr.spec.js │ ├── class.spec.js │ ├── controller.spec.js │ ├── css.spec.js │ ├── duplex.spec.js │ ├── effect.spec.js │ ├── expr.spec.js │ ├── for.spec.js │ ├── hover.spec.js │ ├── if.spec.js │ ├── important.spec.js │ ├── on.spec.js │ ├── rules.spec.js │ ├── text.spec.js │ ├── validate.spec.js │ ├── visible.spec.js │ └── widget.spec.js ├── dom │ ├── attr.compact.spec.js │ ├── attr.modern.spec.js │ ├── class.compact.spec.js │ ├── class.modern.spec.js │ ├── css.compact.spec.js │ ├── css.modern.spec.js │ ├── event.compact.spec.js │ ├── event.modern.spec.js │ ├── html.spec.js │ ├── ready.compact.spec.js │ ├── ready.modern.spec.js │ ├── shim.compact.spec.js │ ├── shim.modern.spec.js │ ├── val.compact.spec.js │ └── val.modern.spec.js ├── filters │ └── index.spec.js ├── jquery.js ├── matchers.js ├── parser │ └── index.js ├── promise.js ├── seed │ ├── browser.spec.js │ ├── cache.spec.js │ ├── core.spec.js │ ├── lang.compact.spec.js │ └── lang.modern.spec.js ├── spec.js ├── spec.modern.js ├── test.js ├── vdom │ ├── compact.spec.js │ └── modern.spec.js ├── vmodel │ ├── compact.spec.js │ └── modern.spec.js └── vtree │ ├── clearString.spec.js │ ├── fromDOM.spec.js │ └── fromString.spec.js ├── tutorials ├── index.md ├── lesson01.md ├── lesson01_0.gif ├── lesson01_1.png ├── lesson02.md ├── lesson02.png ├── lesson03.html ├── lesson03.md ├── lesson03.png ├── lesson04.md ├── lesson04_0.gif ├── lesson04_1.gif ├── lesson04_2.gif ├── lesson05.md ├── lesson06.md ├── lesson06_0.gif ├── lesson06_1.gif ├── lesson06_2.gif ├── lesson07.md ├── lesson07_1.gif ├── lesson07_2.gif ├── lesson07_3.gif ├── lesson07_4.gif ├── lesson08.md ├── lesson08_1.gif ├── lesson08_2.gif ├── lesson09.md ├── lesson09_1.gif ├── lesson09_2.png ├── lesson09_3.gif ├── lesson09_4.gif ├── lesson10.md ├── lesson12.md └── lesson15.md ├── 手机上特殊处理.md └── 谁在用avalon.png /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | csslint: 4 | enabled: false 5 | duplication: 6 | enabled: false 7 | config: 8 | languages: 9 | - javascript 10 | eslint: 11 | enabled: true 12 | fixme: 13 | enabled: true 14 | ratings: 15 | paths: 16 | - "src/**.js" 17 | exclude_paths: 18 | - node_modules/**/* 19 | - test/**/* 20 | - tutorials/**/* 21 | - components/**/* 22 | - perf/**/* 23 | - dist/**/* -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | repo_token: 7PbHs1UhR24n9sP01rhKsHLXHaU4rUCvU 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !/karma/repeat.js 2 | /nbproject/private/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 6 4 | branches: 5 | only: 6 | - master 7 | cache: 8 | directories: 9 | - node_modules 10 | before_script: 11 | - export CHROME_BIN=chromium-browser 12 | - export DISPLAY=:99.0 13 | - sh -e /etc/init.d/xvfb start 14 | install: 15 | - npm install 16 | - npm run buildAvalon 17 | script: 18 | - npm run build 19 | 20 | addons: 21 | code_climate: 22 | repo_token: cecb8ec105dc8e4b3f298312502f078a646f46ff 23 | after_success: 24 | - bash <(curl -s https://codecov.io/bash) 25 | - codeclimate-test-reporter < lcov.info 26 | env: 27 | global: 28 | - CODECOV_TOKEN: 31e8c855-5499-473e-b2b6-ba1d142fbb9e 29 | - secure: oteqj2CHMOma5RCv4YM/QPhqMfEzDtoibgsCT+ABjTUoj36T7HDxQfgiUspv0Iv33i7TmqHBHAPFVSsF+vz00NOTejMaaFfELUuRn4BTZ5zDuz6e6VXy0jgF/7is7dVAf6cqVaQ0Fo+X+XCLg63/CaX23S3IlqS1jyfuX+fpNh8QfaIOIXlaZ8yy6pyfqb+lMfOj4TTwpyFIoyono0qSGLdaSG9BDdZK0skBPeBP3sFiwtptMmY8Mhn7lLiulU0NHl0WSfJMH9FMe9mHdhqsSrFwACKT58JbXCkKdTLjszpMv1gkl+gynzyLOV3f1WqBv/calW65PBWu7uNKNgBILGW5L3THL4Qs6unvzVngeFcZ6jf9B4dtxpS22K3Pq7SkhJOgXNiEa6RavFQm6OFSjBjILiQvMSkWA2PcAxbqDr9oEbe/snUoeK+BbYli2kV3b5TR+wyKBgn6Wms8+QbTr2N8OnAMwhgmPS4if7uqyKNPCfMy4J2HZ3OEbAPTUUYtSSWaEhXAtV5aAXI0/LWTsXsVtvnxcHGdPtN6GKJyKDqAxKWECdCU9Je6fxGcXaEzU3VTlm9HmXuC3UpTERU7Pq9vrpqxYaWtt/M1r0WAeBrPPdZ+XOpL1Qn4fWV9B259+jKI8GNg5+07H54crDBXaV04kLZODPmTe+n8Sq7PsS8= 30 | - secure: QpQKoIDD6GlDglfXBxzuAxM/qLhEAyNO2TwzzeEG7PstLaYo94K+j4gbkiFkwbb2Oj5R/0Jtro1tSXM733DLieVZNyV7VrMkcwkkisR1KfWBynfToDHCkHqM6Rca3PiiVRFHqAh6KmkGZ7EAXXdPbjF49ZqOQMjikD5WKoMkAqagH4dREArBw+Al2qCn41JuLm+uKCJ8YpZysULTSxRoyFqvvXgxDUn6/NjM08Mo5BVmKvANKLO73qjW8y6MeXoAMuQ11YWKz6Wu++GCQvkg9/qy6P6RcOYY5C2+zjmscyI+lZZ9XWEfPNLTUhMxHX+M3LV4dlGcs46q2TX5XItnUKlRqZ2FtjI33nF+kNDD+ts7oD+H/HvsvkZC3wg4KU15XtTe2OP2oxQgOtW2S3j66IHeiWMI+5fj/nVKtmQx9Or41dDCSQQ2+7ieZA+gF4cfVkDMVJcF2joB5Wbzl4E/UoCN7lo1uLdMSPZqW6ePUtBoGlc70/oUj0w6Ct1wqYhIBwX/2P7QLjNLuwW6e0SPOsQDaa3Svqog3/mZL6lae7ARrCjLYh31E6I0mDcz2ea5Y5bzXmgXfViGaGbrN6nlWP+hpHsPvdW+yVyxfHCXDFuHJdGMxErEnIAlEA2dqPtGz8GwV03L0a7z7Vv4bomFnxf5HAtuUwQ1tJToANEGexk= 31 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright RubyLouvre(司徒正美) and other contributors 2 | 3 | http://avalonjs.github.io/ 4 | 5 | The following license applies to all parts of this software except as 6 | documented below: 7 | 8 | ==== 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining 11 | a copy of this software and associated documentation files (the 12 | "Software"), to deal in the Software without restriction, including 13 | without limitation the rights to use, copy, modify, merge, publish, 14 | distribute, sublicense, and/or sell copies of the Software, and to 15 | permit persons to whom the Software is furnished to do so, subject to 16 | the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 25 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 26 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 27 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | ==== 30 | 31 | All files located in the node_modules and external directories are 32 | externally maintained libraries used by this software which have their 33 | own licenses; we recommend you read them, as their terms may differ from 34 | the terms above. -------------------------------------------------------------------------------- /avalon.cookbook.mobi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/avalon.cookbook.mobi -------------------------------------------------------------------------------- /avalon1与avalon2的异同.md: -------------------------------------------------------------------------------- 1 | avalon1与avalon2的异同 2 | ================ 3 | 4 | | 科题 | avalon1 | avalon2 | 5 | | :-------------: |:-----------------| :-----| 6 | | 如何得知某个属性被改动 | 使用VBScript,Object.defineProperty实现对=号的重写 | 在此基础增加Proxy的魔术监听 | 7 | | 如何更新视图 | 找到变动属性对应的订阅者数组,执行这些数组元素的update方法 | 使用vm.$render生成虚拟DOM树,diff,从上到下有序更新 | 8 | | 计算属性 | 支持 | 移除 | 9 | | 绑定属性的处理 | 扫描后删除 | 扫描后还留着| 10 | | 循环指令 | ms-repeat,ms-each,ms-with | ms-for | 11 | | 循环指令的语法 | ms-repeat-el='array' | ms-for="el in @array" | 12 | | 如何辩别指令中的vm属性 | avalon自行进行语法抽取 | 强制在前面带@或##符号 | 13 | | 垃圾回收 | 密封舱机制,负责清空订阅者数组 | 由于不保存绑定对象,没有CG的烦恼 | 14 | | 性能 | 一般,但能撑起上万个指令,瓶颈取决于绑定对象的所占内存| 原来的5倍以上,瓶颈取决于虚拟DOM的规模| 15 | | 最复杂的指令 | ms-repeat| ms-duplex | 16 | | 组件指令 | ms-widget='id,name,opts' | ms-widget='Array'传入一个对象数组,用法更灵活 | 17 | | 组件生命周期 | onInit, onDispose | onInit, onReady, onViewChange, onDispose| 18 | | 动画 |ms-effect |ms-effect(与angular的animate更接近)| 19 | | 如何操作组件 | 通过onInit取得组件vm进行操作 | 直接操作配置对象| 20 | | 如何对组件传入大片内容 | 使用ms-html或改成模板 | 通过slot机制| 21 | | 加载器 | 使用AMD风格的内置加载器 | 移除,使用webpack进行打包| 22 | | 模块化 | 源码里自由划分 | 使用nodejs的require与module.exports组织起来| 23 | | important指令 | 有 |有(让页面渲染更快)| 24 | | {{}}|不完全等价于ms-text | 完全等价ms-text | 25 | | if指令 | ms-if="Boolean" | ms-if="Boolean" | 26 | | attr指令 | ms-attr-name=value | ms-attr="object" object是一个对象,方便每次处理多个属性 | 27 | | class指令 | ms-class='xxx: toggle' | ms-class=’Array|String‘ 用法变了| 28 | | visible指令 | ms-visible="Boolean" | ms-visible="Boolean" | 29 | | 过滤器 | 只能用于innerText中的{{}}及ms-text, ms-html | 数量琳琅满目,所有指令都支持| 30 | | 模板指令 | ms-include | 移除,由于后端无法实现等价功能 | 31 | | 事件指令 | 普通的事件绑定 | 能支持事件代理的都用事件代理 | 32 | | 数据验证 | 使用oniui的validation |使用内置的ms-validate,ms-duplex,ms-rules| 33 | | 后端渲染 | 实现成本高昂 | 支持 | 34 | | 核心架构 | 观察者模式 + 属性劫持 | 大模板函数+虚拟DOM+属性劫持| -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | var array = [ 2 | require('./buildIE6'), 3 | require('./buildIE6Test'), 4 | // require('./buildIE6Sauce'), 5 | 6 | require('./buildIE9'), 7 | require('./buildIE9Test') 8 | ] 9 | Promise.all(array).then(function() { 10 | console.log('build complete!!!') 11 | }).catch(function() { 12 | console.log('build error!!!') 13 | }) -------------------------------------------------------------------------------- /buildIE6Sauce.js: -------------------------------------------------------------------------------- 1 | var rollup = require('rollup'); 2 | var fs = require('fs'); 3 | var less = require('semicolon-less') 4 | 5 | //var babel = require('rollup-plugin-babel'); 6 | var babel = require("babel-core"); 7 | 8 | var transform = require('es3ify').transform; 9 | 10 | // used to track the cache for subsequent bundles 11 | var cache; 12 | 13 | module.exports = rollup.rollup({ 14 | 15 | entry: 'test/spec.js', 16 | 17 | cache: cache, 18 | 19 | plugins: [ 20 | 21 | ] 22 | }).then(function(bundle) { 23 | // Generate bundle + sourcemap 24 | var result = bundle.generate({ 25 | format: 'umd', 26 | moduleName: 'avalon' 27 | }); 28 | // Cache our bundle for later use (optional) 29 | cache = bundle; 30 | result.code = result.code.replace( 31 | /Object\.defineProperty\(exports,\s*'__esModule',\s*\{\s*value:\s*true\s*\}\);/, 32 | "exports.__esModule = true"). 33 | // replace(/'use strict';?/, ''). 34 | replace(/avalon\$1/g, 'avalon') 35 | 36 | result = babel.transform(result.code, { 37 | presets: ['avalon'], 38 | compact: false 39 | }) 40 | 41 | var code = transform(result.code).replace(/\}\)\(undefined,/, '})(this,') 42 | fs.writeFileSync('./dist/avalon.sauce.js', less(code)); 43 | 44 | }).catch(function(e) { 45 | console.log('error', e) 46 | }) -------------------------------------------------------------------------------- /buildIE6Test.js: -------------------------------------------------------------------------------- 1 | var rollup = require('rollup'); 2 | var fs = require('fs'); 3 | var less = require('semicolon-less') 4 | 5 | var istanbul = require('rollup-plugin-istanbul'); 6 | //var babel = require('rollup-plugin-babel'); 7 | var babel = require("babel-core"); 8 | 9 | var transform = require('es3ify').transform; 10 | 11 | // used to track the cache for subsequent bundles 12 | var cache; 13 | 14 | module.exports = rollup.rollup({ 15 | 16 | entry: 'test/spec.js', 17 | 18 | cache: cache, 19 | 20 | plugins: [ 21 | istanbul({ 22 | exclude: ['test/**/*.js'] 23 | }) 24 | ] 25 | }).then(function(bundle) { 26 | // Generate bundle + sourcemap 27 | var result = bundle.generate({ 28 | format: 'umd', 29 | moduleName: 'avalon' 30 | }); 31 | // Cache our bundle for later use (optional) 32 | cache = bundle; 33 | result.code = result.code.replace( 34 | /Object\.defineProperty\(exports,\s*'__esModule',\s*\{\s*value:\s*true\s*\}\);/, 35 | "exports.__esModule = true"). 36 | // replace(/'use strict';?/, ''). 37 | replace(/avalon\$1/g, 'avalon') 38 | 39 | result = babel.transform(result.code, { 40 | presets: ['avalon'], 41 | compact: false 42 | }) 43 | 44 | var code = transform(result.code).replace(/\}\)\(undefined,/, '})(this,') 45 | fs.writeFileSync('./dist/avalon.test.js', less(code)); 46 | 47 | }).catch(function(e) { 48 | console.log('error', e) 49 | }) -------------------------------------------------------------------------------- /buildIE9Test.js: -------------------------------------------------------------------------------- 1 | var rollup = require('rollup'); 2 | var fs = require('fs'); 3 | var istanbul = require('rollup-plugin-istanbul'); 4 | var babel = require("babel-core"); 5 | var less = require('semicolon-less') 6 | 7 | // used to track the cache for subsequent bundles 8 | var cache; 9 | 10 | module.exports = rollup.rollup({ 11 | // The bundle's starting point. This file will be 12 | // included, along with the minimum necessary code 13 | // from its dependencies 14 | entry: 'test/spec.modern.js', 15 | // If you have a bundle you want to re-use (e.g., when using a watcher to rebuild as files change), 16 | // you can tell rollup use a previous bundle as its starting point. 17 | // This is entirely optional! 18 | cache: cache, 19 | 20 | plugins: [ 21 | // istanbul({ 22 | // exclude: ['test/**/*.js'] 23 | // }) 24 | ] 25 | }).then(function(bundle) { 26 | // Generate bundle + sourcemap 27 | var result = bundle.generate({ 28 | format: 'umd', 29 | moduleName: 'avalon' 30 | }); 31 | // Cache our bundle for later use (optional) 32 | cache = bundle; 33 | var code = result.code.replace( 34 | /Object\.defineProperty\(exports,\s*'__esModule',\s*\{\s*value:\s*true\s*\}\);/, 35 | "exports.__esModule = true"). 36 | // replace(/'use strict';?/,'') 37 | replace(/avalon\$1/g, 'avalon') 38 | 39 | result = babel.transform(code, { 40 | presets: ['avalon'], 41 | compact: false 42 | }) 43 | 44 | code = result.code.replace(/\}\)\(undefined,/, '})(this,') 45 | fs.writeFileSync('./dist/avalon.modern.test.js', less(code)); 46 | 47 | }).catch(function(e) { 48 | console.log('error', e) 49 | }) -------------------------------------------------------------------------------- /components/button/index.js: -------------------------------------------------------------------------------- 1 | //var avalon = require('avalon') 2 | 3 | avalon.component('ms-button', { 4 | template: '', 5 | defaults: { 6 | buttonText: "button" 7 | }, 8 | soleSlot: 'buttonText' 9 | }) -------------------------------------------------------------------------------- /components/grid.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | test 6 | 7 | 8 | 9 | 10 | 11 | 26 |
27 | 异步的模板与异步的数据 28 |
29 | 30 | 33 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /components/list/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 27 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /components/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/components/logo.png -------------------------------------------------------------------------------- /components/pager/component形式.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 47 | 48 | 49 | 50 |
51 | 52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /components/panel/index.js: -------------------------------------------------------------------------------- 1 | var button = require('../button/index') 2 | var tmpl = require('./template.html') 3 | avalon.component('ms-panel', { 4 | template: tmpl, 5 | defaults: { 6 | body: "  ", 7 | button: { 8 | buttonText: 'click me!' 9 | } 10 | }, 11 | soleSlot: 'body' 12 | }) -------------------------------------------------------------------------------- /components/panel/template.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |

6 |
-------------------------------------------------------------------------------- /components/router/README.md: -------------------------------------------------------------------------------- 1 | avalon2+mmRouter示例 2 | 3 | 基于2.2.1 4 | 5 | main.js是入口文件,会被打包成 dist目录下的main.js 6 | 7 | main.js构建了一个简单的mmState,大家通过addState方法不断添加子页面 8 | 9 | heredoc用了一些黑魔法,请不要对它进行压缩 10 | 11 | build脚本位于这里 12 | 13 | https://github.com/RubyLouvre/avalon/blob/master/karma.conf.js 14 | 15 | 需要另外npm install raw-loader webpack grunt 16 | -------------------------------------------------------------------------------- /components/router/firstVm.js: -------------------------------------------------------------------------------- 1 | var avalon = require('../../dist/avalon') 2 | avalon.parsers.card = function(a) { 3 | return a.replace(/\s/g, '').replace(/(\d{4})/g, "$1 ").trim() 4 | } 5 | var vm = avalon.define({ 6 | $id: "first", 7 | tabs: [111, 222, 333], 8 | activeIndex: 0, 9 | aaa: '', 10 | panels: ["面板1", "面板2", '

这里可以是复杂的HTML

'], 11 | formatCard: function(e) { 12 | var el = e.target 13 | var caret = el.selectionStart 14 | var value = el.value 15 | var prev = value.slice(0, caret) 16 | var sp = (prev.match(/\s/) || []).length 17 | var curr = value.replace(/\s/g, '').replace(/(\d{4})/g, "$1 ").trim() 18 | var now = curr.slice(0, caret) 19 | var curSp = (now.match(/\s/) || []).length 20 | el.value = curr 21 | //同步到ms-duplex中的pos去 22 | el._ms_duplex_.pos = caret + curSp - sp 23 | } 24 | 25 | }) 26 | //成绩单 27 | //大家可以对比一下1.*的相同实现 28 | //http://www.cnblogs.com/rubylouvre/p/3213430.html 29 | var model = avalon.define({ 30 | $id: 'transcript', 31 | id: '', 32 | name: '', 33 | score: 0, 34 | total: 0, 35 | array: [], 36 | add: function() { 37 | this.array.push({ 38 | id: this.id, 39 | name: this.name, 40 | score: this.score 41 | }) 42 | } 43 | }) 44 | 45 | model.$watch("score", function(a) { 46 | var a = Number(a) || 0 47 | a = a > 100 ? 100 : a < 0 ? 0 : a //强制转换为0~100间 48 | model.score = a 49 | }) 50 | model.$watch("array", function() { 51 | var a = 0 52 | model.array.forEach(function(el) { 53 | a += el.score //求得总数 54 | }) 55 | model.total = a; 56 | model.id = "" 57 | model.name = "" 58 | model.score = 0 59 | }) 60 | model.array = [ 61 | { id: "d1", name: "李世民", score: 67 }, 62 | { id: "d2", name: "赢政", score: 90 } 63 | ] 64 | module.exports = vm -------------------------------------------------------------------------------- /components/router/heredoc.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 注意不要压缩这个模块 3 | */ 4 | module.exports = function heredoc(fn) { 5 | return fn.toString().replace(/^[^\/]+\/\*!?\s?/, ''). 6 | replace(/\*\/[^\/]+$/, '').trim().replace(/>\s*<') 7 | } 8 | -------------------------------------------------------------------------------- /components/router/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TODO supply a title 5 | 6 | 7 | 8 | 9 | 10 |

点我|点我|点我

11 |

{{@currPath}}

12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /components/router/main.js: -------------------------------------------------------------------------------- 1 | var avalon = require('../../dist/avalon') 2 | var mmRouter = require('./mmRouter') 3 | var html1 = require('./first.html') 4 | var html2 = require('./second.html') 5 | var html3 = require('./third.html') 6 | var vm1 = require('./firstVm') 7 | var vm2 = require('./secondVm') 8 | 9 | var root = avalon.define({ 10 | $id: 'main', 11 | currPath: 'aaa',//只是用于测试 12 | currPage: 'aaa' //这是有用的 13 | }) 14 | 15 | 16 | var states = {} 17 | 18 | function addState(path, vm, html) { 19 | states[path] = { 20 | vm: vm, 21 | html: html 22 | } 23 | } 24 | 25 | addState('aaa', vm1, html1) 26 | 27 | addState('bbb', vm2, html2) 28 | 29 | addState('ccc', avalon.define({ 30 | $id: 'third', 31 | aaa: 333 32 | }), html3) 33 | 34 | 35 | avalon.component('ms-view', { 36 | template: '
', 37 | defaults: { 38 | page: ' ', 39 | path: 'no', 40 | 41 | onReady: function(e) { 42 | var path = e.vmodel.path 43 | var state = states[path] 44 | avalon.vmodels[state.vm.$id] = state.vm 45 | setTimeout(function() {//必须等它扫描完这个template,才能替换 46 | e.vmodel.page = state.html 47 | },100) 48 | 49 | }, 50 | onDispose: function(e) { 51 | var path = e.vmodel.path 52 | var state = states[path] 53 | var vm = state.vm 54 | var render = vm.render 55 | render && render.dispose() 56 | delete avalon.vmodels[vm.$id] 57 | } 58 | } 59 | }) 60 | 61 | function getPage(path) { 62 | path = path.slice(1) 63 | var html = '<xmp>' 64 | return html 65 | } 66 | 67 | avalon.router.add("/aaa", function(a) { 68 | root.currPath = this.path 69 | 70 | root.currPage = getPage(this.path) 71 | }) 72 | avalon.router.add("/bbb", function(a) { 73 | root.currPath = this.path 74 | root.currPage = getPage(this.path) 75 | }) 76 | avalon.router.add("/ccc", function(a) { 77 | root.currPath = this.path 78 | root.currPage = getPage(this.path) 79 | }) 80 | 81 | 82 | avalon.history.start({ 83 | root: "/mmRouter" 84 | }) 85 | avalon.ready(function() { 86 | avalon.scan(document.body) 87 | }) -------------------------------------------------------------------------------- /components/router/pagination.js: -------------------------------------------------------------------------------- 1 | var heredoc = require('./heredoc') 2 | var avalon = require('../../dist/avalon') 3 | avalon.component("ms-pager", { 4 | template: heredoc(function () { 5 | /* 6 | <div class="pagination"> 7 | <ul> 8 | <li :for="el in @pages" 9 | :class="[ el == @currentPage && 'active' ]"> 10 | <a href="javascript:void(0)" :click="@gotoPage(el, $event)">{{el}}</a> 11 | </li> 12 | </ul> 13 | </div> 14 | */ 15 | }), 16 | defaults: { 17 | totalPage: 25, 18 | currentPage: 1, 19 | showPage: 7, 20 | pages: [1, 2, 3, 4, 5, 6, 7], 21 | gotoPage: function (page, e) { 22 | this.currentPage = page; 23 | this.pages = this.getPages(); 24 | }, 25 | getPages: function () { 26 | var pages = []; 27 | var s = this.showPage, l = this.currentPage, r = this.currentPage, c = this.totalPage; 28 | pages.push(l); 29 | while (true) { 30 | if (pages.length >= s) { 31 | break; 32 | } 33 | if (l > 1) { 34 | pages.unshift(--l); 35 | } 36 | if (pages.length >= s) { 37 | break; 38 | } 39 | if (r < c) { 40 | pages.push(++r); 41 | } 42 | } 43 | 44 | return pages; 45 | } 46 | } 47 | }); -------------------------------------------------------------------------------- /components/router/second.html: -------------------------------------------------------------------------------- 1 | <div ms-important="widget1"> 2 | <style> 3 | .header { 4 | border:1px solid #000; 5 | width: 600px; 6 | border-collapse: collapse; 7 | } 8 | .header td{ 9 | border:1px solid #000; 10 | text-align: center; 11 | font-weight: 700; 12 | height:30px; 13 | color: #607fa6; 14 | font-weight: 700; 15 | } 16 | .tbody{ 17 | width: 600px; 18 | margin-top: -1px; 19 | border:1px solid #000; 20 | border-collapse: collapse; 21 | } 22 | .tbody td{ 23 | border:1px solid #000; 24 | height: 30px; 25 | } 26 | 27 | .pagination ul{ 28 | list-style: none; 29 | margin: 0; 30 | padding: 0; 31 | } 32 | .pagination li{ 33 | float: left; 34 | } 35 | .pagination li a{ 36 | text-decoration: none; 37 | display: inline-block; 38 | width:40px; 39 | height: 30px; 40 | line-height: 30px; 41 | text-align: center; 42 | background: #fafafa; 43 | color:#000; 44 | 45 | } 46 | .pagination .active a{ 47 | background: #009a61; 48 | color:#fff; 49 | } 50 | .pager{ 51 | width:600px; 52 | background: #fafafa; 53 | } 54 | .pager > *{ 55 | float: right; 56 | 57 | } 58 | 59 | </style> 60 | <xmp cached="true" :widget="{id:'grid',is:'ms-grid'}"> 61 | <table slot='header' class="header"> 62 | <tr> 63 | <td :for="el in @header" style="width:200px" > 64 | {{el}} 65 | </td> 66 | </tr> 67 | </table> 68 | <table slot="tbody" class="tbody"> 69 | <tr :for="obj in @data | limitBy(@count, @start)"> 70 | <td :for="el in obj | selectBy(@header)" style="width:200px">{{el}}</td> 71 | </tr> 72 | </table> 73 | <ms-pager slot="pager" :widget="{onReady:@ready}" /> 74 | 75 | -------------------------------------------------------------------------------- /components/router/secondVm.js: -------------------------------------------------------------------------------- 1 | var avalon = require('../../dist/avalon') 2 | var heredoc = require('./heredoc') 3 | require('./pagination') 4 | 5 | 6 | function genData(n) { 7 | var list = [] 8 | for (var i = 0; i < n; i++) { 9 | list.push({ 10 | aaa: new Date - i, 11 | bbb: Math.random().toString(32).replace(/0\./, ""), 12 | ccc: (Math.random() + "").replace(/0\./, ""), 13 | ddd: i 14 | }) 15 | } 16 | return list 17 | } 18 | var vm = avalon.define({ 19 | $id: 'widget1', 20 | header: ['aaa', 'bbb', 'ccc'], 21 | start: 0, 22 | count: 10, 23 | data: genData(300), 24 | ready: function (e) { 25 | e.vmodel.$watch('currentPage', function (a) { 26 | vm.start = a - 1 27 | avalon.log(vm.start) 28 | }) 29 | }, 30 | ddd: 'bbb' 31 | }) 32 | avalon.component('ms-grid', { 33 | template: heredoc(function () { 34 | /* 35 |
36 |
37 |
38 |
39 |
40 | */ 41 | }), 42 | defaults: {} 43 | }) 44 | 45 | module.exports = vm -------------------------------------------------------------------------------- /components/router/third.html: -------------------------------------------------------------------------------- 1 |
2 | {{@aaa}} 3 |
-------------------------------------------------------------------------------- /index2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TODO supply a title 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 59 | 60 | 61 |
全选
55 | {{$index}}::{{el.checked}} 56 | 57 | 58 |
62 | 63 | 64 | -------------------------------------------------------------------------------- /issue_template.md: -------------------------------------------------------------------------------- 1 | 39 | 40 |

41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /perf/component/不带ms-的自定义标签.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 27 | 28 | 29 | 30 |

不带ms-的自定义标签

31 |
32 | 33 |
34 | 35 | -------------------------------------------------------------------------------- /perf/component/与duplex联动.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 40 | 41 | 42 | 43 |
44 | 45 |
46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /perf/component/单个按钮.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TODO supply a title 6 | 7 | 8 | 9 | 31 | 32 | 33 |

soleSlot的使用, 它会组件容器中innerHTML替换生成组件的slot元素

34 |
35 | {{@bb}} 36 | 37 |

38 | 39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /perf/component/多个slot.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TODO supply a title 6 | 7 | 8 | 9 | 20 | 21 | 22 |
23 | 24 | <div slot="tab">面板1</div> 25 | <hr> 26 | <div slot="tab">面板2</div> 27 | <hr> 28 | <div slot="tab">面板3</div> 29 | <hr> 30 | 31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /perf/component/循环生成必须加id.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | TODO supply a title 7 | 8 | 9 | 10 | 11 | 12 |
13 |

循环生成组件必须加$id

14 | 19 |
20 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /perf/component/按钮组件的不同声明方式.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ms-validate 5 | 6 | 7 | 8 | 27 | 28 | 29 | 30 |

avalon2的组件在高级浏览器拥有4种声明方式

31 |

换言之,一共可以用4种元素声明组件,包括ms-button与xmp, wbr, template

32 |

ms-button为自定义标签, 标签名已经声明了组件的类型, 可以省去ms-widget配置对象

33 |

其他方式,必须使用ms-widget配置对象,里面的is是必须存在的

34 | 35 | 36 | 37 | 38 | 39 |
40 |
41 | 42 | -------------------------------------------------------------------------------- /perf/component/组件容器与组件模板的属性的优先级.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TODO supply a title 6 | 7 | 8 | 9 | 33 | 34 | 35 |

组件容器与组件模板的属性的优先级

36 |

组件容器是用于声明组件的类型与位置,及将它的属性(普通属性与绑定)与属性名与里面slot元素传进组件

37 |

因此它们的优先级比组件模板中定义的优先级要高

38 |
39 | 40 | 41 |

42 |

{{@buttonText}}

43 |
44 | 45 | 46 | -------------------------------------------------------------------------------- /perf/component/组件的VM.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 45 | 46 | 47 |

组件的VM

48 |

组件的VM理论上是继承了它上方的所有组件的属性与方法, 49 | 但本例直接在组件模板上使用defaults中没有声明的方法是一个不好的写法

50 |
template中只允许使用defaults中的变量, slot中可以用上层VM中的变量,但最好都能过ms-widget对象传入
51 |
52 | 53 | {{@bbb}} 54 |
55 | 56 | 57 | -------------------------------------------------------------------------------- /perf/controller/controller.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | test 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

{{@page}}

14 |
15 |

16 |
17 |
18 |

中间调试用

19 |

20 |
{{@level}}-{{@page}}-{{@kind}}
21 |
22 |
23 |
24 |
25 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /perf/duplex/duplex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TODO supply a title 6 | 7 | 8 | 9 | 18 | 19 | 20 | 21 |
22 | 27 | 32 |

{{@bbb}}

33 |

{{@ccc}}

34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /perf/duplex/duplex2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TODO supply a title 5 | 6 | 7 | 8 | 23 | 26 | 27 | 28 | 29 |
30 |

使用了ms-duplex-number后只能输入number

31 | {{@aaa}} 32 | {{@bbb}} 33 | {{@ccc}} 34 | {{@ddd}} 35 |
36 | 37 | 38 | 39 |
{{@aa}} 40 | 46 |
47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /perf/duplex/duplex3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TODO supply a title 5 | 6 | 7 | 8 | 14 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /perf/effect/动画系统.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 15 | 50 | 51 | 52 | 53 |
54 | {{@aaa}} 58 |
59 | 60 | 61 | -------------------------------------------------------------------------------- /perf/event/on.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | test 5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 | {{el.name}} 13 | 删除 14 |
15 | {{item.name}} 16 |
17 |
18 |
19 |
20 | 31 | 32 | -------------------------------------------------------------------------------- /perf/event/on2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | test 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 | {{el}} 16 |
17 |
18 | 19 |
20 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /perf/event/swipe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TODO supply a title 5 | 6 | 7 | 8 | 16 | 23 | 24 | 25 |
TODO write content
26 | 27 | 28 | -------------------------------------------------------------------------------- /perf/index-vue.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TODO supply a title 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 |
{{y}}
18 | 19 |
20 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /perf/monitor.js: -------------------------------------------------------------------------------- 1 | var Monitoring = Monitoring || (function() { 2 | 3 | var stats = new MemoryStats(); 4 | stats.domElement.style.position = 'fixed'; 5 | stats.domElement.style.right = '0px'; 6 | stats.domElement.style.bottom = '0px'; 7 | document.body.appendChild( stats.domElement ); 8 | requestAnimationFrame(function rAFloop(){ 9 | stats.update(); 10 | requestAnimationFrame(rAFloop); 11 | }); 12 | 13 | var RenderRate = function () { 14 | var container = document.createElement( 'div' ); 15 | container.id = 'stats'; 16 | container.style.cssText = 'width:150px;opacity:0.9;cursor:pointer;position:fixed;right:80px;bottom:0px;'; 17 | 18 | var msDiv = document.createElement( 'div' ); 19 | msDiv.id = 'ms'; 20 | msDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#020;'; 21 | container.appendChild( msDiv ); 22 | 23 | var msText = document.createElement( 'div' ); 24 | msText.id = 'msText'; 25 | msText.style.cssText = 'color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px'; 26 | msText.innerHTML= 'Repaint rate: 0/sec'; 27 | msDiv.appendChild( msText ); 28 | 29 | var bucketSize = 20; 30 | var bucket = []; 31 | var lastTime = Date.now(); 32 | return { 33 | domElement: container, 34 | ping: function () { 35 | var start = lastTime; 36 | var stop = Date.now(); 37 | var rate = 1000 / (stop - start); 38 | bucket.push(rate); 39 | if (bucket.length > bucketSize) { 40 | bucket.shift(); 41 | } 42 | var sum = 0; 43 | for (var i = 0; i < bucket.length; i++) { 44 | sum = sum + bucket[i]; 45 | } 46 | msText.textContent = "Repaint rate: " + (sum / bucket.length).toFixed(2) + "/sec"; 47 | lastTime = stop; 48 | } 49 | } 50 | }; 51 | 52 | var renderRate = new RenderRate(); 53 | document.body.appendChild( renderRate.domElement ); 54 | 55 | return { 56 | memoryStats: stats, 57 | renderRate: renderRate 58 | }; 59 | 60 | })(); -------------------------------------------------------------------------------- /perf/repaint.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | dbmon (angular) 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 20 | 21 | 26 | 27 | 31 | 32 | 33 |
18 | {{db.dbname}} 19 | 22 | 23 | {{db.lastSample.nbQueries}} 24 | 25 | 28 | {{q.formatElapsed}} 29 | 30 |
34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /perf/repeat/for2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 18 | 26 | 27 | 28 |
29 | 30 |

Group这是标题

31 | 32 |
  
33 |
测试
34 |
35 | 内容2 {{ (idx1 < 1 ? 'red' : idx1 > 1 ? 'green' : 'blue') + '-' + item2 }} 36 |
37 | 38 | 39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /perf/repeat/for3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | TODO supply a title 7 | 8 | 9 | 10 | 29 | 30 | 31 |
32 | 35 | 36 | 37 | 38 | 39 |
40 |

41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /perf/repeat/for4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | TODO supply a title 7 | 8 | 9 | 10 | 31 | 32 | 33 |
34 | 35 | 36 | 37 | 40 | 41 |
38 |
39 |
42 |

43 |
44 | 45 | 46 | -------------------------------------------------------------------------------- /perf/repeat/for5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TODO supply a title 5 | 6 | 7 | 8 | 19 | 20 | 21 |
22 | 27 | 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /perf/style.css: -------------------------------------------------------------------------------- 1 | body {color:#333;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;margin:0;} 2 | label {display:inline-block;font-weight:700;margin-bottom:5px;} 3 | input[type=range] {display:block;width:100%;} 4 | table {border-collapse:collapse;border-spacing:0;} 5 | :before,:after {box-sizing: border-box;} 6 | 7 | .table > thead > tr > th,.table > tbody > tr > th,.table > tfoot > tr > th,.table > thead > tr > td,.table > tbody > tr > td,.table > tfoot > tr > td {border-top:1px solid #ddd;line-height:1.42857143;padding:8px;vertical-align:top;} 8 | .table {width:100%;} 9 | .table-striped > tbody > tr:nth-child(odd) > td,.table-striped > tbody > tr:nth-child(odd) > th {background:#f9f9f9;} 10 | 11 | .label {border-radius:.25em;color:#fff;display:inline;font-size:75%;font-weight:700;line-height:1;padding:.2em .6em .3em;text-align:center;vertical-align:baseline;white-space:nowrap;} 12 | .label-success {background-color:#5cb85c;} 13 | .label-warning {background-color:#f0ad4e;} 14 | 15 | .popover {background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;box-shadow:0 5px 10px rgba(0,0,0,.2);display:none;left:0;max-width:276px;padding:1px;position:absolute;text-align:left;top:0;white-space:normal;z-index:1010;} 16 | .popover>.arrow:after {border-width:10px;content:"";} 17 | .popover.left {margin-left:-10px;} 18 | .popover.left > .arrow {border-right-width:0;border-left-color:rgba(0,0,0,.25);margin-top:-11px;right:-11px;top:50%;} 19 | .popover.left > .arrow:after {border-left-color:#fff;border-right-width:0;bottom:-10px;content:" ";right:1px;} 20 | .popover > .arrow {border-width:11px;} 21 | .popover > .arrow,.popover>.arrow:after {border-color:transparent;border-style:solid;display:block;height:0;position:absolute;width:0;} 22 | 23 | .popover-content {padding:9px 14px;} 24 | 25 | .Query {position:relative;} 26 | .Query:hover .popover {display:block;left:-100%;width:100%;} 27 | -------------------------------------------------------------------------------- /perf/widget/index0.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 新组件系统测试 6 | 7 | 8 | 9 |
10 | {{@aaa}} 11 |
12 |

组件没有加载时

13 | 21 | 22 | -------------------------------------------------------------------------------- /perf/widget/index1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 新组件系统测试 6 | 7 | 8 | 9 |
10 | {{@aaa}} 11 |
12 |

你会看到111

13 | 27 | 28 | -------------------------------------------------------------------------------- /perf/widget/index10.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 新组件系统测试 6 | 7 | 8 | 9 |
10 | {{@aaa}} 11 |
12 |

ms-panel+ms-button组件

13 | 50 | 51 | -------------------------------------------------------------------------------- /perf/widget/index11.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 新组件系统测试 6 | 7 | 8 | 9 |
10 | {{@aaa}} 11 |
12 |

你会看到111,一会不见,一会又出现

13 | 38 | 39 | -------------------------------------------------------------------------------- /perf/widget/index12.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TODO supply a title 6 | 7 | 8 | 9 | 29 | 30 | 31 |

soleSlot的使用

32 |

它会组件容器中innerHTML替换生成组件的slot元素

33 |
34 | {{@bb}} 35 |

36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /perf/widget/index13.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TODO supply a title 6 | 7 | 8 | 9 | 31 | 32 | 33 |

soleSlot的使用

34 |

它会组件容器中innerHTML替换生成组件的slot元素

35 |
36 | {{@bb}} 37 |

38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /perf/widget/index2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 新组件系统测试 6 | 7 | 8 | 9 |
10 | {{@aaa}} 11 |
12 |

你会看到111

13 | 27 | 28 | -------------------------------------------------------------------------------- /perf/widget/index3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 新组件系统测试 6 | 7 | 8 | 9 |
10 | {{@aaa}} 11 |
12 |

你会看到111, 注意ms-attr会绑不上

13 | 29 | 30 | -------------------------------------------------------------------------------- /perf/widget/index4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 新组件系统测试 6 | 7 | 8 | 9 |
10 | {{@aaa}} 11 |
12 |

你会看到111,然后又变成222

13 | 31 | 32 | -------------------------------------------------------------------------------- /perf/widget/index5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 新组件系统测试 6 | 7 | 8 | 9 |
10 |
11 | {{@aaa}} 12 |
13 |

这个元素不受影响

14 |
15 |

使用ms-if, 开始看不见, 后来又出现

16 | 50 | 51 | -------------------------------------------------------------------------------- /perf/widget/index6.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 新组件系统测试 6 | 7 | 8 | 9 |
10 | {{@aaa}} 11 |
12 |

测试生命周期

13 | 45 | 46 | -------------------------------------------------------------------------------- /perf/widget/index7.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 新组件系统测试 6 | 7 | 8 | 9 |
10 | {{@aaa}} 11 |
12 |

使用ms-attr, 为元素修改title

13 | 46 | 47 | -------------------------------------------------------------------------------- /perf/widget/index8.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 新组件系统测试 6 | 7 | 8 | 9 |
10 | {{@aaa}} 11 |
12 |

ms-panel+ms-button组件

13 | 66 | 67 | -------------------------------------------------------------------------------- /perf/widget/index9.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 新组件系统测试 6 | 7 | 8 | 9 |
10 | {{@aaa}} 11 |
12 |

ms-panel+ms-button组件

13 | 61 | 62 | -------------------------------------------------------------------------------- /perf/重点问题.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Title 7 | 8 | 9 | 10 |
11 | 12 |
13 |

父组件数据同步测试

14 |
15 | 父组件:{{@str}} 16 |
父组件:{{@config.test}} 17 |
父组件:{{@config.testObj.objStr}} 18 |
19 |
20 |
21 | 22 | 23 | 24 | 25 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /router.build.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack') 2 | var path = require('path') 3 | //sudo npm install raw-loader webpack grunt@~0.4.0 4 | module.exports = { 5 | entry: { 6 | main: './components/router/main', 7 | }, 8 | output: { 9 | path: path.join(__dirname, 'dist'), 10 | filename: '[name].js' 11 | }, //页面引用的文件 12 | module: { 13 | loaders: [ 14 | //http://react-china.org/t/webpack-extracttextplugin-autoprefixer/1922/4 15 | // https://github.com/b82/webpack-basic-starter/blob/master/webpack.config.js 16 | { test: /\.html$/, loader: 'raw' }, 17 | ] 18 | }, 19 | resolve: { 20 | alias: { 21 | 'avalon': path.join(__dirname,'./dist/avalon.modern') 22 | }, 23 | extensions: ['.js', '', 'html'] 24 | } 25 | } -------------------------------------------------------------------------------- /src/REAMD.md: -------------------------------------------------------------------------------- 1 | codeclimate 需要每次进去刷新 2 | 3 | http://shields.io/ 4 | 5 | https://snyk.io/ -------------------------------------------------------------------------------- /src/avalon.js: -------------------------------------------------------------------------------- 1 | import {avalon} from './seed/core' 2 | import './seed/lang.compact' 3 | 4 | 5 | import './filters/index' 6 | import './dom/compact' 7 | 8 | import './vtree/fromString' 9 | import './vtree/fromDOM' 10 | 11 | import './vdom/compact' 12 | import './vmodel/compact' 13 | import './directives/compact' 14 | 15 | import './renders/domRender' 16 | 17 | import './effect/index' 18 | import './component/index' 19 | export default avalon -------------------------------------------------------------------------------- /src/avalon.modern.js: -------------------------------------------------------------------------------- 1 | import {avalon} from './seed/core' 2 | import './seed/lang.modern' 3 | 4 | import './filters/index' 5 | import './dom/modern' 6 | 7 | import './vtree/fromString' 8 | import './vtree/fromDOM' 9 | 10 | import './vdom/modern' 11 | 12 | import './vmodel/modern' 13 | import './vmodel/proxy' 14 | 15 | import './directives/modern' 16 | import './renders/domRender' 17 | 18 | import './effect/index' 19 | import './component/index' 20 | export default avalon 21 | 22 | -------------------------------------------------------------------------------- /src/avalon.tap.js: -------------------------------------------------------------------------------- 1 | import { avalon } from './seed/core' 2 | import './seed/lang.compact' 3 | 4 | 5 | import './filters/index' 6 | import './dom/compact' 7 | 8 | import './vtree/fromString' 9 | import './vtree/fromDOM' 10 | 11 | import './vdom/compact' 12 | import './vmodel/compact' 13 | import './directives/compact' 14 | 15 | import './renders/domRender' 16 | 17 | import './effect/index.js' 18 | import './component/index' 19 | 20 | import './gesture/tap' 21 | 22 | export default avalon -------------------------------------------------------------------------------- /src/directives/attr.compact.js: -------------------------------------------------------------------------------- 1 | 2 | import { avalon } from '../seed/core' 3 | import { cssDiff } from './css' 4 | import { updateAttrs } from '../dom/attr/compact' 5 | 6 | avalon.directive('attr', { 7 | diff: cssDiff, 8 | update: function (vdom, value) { 9 | var props = vdom.props 10 | for(var i in value){ 11 | if(!!value[i] === false){ 12 | delete props[i] 13 | }else{ 14 | props[i] = value[i] 15 | } 16 | } 17 | var dom = vdom.dom 18 | if (dom && dom.nodeType === 1) { 19 | updateAttrs(dom, value) 20 | } 21 | } 22 | }) 23 | 24 | -------------------------------------------------------------------------------- /src/directives/attr.modern.js: -------------------------------------------------------------------------------- 1 | 2 | import { avalon } from '../seed/core' 3 | import { cssDiff } from './css' 4 | import { updateAttrs } from '../dom/attr/modern' 5 | 6 | avalon.directive('attr', { 7 | diff: cssDiff, 8 | update: function (vdom, value) { 9 | var props = vdom.props 10 | for(var i in value){ 11 | if(!!value[i] === false){ 12 | delete props[i] 13 | }else{ 14 | props[i] = value[i] 15 | } 16 | } 17 | var dom = vdom.dom 18 | if (dom && dom.nodeType === 1) { 19 | updateAttrs(dom, value) 20 | } 21 | } 22 | }) 23 | 24 | -------------------------------------------------------------------------------- /src/directives/compact.js: -------------------------------------------------------------------------------- 1 | import './important' 2 | import './controller' 3 | 4 | import './skip' 5 | import './visible' 6 | import './text' 7 | 8 | import './css' 9 | import './expr' 10 | 11 | import './attr.compact' 12 | import './html' 13 | import './if' 14 | import './on' 15 | import './for' 16 | 17 | import './class.hover.active' 18 | import './duplex/compact' 19 | import './rules' 20 | import './validate' 21 | -------------------------------------------------------------------------------- /src/directives/controller.js: -------------------------------------------------------------------------------- 1 | import { avalon,platform } from '../seed/core' 2 | import { impCb } from './important' 3 | avalon.directive('controller', { 4 | priority: 2, 5 | getScope: function (name, scope) { 6 | var v = avalon.vmodels[name] 7 | if (v){ 8 | v.$render = this 9 | if(scope && scope !== v){ 10 | return platform.fuseFactory(scope, v) 11 | } 12 | return v 13 | } 14 | return scope 15 | }, 16 | update: impCb 17 | }) -------------------------------------------------------------------------------- /src/directives/duplex/compact.js: -------------------------------------------------------------------------------- 1 | 2 | import { avalon } from '../../seed/core' 3 | import { duplexBeforeInit, duplexInit, duplexDiff, duplexBind, valueHijack, updateView } from './share' 4 | import { updateDataEvents } from './updateDataEvents.compact' 5 | import { updateModel } from './updateDataHandle' 6 | 7 | 8 | avalon.directive('duplex', { 9 | priority: 9999999, 10 | beforeInit: duplexBeforeInit, 11 | init: duplexInit, 12 | diff: duplexDiff, 13 | update: function (vdom, value) { 14 | if (!this.dom) { 15 | duplexBind.call(this, vdom, updateDataEvents) 16 | } 17 | //如果不支持input.value的Object.defineProperty的属性支持, 18 | //需要通过轮询同步, chrome 42及以下版本需要这个hack 19 | pollValue.call(this, avalon.msie, valueHijack) 20 | //更新视图 21 | 22 | updateView[this.dtype].call(this) 23 | 24 | } 25 | }) 26 | 27 | function pollValue(isIE, valueHijack) { 28 | var dom = this.dom 29 | if (this.isString 30 | && valueHijack 31 | && !isIE 32 | && !dom.valueHijack) { 33 | dom.valueHijack = updateModel 34 | var intervalID = setInterval(function () { 35 | if (!avalon.contains(avalon.root, dom)) { 36 | clearInterval(intervalID) 37 | } else { 38 | dom.valueHijack({ type: 'poll' }) 39 | } 40 | }, 30) 41 | return intervalID 42 | } 43 | } 44 | avalon.__pollValue = pollValue //export to test 45 | /* istanbul ignore if */ 46 | if (avalon.msie < 8) { 47 | var oldUpdate = updateView.updateChecked 48 | updateView.updateChecked = function (vdom, checked) { 49 | var dom = vdom.dom 50 | if (dom) { 51 | setTimeout(function () { 52 | oldUpdate(vdom, checked) 53 | dom.firstCheckedIt = 1 54 | }, dom.firstCheckedIt ? 31 : 16) 55 | //IE6,7 checkbox, radio是使用defaultChecked控制选中状态, 56 | //并且要先设置defaultChecked后设置checked 57 | //并且必须设置延迟(因为必须插入DOM树才生效) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/directives/duplex/modern.js: -------------------------------------------------------------------------------- 1 | 2 | import { avalon } from '../../seed/core' 3 | import { duplexBeforeInit, duplexInit, duplexDiff, duplexBind, valueHijack, updateView } from './share' 4 | import { updateDataEvents } from './updateDataEvents.modern' 5 | 6 | 7 | avalon.directive('duplex', { 8 | priority: 2000, 9 | beforeInit: duplexBeforeInit, 10 | init: duplexInit, 11 | diff: duplexDiff, 12 | update: function (vdom, value) { 13 | if (!this.dom) { 14 | duplexBind.call(this, vdom, updateDataEvents) 15 | } 16 | updateView[this.dtype].call(this) 17 | } 18 | }) 19 | 20 | -------------------------------------------------------------------------------- /src/directives/duplex/option.js: -------------------------------------------------------------------------------- 1 | export function lookupOption(vdom, values) { 2 | vdom.children && vdom.children.forEach(function (el) { 3 | if (el.nodeName === 'option') { 4 | setOption(el, values) 5 | } else { 6 | lookupOption(el, values) 7 | } 8 | }) 9 | } 10 | 11 | function setOption(vdom, values) { 12 | var props = vdom.props 13 | if (!('disabled' in props)) { 14 | var value = getOptionValue(vdom, props) 15 | value = String(value || '').trim() 16 | if(typeof values === 'string'){ 17 | props.selected = value === values 18 | }else{ 19 | props.selected = values.indexOf(value) !== -1; 20 | } 21 | 22 | if (vdom.dom) { 23 | vdom.dom.selected = props.selected 24 | var v = vdom.dom.selected //必须加上这个,防止移出节点selected失效 25 | } 26 | 27 | } 28 | } 29 | 30 | function getOptionValue(vdom, props) { 31 | if (props && 'value' in props) { 32 | return props.value+ '' 33 | } 34 | var arr = [] 35 | vdom.children.forEach(function (el) { 36 | if (el.nodeName === '#text') { 37 | arr.push(el.nodeValue) 38 | } else if (el.nodeName === '#document-fragment') { 39 | arr.push(getOptionValue(el)) 40 | } 41 | }) 42 | return arr.join('') 43 | } 44 | 45 | export function getSelectedValue(vdom, arr) { 46 | vdom.children.forEach(function (el) { 47 | if (el.nodeName === 'option') { 48 | if(el.props.selected === true) 49 | arr.push(getOptionValue(el, el.props)) 50 | } else if (el.children) { 51 | getSelectedValue(el,arr) 52 | } 53 | }) 54 | return arr 55 | } -------------------------------------------------------------------------------- /src/directives/duplex/updateDataHandle.js: -------------------------------------------------------------------------------- 1 | import { updateDataActions } from './updateDataActions' 2 | 3 | export function updateDataHandle(event) { 4 | var elem = this 5 | var field = elem._ms_duplex_ 6 | if (elem.composing) { 7 | //防止onpropertychange引发爆栈 8 | return 9 | } 10 | if (elem.value === field.value) { 11 | return 12 | } 13 | /* istanbul ignore if*/ 14 | if (elem.caret) { 15 | try { 16 | var pos = field.getCaret(elem) 17 | field.pos = pos 18 | } catch (e) {} 19 | } 20 | /* istanbul ignore if*/ 21 | if (field.debounceTime > 4) { 22 | var timestamp = new Date() 23 | var left = timestamp - field.time || 0 24 | field.time = timestamp 25 | /* istanbul ignore if*/ 26 | if (left >= field.debounceTime) { 27 | updateDataActions[field.dtype].call(field) 28 | /* istanbul ignore else*/ 29 | } else { 30 | clearTimeout(field.debounceID) 31 | field.debounceID = setTimeout(function() { 32 | updateDataActions[field.dtype].call(field) 33 | }, left) 34 | } 35 | } else if(field.isChanged){ 36 | setTimeout(function() { 37 | //https://github.com/RubyLouvre/avalon/issues/1908 38 | updateDataActions[field.dtype].call(field) 39 | }, 4) 40 | } else { 41 | updateDataActions[field.dtype].call(field) 42 | } 43 | } 44 | 45 | export { 46 | updateDataHandle as updateModel 47 | } -------------------------------------------------------------------------------- /src/directives/expr.js: -------------------------------------------------------------------------------- 1 | import { avalon } from '../seed/core' 2 | 3 | avalon.directive('expr', { 4 | update: function (vdom, value) { 5 | value = (value == null || value === '') ? '\u200b' : value 6 | vdom.nodeValue = value 7 | //https://github.com/RubyLouvre/avalon/issues/1834 8 | if(vdom.dom) 9 | vdom.dom.data = value 10 | } 11 | }) -------------------------------------------------------------------------------- /src/directives/html.js: -------------------------------------------------------------------------------- 1 | import { avalon } from '../seed/core' 2 | 3 | avalon.directive('html', { 4 | 5 | update: function(vdom, value) { 6 | this.beforeDispose() 7 | 8 | this.innerRender = avalon.scan('
' + value + '
', this.vm, function() { 9 | var oldRoot = this.root 10 | if(vdom.children) 11 | vdom.children.length = 0 12 | vdom.children = oldRoot.children 13 | this.root = vdom 14 | if (vdom.dom) 15 | avalon.clearHTML(vdom.dom) 16 | }) 17 | 18 | }, 19 | beforeDispose: function() { 20 | if (this.innerRender) { 21 | this.innerRender.dispose() 22 | } 23 | }, 24 | delay: true 25 | }) -------------------------------------------------------------------------------- /src/directives/if.js: -------------------------------------------------------------------------------- 1 | import { avalon, createAnchor } from '../seed/core' 2 | 3 | avalon.directive('if', { 4 | delay: true, 5 | priority: 5, 6 | init: function() { 7 | this.placeholder = createAnchor('if') 8 | var props = this.node.props 9 | delete props['ms-if'] 10 | delete props[':if'] 11 | this.fragment = avalon.vdom(this.node, 'toHTML') 12 | }, 13 | diff: function(newVal, oldVal) { 14 | var n = !!newVal 15 | if (oldVal === void 0 || n !== oldVal) { 16 | this.value = n 17 | return true 18 | } 19 | }, 20 | update: function(vdom, value) { 21 | if (this.isShow === void 0 && value) { 22 | continueScan(this, vdom) 23 | return 24 | } 25 | this.isShow = value 26 | var placeholder = this.placeholder 27 | 28 | if (value) { 29 | var p = placeholder.parentNode 30 | continueScan(this, vdom) 31 | p && p.replaceChild(vdom.dom, placeholder) 32 | } else { //移除DOM 33 | this.beforeDispose() 34 | vdom.nodeValue = 'if' 35 | vdom.nodeName = '#comment' 36 | delete vdom.children 37 | var dom = vdom.dom 38 | var p = dom && dom.parentNode 39 | vdom.dom = placeholder 40 | if (p) { 41 | p.replaceChild(placeholder, dom) 42 | } 43 | } 44 | }, 45 | beforeDispose: function(){ 46 | if (this.innerRender) { 47 | this.innerRender.dispose() 48 | } 49 | } 50 | }) 51 | 52 | function continueScan(instance, vdom) { 53 | var innerRender = instance.innerRender = avalon.scan(instance.fragment, instance.vm) 54 | avalon.shadowCopy(vdom, innerRender.root) 55 | delete vdom.nodeValue 56 | } -------------------------------------------------------------------------------- /src/directives/important.js: -------------------------------------------------------------------------------- 1 | import { avalon } from '../seed/core' 2 | 3 | var impDir = avalon.directive('important', { 4 | priority: 1, 5 | getScope: function(name, scope) { 6 | var v = avalon.vmodels[name] 7 | if (v) 8 | return v 9 | throw 'error! no vmodel called ' + name 10 | }, 11 | update: function(node, attrName, $id) { 12 | if (!avalon.inBrowser) 13 | return 14 | var dom = avalon.vdom(node, 'toDOM') 15 | if (dom.nodeType === 1) { 16 | dom.removeAttribute(attrName) 17 | avalon(dom).removeClass('ms-controller') 18 | } 19 | var vm = avalon.vmodels[$id] 20 | if(vm){ 21 | vm.$element = dom 22 | vm.$render = this 23 | vm.$fire('onReady') 24 | delete vm.$events.onReady 25 | } 26 | 27 | } 28 | }) 29 | 30 | export var impCb = impDir.update -------------------------------------------------------------------------------- /src/directives/modern.js: -------------------------------------------------------------------------------- 1 | import './important' 2 | import './controller' 3 | 4 | import './skip' 5 | import './visible' 6 | import './text' 7 | 8 | import './css' 9 | import './expr' 10 | 11 | import './attr.modern' 12 | import './html' 13 | import './if' 14 | import './on' 15 | import './for' 16 | 17 | import './class.hover.active' 18 | import './duplex/modern' 19 | import './rules' 20 | import './validate' 21 | -------------------------------------------------------------------------------- /src/directives/on.js: -------------------------------------------------------------------------------- 1 | import { avalon, inBrowser } from '../seed/core' 2 | 3 | import { addScope, makeHandle } from '../parser/index' 4 | 5 | avalon.directive('on', { 6 | beforeInit: function() { 7 | this.getter = avalon.noop 8 | }, 9 | init: function() { 10 | var vdom = this.node 11 | var underline = this.name.replace('ms-on-', 'e').replace('-', '_') 12 | var uuid = underline + '_' + this.expr. 13 | replace(/\s/g, ''). 14 | replace(/[^$a-z]/ig, function(e) { 15 | return e.charCodeAt(0) 16 | }) 17 | var fn = avalon.eventListeners[uuid] 18 | if (!fn) { 19 | var arr = addScope(this.expr) 20 | var body = arr[0], 21 | filters = arr[1] 22 | body = makeHandle(body) 23 | 24 | if (filters) { 25 | filters = filters.replace(/__value__/g, '$event') 26 | filters += '\nif($event.$return){\n\treturn;\n}' 27 | } 28 | var ret = [ 29 | 'try{', 30 | '\tvar __vmodel__ = this;', 31 | '\t' + filters, 32 | '\treturn ' + body, 33 | '}catch(e){avalon.log(e, "in on dir")}' 34 | ].filter(function(el) { 35 | return /\S/.test(el) 36 | }) 37 | fn = new Function('$event', ret.join('\n')) 38 | fn.uuid = uuid 39 | avalon.eventListeners[uuid] = fn 40 | } 41 | 42 | 43 | var dom = avalon.vdom(vdom, 'toDOM') 44 | dom._ms_context_ = this.vm 45 | 46 | this.eventType = this.param.replace(/\-(\d)$/, '') 47 | delete this.param 48 | avalon(dom).bind(this.eventType, fn) 49 | }, 50 | 51 | beforeDispose: function() { 52 | avalon(this.node.dom).unbind(this.eventType) 53 | } 54 | }) -------------------------------------------------------------------------------- /src/directives/skip.js: -------------------------------------------------------------------------------- 1 | import { avalon } from '../seed/core' 2 | 3 | avalon.directive('skip', { 4 | delay: true 5 | }) 6 | -------------------------------------------------------------------------------- /src/directives/text.js: -------------------------------------------------------------------------------- 1 | import { avalon, inBrowser } from '../seed/core' 2 | 3 | 4 | avalon.directive('text', { 5 | delay: true, 6 | init: function () { 7 | 8 | var node = this.node 9 | if (node.isVoidTag) { 10 | avalon.error('自闭合元素不能使用ms-text') 11 | } 12 | var child = { nodeName: '#text', nodeValue: this.getValue() } 13 | node.children.splice(0, node.children.length, child) 14 | if(inBrowser){ 15 | avalon.clearHTML(node.dom) 16 | node.dom.appendChild( avalon.vdom(child,'toDOM')) 17 | } 18 | this.node = child 19 | var type = 'expr' 20 | this.type = this.name = type 21 | var directive = avalon.directives[type] 22 | var me = this 23 | this.callback = function (value) { 24 | directive.update.call(me, me.node, value) 25 | } 26 | } 27 | }) 28 | -------------------------------------------------------------------------------- /src/dom/attr/isVML.js: -------------------------------------------------------------------------------- 1 | export function isVML(src) { 2 | var nodeName = src.nodeName 3 | return nodeName.toLowerCase() === nodeName && !!src.scopeName && src.outerText === '' 4 | } 5 | 6 | -------------------------------------------------------------------------------- /src/dom/attr/modern.js: -------------------------------------------------------------------------------- 1 | import { avalon, window, document } from '../../seed/core' 2 | import { propMap } from './propMap' 3 | 4 | var rsvg = /^\[object SVG\w*Element\]$/ 5 | export function updateAttrs(node, attrs) { 6 | for (var attrName in attrs) { 7 | var val = attrs[attrName] 8 | /* istanbul ignore if*/ 9 | if (attrName.indexOf('data-') === 0 || rsvg.test(node)) { 10 | node.setAttribute(attrName, val) 11 | } else { 12 | var propName = propMap[attrName] || attrName 13 | if (typeof node[propName] === 'boolean') { 14 | //布尔属性必须使用el.xxx = true|false方式设值 15 | //如果为false, IE全系列下相当于setAttribute(xxx,''), 16 | //会影响到样式,需要进一步处理 17 | node[propName] = !!val 18 | } 19 | if (val === false) { 20 | node.removeAttribute(attrName) 21 | continue 22 | } 23 | 24 | //SVG只能使用setAttribute(xxx, yyy), VML只能使用node.xxx = yyy , 25 | //HTML的固有属性必须node.xxx = yyy 26 | var isInnate = attrName in node.cloneNode(false) 27 | if (isInnate) { 28 | node[propName] = val + '' 29 | } else { 30 | node.setAttribute(attrName, val) 31 | } 32 | } 33 | } 34 | } 35 | 36 | avalon.parseJSON = JSON.parse 37 | 38 | avalon.fn.attr = function (name, value) { 39 | if (arguments.length === 2) { 40 | this[0].setAttribute(name, value) 41 | return this 42 | } else { 43 | return this[0].getAttribute(name) 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /src/dom/attr/parseJSON.compact.js: -------------------------------------------------------------------------------- 1 | var rvalidchars = /^[\],:{}\s]*$/, 2 | rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, 3 | rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g, 4 | rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g 5 | 6 | export function compactParseJSON(data) { 7 | if (typeof data === 'string') { 8 | data = data.trim() 9 | if (data) { 10 | if (rvalidchars.test(data.replace(rvalidescape, '@') 11 | .replace(rvalidtokens, ']') 12 | .replace(rvalidbraces, ''))) { 13 | return (new Function('return ' + data))() // jshint ignore:line 14 | } 15 | } 16 | throw TypeError('Invalid JSON: [' + data + ']') 17 | } 18 | return data 19 | } -------------------------------------------------------------------------------- /src/dom/attr/propMap.js: -------------------------------------------------------------------------------- 1 | export var propMap = {}//不规则的属性名映射 2 | 3 | 4 | //防止压缩时出错 5 | 'accept-charset,acceptCharset|char,ch|charoff,chOff|class,className|for,htmlFor|http-equiv,httpEquiv'.replace(/[^\|]+/g,function(a){ 6 | var k = a.split(',') 7 | propMap[k[0]] = k[1] 8 | }) 9 | /* 10 | contenteditable不是布尔属性 11 | http://www.zhangxinxu.com/wordpress/2016/01/contenteditable-plaintext-only/ 12 | contenteditable='' 13 | contenteditable='events' 14 | contenteditable='caret' 15 | contenteditable='plaintext-only' 16 | contenteditable='true' 17 | contenteditable='false' 18 | */ 19 | var bools = ['autofocus,autoplay,async,allowTransparency,checked,controls', 20 | 'declare,disabled,defer,defaultChecked,defaultSelected,', 21 | 'isMap,loop,multiple,noHref,noResize,noShade', 22 | 'open,readOnly,selected' 23 | ].join(',') 24 | 25 | bools.replace(/\w+/g, function (name) { 26 | propMap[name.toLowerCase()] = name 27 | }) 28 | 29 | var anomaly = ['accessKey,bgColor,cellPadding,cellSpacing,codeBase,codeType,colSpan', 30 | 'dateTime,defaultValue,contentEditable,frameBorder,longDesc,maxLength,'+ 31 | 'marginWidth,marginHeight,rowSpan,tabIndex,useMap,vSpace,valueType,vAlign' 32 | ].join(',') 33 | 34 | anomaly.replace(/\w+/g, function (name) { 35 | propMap[name.toLowerCase()] = name 36 | }) 37 | 38 | //module.exports = propMap 39 | -------------------------------------------------------------------------------- /src/dom/class/modern.js: -------------------------------------------------------------------------------- 1 | import { avalon, rnowhite, rword } from '../../seed/core' 2 | 3 | 'add,remove'.replace(rword, function (method) { 4 | avalon.fn[method + 'Class'] = function (cls) { 5 | var el = this[0] || {} 6 | //https://developer.mozilla.org/zh-CN/docs/Mozilla/Firefox/Releases/26 7 | if (cls && typeof cls === 'string' && el.nodeType === 1) { 8 | cls.replace(rnowhite, function (c) { 9 | el.classList[method](c) 10 | }) 11 | } 12 | return this 13 | } 14 | }) 15 | 16 | avalon.shadowCopy(avalon.fn, { 17 | hasClass: function (cls) { 18 | var el = this[0] || {} 19 | //IE10+, chrome8+, firefox3.6+, safari5.1+,opera11.5+支持classList, 20 | //chrome24+,firefox26+支持classList2.0 21 | return el.nodeType === 1 && el.classList.contains(cls) 22 | }, 23 | toggleClass: function (value, stateVal) { 24 | var isBool = typeof stateVal === 'boolean' 25 | var me = this 26 | String(value).replace(rnowhite, function (c) { 27 | var state = isBool ? stateVal : !me.hasClass(c) 28 | me[state ? 'addClass' : 'removeClass'](c) 29 | }) 30 | return this 31 | } 32 | }) 33 | 34 | -------------------------------------------------------------------------------- /src/dom/compact.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ------------------------------------------------------------ 3 | * DOM Api 4 | * shim,class,data,css,val,html,event,ready 5 | * ------------------------------------------------------------ 6 | */ 7 | 8 | import './shim/compact' 9 | import './class/compact' 10 | import './attr/compact' 11 | import './css/compact' 12 | import './val/compact' 13 | import './html/index' 14 | import './event/compact' 15 | import './ready/compact' 16 | 17 | -------------------------------------------------------------------------------- /src/dom/css/modern.js: -------------------------------------------------------------------------------- 1 | import { avalon } from '../../seed/core' 2 | import { getWindow } from './share' 3 | 4 | 5 | avalon.fn.offset = function () { //取得距离页面左右角的坐标 6 | var node = this[0] 7 | try { 8 | var rect = node.getBoundingClientRect() 9 | // Make sure element is not hidden (display: none) or disconnected 10 | // https://github.com/jquery/jquery/pull/2043/files#r23981494 11 | if (rect.width || rect.height || node.getClientRects().length) { 12 | var doc = node.ownerDocument 13 | var root = doc.documentElement 14 | var win = doc.defaultView 15 | return { 16 | top: rect.top + win.pageYOffset - root.clientTop, 17 | left: rect.left + win.pageXOffset - root.clientLeft 18 | } 19 | } 20 | } catch (e) { 21 | return { 22 | left: 0, 23 | top: 0 24 | } 25 | } 26 | } 27 | 28 | avalon.each({ 29 | scrollLeft: "pageXOffset", 30 | scrollTop: "pageYOffset" 31 | }, function (method, prop) { 32 | avalon.fn[method] = function (val) { 33 | var node = this[0] || {} 34 | var win = getWindow(node) 35 | var top = method === "scrollTop" 36 | if (!arguments.length) { 37 | return win ? win[prop] : node[method] 38 | } else { 39 | if (win) { 40 | win.scrollTo(!top ? val : win[prop], top ? val : win[prop]) 41 | } else { 42 | node[method] = val 43 | } 44 | } 45 | } 46 | }) 47 | 48 | function getWindow(node) { 49 | return node.window || node.defaultView || false 50 | } -------------------------------------------------------------------------------- /src/dom/event/canBubbleUp.js: -------------------------------------------------------------------------------- 1 | //http://www.feiesoft.com/html/events.html 2 | //http://segmentfault.com/q/1010000000687977/a-1020000000688757 3 | export var canBubbleUp = { 4 | click: true, 5 | dblclick: true, 6 | keydown: true, 7 | keypress: true, 8 | keyup: true, 9 | mousedown: true, 10 | mousemove: true, 11 | mouseup: true, 12 | mouseover: true, 13 | mouseout: true, 14 | wheel: true, 15 | mousewheel: true, 16 | input: true, 17 | change: true, 18 | beforeinput: true, 19 | compositionstart: true, 20 | compositionupdate: true, 21 | compositionend: true, 22 | select: true, 23 | //http://blog.csdn.net/lee_magnum/article/details/17761441 24 | cut: true, 25 | copy: true, 26 | paste: true, 27 | beforecut: true, 28 | beforecopy: true, 29 | beforepaste: true, 30 | focusin: true, 31 | focusout: true, 32 | DOMFocusIn: true, 33 | DOMFocusOut: true, 34 | DOMActivate: true, 35 | dragend: true, 36 | datasetchanged: true 37 | } -------------------------------------------------------------------------------- /src/dom/event/modern.js: -------------------------------------------------------------------------------- 1 | import { avalon, document } from '../../seed/core' 2 | 3 | import {avEvent} from './share' 4 | export { 5 | avEvent 6 | } 7 | /* istanbul ignore next */ 8 | avalon._nativeBind = function (el, type, fn, capture) { 9 | el.addEventListener(type, fn, !!capture) 10 | } 11 | 12 | /* istanbul ignore next */ 13 | avalon._nativeUnBind = function (el, type, fn, a) { 14 | el.removeEventListener(type, fn, !!a) 15 | } 16 | 17 | /* istanbul ignore next */ 18 | avalon.fireDom = function (elem, type, opts) { 19 | /* istanbul ignore else */ 20 | if (document.createEvent) { 21 | var hackEvent = document.createEvent('Events') 22 | hackEvent.initEvent(type, true, true, opts) 23 | avalon.shadowCopy(hackEvent, opts) 24 | elem.dispatchEvent(hackEvent) 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/dom/html/index.js: -------------------------------------------------------------------------------- 1 | import {avalon, Cache, document, createFragment} from '../../seed/core' 2 | import {fromString} from '../../vtree/fromString' 3 | export {avalon} 4 | 5 | var rhtml = /<|&#?\w+;/ 6 | var htmlCache = new Cache(128) 7 | var rxhtml = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig 8 | 9 | avalon.parseHTML = function (html) { 10 | var fragment = createFragment() 11 | //处理非字符串 12 | if (typeof html !== 'string') { 13 | return fragment 14 | } 15 | //处理非HTML字符串 16 | if (!rhtml.test(html)) { 17 | return document.createTextNode(html) 18 | } 19 | 20 | html = html.replace(rxhtml, '<$1>').trim() 21 | var hasCache = htmlCache.get(html) 22 | if (hasCache) { 23 | return avalon.cloneNode(hasCache) 24 | } 25 | var vnodes = fromString(html) 26 | for (var i = 0, el; el = vnodes[i++]; ) { 27 | var child = avalon.vdom(el, 'toDOM') 28 | fragment.appendChild(child) 29 | } 30 | if (html.length < 1024) { 31 | htmlCache.put(html, fragment) 32 | } 33 | return fragment 34 | } 35 | 36 | avalon.innerHTML = function (node, html) { 37 | var parsed = avalon.parseHTML(html) 38 | this.clearHTML(node) 39 | node.appendChild(parsed) 40 | } 41 | 42 | //https://github.com/karloespiritu/escapehtmlent/blob/master/index.js 43 | avalon.unescapeHTML = function (html) { 44 | return String(html) 45 | .replace(/"/g, '"') 46 | .replace(/'/g, '\'') 47 | .replace(/</g, '<') 48 | .replace(/>/g, '>') 49 | .replace(/&/g, '&') 50 | } 51 | 52 | 53 | 54 | avalon.clearHTML = function (node) { 55 | /* istanbul ignore next */ 56 | while (node.lastChild) { 57 | node.removeChild(node.lastChild) 58 | } 59 | return node 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/dom/modern.js: -------------------------------------------------------------------------------- 1 | 2 | /******************************************************************* 3 | * DOM Api * 4 | * shim,class,data,css,val,html,event,ready * 5 | ********************************************************************/ 6 | 7 | import './shim/modern' 8 | import './class/modern' 9 | import './attr/modern' 10 | import './css/modern' 11 | import './val/modern' 12 | import './html/index' 13 | import './event/modern' 14 | import './ready/modern' -------------------------------------------------------------------------------- /src/dom/rcheckedType.js: -------------------------------------------------------------------------------- 1 | export var rcheckedType = /^(?:checkbox|radio)$/ 2 | 3 | -------------------------------------------------------------------------------- /src/dom/ready/compact.js: -------------------------------------------------------------------------------- 1 | import { avalon, window, document, root, inBrowser } from '../../seed/core' 2 | 3 | var readyList = [] 4 | 5 | export function fireReady(fn) { 6 | avalon.isReady = true 7 | while (fn = readyList.shift()) { 8 | fn(avalon) 9 | } 10 | } 11 | 12 | avalon.ready = function (fn) { 13 | readyList.push(fn) 14 | if (avalon.isReady) { 15 | fireReady() 16 | } 17 | } 18 | 19 | avalon.ready(function () { 20 | avalon.scan && avalon.scan(document.body) 21 | }) 22 | 23 | /* istanbul ignore next */ 24 | function bootstrap() { 25 | function doScrollCheck() { 26 | try { //IE下通过doScrollCheck检测DOM树是否建完 27 | root.doScroll('left') 28 | fireReady() 29 | } catch (e) { 30 | setTimeout(doScrollCheck) 31 | } 32 | } 33 | if (document.readyState === 'complete') { 34 | setTimeout(fireReady) //如果在domReady之外加载 35 | } else if (document.addEventListener) { 36 | document.addEventListener('DOMContentLoaded', fireReady,false) 37 | } else if (document.attachEvent) { 38 | //必须传入三个参数,否则在firefox4-26中报错 39 | //caught exception: [Exception... "Not enough arguments" nsresult: "0x 40 | document.attachEvent('onreadystatechange', function () { 41 | if (document.readyState === 'complete') { 42 | fireReady() 43 | } 44 | }) 45 | try { 46 | var isTop = window.frameElement === null 47 | } catch (e) { 48 | } 49 | if (root.doScroll && isTop && window.external) {//fix IE iframe BUG 50 | doScrollCheck() 51 | } 52 | } 53 | 54 | avalon.bind(window, 'load', fireReady) 55 | } 56 | if (inBrowser) { 57 | bootstrap() 58 | } 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/dom/ready/modern.js: -------------------------------------------------------------------------------- 1 | import { avalon, window, document, root, inBrowser } from '../../seed/core' 2 | 3 | var readyList = [] 4 | 5 | export function fireReady(fn) { 6 | avalon.isReady = true 7 | while (fn = readyList.shift()) { 8 | fn(avalon) 9 | } 10 | } 11 | 12 | avalon.ready = function(fn) { 13 | readyList.push(fn) 14 | if (avalon.isReady) { 15 | fireReady() 16 | } 17 | } 18 | 19 | avalon.ready(function() { 20 | avalon.scan && avalon.scan(document.body) 21 | }) 22 | 23 | /* istanbul ignore next */ 24 | function bootstrap() { 25 | if (document.readyState === 'complete') { 26 | setTimeout(fireReady) //如果在domReady之外加载 27 | } else { 28 | //必须传入三个参数,否则在firefox4-26中报错 29 | //caught exception: [Exception... "Not enough arguments" nsresult: "0x80570001 (NS_ERROR_XPC_NOT_ENOUGH_ARGS)" 30 | document.addEventListener('DOMContentLoaded', fireReady, false) 31 | } 32 | 33 | avalon.bind(window, 'load', fireReady) 34 | } 35 | 36 | 37 | if (inBrowser) { 38 | bootstrap() 39 | } -------------------------------------------------------------------------------- /src/dom/shim/compact.js: -------------------------------------------------------------------------------- 1 | import { avalon, document, window, inBrowser, msie, root } from '../../seed/core' 2 | import { fixClone } from './fixClone' 3 | import { fixContains } from './fixContains' 4 | export { avalon } 5 | avalon.contains = fixContains 6 | 7 | avalon.cloneNode = function(a) { 8 | return a.cloneNode(true) 9 | } 10 | 11 | //IE6-11的文档对象没有contains 12 | /* istanbul ignore next */ 13 | function shimHack() { 14 | if (msie < 10) { 15 | avalon.cloneNode = fixClone 16 | } 17 | if (!document.contains) { 18 | document.contains = function(b) { 19 | return fixContains(document, b) 20 | } 21 | } 22 | if (avalon.modern) { 23 | if (!document.createTextNode('x').contains) { 24 | Node.prototype.contains = function(child) { //IE6-8没有Node对象 25 | return fixContains(this, child) 26 | } 27 | } 28 | } 29 | //firefox 到11时才有outerHTML 30 | function fixFF(prop, cb) { 31 | if (!(prop in root) && HTMLElement.prototype.__defineGetter__) { 32 | HTMLElement.prototype.__defineGetter__(prop, cb) 33 | } 34 | } 35 | fixFF('outerHTML', function() { 36 | var div = document.createElement('div') 37 | div.appendChild(this) 38 | return div.innerHTML 39 | }) 40 | fixFF('children', function() { 41 | var children = [] 42 | for (var i = 0, el; el = this.childNodes[i++];) { 43 | if (el.nodeType === 1) { 44 | children.push(el) 45 | } 46 | } 47 | return children 48 | }) 49 | fixFF('innerText', function() { //firefox45+, chrome4+ http://caniuse.com/#feat=innertext 50 | return this.textContent 51 | }) 52 | 53 | } 54 | 55 | if (inBrowser) { 56 | shimHack() 57 | } -------------------------------------------------------------------------------- /src/dom/shim/fixClone.js: -------------------------------------------------------------------------------- 1 | import {rcheckedType} from '../rcheckedType' 2 | 3 | /* istanbul ignore next */ 4 | function fixElement(dest, src) { 5 | if (dest.nodeType !== 1) { 6 | return 7 | } 8 | var nodeName = dest.nodeName.toLowerCase() 9 | 10 | if ( nodeName === "script" ) { 11 | if( dest.text !== src.text){ 12 | dest.type = "noexec" 13 | dest.text = src.text; 14 | dest.type = src.type || "" 15 | } 16 | }else if (nodeName === 'object') { 17 | var params = src.childNodes 18 | if (dest.childNodes.length !== params.length) { 19 | avalon.clearHTML(dest) 20 | for(var i = 0, el ; el = params[i++]; ) { 21 | dest.appendChild(el.cloneNode(true)) 22 | } 23 | } 24 | } else if (nodeName === 'input' && rcheckedType.test(src.nodeName)) { 25 | 26 | dest.defaultChecked = dest.checked = src.checked 27 | if (dest.value !== src.value) { 28 | dest.value = src.value 29 | } 30 | 31 | } else if (nodeName === 'option') { 32 | dest.defaultSelected = dest.selected = src.defaultSelected 33 | } else if (nodeName === 'input' || nodeName === 'textarea') { 34 | dest.defaultValue = src.defaultValue 35 | } 36 | } 37 | 38 | /* istanbul ignore next */ 39 | function getAll(context) { 40 | return typeof context.getElementsByTagName !== 'undefined' ? 41 | context.getElementsByTagName('*') : 42 | typeof context.querySelectorAll !== 'undefined' ? 43 | context.querySelectorAll('*') : [] 44 | } 45 | 46 | /* istanbul ignore next */ 47 | export function fixClone(src) { 48 | var target = src.cloneNode(true) 49 | //http://www.myexception.cn/web/665613.html 50 | // target.expando = null 51 | var t = getAll(target) 52 | var s = getAll(src) 53 | for(var i = 0; i < s.length; i++){ 54 | fixElement(t[i], s[i]) 55 | } 56 | return target 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/dom/shim/fixContains.js: -------------------------------------------------------------------------------- 1 | /* istanbul ignore next */ 2 | export function fixContains(root, el) { 3 | try { //IE6-8,游离于DOM树外的文本节点,访问parentNode有时会抛错 4 | while ((el = el.parentNode)){ 5 | if (el === root) 6 | return true 7 | } 8 | } catch (e) { 9 | } 10 | return false 11 | } -------------------------------------------------------------------------------- /src/dom/shim/modern.js: -------------------------------------------------------------------------------- 1 | //safari5+是把contains方法放在Element.prototype上而不是Node.prototype 2 | import { avalon, document, window, root } from '../../seed/core' 3 | import { fixContains } from './fixContains' 4 | export { avalon } 5 | 6 | avalon.contains = fixContains 7 | 8 | avalon.cloneNode = function(a) { 9 | return a.cloneNode(true) 10 | } 11 | 12 | 13 | if (avalon.modern) { 14 | if (!document.contains) { 15 | Node.prototype.contains = function(child) { //IE6-8没有Node对象 16 | return fixContains(this, child) 17 | } 18 | } 19 | function fixFF(prop, cb) {//firefox12 http://caniuse.com/#search=outerHTML 20 | if (!(prop in root)) { 21 | HTMLElement.prototype.__defineGetter__(prop, cb) 22 | } 23 | } 24 | fixFF('outerHTML', function() {//https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/children 25 | var div = document.createElement('div') 26 | div.appendChild(this) 27 | return div.innerHTML 28 | }) 29 | fixFF('children', function() { 30 | var children = [] 31 | for (var i = 0, el; el = this.childNodes[i++];) { 32 | if (el.nodeType === 1) { 33 | children.push(el) 34 | } 35 | } 36 | return children 37 | }) 38 | fixFF('innerText', function() { 39 | return this.textContent 40 | }) 41 | } -------------------------------------------------------------------------------- /src/dom/val/getDuplexType.js: -------------------------------------------------------------------------------- 1 | import { rcheckedType } from '../rcheckedType' 2 | export function getDuplexType(elem) { 3 | var ret = elem.tagName.toLowerCase() 4 | if (ret === 'input') { 5 | return rcheckedType.test(elem.type) ? 'checked' : elem.type 6 | } 7 | return ret 8 | } 9 | -------------------------------------------------------------------------------- /src/dom/val/modern.js: -------------------------------------------------------------------------------- 1 | import { avalon } from '../../seed/core' 2 | import { getDuplexType } from './getDuplexType' 3 | export { getDuplexType } 4 | 5 | 6 | var valHooks = { 7 | 'select:get': function self(node, ret, index, singleton) { 8 | var nodes = node.children, value, 9 | index = ret ? index : node.selectedIndex 10 | singleton = ret ? singleton : node.type === 'select-one' || index < 0 11 | ret = ret || [] 12 | for (var i = 0, el; el = nodes[i++];) { 13 | if (!el.disabled) { 14 | switch (el.nodeName.toLowerCase()) { 15 | case 'option': 16 | if ((el.selected || el.index === index)) { 17 | value = el.value 18 | if (singleton) { 19 | return value 20 | } else { 21 | ret.push(value) 22 | } 23 | } 24 | break 25 | case 'optgroup': 26 | value = self(el, ret, index, singleton) 27 | if (typeof value === 'string') { 28 | return value 29 | } 30 | break 31 | } 32 | } 33 | } 34 | return singleton ? null : ret 35 | }, 36 | 'select:set': function (node, values, optionSet) { 37 | values = [].concat(values) //强制转换为数组 38 | for (var i = 0, el; el = node.options[i++];) { 39 | if ((el.selected = values.indexOf(el.value) > -1)) { 40 | optionSet = true 41 | } 42 | } 43 | if (!optionSet) { 44 | node.selectedIndex = -1 45 | } 46 | } 47 | } 48 | 49 | avalon.fn.val = function (value) { 50 | var node = this[0] 51 | if (node && node.nodeType === 1) { 52 | var get = arguments.length === 0 53 | var access = get ? ':get' : ':set' 54 | var fn = valHooks[getDuplexType(node) + access] 55 | if (fn) { 56 | var val = fn(node, value) 57 | } else if (get) { 58 | return (node.value || '').replace(/\r/g, '') 59 | } else { 60 | node.value = value 61 | } 62 | } 63 | return get ? val : this 64 | } -------------------------------------------------------------------------------- /src/dom/val/option.compact.js: -------------------------------------------------------------------------------- 1 | /** 2 | * IE6/7/8中,如果option没有value值,那么将返回空字符串。 3 | * IE9/Firefox/Safari/Chrome/Opera 中先取option的value值,如果没有value属性,则取option的innerText值。 4 | * IE11及W3C,如果没有指定value,那么node.value默认为node.text(存在trim作),但IE9-10则是取innerHTML(没trim操作) 5 | */ 6 | 7 | export function getOption(node) { 8 | if(node.hasAttribute && node.hasAttribute('value')){ 9 | return node.getAttribute('value') 10 | } 11 | var attr = node.getAttributeNode('value') 12 | if(attr && attr.specified){ 13 | return attr.value 14 | } 15 | return node.innerHTML.trim() 16 | } -------------------------------------------------------------------------------- /src/effect/detect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ------------------------------------------------------------ 3 | * 检测浏览器对CSS动画的支持与API名 4 | * ------------------------------------------------------------ 5 | */ 6 | 7 | import { window } from '../seed/core' 8 | let checker = { 9 | TransitionEvent: 'transitionend', 10 | WebKitTransitionEvent: 'webkitTransitionEnd', 11 | OTransitionEvent: 'oTransitionEnd', 12 | otransitionEvent: 'otransitionEnd' 13 | } 14 | let css3,tran,ani,name,animationEndEvent,transitionEndEvent 15 | let transition = false 16 | let animation = false 17 | //有的浏览器同时支持私有实现与标准写法,比如webkit支持前两种,Opera支持1、3、4 18 | for (name in checker) { 19 | if (window[name]) { 20 | tran = checker[name] 21 | break 22 | } 23 | /* istanbul ignore next */ 24 | try { 25 | let a = document.createEvent(name) 26 | tran = checker[name] 27 | break 28 | } catch (e) { 29 | } 30 | } 31 | if (typeof tran === 'string') { 32 | transition = css3 = true 33 | transitionEndEvent = tran 34 | } 35 | 36 | //animationend有两个可用形态 37 | //IE10+, Firefox 16+ & Opera 12.1+: animationend 38 | //Chrome/Safari: webkitAnimationEnd 39 | //http://blogs.msdn.com/b/davrous/archive/2011/12/06/introduction-to-css3-animat ions.aspx 40 | //IE10也可以使用MSAnimationEnd监听,但是回调里的事件 type依然为animationend 41 | // el.addEventListener('MSAnimationEnd', function(e) { 42 | // alert(e.type)// animationend!!! 43 | // }) 44 | checker = { 45 | 'AnimationEvent': 'animationend', 46 | 'WebKitAnimationEvent': 'webkitAnimationEnd' 47 | } 48 | for (name in checker) { 49 | if (window[name]) { 50 | ani = checker[name] 51 | break 52 | } 53 | } 54 | if (typeof ani === 'string') { 55 | animation = css3 = true 56 | animationEndEvent = ani 57 | } 58 | export { 59 | css3, 60 | animation, 61 | transition, 62 | animationEndEvent, 63 | transitionEndEvent 64 | } 65 | -------------------------------------------------------------------------------- /src/filters/escape.js: -------------------------------------------------------------------------------- 1 | //https://github.com/teppeis/htmlspecialchars 2 | export function escapeFilter(str) { 3 | if (str == null) 4 | return '' 5 | 6 | return String(str). 7 | replace(/&/g, '&'). 8 | replace(//g, '>'). 10 | replace(/"/g, '"'). 11 | replace(/'/g, ''') 12 | } 13 | -------------------------------------------------------------------------------- /src/filters/event.js: -------------------------------------------------------------------------------- 1 | var eventFilters = { 2 | stop: function (e) { 3 | e.stopPropagation() 4 | return e 5 | }, 6 | prevent: function (e) { 7 | e.preventDefault() 8 | return e 9 | } 10 | } 11 | var keys = { 12 | esc: 27, 13 | tab: 9, 14 | enter: 13, 15 | space: 32, 16 | del: 46, 17 | up: 38, 18 | left: 37, 19 | right: 39, 20 | down: 40 21 | } 22 | for (var name in keys) { 23 | (function (filter, key) { 24 | eventFilters[filter] = function (e) { 25 | if (e.which !== key) { 26 | e.$return = true 27 | } 28 | return e 29 | } 30 | })(name, keys[name]) 31 | } 32 | 33 | 34 | export { eventFilters } -------------------------------------------------------------------------------- /src/filters/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | avalon 3 | } from '../seed/core' 4 | 5 | import { numberFilter } from "./number" 6 | import { sanitizeFilter } from "./sanitize" 7 | import { dateFilter } from "./date" 8 | import { filterBy, orderBy, selectBy, limitBy } from "./array" 9 | import { eventFilters } from "./event" 10 | import { escapeFilter } from "./escape" 11 | var filters = avalon.filters = {} 12 | 13 | avalon.composeFilters = function() { 14 | var args = arguments 15 | return function(value) { 16 | for (var i = 0, arr; arr = args[i++];) { 17 | var name = arr[0] 18 | var filter = avalon.filters[name] 19 | if (typeof filter === 'function') { 20 | arr[0] = value 21 | try { 22 | value = filter.apply(0, arr) 23 | } catch (e) {} 24 | } 25 | } 26 | return value 27 | } 28 | } 29 | 30 | avalon.escapeHtml = escapeFilter 31 | 32 | avalon.mix(filters, { 33 | uppercase(str) { 34 | return String(str).toUpperCase() 35 | }, 36 | lowercase(str) { 37 | return String(str).toLowerCase() 38 | }, 39 | truncate(str, length, end) { 40 | //length,新字符串长度,truncation,新字符串的结尾的字段,返回新字符串 41 | if (!str) { 42 | return '' 43 | } 44 | str = String(str) 45 | if (isNaN(length)) { 46 | length = 30 47 | } 48 | end = typeof end === "string" ? end : "..." 49 | return str.length > length ? 50 | str.slice(0, length - end.length) + end : /* istanbul ignore else*/ 51 | str 52 | }, 53 | camelize: avalon.camelize, 54 | date: dateFilter, 55 | escape: escapeFilter, 56 | sanitize: sanitizeFilter, 57 | number: numberFilter, 58 | currency(amount, symbol, fractionSize) { 59 | return (symbol || '\u00a5') + 60 | numberFilter(amount, 61 | isFinite(fractionSize) ? /* istanbul ignore else*/ fractionSize : 2) 62 | } 63 | }, { filterBy, orderBy, selectBy, limitBy }, eventFilters) 64 | 65 | export { avalon } -------------------------------------------------------------------------------- /src/filters/number.js: -------------------------------------------------------------------------------- 1 | 2 | import { avalon } from '../seed/core' 3 | function toFixedFix(n, prec) { 4 | var k = Math.pow(10, prec) 5 | return '' + (Math.round(n * k) / k).toFixed(prec) 6 | } 7 | export function numberFilter(number, decimals, point, thousands) { 8 | //https://github.com/txgruppi/number_format 9 | //form http://phpjs.org/functions/number_format/ 10 | //number 必需,要格式化的数字 11 | //decimals 可选,规定多少个小数位。 12 | //point 可选,规定用作小数点的字符串(默认为 . )。 13 | //thousands 可选,规定用作千位分隔符的字符串(默认为 , ),如果设置了该参数,那么所有其他参数都是必需的。 14 | number = (number + '') 15 | .replace(/[^0-9+\-Ee.]/g, '') 16 | var n = !isFinite(+number) ? 0 : +number, 17 | prec = !isFinite(+decimals) ? 3 : Math.abs(decimals), 18 | sep = typeof thousands === 'string' ? thousands: "," , 19 | dec = point || ".", 20 | s = '' 21 | 22 | // Fix for IE parseFloat(0.55).toFixed(0) = 0; 23 | s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)) 24 | .split('.') 25 | if (s[0].length > 3) { 26 | s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep) 27 | } 28 | /** //好像没有用 29 | var s1 = s[1] || '' 30 | 31 | if (s1.length < prec) { 32 | s1 += new Array(prec - s[1].length + 1).join('0') 33 | s[1] = s1 34 | } 35 | **/ 36 | return s.join(dec) 37 | } 38 | -------------------------------------------------------------------------------- /src/filters/sanitize.js: -------------------------------------------------------------------------------- 1 | var rscripts = /]*>([\S\s]*?)<\/script\s*>/gim 2 | var ron = /\s+(on[^=\s]+)(?:=("[^"]*"|'[^']*'|[^\s>]+))?/g 3 | var ropen = /<\w+\b(?:(["'])[^"]*?(\1)|[^>])*>/ig 4 | var rsanitize = { 5 | a: /\b(href)\=("javascript[^"]*"|'javascript[^']*')/ig, 6 | img: /\b(src)\=("javascript[^"]*"|'javascript[^']*')/ig, 7 | form: /\b(action)\=("javascript[^"]*"|'javascript[^']*')/ig 8 | } 9 | 10 | //https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet 11 | // chrome 12 | // chrome 13 | // IE67chrome 14 | // IE67chrome 15 | // IE67chrome 16 | export function sanitizeFilter(str) { 17 | return str.replace(rscripts, "").replace(ropen, function (a, b) { 18 | var match = a.toLowerCase().match(/<(\w+)\s/) 19 | if (match) { //处理a标签的href属性,img标签的src属性,form标签的action属性 20 | var reg = rsanitize[match[1]] 21 | if (reg) { 22 | a = a.replace(reg, function (s, name, value) { 23 | var quote = value.charAt(0) 24 | return name + "=" + quote + "javascript:void(0)" + quote// jshint ignore:line 25 | }) 26 | } 27 | } 28 | return a.replace(ron, " ").replace(/\s+/g, " ") //移除onXXX事件 29 | }) 30 | } -------------------------------------------------------------------------------- /src/gesture/drag.js: -------------------------------------------------------------------------------- 1 | import { Recognizer } from './recognizer' 2 | 3 | var dragRecognizer = { 4 | events: ['dragstart', 'drag', 'dragend'], 5 | touchstart: function(event) { 6 | Recognizer.start(event, avalon.noop) 7 | }, 8 | touchmove: function(event) { 9 | Recognizer.move(event, function(pointer, touch) { 10 | var extra = { 11 | deltaX: pointer.deltaX, 12 | deltaY: pointer.deltaY, 13 | touch: touch, 14 | touchEvent: event, 15 | isVertical: pointer.isVertical 16 | } 17 | if ((pointer.status === 'tapping') && pointer.distance > 10) { 18 | pointer.status = 'panning' 19 | Recognizer.fire(pointer.element, 'dragstart', extra) 20 | } else if (pointer.status === 'panning') { 21 | Recognizer.fire(pointer.element, 'drag', extra) 22 | } 23 | }) 24 | 25 | event.preventDefault() 26 | }, 27 | touchend: function(event) { 28 | Recognizer.end(event, function(pointer, touch) { 29 | if (pointer.status === 'panning') { 30 | Recognizer.fire(pointer.element, 'dragend', { 31 | deltaX: pointer.deltaX, 32 | deltaY: pointer.deltaY, 33 | touch: touch, 34 | touchEvent: event, 35 | isVertical: pointer.isVertical 36 | }) 37 | } 38 | }) 39 | Recognizer.pointers = {} 40 | } 41 | } 42 | dragRecognizer.touchcancel = dragRecognizer.touchend 43 | 44 | Recognizer.add('drag', dragRecognizer) -------------------------------------------------------------------------------- /src/gesture/press.js: -------------------------------------------------------------------------------- 1 | import { Recognizer } from './recognizer' 2 | 3 | var pressRecognizer = { 4 | events: ['longtap', 'doubletap'], 5 | cancelPress: function(pointer) { 6 | clearTimeout(pointer.pressingHandler) 7 | pointer.pressingHandler = null 8 | }, 9 | touchstart: function(event) { 10 | Recognizer.start(event, function(pointer, touch) { 11 | pointer.pressingHandler = setTimeout(function() { 12 | if (pointer.status === 'tapping') { 13 | Recognizer.fire(event.target, 'longtap', { 14 | touch: touch, 15 | touchEvent: event 16 | }) 17 | } 18 | pressRecognizer.cancelPress(pointer) 19 | }, 500) 20 | if (event.changedTouches.length !== 1) { 21 | pointer.status = 0 22 | } 23 | }) 24 | 25 | }, 26 | touchmove: function(event) { 27 | Recognizer.move(event, function(pointer) { 28 | if (pointer.distance > 10 && pointer.pressingHandler) { 29 | pressRecognizer.cancelPress(pointer) 30 | if (pointer.status === 'tapping') { 31 | pointer.status = 'panning' 32 | } 33 | } 34 | }) 35 | }, 36 | touchend: function(event) { 37 | Recognizer.end(event, function(pointer, touch) { 38 | pressRecognizer.cancelPress(pointer) 39 | if (pointer.status === 'tapping') { 40 | pointer.lastTime = Date.now() 41 | if (pressRecognizer.lastTap && pointer.lastTime - pressRecognizer.lastTap.lastTime < 300) { 42 | Recognizer.fire(pointer.element, 'doubletap', { 43 | touch: touch, 44 | touchEvent: event 45 | }) 46 | } 47 | 48 | pressRecognizer.lastTap = pointer 49 | } 50 | }) 51 | 52 | }, 53 | touchcancel: function(event) { 54 | Recognizer.end(event, function(pointer) { 55 | pressRecognizer.cancelPress(pointer) 56 | }) 57 | } 58 | } 59 | Recognizer.add('press', pressRecognizer) -------------------------------------------------------------------------------- /src/gesture/readme.md: -------------------------------------------------------------------------------- 1 | 请使用**webpack**打包,它们不包含在核心库里 2 | 3 | 4 | 这些手势是依赖于reconginzer模块 5 | 用到什么就包含什么,比如你想用tap模块, 6 | 7 | 在src建立一个avalon.tap.js 8 | 9 | 在export前引入这个模块 10 | ```javascript 11 | import { avalon } from './seed/core' 12 | import './seed/lang.compact' 13 | 14 | 15 | import './filters/index' 16 | import './dom/compact' 17 | 18 | import './vtree/fromString' 19 | import './vtree/fromDOM' 20 | 21 | import './vdom/compact' 22 | import './vmodel/compact' 23 | import './directives/compact' 24 | 25 | import './renders/domRender' 26 | 27 | import './effect/index.js' 28 | import './component/index' 29 | 30 | import './gesture/tap' 31 | 32 | export default avalon 33 | ``` 34 | 35 | 36 | 37 | 然后模仿buildIE6,建议一个buildTap的打包文件 38 | 39 | 最后`node buildtap` 就行了 40 | 41 | ``` 42 | 43 | 44 | //你的业务代码 45 | 46 |
xxxx
47 | 48 | ``` -------------------------------------------------------------------------------- /src/gesture/swipe.js: -------------------------------------------------------------------------------- 1 | import { Recognizer } from './recognizer' 2 | 3 | var swipeRecognizer = { 4 | events: ['swipe', 'swipeleft', 'swiperight', 'swipeup', 'swipedown'], 5 | getAngle: function(x, y) { 6 | return Math.atan2(y, x) * 180 / Math.PI 7 | }, 8 | getDirection: function(x, y) { 9 | if (Math.abs(x) >= Math.abs(y)) { 10 | return x < 0 ? 'left' : 'right' 11 | } 12 | return y < 0 ? 'up' : 'down' 13 | }, 14 | touchstart: function(event) { 15 | Recognizer.start(event, avalon.noop) 16 | }, 17 | touchmove: function(event) { 18 | Recognizer.move(event, avalon.noop) 19 | }, 20 | touchend: function(event) { 21 | if (event.changedTouches.length !== 1) { 22 | return 23 | } 24 | Recognizer.end(event, function(pointer, touch) { 25 | var isflick = (pointer.distance > 30 && pointer.distance / pointer.duration > 0.65) 26 | if (isflick) { 27 | var extra = { 28 | deltaX: pointer.deltaX, 29 | deltaY: pointer.deltaY, 30 | touch: touch, 31 | touchEvent: event, 32 | direction: swipeRecognizer.getDirection(pointer.deltaX, pointer.deltaY), 33 | isVertical: pointer.isVertical 34 | } 35 | var target = pointer.element 36 | Recognizer.fire(target, 'swipe', extra) 37 | Recognizer.fire(target, 'swipe' + extra.direction, extra) 38 | } 39 | }) 40 | } 41 | } 42 | 43 | swipeRecognizer.touchcancel = swipeRecognizer.touchend 44 | Recognizer.add('swipe', swipeRecognizer) -------------------------------------------------------------------------------- /src/pager.js: -------------------------------------------------------------------------------- 1 | var avalon = require('avalon2') 2 | require('ms-pager') 3 | 4 | window.vm = avalon.define({ 5 | $id: 'test', 6 | config: { 7 | totalPages: 0, 8 | showPages: 2, 9 | currentPage:1, 10 | nextText: '下一页', 11 | prevText: '上一页' 12 | } 13 | }) 14 | 15 | setTimeout(function(){ 16 | window.vm.config = { 17 | totalPages: 2, 18 | showPages: 2 19 | } 20 | }, 3000) -------------------------------------------------------------------------------- /src/parser/attributes.js: -------------------------------------------------------------------------------- 1 | 2 | import {avalon, directives} from '../seed/core' 3 | export var eventMap = avalon.oneObject('animationend,blur,change,input,'+ 4 | 'click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,'+ 5 | 'mouseleave,mousemove,mouseout,mouseover,mouseup,scan,scroll,submit','on') 6 | export function parseAttributes(dirs, tuple ) { 7 | var node = tuple[0], uniq = {}, bindings = [] 8 | var hasIf = false 9 | for (var name in dirs) { 10 | var value = dirs[name] 11 | var arr = name.split('-') 12 | // ms-click 13 | if(name in node.props){ 14 | var attrName = name 15 | }else{ 16 | attrName = ':'+name.slice(3) 17 | } 18 | if (eventMap[arr[1]]) { 19 | arr.splice(1, 0, 'on') 20 | } 21 | //ms-on-click 22 | if (arr[1] === 'on') { 23 | arr[3] = parseFloat(arr[3]) || 0 24 | } 25 | 26 | var type = arr[1] 27 | if(type === 'controller' || type === 'important') 28 | continue 29 | if (directives[type]) { 30 | 31 | var binding = { 32 | type: type, 33 | param: arr[2], 34 | attrName: attrName, 35 | name: arr.join('-'), 36 | expr: value, 37 | priority: directives[type].priority || type.charCodeAt(0) * 100 38 | } 39 | if(type === 'if'){ 40 | hasIf = true 41 | } 42 | if (type === 'on') { 43 | binding.priority += arr[3] 44 | } 45 | if (!uniq[binding.name]) { 46 | uniq[binding.name] = value 47 | bindings.push(binding) 48 | if (type === 'for') { 49 | return [avalon.mix(binding, tuple[3])] 50 | } 51 | } 52 | 53 | } 54 | } 55 | bindings.sort(byPriority) 56 | 57 | if(hasIf){ 58 | var ret = [] 59 | for(var i = 0, el; el = bindings[i++];){ 60 | ret.push(el) 61 | if(el.type === 'if'){ 62 | return ret 63 | } 64 | } 65 | } 66 | return bindings 67 | } 68 | export function byPriority(a, b) { 69 | return a.priority - b.priority 70 | } -------------------------------------------------------------------------------- /src/parser/interpolate.js: -------------------------------------------------------------------------------- 1 | import { avalon, config } from '../seed/core' 2 | import { addScope } from './index' 3 | var rimprovePriority = /[+-\?]/ 4 | var rinnerValue = /__value__\)$/ 5 | export function parseInterpolate(dir) { 6 | var rlineSp = /\n\r?/g 7 | var str = dir.nodeValue.trim().replace(rlineSp, '') 8 | var tokens = [] 9 | do {//aaa{{@bbb}}ccc 10 | var index = str.indexOf(config.openTag) 11 | index = index === -1 ? str.length : index 12 | var value = str.slice(0, index) 13 | if (/\S/.test(value)) { 14 | tokens.push(avalon.quote(avalon._decode(value))) 15 | } 16 | str = str.slice(index + config.openTag.length) 17 | if (str) { 18 | index = str.indexOf(config.closeTag) 19 | var value = str.slice(0, index) 20 | var expr = avalon.unescapeHTML(value) 21 | if (/\|\s*\w/.test(expr)) {//如果存在过滤器,优化干掉 22 | var arr = addScope(expr, 'expr') 23 | if (arr[1]) { 24 | expr = arr[1].replace(rinnerValue, arr[0]+')') 25 | } 26 | } 27 | if(rimprovePriority){ 28 | expr = '('+expr+')' 29 | } 30 | tokens.push(expr) 31 | 32 | str = str.slice(index + config.closeTag.length) 33 | } 34 | } while (str.length) 35 | return [{ 36 | expr: tokens.join('+'), 37 | name: 'expr', 38 | type: 'expr' 39 | }] 40 | } -------------------------------------------------------------------------------- /src/renders/Directive.js: -------------------------------------------------------------------------------- 1 | import { avalon, inBrowser } from '../seed/core' 2 | 3 | import { Action, protectedMenbers } from '../vmodel/Action' 4 | 5 | /** 6 | * 一个directive装饰器 7 | * @returns {directive} 8 | */ 9 | // DirectiveDecorator(scope, binding, vdom, this) 10 | // Decorator(vm, options, callback) 11 | export function Directive(vm, binding, vdom, render) { 12 | var type = binding.type 13 | var decorator = avalon.directives[type] 14 | if (inBrowser) { 15 | var dom = avalon.vdom(vdom, 'toDOM') 16 | if (dom.nodeType === 1) { 17 | dom.removeAttribute(binding.attrName) 18 | } 19 | vdom.dom = dom 20 | } 21 | var callback = decorator.update ? function (value) { 22 | if (!render.mount && /css|visible|duplex/.test(type)) { 23 | render.callbacks.push(function () { 24 | decorator.update.call(directive, directive.node, value) 25 | }) 26 | } else { 27 | decorator.update.call(directive, directive.node, value) 28 | } 29 | 30 | } : avalon.noop 31 | for (var key in decorator) { 32 | binding[key] = decorator[key] 33 | } 34 | binding.node = vdom 35 | var directive = new Action(vm, binding, callback) 36 | if(directive.init){ 37 | //这里可能会重写node, callback, type, name 38 | directive.init() 39 | } 40 | directive.update() 41 | return directive 42 | } 43 | -------------------------------------------------------------------------------- /src/renders/serverRender.js: -------------------------------------------------------------------------------- 1 | function serverRender(vm, str) { 2 | var nodes = avalon.lexer(str) 3 | var templates = {} 4 | collectTemplate(nodes, templates) 5 | var render = avalon.scan(str) 6 | var html = avalon.vdom(render.root, 'toHTML', false) 7 | return { 8 | templates: templates, 9 | html: html 10 | } 11 | } 12 | 13 | function collectTemplate(vdoms, obj) { 14 | for (var i = 0, el; el = vdoms[i++]; ) { 15 | var props = el.props 16 | if (props) { 17 | var id = props['ms-controller'] || 18 | props[':controller'] || 19 | props['ms-important'] || 20 | props[':important'] 21 | if (id) { 22 | obj[id] = avalon.VElement.prototype.toHTML.call(el, true) 23 | continue 24 | } 25 | } 26 | if (el.children) { 27 | collectTemplate(el.children, obj) 28 | } 29 | } 30 | } 31 | 32 | if(typeof module === 'object') 33 | 34 | module.exports = serverRender -------------------------------------------------------------------------------- /src/routergrid.js: -------------------------------------------------------------------------------- 1 | var avalon = require('avalon2') 2 | require('mmRouter') 3 | var a = require('../perf/component/router/tab1.html') 4 | var b = require('../perf/component/router/tab2.html') 5 | var c = require('../perf/component/router/tab3.html') 6 | require('../perf/component/router/tab1.js') 7 | var vm = avalon.define({ 8 | $id: 'main', 9 | main: '', 10 | aaa: "第一页的内容", 11 | bbb: "第二页的内容", 12 | ccc: "第三页的内容" 13 | }) 14 | var map = { 15 | 'aaa': a, 16 | 'bbb': b, 17 | 'ccc': c 18 | } 19 | avalon.router.add("/page-{count:\\d+}", function (param) { 20 | 21 | return '/aaa?'+ this.path.slice(1) 22 | }) 23 | 24 | avalon.router.add("/:tab", function (param) { 25 | console.log(param,'!!') 26 | vm.main = map[param] 27 | }) 28 | 29 | 30 | 31 | 32 | 33 | avalon.history.start({ 34 | root: "/mmRouter" 35 | }) 36 | 37 | 38 | var hash = location.hash.replace(/#!?/, '') 39 | avalon.router.navigate(hash || '/aaa', 1)//默认打开 40 | avalon.ready(function(){ 41 | avalon.scan(document.body) 42 | }) 43 | -------------------------------------------------------------------------------- /src/seed/browser.js: -------------------------------------------------------------------------------- 1 | export let win = typeof window === 'object' ? window : 2 | typeof global === 'object' ? global : {} 3 | 4 | export let inBrowser = !!win.location && win.navigator 5 | /* istanbul ignore if */ 6 | 7 | 8 | export let document = inBrowser ? win.document : { 9 | createElement: Object, 10 | createElementNS: Object, 11 | documentElement: 'xx', 12 | contains: Boolean 13 | } 14 | export var root = inBrowser ? document.documentElement : { 15 | outerHTML: 'x' 16 | } 17 | 18 | let versions = { 19 | objectobject: 7, //IE7-8 20 | objectundefined: 6, //IE6 21 | undefinedfunction: NaN, // other modern browsers 22 | undefinedobject: NaN, //Mobile Safari 8.0.0 (iOS 8.4.0) 23 | //objectfunction chrome 47 24 | } 25 | /* istanbul ignore next */ 26 | export var msie = document.documentMode || 27 | versions[typeof document.all + typeof XMLHttpRequest] 28 | 29 | export var modern = /NaN|undefined/.test(msie) || msie > 8 -------------------------------------------------------------------------------- /src/seed/directive.js: -------------------------------------------------------------------------------- 1 | 2 | var delayCompile = {} 3 | 4 | export var directives = {} 5 | 6 | export function directive(name, opts) { 7 | if( directives[name]){ 8 | avalon.warn(name, 'directive have defined! ') 9 | } 10 | directives[name] = opts 11 | if(!opts.update){ 12 | opts.update = function(){} 13 | } 14 | if (opts.delay) { 15 | delayCompile[name] = 1 16 | } 17 | return opts 18 | } 19 | 20 | export function delayCompileNodes(dirs) { 21 | for (var i in delayCompile) { 22 | if (('ms-' + i) in dirs) { 23 | return true 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/vdom/VComment.js: -------------------------------------------------------------------------------- 1 | import { document } from '../seed/core' 2 | 3 | export function VComment(text) { 4 | this.nodeName = '#comment' 5 | this.nodeValue = text 6 | } 7 | VComment.prototype = { 8 | constructor: VComment, 9 | toDOM: function() { 10 | if (this.dom) 11 | return this.dom 12 | return this.dom = document.createComment(this.nodeValue) 13 | }, 14 | toHTML: function() { 15 | return '' 16 | } 17 | } -------------------------------------------------------------------------------- /src/vdom/VFragment.js: -------------------------------------------------------------------------------- 1 | import { avalon, createFragment } from '../seed/core' 2 | 3 | export function VFragment(children, key, val, index) { 4 | this.nodeName = '#document-fragment' 5 | this.children = children 6 | this.key = key 7 | this.val = val 8 | this.index = index 9 | this.props = {} 10 | } 11 | VFragment.prototype = { 12 | constructor: VFragment, 13 | toDOM() { 14 | if (this.dom) 15 | return this.dom 16 | var f = this.toFragment() 17 | //IE6-11 docment-fragment都没有children属性 18 | this.split = f.lastChild 19 | return this.dom = f 20 | }, 21 | dispose() { 22 | this.toFragment() 23 | this.innerRender && this.innerRender.dispose() 24 | for (var i in this) { 25 | this[i] = null 26 | } 27 | }, 28 | toFragment() { 29 | var f = createFragment() 30 | this.children.forEach(el => 31 | f.appendChild(avalon.vdom(el, 'toDOM')) 32 | ) 33 | return f 34 | }, 35 | toHTML() { 36 | var c = this.children 37 | return c.map(el => avalon.vdom(el, 'toHTML')).join('') 38 | } 39 | } -------------------------------------------------------------------------------- /src/vdom/VText.js: -------------------------------------------------------------------------------- 1 | import { avalon, document } from '../seed/core' 2 | 3 | export function VText(text) { 4 | this.nodeName = '#text' 5 | this.nodeValue = text 6 | } 7 | 8 | VText.prototype = { 9 | constructor: VText, 10 | toDOM() { 11 | /* istanbul ignore if*/ 12 | if (this.dom) 13 | return this.dom 14 | var v = avalon._decode(this.nodeValue) 15 | return this.dom = document.createTextNode(v) 16 | }, 17 | toHTML() { 18 | return this.nodeValue 19 | } 20 | } -------------------------------------------------------------------------------- /src/vdom/compact.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 虚拟DOM的4大构造器 3 | */ 4 | import { avalon, createFragment } from '../seed/core' 5 | import { VText } from './VText' 6 | import { VComment } from './VComment' 7 | import { VElement } from './VElement' 8 | import { VFragment } from './VFragment' 9 | 10 | 11 | avalon.mix(avalon, { 12 | VText, 13 | VComment, 14 | VElement, 15 | VFragment 16 | }) 17 | 18 | var constNameMap = { 19 | '#text': 'VText', 20 | '#document-fragment': 'VFragment', 21 | '#comment': 'VComment' 22 | } 23 | 24 | var vdom = avalon.vdomAdaptor = avalon.vdom = function(obj, method) { 25 | if (!obj) { //obj在ms-for循环里面可能是null 26 | return method === "toHTML" ? '' : createFragment() 27 | } 28 | var nodeName = obj.nodeName 29 | if (!nodeName) { 30 | return (new avalon.VFragment(obj))[method]() 31 | } 32 | var constName = constNameMap[nodeName] || 'VElement' 33 | return avalon[constName].prototype[method].call(obj) 34 | } 35 | 36 | avalon.domize = function(a) { 37 | return avalon.vdom(a, 'toDOM') 38 | } 39 | 40 | export { vdom, avalon, VText, VComment, VElement, VFragment } -------------------------------------------------------------------------------- /src/vdom/modern.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 虚拟DOM的4大构造器 3 | */ 4 | import { avalon, createFragment } from '../seed/core' 5 | import { VText } from './VText' 6 | import { VComment } from './VComment' 7 | import { VElement } from './VElement.modern' 8 | import { VFragment } from './VFragment' 9 | 10 | avalon.mix(avalon, { 11 | VText, 12 | VComment, 13 | VElement, 14 | VFragment 15 | }) 16 | 17 | var constNameMap = { 18 | '#text': 'VText', 19 | '#document-fragment': 'VFragment', 20 | '#comment': 'VComment' 21 | } 22 | 23 | var vdom = avalon.vdomAdaptor = avalon.vdom = function(obj, method) { 24 | if (!obj) { //obj在ms-for循环里面可能是null 25 | return method === "toHTML" ? '' : createFragment() 26 | } 27 | var nodeName = obj.nodeName 28 | if (!nodeName) { 29 | return (new avalon.VFragment(obj))[method]() 30 | } 31 | var constName = constNameMap[nodeName] || 'VElement' 32 | return avalon[constName].prototype[method].call(obj) 33 | } 34 | 35 | avalon.domize = function(a) { 36 | return avalon.vdom(a, 'toDOM') 37 | } 38 | 39 | export { vdom, avalon, VText, VComment, VElement, VFragment } -------------------------------------------------------------------------------- /src/vmodel/modern.js: -------------------------------------------------------------------------------- 1 | import { avalon, platform, modern } from '../seed/core' 2 | import { $$skipArray } from './reserved' 3 | import { Action } from './Action' 4 | import './share' 5 | import './ProxyArray' 6 | export { avalon, platform } 7 | 8 | 9 | export function hideProperty(host, name, value) { 10 | Object.defineProperty(host, name, { 11 | value: value, 12 | writable: true, 13 | enumerable: false, 14 | configurable: true 15 | }) 16 | } 17 | 18 | function $fire(expr, a) { 19 | var list = this.$events[expr] 20 | if (Array.isArray(list)) { 21 | for (var i = 0, w; w = list[i++];) { 22 | w.callback.call(w.vm, a, w.value, w.expr) 23 | } 24 | } 25 | } 26 | 27 | function $watch(expr, callback, deep) { 28 | var core = this.$events 29 | var w = new Action(this, { 30 | deep: deep, 31 | type: 'user', 32 | expr: expr 33 | }, callback) 34 | if (!core[expr]) { 35 | core[expr] = [w] 36 | } else { 37 | core[expr].push(w) 38 | } 39 | return function() { 40 | w.dispose() 41 | avalon.Array.remove(core[expr], w) 42 | if (core[expr].length === 0) { 43 | delete core[expr] 44 | } 45 | } 46 | } 47 | export function watchFactory(core) { 48 | return $watch 49 | } 50 | 51 | export function fireFactory(core) { 52 | return $fire 53 | } 54 | 55 | 56 | export function afterCreate(vm, core, keys, bindThis) { 57 | var ac = vm.$accessors 58 | //隐藏系统属性 59 | for (var key in $$skipArray) { 60 | hideProperty(vm, key, vm[key]) 61 | } 62 | //为不可监听的属性或方法赋值 63 | for (let i = 0; i < keys.length; i++) { 64 | let key = keys[i] 65 | if (!(key in ac)) { 66 | let val = core[key] 67 | if (bindThis && typeof val === 'function') { 68 | vm[key] = val.bind(vm) 69 | vm[key]._orig = val 70 | continue 71 | } 72 | vm[key] = val 73 | } 74 | } 75 | vm.$track = keys.join('☥') 76 | vm.$events.__proxy__ = vm 77 | } 78 | 79 | platform.fireFactory = fireFactory 80 | platform.watchFactory = watchFactory 81 | platform.afterCreate = afterCreate 82 | platform.hideProperty = hideProperty 83 | platform.createViewModel = Object.defineProperties -------------------------------------------------------------------------------- /src/vmodel/reserved.js: -------------------------------------------------------------------------------- 1 | /** 2 | $$skipArray:是系统级通用的不可监听属性 3 | $skipArray: 是当前对象特有的不可监听属性 4 | 5 | 不同点是 6 | $$skipArray被hasOwnProperty后返回false 7 | $skipArray被hasOwnProperty后返回true 8 | */ 9 | var falsy 10 | export var $$skipArray = { 11 | $id: falsy, 12 | $render: falsy, 13 | $track: falsy, 14 | $element: falsy, 15 | $computed:falsy, 16 | $watch: falsy, 17 | $fire: falsy, 18 | $events: falsy, 19 | $accessors: falsy, 20 | $hashcode: falsy, 21 | $mutations: falsy, 22 | $vbthis:falsy, 23 | $vbsetter: falsy 24 | } -------------------------------------------------------------------------------- /src/vtree/clearString.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 将要检测的字符串的字符串替换成??123这样的格式 3 | */ 4 | export var stringNum = 0 5 | export var stringPool = { 6 | map: {} 7 | } 8 | export var rfill = /\?\?\d+/g 9 | export function dig(a) { 10 | var key = '??' + stringNum++ 11 | stringPool.map[key] = a 12 | return key + ' ' 13 | } 14 | export function fill(a) { 15 | var val = stringPool.map[a] 16 | return val 17 | } 18 | export function clearString(str) { 19 | var array = readString(str) 20 | for (var i = 0, n = array.length; i < n; i++) { 21 | str = str.replace(array[i], dig) 22 | } 23 | return str 24 | } 25 | //https://github.com/RubyLouvre/avalon/issues/1944 26 | function readString(str, i, ret) { 27 | var end = false, 28 | s = 0, 29 | i = i || 0 30 | ret = ret || []; 31 | for (var n = str.length; i < n; i++) { 32 | var c = str.charAt(i); 33 | if (!end) { 34 | if (c === "'") { 35 | end = "'"; 36 | s = i; 37 | } else if (c === '"') { 38 | end = '"'; 39 | s = i; 40 | } 41 | } else { 42 | if (c === end) { 43 | ret.push(str.slice(s, i + 1)); 44 | end = false; 45 | } 46 | } 47 | } 48 | if (end !== false) { 49 | return readString(str, s + 1, ret) 50 | } 51 | return ret; 52 | } -------------------------------------------------------------------------------- /src/vtree/makeOrphan.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 此模块只用于文本转虚拟DOM, 3 | * 因为在真实浏览器会对我们的HTML做更多处理, 4 | * 如, 添加额外属性, 改变结构 5 | * 此模块就是用于模拟这些行为 6 | */ 7 | export function makeOrphan(node, nodeName, innerHTML) { 8 | switch (nodeName) { 9 | case 'style': 10 | case 'script': 11 | case 'noscript': 12 | case 'template': 13 | case 'xmp': 14 | node.children = [{ 15 | nodeName: '#text', 16 | nodeValue: innerHTML 17 | }] 18 | break 19 | case 'textarea': 20 | var props = node.props 21 | props.type = nodeName 22 | props.value = innerHTML 23 | node.children = [{ 24 | nodeName: '#text', 25 | nodeValue: innerHTML 26 | }] 27 | break 28 | case 'option': 29 | node.children = [{ 30 | nodeName: '#text', 31 | nodeValue: trimHTML(innerHTML) 32 | }] 33 | break 34 | } 35 | 36 | } 37 | 38 | //专门用于处理option标签里面的标签 39 | var rtrimHTML = /<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi 40 | function trimHTML(v) { 41 | return String(v).replace(rtrimHTML, '').trim() 42 | } 43 | 44 | //widget rule duplex validate -------------------------------------------------------------------------------- /src/vtree/makeTbody.js: -------------------------------------------------------------------------------- 1 | //只有遇到第一个直接放在table下的tr元素,才会插入新tbody,并收集接下来的其他非tbody, thead, tfoot元素 2 | 3 | var rtbody = /^(tbody|thead|tfoot)$/ 4 | export function makeTbody(nodes) { 5 | var tbody = false 6 | for (var i = 0, n = nodes.length; i < n; i++) { 7 | var node = nodes[i] 8 | if (rtbody.test(node.nodeName)) { 9 | tbody = false 10 | continue 11 | } 12 | if (node.nodeName === 'tr') { 13 | if (tbody) { 14 | nodes.splice(i, 1) 15 | tbody.children.push(node) 16 | n-- 17 | i-- 18 | } else { 19 | tbody = { 20 | nodeName: 'tbody', 21 | props: {}, 22 | children: [node] 23 | } 24 | nodes.splice(i, 1, tbody) 25 | } 26 | } else { 27 | if (tbody) { 28 | nodes.splice(i, 1) 29 | tbody.children.push(node) 30 | n-- 31 | i-- 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/vtree/orphanTag.js: -------------------------------------------------------------------------------- 1 | export var orphanTag = { 2 | script: 1, 3 | style: 1, 4 | textarea: 1, 5 | xmp: 1, 6 | noscript: 1, 7 | template: 1 8 | } -------------------------------------------------------------------------------- /src/vtree/voidTag.js: -------------------------------------------------------------------------------- 1 | export var voidTag = { 2 | area: 1, 3 | base: 1, 4 | basefont: 1, 5 | bgsound: 1, 6 | br: 1, 7 | col: 1, 8 | command: 1, 9 | embed: 1, 10 | frame: 1, 11 | hr: 1, 12 | img: 1, 13 | input: 1, 14 | keygen: 1, 15 | link: 1, 16 | meta: 1, 17 | param: 1, 18 | source: 1, 19 | track: 1, 20 | wbr: 1 21 | } -------------------------------------------------------------------------------- /structure.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/structure.jpg -------------------------------------------------------------------------------- /test/beforeIt.js: -------------------------------------------------------------------------------- 1 | function heredoc(fn) { 2 | return fn.toString().replace(/^[^\/]+\/\*!?\s?/, ''). 3 | replace(/\*\/[^\/]+$/, '').trim().replace(/>\s*<') 4 | } 5 | function getInnerHTML(el){ 6 | return el.innerHTML.toLowerCase().replace(/\s*class=""/,'') 7 | } 8 | var textProp = 'textContent' in document ? 'textContent': 'innerText' 9 | function fireClick(el) { 10 | if (el.click) { 11 | el.click() 12 | }else { 13 | //https://developer.mozilla.org/samples/domref/dispatchEvent.html 14 | var evt = document.createEvent('MouseEvents') 15 | evt.initMouseEvent('click', true, true, window, 16 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 17 | !el.dispatchEvent(evt); 18 | } 19 | } 20 | 21 | var dddDIV = document.getElementById('aaa') 22 | var expect1 = function(a){ 23 | return { 24 | toBe: function(b){ 25 | console.log(a, b, a=== b) 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /test/directives/active.spec.js: -------------------------------------------------------------------------------- 1 | import { avalon } from '../../src/seed/core' 2 | 3 | describe('active', function() { 4 | var body = document.body, 5 | div, vm 6 | beforeEach(function() { 7 | div = document.createElement('div') 8 | body.appendChild(div) 9 | }) 10 | afterEach(function() { 11 | body.removeChild(div) 12 | delete avalon.vmodels[vm.$id] 13 | }) 14 | it('test', function(done) { 15 | div.innerHTML = heredoc(function() { 16 | /* 17 |
111 18 |
19 | */ 20 | }) 21 | vm = avalon.define({ 22 | $id: 'hover1', 23 | aaa: 'h' 24 | }) 25 | avalon.scan(div) 26 | var el = div.getElementsByTagName('div')[0] 27 | var v = el.getAttribute('avalon-events') 28 | var map = {} 29 | v.replace(/[^,]+/g, function(vv) { 30 | var arr = vv.split(':') 31 | map[arr[0]] = arr[1] 32 | }) 33 | expect(Object.keys(map).sort().join('')).toMatch(/(mousedownmouseleavemouseup|mousedownmouseoutmouseup)/) 34 | var fn = avalon.eventListeners[map.mousedown] 35 | fn({ 36 | type: 'mousedown', 37 | target: el 38 | }) 39 | expect(avalon(el).hasClass('h')).toBe(true) 40 | fn = avalon.eventListeners[map.mouseup] 41 | fn({ 42 | type: 'mouseup', 43 | target: el 44 | }) 45 | expect(avalon(el).hasClass('h')).toBe(false) 46 | done() 47 | 48 | }) 49 | }) -------------------------------------------------------------------------------- /test/directives/class.spec.js: -------------------------------------------------------------------------------- 1 | import { avalon } from '../../src/seed/core' 2 | 3 | describe('class', function () { 4 | var body = document.body, div, vm 5 | beforeEach(function () { 6 | div = document.createElement('div') 7 | body.appendChild(div) 8 | }) 9 | afterEach(function () { 10 | body.removeChild(div) 11 | delete avalon.vmodels[vm.$id] 12 | }) 13 | it('test', function (done) { 14 | div.innerHTML = heredoc(function () { 15 | /* 16 | 21 | */ 22 | }) 23 | vm = avalon.define({ 24 | $id: 'class1', 25 | aa: {a: 1, b: 1, c: 0}, 26 | bb: 'd', 27 | cc: 'a b c' 28 | }) 29 | avalon.scan(div) 30 | var lis = div.getElementsByTagName('li') 31 | expect(lis.length).toBe(3) 32 | expect(lis[0].className).toBe('a b') 33 | expect(lis[1].className).toBe('d e f') 34 | expect(lis[2].className).toBe('e a b c') 35 | vm.aa = { 36 | a: 0, 37 | b: 1, 38 | c: 1 39 | } 40 | vm.bb = 'z' 41 | vm.cc = 'aa bb' 42 | setTimeout(function () { 43 | expect(lis[0].className).toBe('b c') 44 | expect(lis[1].className).toBe('z e f') 45 | expect(lis[2].className).toBe('e aa bb') 46 | done() 47 | }) 48 | 49 | }) 50 | }) -------------------------------------------------------------------------------- /test/directives/hover.spec.js: -------------------------------------------------------------------------------- 1 | import { avalon } from '../../src/seed/core' 2 | 3 | describe('hover', function() { 4 | var body = document.body, 5 | div, vm 6 | beforeEach(function() { 7 | div = document.createElement('div') 8 | body.appendChild(div) 9 | }) 10 | afterEach(function() { 11 | body.removeChild(div) 12 | delete avalon.vmodels[vm.$id] 13 | }) 14 | it('test', function(done) { 15 | div.innerHTML = heredoc(function() { 16 | /* 17 |
111 18 |
19 | */ 20 | }) 21 | vm = avalon.define({ 22 | $id: 'hover1', 23 | aaa: 'h' 24 | }) 25 | avalon.scan(div) 26 | var el = div.getElementsByTagName('div')[0] 27 | var v = el.getAttribute('avalon-events') || '' 28 | avalon.log('ms-hover上的所有事件', v) 29 | var map = {} 30 | v.replace(/[^,]+/g, function(vv) { 31 | var arr = vv.split(':') 32 | map[arr[0]] = arr[1] 33 | }) 34 | expect(Object.keys(map).sort().join('')).toMatch(/(mouseentermouseleave|mouseout|mouseover)/) 35 | var fn = avalon.eventListeners[map.mouseenter||map.mouseover] 36 | fn({ 37 | type: 'mouseenter', 38 | target: el 39 | }) 40 | expect(avalon(el).hasClass('h')).toBe(true) 41 | fn = avalon.eventListeners[map.mouseleave||map.mouseout] 42 | fn({ 43 | type: 'mouseleave', 44 | target: el 45 | }) 46 | expect(avalon(el).hasClass('h')).toBe(false) 47 | 48 | vm.aaa = 'ddd ccc' 49 | setTimeout(function() { 50 | expect(avalon(el).attr('change-hover')).toBe('ddd ccc') 51 | done() 52 | }, 100) 53 | 54 | 55 | }) 56 | }) -------------------------------------------------------------------------------- /test/directives/text.spec.js: -------------------------------------------------------------------------------- 1 | import { avalon } from '../../src/seed/core' 2 | 3 | describe('text', function () { 4 | var body = document.body, div, vm 5 | beforeEach(function () { 6 | div = document.createElement('div') 7 | body.appendChild(div) 8 | }) 9 | afterEach(function () { 10 | body.removeChild(div) 11 | delete avalon.vmodels[vm.$id] 12 | }) 13 | it('test', function (done) { 14 | div.innerHTML = heredoc(function () { 15 | /* 16 |
{{@bb}}
17 | */ 18 | }) 19 | vm = avalon.define({ 20 | $id: 'text1', 21 | aa: '清风炎羽', 22 | bb: '司徒正美' 23 | }) 24 | avalon.scan(div) 25 | expect(div.children[0].innerHTML).toBe('清风炎羽') 26 | vm.aa = '新的内容' 27 | setTimeout(function () { 28 | expect(div.children[0].innerHTML).toBe('新的内容') 29 | done() 30 | }) 31 | 32 | }) 33 | it('测试date过滤器', function (done) { 34 | div.innerHTML = heredoc(function () { 35 | /* 36 |
{{@bb}}
37 | */ 38 | }) 39 | vm = avalon.define({ 40 | $id: 'text2', 41 | aa: new Date(2007,8,9)-0, 42 | bb: '司徒正美' 43 | }) 44 | avalon.scan(div) 45 | expect(div.children[0].innerHTML).toBe('2007-09-09') 46 | vm.aa = new Date(2007,5,1)-0 47 | setTimeout(function () { 48 | expect(div.children[0].innerHTML).toBe('2007-06-01') 49 | done() 50 | }) 51 | 52 | }) 53 | 54 | it('voidTag', function () { 55 | 56 | div.innerHTML = heredoc(function () { 57 | /* 58 |
59 | */ 60 | }) 61 | vm = avalon.define({ 62 | $id: 'text3', 63 | aaa: 'xxxxx', 64 | bbb: 222 65 | }) 66 | try{ 67 | avalon.scan(div) 68 | }catch(e){ 69 | expect(div.innerHTML).not.toMatch(/xxxxx/i) 70 | } 71 | }) 72 | }) -------------------------------------------------------------------------------- /test/dom/class.compact.spec.js: -------------------------------------------------------------------------------- 1 | import { avalon } from '../../src/seed/core' 2 | import { ClassList, classListFactory } from '../../src/dom/class/compact' 3 | 4 | describe('class', function () { 5 | it('ClassList', function () { 6 | var node = { 7 | setAttribute: function (name, cls) { 8 | this.className = { 9 | baseVal: cls 10 | } 11 | }, 12 | className: { 13 | baseVal: '' 14 | } 15 | } 16 | var ss = classListFactory(node) 17 | expect(ss).toInstanceOf(ClassList) 18 | ss.add('aaa') 19 | ss.add('bbb') 20 | 21 | expect(ss + "").toBe('aaa bbb') 22 | expect(ss.contains('bbb')).toBe(true) 23 | ss.remove('bbb') 24 | ss.add('er') 25 | expect(ss + '').toBe('aaa er') 26 | //========= 27 | node.className = "" 28 | node.setAttribute = function (_, cls) { 29 | node.className = cls 30 | } 31 | //======= 32 | ss.add('last') 33 | //jasmine 34 | expect(ss.node.className).toBe('last') 35 | if (avalon.modern) { 36 | var textPath = document.createElementNS("http://www.w3.org/2000/svg", "textPath"); 37 | expect(typeof textPath).toBe('object') 38 | avalon(textPath).addClass('aaa bbb ccc') 39 | expect(textPath.getAttribute('class')).toBe('aaa bbb ccc') 40 | avalon(textPath).removeClass('aaa ccc') 41 | expect(textPath.getAttribute('class')).toBe('bbb') 42 | } 43 | var div = document.createElement("div"); 44 | 45 | avalon(div).addClass('aaa bbb ccc') 46 | expect(div.className).toBe('aaa bbb ccc') 47 | avalon(div).removeClass('aaa ccc') 48 | expect(div.className).toBe('bbb') 49 | avalon(div).toggleClass('eee') 50 | expect(div.className).toBe('bbb eee') 51 | avalon(div).toggleClass('eee') 52 | expect(div.className).toBe('bbb') 53 | avalon(div).toggleClass('bbb', false) 54 | expect(div.className).toBe('') 55 | avalon(div).toggleClass('ccc fff', true) 56 | expect(div.className).toBe('ccc fff') 57 | }) 58 | 59 | }) 60 | -------------------------------------------------------------------------------- /test/dom/class.modern.spec.js: -------------------------------------------------------------------------------- 1 | import { avalon } from '../../src/seed/core' 2 | import '../../src/dom/class/modern' 3 | 4 | describe('class', function () { 5 | it('classList', function () { 6 | 7 | if (avalon.modern) { 8 | var textPath = document.createElementNS("http://www.w3.org/2000/svg", "textPath"); 9 | expect(typeof textPath).toBe('object') 10 | avalon(textPath).addClass('aaa bbb ccc') 11 | expect(textPath.getAttribute('class')).toBe('aaa bbb ccc') 12 | avalon(textPath).removeClass('aaa ccc') 13 | expect(textPath.getAttribute('class')).toBe('bbb') 14 | } 15 | var div = document.createElement("div"); 16 | 17 | avalon(div).addClass('aaa bbb ccc') 18 | expect(div.className).toBe('aaa bbb ccc') 19 | avalon(div).removeClass('aaa ccc') 20 | expect(div.className).toBe('bbb') 21 | avalon(div).toggleClass('eee') 22 | expect(div.className).toBe('bbb eee') 23 | avalon(div).toggleClass('eee') 24 | expect(div.className).toBe('bbb') 25 | avalon(div).toggleClass('bbb', false) 26 | expect(div.className).toBe('') 27 | avalon(div).toggleClass('ccc fff', true) 28 | expect(div.className).toBe('ccc fff') 29 | }) 30 | 31 | }) 32 | -------------------------------------------------------------------------------- /test/dom/css.compact.spec.js: -------------------------------------------------------------------------------- 1 | import { avalon } from '../../src/seed/core' 2 | import '../../src/dom/css/compact' 3 | 4 | describe('css', function () { 5 | it('test', function () { 6 | var $root = avalon(avalon.root) 7 | expect( $root.position() ).toEqual({ 8 | top: 0, 9 | left: 0 10 | }) 11 | expect( $root.offsetParent()[0] ).toBe(avalon.root) 12 | expect( $root.css('color', 'red') ).toBe($root) 13 | expect( $root.css('color') ).toMatch(/red|rgb\(255,\s*0,\s*0\)/) 14 | $root.css('color', '') 15 | expect( avalon.root.style.color ).toBe('') 16 | expect( $root.offset() ).toEqual({left: 0, top: 0}) 17 | 18 | expect( $root.css('opacity') ).toBe('1') 19 | $root.css('opacity', 0.55) 20 | //phantomjs在这里返回 0.550000011920929 21 | expect( $root.css('opacity') ).toMatch(/0\.55/) 22 | $root.css('opacity', '0.8') 23 | //phantomjs在这里返回 0.800000011920929 24 | expect( $root.css('opacity') ).toMatch(/0\.8/) 25 | $root.css('opacity', '0.823') 26 | //phantomjs在这里返回'0.8230000138282776 27 | expect( $root.css('opacity') ).toMatch(/0\.823/) 28 | expect( $root.css('top') ).toBe('0px') 29 | expect( $root.css('left') ).toBe('0px') 30 | }) 31 | 32 | }) 33 | -------------------------------------------------------------------------------- /test/dom/css.modern.spec.js: -------------------------------------------------------------------------------- 1 | import { avalon } from '../../src/seed/core' 2 | import '../../src/dom/css/modern' 3 | 4 | describe('css', function () { 5 | it('test', function () { 6 | var $root = avalon(avalon.root) 7 | expect( $root.position() ).toEqual({ 8 | top: 0, 9 | left: 0 10 | }) 11 | expect( $root.offsetParent()[0] ).toBe(avalon.root) 12 | expect( $root.css('color', 'red') ).toBe($root) 13 | expect( $root.css('color') ).toMatch(/red|rgb\(255,\s*0,\s*0\)/) 14 | $root.css('color', '') 15 | expect( avalon.root.style.color ).toBe('') 16 | expect( $root.offset() ).toEqual({left: 0, top: 0}) 17 | 18 | expect( $root.css('opacity') ).toBe('1') 19 | $root.css('opacity', 0.55) 20 | //phantomjs在这里返回 0.550000011920929 21 | expect( $root.css('opacity') ).toMatch(/0\.55/) 22 | $root.css('opacity', '0.8') 23 | //phantomjs在这里返回 0.800000011920929 24 | expect( $root.css('opacity') ).toMatch(/0\.8/) 25 | $root.css('opacity', '0.823') 26 | //phantomjs在这里返回'0.8230000138282776 27 | expect( $root.css('opacity') ).toMatch(/0\.823/) 28 | expect( $root.css('top') ).toBe('0px') 29 | expect( $root.css('left') ).toBe('0px') 30 | }) 31 | it('offset', function () { 32 | expect( avalon({}).offset() ).toEqual({left: 0, top: 0}) 33 | }) 34 | it('scrollTop/scrollLeft', function () { 35 | avalon(document).scrollTop(100) 36 | avalon(document).scrollLeft(100) 37 | avalon(document.createElement('div')).scrollLeft(100) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /test/dom/event.compact.spec.js: -------------------------------------------------------------------------------- 1 | import { avalon } from '../../src/seed/core' 2 | import { avEvent } from '../../src/dom/event/compact' 3 | import '../../src/dom/ready/compact' 4 | 5 | describe('event', function () { 6 | it('getAttribute', function () { 7 | var div2 = document.createElement('div') 8 | div2.setAttribute("aaa", 'ddd') 9 | 10 | expect(div2.getAttribute('aaa')).toBe('ddd') 11 | }) 12 | it('avEvent', function () { 13 | var event = { 14 | srcElement: { 15 | ownerDocument: { 16 | documentElement: {}, 17 | body: {} 18 | } 19 | }, 20 | type: 'click', 21 | keyCode: 12, 22 | clientX: 11, 23 | clientY: 118, 24 | wheelDelta: 0 25 | } 26 | var e = new avEvent(event) 27 | 28 | expect(e.target).toBe(event.srcElement) 29 | expect(e.originalEvent).toBe(event) 30 | expect(e.type).toBe('click') 31 | expect(e.pageX).toBe(11) 32 | expect(e.pageY).toBe(118) 33 | expect(e.wheelDelta).toBe(0) 34 | expect(e.preventDefault).toA('function') 35 | expect(e.stopPropagation).toA('function') 36 | expect(e.stopImmediatePropagation).toA('function') 37 | e.preventDefault() 38 | expect(e.returnValue).toBe(false) 39 | e.stopPropagation() 40 | expect(e.cancelBubble).toBe(true) 41 | e.cancelBubble = 2 42 | e.stopImmediatePropagation() 43 | expect(e.cancelBubble).toBe(true) 44 | expect(e.stopImmediate).toBe(true) 45 | expect(e + "").toMatch(/object\s+Event/) 46 | var e2 = new avEvent(e) 47 | expect(e2).toBe(e) 48 | 49 | }) 50 | it('bind', function () { 51 | var div = document.createElement('div') 52 | document.body.appendChild(div) 53 | var changed = false 54 | avalon(div).bind('click', function () { 55 | changed = true 56 | return false 57 | }) 58 | avalon.fireDom(div, 'click') 59 | fireClick(div) 60 | expect(changed).toBe(true) 61 | avalon(div).unbind('click') 62 | 63 | }) 64 | 65 | 66 | }) 67 | -------------------------------------------------------------------------------- /test/dom/event.modern.spec.js: -------------------------------------------------------------------------------- 1 | import { avalon } from '../../src/seed/core' 2 | import { avEvent } from '../../src/dom/event/modern' 3 | import '../../src/dom/ready/modern' 4 | 5 | describe('event', function () { 6 | it('getAttribute', function () { 7 | var div2 = document.createElement('div') 8 | div2.setAttribute("aaa", 'ddd') 9 | 10 | expect(div2.getAttribute('aaa')).toBe('ddd') 11 | }) 12 | it('avEvent', function () { 13 | var event = { 14 | srcElement: { 15 | ownerDocument: { 16 | documentElement: {}, 17 | body: {} 18 | } 19 | }, 20 | type: 'click', 21 | keyCode: 12, 22 | clientX: 11, 23 | clientY: 118, 24 | wheelDelta: 0 25 | } 26 | var e = new avEvent(event) 27 | 28 | expect(e.target).toBe(event.srcElement) 29 | expect(e.originalEvent).toBe(event) 30 | expect(e.type).toBe('click') 31 | if(!avalon.modern){ 32 | expect(e.pageX).toBe(11) 33 | expect(e.pageY).toBe(118) 34 | expect(e.wheelDelta).toBe(0) 35 | } 36 | expect(e.preventDefault).toA('function') 37 | expect(e.stopPropagation).toA('function') 38 | expect(e.stopImmediatePropagation).toA('function') 39 | e.preventDefault() 40 | expect(e.returnValue).toBe(false) 41 | e.stopPropagation() 42 | expect(e.cancelBubble).toBe(true) 43 | e.cancelBubble = 2 44 | e.stopImmediatePropagation() 45 | expect(e.cancelBubble).toBe(true) 46 | expect(e.stopImmediate).toBe(true) 47 | expect(e + "").toMatch(/object\s+Event/) 48 | var e2 = new avEvent(e) 49 | expect(e2).toBe(e) 50 | 51 | }) 52 | it('bind', function () { 53 | var div = document.createElement('div') 54 | document.body.appendChild(div) 55 | var changed = false 56 | avalon(div).bind('click', function () { 57 | changed = true 58 | return false 59 | }) 60 | avalon.fireDom(div, 'click') 61 | fireClick(div) 62 | expect(changed).toBe(true) 63 | avalon(div).unbind('click') 64 | 65 | }) 66 | 67 | 68 | }) 69 | -------------------------------------------------------------------------------- /test/dom/ready.compact.spec.js: -------------------------------------------------------------------------------- 1 | import { avalon } from '../../src/seed/core' 2 | import { fireReady } from '../../src/dom/ready/compact' 3 | 4 | 5 | describe('ready', function () { 6 | it('isReady', function () { 7 | 8 | expect(avalon.isReady).toBe(true) 9 | var a = 1 10 | avalon.isReady = false 11 | avalon.ready(function () { 12 | a = 2 13 | }) 14 | fireReady() 15 | expect(avalon.isReady).toBe(true) 16 | expect(a).toBe(2) 17 | 18 | avalon.ready(function () { 19 | a = 3 20 | }) 21 | expect(a).toBe(3) 22 | }) 23 | 24 | }) 25 | -------------------------------------------------------------------------------- /test/dom/ready.modern.spec.js: -------------------------------------------------------------------------------- 1 | import { avalon } from '../../src/seed/core' 2 | import { fireReady } from '../../src/dom/ready/modern' 3 | 4 | 5 | describe('ready', function () { 6 | it('isReady', function () { 7 | 8 | expect(avalon.isReady).toBe(true) 9 | var a = 1 10 | avalon.isReady = false 11 | avalon.ready(function () { 12 | a = 2 13 | }) 14 | fireReady() 15 | expect(avalon.isReady).toBe(true) 16 | expect(a).toBe(2) 17 | 18 | avalon.ready(function () { 19 | a = 3 20 | }) 21 | expect(a).toBe(3) 22 | }) 23 | 24 | }) 25 | -------------------------------------------------------------------------------- /test/dom/shim.compact.spec.js: -------------------------------------------------------------------------------- 1 | import { avalon } from '../../src/dom/shim/compact' 2 | import { fixClone } from '../../src/dom/shim/fixClone' 3 | 4 | import '../../src/dom/ready/compact' 5 | 6 | describe('shim', function () { 7 | var clone 8 | beforeEach(function () { 9 | var div = document.createElement('div') 10 | div.innerHTML = heredoc(function () { 11 | /* 12 | 13 | 14 | 15 | 16 | 17 | */ 18 | }) 19 | clone = fixClone(div) 20 | }) 21 | it('avalon.cloneNode', function () { 22 | //注意,不要复制html元素 23 | var div2 = document.createElement('map') 24 | var map = avalon.cloneNode(div2) 25 | expect(map.nodeName).toBe('MAP') 26 | 27 | }) 28 | it('fixClone1', function () { 29 | 30 | var inputs = clone.getElementsByTagName('input') 31 | expect(inputs[0].checked).toBe(true) 32 | expect(inputs[1].checked).toBe(true) 33 | expect(inputs[0].value).toBe('333') 34 | expect(inputs[1].value).toBe('444') 35 | 36 | }) 37 | it('fixClone2', function () { 38 | var option = clone.getElementsByTagName('option')[0] 39 | expect(option.selected).toBe(true) 40 | var textarea = clone.getElementsByTagName('textarea')[0] 41 | expect(textarea.value).toBe('222') 42 | var param = clone.getElementsByTagName('param') 43 | expect(param.length).toBe(7) 44 | }) 45 | it('avalon.contains', function (done) { 46 | avalon.ready(function () { 47 | expect(avalon.contains(avalon.root, document.body)).toBe(true) 48 | done() 49 | }) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /test/parser/index.js: -------------------------------------------------------------------------------- 1 | import { createGetter, createSetter, addScope } from '../../src/parser/index' 2 | 3 | describe('parser', function() { 4 | it('test', function() { 5 | var a = createGetter("ssss throw eee ") 6 | expect(a).toBe(avalon.noop) 7 | var b = createSetter("ssss throw eee ") 8 | expect(b).toBe(avalon.noop) 9 | }) 10 | 11 | it('处理正则', function() { 12 | var arr = addScope("{required:true,pattern:/[\u4e00-\u9fa5a-z]{2-8}/i}") 13 | expect(arr[0]).toBe('{required :true,pattern :/[\u4e00-\u9fa5a-z]{2-8}/i }') 14 | 15 | }) 16 | it('处理数组', function() { 17 | var arr = addScope("[{is:'ms-address-wrap', $id:'address'}]") 18 | expect(arr[0]).toBe("[{is :'ms-address-wrap' ,$id :'address' }]") 19 | 20 | }) 21 | 22 | it('avalon.lexer', function() { 23 | var str = `
{{ el['a'] }}{{ el['b'] }}{{ el['c'] }}
` 24 | var a = avalon.lexer(str) 25 | expect(a).toEqual([{ 26 | nodeName: 'div', 27 | props: {}, 28 | children: [{ 29 | nodeName: "tr", 30 | props: {}, 31 | children: [{ 32 | nodeName: 'td', 33 | props: {}, 34 | children: [{ 35 | nodeName: "#text", 36 | nodeValue: "{{ el['a'] }}" 37 | }] 38 | }, { 39 | nodeName: 'td', 40 | props: {}, 41 | children: [{ 42 | nodeName: "#text", 43 | nodeValue: "{{ el['b'] }}" 44 | }] 45 | }, { 46 | nodeName: 'td', 47 | props: {}, 48 | children: [{ 49 | nodeName: "#text", 50 | nodeValue: "{{ el['c'] }}" 51 | }] 52 | }] 53 | }, { 54 | nodeName: '#comment', 55 | nodeValue: 'for3061628999' 56 | }] 57 | }]) 58 | 59 | }) 60 | }) -------------------------------------------------------------------------------- /test/seed/browser.spec.js: -------------------------------------------------------------------------------- 1 | import {avalon} from '../../src/seed/core' 2 | describe('seed/browser', function () { 3 | 4 | it('browser', function () { 5 | expect(avalon).toHaveKeys(['window', 'document', 6 | 'root', 'msie', 'modern', 'inBrowser']) 7 | }) 8 | }) -------------------------------------------------------------------------------- /test/seed/cache.spec.js: -------------------------------------------------------------------------------- 1 | import {Cache} from '../../src/seed/core' 2 | 3 | describe('测试cache文件的API', function () { 4 | 5 | describe('Cache', function () { 6 | var cache = new Cache(3) 7 | it('test', function () { 8 | 9 | expect(cache.get).toA('function') 10 | expect(cache.put).toA('function') 11 | expect(cache.shift).toA('function') 12 | var e = cache.put('aa', 'bb') 13 | expect(e).toBe('bb') 14 | expect(cache.limit).toBe(3) 15 | expect(cache.size).toBe(1) 16 | cache.put('eee', 'bb') 17 | cache.put('ddd', '111') 18 | cache.get('aa') 19 | cache.put('fff', '111') 20 | cache.get('aa') 21 | cache.put('999', '111') 22 | expect(cache.size).toBe(3) 23 | expect(cache.get('eee')).toBe(void 0) 24 | }) 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /test/spec.js: -------------------------------------------------------------------------------- 1 | import './seed/core.spec' 2 | import './seed/browser.spec' 3 | import './seed/cache.spec' 4 | import './seed/lang.compact.spec' 5 | 6 | import './filters/index.spec' 7 | 8 | 9 | import './vdom/compact.spec' 10 | 11 | //DOM相关测试 12 | 13 | import './dom/shim.compact.spec' 14 | import './dom/ready.compact.spec' 15 | import './dom/val.compact.spec' 16 | import './dom/class.compact.spec' 17 | import './dom/html.spec' 18 | import './dom/attr.compact.spec' 19 | import './dom/event.compact.spec' 20 | import './dom/css.compact.spec' 21 | 22 | 23 | import './vtree/clearString.spec' 24 | import './vtree/fromString.spec' 25 | import './vtree/fromDOM.spec' 26 | 27 | 28 | import './vmodel/compact.spec' 29 | // 30 | import './parser/index' 31 | // 32 | //这不是测试,但下面的模块都依赖这个 33 | // 34 | import '../src/directives/compact' 35 | import '../src/renders/domRender' 36 | 37 | import './directives/attr.spec' 38 | // 39 | // 40 | import './directives/duplex.spec' 41 | 42 | import './directives/expr.spec' 43 | import './directives/css.spec' 44 | import './directives/important.spec' 45 | import './directives/on.spec' 46 | 47 | import './directives/controller.spec' 48 | import './directives/if.spec' 49 | 50 | import './directives/text.spec' 51 | import './directives/class.spec' 52 | import './directives/hover.spec' 53 | import './directives/active.spec' 54 | import './directives/visible.spec' 55 | import './directives/validate.spec' 56 | import './directives/rules.spec' 57 | 58 | import './directives/for.spec' 59 | import './directives/effect.spec' 60 | 61 | import './directives/widget.spec' 62 | // 63 | -------------------------------------------------------------------------------- /test/spec.modern.js: -------------------------------------------------------------------------------- 1 | import './seed/core.spec' 2 | import './seed/browser.spec' 3 | import './seed/cache.spec' 4 | import './seed/lang.modern.spec' 5 | 6 | import './filters/index.spec' 7 | import './vdom/modern.spec' 8 | 9 | //DOM相关 10 | import './dom/shim.modern.spec' 11 | import './dom/ready.modern.spec' 12 | import './dom/val.modern.spec' 13 | import './dom/class.modern.spec' 14 | import './dom/html.spec' 15 | import './dom/attr.modern.spec' 16 | import './dom/event.modern.spec' 17 | import './dom/css.modern.spec' 18 | 19 | import './vtree/clearString.spec' 20 | import './vtree/fromString.spec' 21 | import './vtree/fromDOM.spec' 22 | 23 | 24 | import './vmodel/modern.spec' 25 | 26 | import './parser/index' 27 | 28 | 29 | //这不是测试,但下面的模块都依赖这个 30 | 31 | import '../src/directives/modern' 32 | import '../src/renders/domRender' 33 | 34 | import './directives/attr.spec' 35 | import './directives/duplex.spec' 36 | import './directives/controller.spec' 37 | import './directives/if.spec' 38 | 39 | 40 | import './directives/expr.spec' 41 | import './directives/css.spec' 42 | import './directives/important.spec' 43 | import './directives/on.spec' 44 | 45 | import './directives/text.spec' 46 | import './directives/class.spec' 47 | import './directives/hover.spec' 48 | import './directives/active.spec' 49 | import './directives/visible.spec' 50 | import './directives/validate.spec' 51 | import './directives/rules.spec' 52 | import './directives/for.spec' 53 | import './directives/effect.spec' 54 | 55 | import './directives/widget.spec' -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | describe('add', function () { 2 | it('should add two numbers and return the result', function () { 3 | expect(window.add(1, 2)).toBe(3); 4 | }); 5 | }); 6 | 7 | describe('subtract', function () { 8 | it('should subtract two numbers', function () { 9 | expect(window.subtract(2, 1)).toBe(1); 10 | }); 11 | }); 12 | 13 | describe('innerHTML', function () { 14 | it('should subtract two numbers', function (done) { 15 | var div = document.createElement('div') 16 | div.style.cssText = 'width:300px;height:300px;background:red' 17 | document.body.appendChild(div) 18 | div.innerHTML = 'test3' 19 | setTimeout(function(){ 20 | expect(div.innerHTML).toBe('test3'); 21 | done() 22 | },100) 23 | 24 | }); 25 | }); -------------------------------------------------------------------------------- /test/vtree/clearString.spec.js: -------------------------------------------------------------------------------- 1 | import { dig, clearString, fill, rfill } from '../../src/vtree/clearString' 2 | 3 | describe('clearString', function() { 4 | 5 | it('test', function() { 6 | var a = "111+'ddd'+'eee'" 7 | var a2 = dig(a) 8 | expect(/'/.test(a2)).toBe(false) 9 | 10 | var b = '111+\n"ddd"+"eee"' 11 | var b2 = clearString(b) 12 | expect(/"/.test(b2)).toBe(false) 13 | 14 | var b3 = b.replace(rfill, fill) 15 | 16 | expect(/\?\?/.test(b3)).toBe(false) 17 | }) 18 | }) -------------------------------------------------------------------------------- /tutorials/index.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TODO supply a title 6 | 7 | 8 | 9 | 10 |
目录
11 | 12 | 13 | -------------------------------------------------------------------------------- /tutorials/lesson01.md: -------------------------------------------------------------------------------- 1 | #预览 2 | ##overview 3 | 4 | avalon是一个来自中国的MVVM框架,以良好的浏览器兼容性著称,体积少,性能卓越,简单易用,支持后端渲染,能帮大家快速搞定高度交互的页面。 5 | 6 | Avalon is a MVVM framework from China, known for good browser compatibility, small size, high-performance, ease of use, support server rendering, can help you quickly to build the rich Interactive pages. 7 | 8 | ```javascript 9 | avalon.define({ 10 | $id: "test", 11 | aaa: "avalon", 12 | bbb: "阿瓦隆" 13 | }) 14 | ``` 15 | 16 | ```html 17 |
18 |

{{@aaa}}

19 |

{{@bbb}}

20 |
21 | ``` 22 | 23 | avalon 把我们的前端代码分为两部分,VM与视图。VM位于JS文件里,用于操作视图的改变。 视图位于html文件里,用于响应JS的改动或用户的操作。 视图被avalon划分为一个个区域,每一个区域添加上ms-controller,它们对应JS中的VM。 当VM的$id等于ms-controller的值,这个区域就交由这个VM来渲染。 24 | 25 | Avalon divides our front-end code into two parts, VM and View. The VM is located in the JS file, used to change the view, The View is located in the HTML file, used to respond to changes in the JS and the user's operation. The Views are divided into some areas by Avalon, each of which is added on the "ms-controller" attributes, which corresponds to the VM in the JS. When the $id of the VM equals ms-controller, this area is rendered by this VM 26 | 27 | ![](lesson01_1.png) 28 | 29 | 如果你不想这个区域被VM处理,可以使用ms-skip属性,让avalon忽略这个元素及它的后代们 30 | 31 | If you don't want this area to be processed by VM, you can use the ms-skip attribute to let avalon ignore the element and its descendants. 32 | 33 | 大家可以打开chrome控制台,修改VM的属性,观察视图对应文字的变化,感受一下这种魔幻效果 34 | 35 | You can open the chrome console, modify the properties of VM, observe the changes in the corresponding text, feel this magic! 36 | 37 | npm install avalon2 38 | 39 | ![](./lesson01_0.gif) 40 | 41 | [index](./index.md) 42 | 43 | [next](./lesson02.md) -------------------------------------------------------------------------------- /tutorials/lesson01_0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/tutorials/lesson01_0.gif -------------------------------------------------------------------------------- /tutorials/lesson01_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/tutorials/lesson01_1.png -------------------------------------------------------------------------------- /tutorials/lesson02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/tutorials/lesson02.png -------------------------------------------------------------------------------- /tutorials/lesson03.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | lesson03 5 | 6 | 7 | 8 | 9 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tutorials/lesson03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/tutorials/lesson03.png -------------------------------------------------------------------------------- /tutorials/lesson04_0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/tutorials/lesson04_0.gif -------------------------------------------------------------------------------- /tutorials/lesson04_1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/tutorials/lesson04_1.gif -------------------------------------------------------------------------------- /tutorials/lesson04_2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/tutorials/lesson04_2.gif -------------------------------------------------------------------------------- /tutorials/lesson06_0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/tutorials/lesson06_0.gif -------------------------------------------------------------------------------- /tutorials/lesson06_1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/tutorials/lesson06_1.gif -------------------------------------------------------------------------------- /tutorials/lesson06_2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/tutorials/lesson06_2.gif -------------------------------------------------------------------------------- /tutorials/lesson07_1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/tutorials/lesson07_1.gif -------------------------------------------------------------------------------- /tutorials/lesson07_2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/tutorials/lesson07_2.gif -------------------------------------------------------------------------------- /tutorials/lesson07_3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/tutorials/lesson07_3.gif -------------------------------------------------------------------------------- /tutorials/lesson07_4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/tutorials/lesson07_4.gif -------------------------------------------------------------------------------- /tutorials/lesson08_1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/tutorials/lesson08_1.gif -------------------------------------------------------------------------------- /tutorials/lesson08_2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/tutorials/lesson08_2.gif -------------------------------------------------------------------------------- /tutorials/lesson09_1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/tutorials/lesson09_1.gif -------------------------------------------------------------------------------- /tutorials/lesson09_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/tutorials/lesson09_2.png -------------------------------------------------------------------------------- /tutorials/lesson09_3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/tutorials/lesson09_3.gif -------------------------------------------------------------------------------- /tutorials/lesson09_4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/tutorials/lesson09_4.gif -------------------------------------------------------------------------------- /tutorials/lesson15.md: -------------------------------------------------------------------------------- 1 | ##avalon的指令在上一节已经全部介绍完毕,当然有的语焉不详,如ms-js。本节主要讲述一下我对这方面的思考与探索。 2 | ##avalon directives have been all introduced to you in last section,of course there were some vagues like ms-js.I will talk about my thinkings and exploring of directives in this section. 3 | ##MVVM的成功很大一语分是来自于其指令,或叫绑定。让操作视图的功能交由形形式式的指令来代劳。VM,成了一个大管家。它只一个反射体。我们对它的操作,直接影响到视图。因此俗称“操作数据即操作视图”!至于它是怎么影响视图,avalon视其版本的不同,也有不同的解法。如果抛开avalon,纵观世上所有MVVM框架,大抵有如下几种方式 4 | ##a lot of parts of MVVM success come from diretives,or binding,Diretives operates DOM for you.VM, has become a butler.It‘s a reflector,operations to VM will reflect to View.This is why we all call it“operating variables is operating View“!As to exactly how does VM changes View,different avalon has different method。Regardless of avalon,all MVVM frameworks use the following methods. 5 | 1.函数wrapper:将原数据对象重新改造,所有属性都变成一个函数,有参数时就是赋值,进行视图同步与回调派发,没有参数时就取值,进行依赖收集。如knockout.js。 6 | 1.function wrapper:transform the original data object,make every property a function,while with a parameter it‘s a value assigning,the function Synchronize View and distribute callbacks,while no parameter,it gets it's value and collects relys。For example:knockout.js. 7 | 8 | 2.上帝getter,setter: 将原数据对象重新包装,但对数据的操作必须经过统一的set,get方法。在set方法进行视图同步与回调派发,没有参数时进行依赖收集。如reactive.js。如果放松要求,react.js也是这种方式,它使用setState进行视图同步。但它们依赖收集的过程。 9 | 2.God getter and setter:repack original data object,makes operating values must use unified set,get functions.Use set to Synchronize View and distribute callbacks,get it's value and collects relys while no parameters.For example:reactive.js.If imprecisely,react use this method as well,react.js use setState to Synchronize View,but setState rely on process of collecting. 10 | 11 | 3.函数编译及脏检测:将VM放到一个函数体内,取toString重新编译,内部是第一种方式。如angular.js. 12 | 3.Function compilation and dirty-checking:put VM inside a function,get it's toString for recompiling,and use method1 inside.f】For example:angular.js. 13 | 14 | -------------------------------------------------------------------------------- /谁在用avalon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/avalon/e405ca6e70d622a5c771079b4294a12fddfc1ca5/谁在用avalon.png --------------------------------------------------------------------------------