├── .babelrc ├── .gitignore ├── README.md ├── dist ├── soda.js ├── soda.min.js ├── soda.node.js └── soda.node.min.js ├── example ├── expression.html ├── filter.html ├── html.html ├── if.html ├── prefix.html ├── repeat.html ├── replace.html └── simple.html ├── index.html ├── node └── index.js ├── package-lock.json ├── package.json ├── pg ├── index.html ├── preview.html └── rd.html ├── plugin └── koa-view-ng.js ├── readme_zh.md ├── src ├── const.js ├── directive │ ├── class.js │ ├── html.js │ ├── if.js │ ├── include.js │ ├── repeat.js │ ├── replace.js │ ├── src.js │ └── style.js ├── index.js ├── soda.js ├── soda.old.js └── util.js ├── test ├── index.js ├── soda-browser.html ├── soda-mocha.html ├── soda.node-mocha.html ├── test.soda.js └── test.soda.node.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["transform-class-properties"], 4 | "env": { 5 | "development": { 6 | "presets": [] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.swp 3 | *.swo 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sodajs 2 | 3 | An amazing directive template engine for JavaScript. 4 | 5 | ## [中文说明](https://github.com/AlloyTeam/sodajs/blob/master/readme_zh.md) 6 | 7 | ## Fetures 8 | * super tiny size (4kb gzipped) 9 | * dom directives support 10 | * good compatibility (IE8 +, node) 11 | * prevents XSS holes out of your template file 12 | * high-performance DOM parser 13 | * directive Api compatibile with AngularJS 14 | * custom directive and prefix 15 | 16 | 17 | ## Install 18 | ### npm 19 | ``` js 20 | npm install --save sodajs 21 | ``` 22 | 23 | ### CDN 24 | * [https://unpkg.com/sodajs@0.4.10/dist/soda.min.js](https://unpkg.com/sodajs@0.4.10/dist/soda.min.js) 25 | * [https://unpkg.com/sodajs@0.4.10/dist/soda.js](https://unpkg.com/sodajs@0.4.10/dist/soda.js) 26 | * [https://unpkg.com/sodajs@0.4.10/dist/soda.node.min.js](https://unpkg.com/sodajs@0.4.10/dist/soda.node.min.js) 27 | * [https://unpkg.com/sodajs@0.4.10/dist/soda.node.js](https://unpkg.com/sodajs@0.4.10/dist/soda.node.js) 28 | 29 | 30 | ## Usage 31 | ### Difference between soda & soda.node 32 | | version | soda | soda.node | 33 | | ------------ | ------------ | ------------ | 34 | | Mordern Browsers | ✓| ✓| 35 | | Mobile Browsers | ✓ | ✓ | 36 | | ie | ≥8 | ≥9| 37 | | node | ✗ | ✓| 38 | | DOM Parsor| Native | Self | 39 | 40 | warning: ie 8 needs es5-shim or es5-sham and console-polyfill 41 | 42 | check ie 8 test below 43 | * [ie8 browser test](http://alloyteam.github.io/sodajs/test/soda-browser.html) 44 | 45 | ### Browser 46 | * script tag 47 | 48 | ```html 49 | 50 | ``` 51 | * with webpack 52 | 53 | ```javascript 54 | import soda from "sodajs" 55 | ``` 56 | 57 | ### Node 58 | ```js 59 | let soda = require('sodajs/node'); 60 | ``` 61 | or use dist version for older node 62 | ```js 63 | let soda = require('sodajs/dist/soda.node') 64 | ``` 65 | ## API 66 | ### Output 67 | 68 | #### plain 69 | 70 | ```js 71 | var tpl = '
{{name}}
'; 72 | 73 | document.body.innerHTML = soda(tpl,{ name : 'soda' }) 74 | 75 | ``` 76 | ➜ [plain example](http://alloyteam.github.io/sodajs/pg/rd.html?type=simple) 77 | 78 | 79 | #### safe propery chain output 80 | ```js 81 | var data = { 82 | name: 'soda', 83 | info: { 84 | version: '2.0' 85 | } 86 | } 87 | 88 | soda("{{info.version}}", data); 89 | // result => "2.0" 90 | 91 | 92 | soda("{{info.foo.foo1}}", data) 93 | // result => "" without errors 94 | 95 | soda("{{info['name']}}", data) 96 | // result => "2.0" 97 | 98 | ``` 99 | 100 | #### expression 101 | 102 | ```js 103 | var data = {} 104 | 105 | soda("{{1 + 2}}", data); 106 | // result => 2 107 | 108 | 109 | soda("{{true ? 'soda' : 'foo'}}", data) 110 | // result => "soda" 111 | 112 | soda("{{1 < 3 && 'soda'}}", data) 113 | // result => "soda" 114 | 115 | ``` 116 | ➜ [expression example](http://alloyteam.github.io/sodajs/pg/rd.html?type=expression) 117 | 118 | #### complex expression 119 | ```js 120 | var data = { 121 | list: [ 122 | {list: [{'title': '<>aa'}, {'title': 'bb'}], name: 0, show: 1}, 123 | {list: [{'title': 0 }, {'title': 'bb'}], name: 'b'} 124 | ] 125 | 126 | }; 127 | 128 | soda('{{list[list[0].show === 1 ? list[0].name : 1].list[0].title}}', data) 129 | // result => '<>aa' 130 | ``` 131 | 132 | ### Directives 133 | 134 | #### if 135 | 136 | ``` js 137 | var data = { name : 'soda',show: true }; 138 | soda(`
Hello, {{name}}
139 |
I\'m hidden!
`, 140 | data 141 | ) 142 | // result =>
Hello, soda
143 | ``` 144 | 145 | ➜ [if example](http://alloyteam.github.io/sodajs/pg/rd.html?type=if) 146 | 147 | 148 | ### repeat 149 | 150 | > soda-repeat="item in array" 151 | 152 | > soda-repeat="item in object" 153 | 154 | > soda-repeat="item in array by index" 155 | 156 | > soda-repeat="item in object by key" 157 | 158 | > soda-repeat="(index, value) in array" 159 | 160 | > soda-repeat="(key, value) in object" 161 | 162 | default index or key is $index 163 | 164 | 165 | ``` js 166 | var tpl = '\ 167 | ' 173 | 174 | var data = { 175 | list: [ 176 | {name: "Hello" ,show: true}, 177 | {name: "sodajs" ,show: true}, 178 | {name: "AlloyTeam"} 179 | ] 180 | }; 181 | 182 | document.body.innerHTML = soda(tpl, data); 183 | ``` 184 | 185 | ➜ [repeat example](http://alloyteam.github.io/sodajs/pg/rd.html?type=repeat) 186 | 187 | 188 | ### filter 189 | 190 | > soda.filter(String filterName, Function func(input, args...)) 191 | > {{input|filte1:args1:args2...|filter2:args...}} 192 | 193 | example: 194 | 195 | ``` js 196 | soda.filter('shortTitle', function(input, length){ 197 | return (input || '').substr(0, length); 198 | }); 199 | 200 | var tpl = '\ 201 | ' 206 | 207 | 208 | document.body.innerHTML = soda(tpl,{ list : [ 209 | {title:'short'}, 210 | {title:'i am too long!'} 211 | ] }) 212 | ``` 213 | 214 | ➜ [filter example](http://alloyteam.github.io/sodajs/pg/rd.html?type=filter) 215 | 216 | ### html 217 | output origin html as innerHTML 218 | ```js 219 | var tpl = '
' 220 | document.body.innerHTML = soda(tpl,{ html : 'test soda-html' }) 221 | ``` 222 | 223 | ➜ [html example](http://alloyteam.github.io/sodajs/pg/rd.html?type=html) 224 | 225 | ### replace 226 | replace this node with html 227 | 228 | ```js 229 | var tpl = '
' 230 | document.body.innerHTML = soda(tpl,{ html : 'test soda-html' }) 231 | ``` 232 | 233 | ➜ [replace example](http://alloyteam.github.io/sodajs/pg/rd.html?type=replace) 234 | 235 | div will be replaced with given html 236 | 237 | #### include 238 | include template 239 | 240 | soda-include="tmplateName:arg1:arg2:..." 241 | with soda.discribe, we can include sub templates 242 | 243 | ```js 244 | var data = { 245 | name: "soda" 246 | }; 247 | 248 | // define sub template named tmpl1 249 | soda.discribe('tmpl1', `

{{name}}

`); 250 | 251 | 252 | // use template tmpl1 by soda-include 253 | soda(`1`, data); 254 | // result =>

dorsy

255 | 256 | // set compile false not to compile sub template 257 | soda.discribe('tmpl1', `

{{name}}

`, { 258 | compile: false 259 | }); 260 | 261 | // show origin template 262 | soda(`1`, data); 263 | // result =>

{{name}}

264 | 265 | soda.discribe('tmpl2', function(path){ 266 | return `

{{name}}_${path}

`; 267 | }); 268 | 269 | soda(`1`, data); 270 | // result =>

soda_subpath1

271 | 272 | 273 | // In node env 274 | soda.discribe('tmplNode', function(path){ 275 | return fs.readFileSync(path, 'utf-8'); 276 | }); 277 | 278 | soda(`1`, data); 279 | // result => view.html Tmplate 280 | 281 | 282 | ``` 283 | 284 | ### Others 285 | 286 | #### soda-class 287 | > soda-class="currItem === 'list1' ? 'active' : ''" 288 | 289 | 290 | #### soda-src 291 | > soda-src="hello{{index}}.png" 292 | 293 | #### soda-style 294 | > soda-style="style" 295 | 296 | data example: 297 | 298 | ```js 299 | var data = { style : { width : '100px', height : '100px' } }; 300 | ``` 301 | 302 | 303 | #### soda-* 304 | > soda-rx="{{rx}}%" 305 | 306 | > soda-checked="{{false}}" 307 | 308 | if the value is false or "", the attribute will be removed 309 | 310 | ## Custom 311 | 312 | ### soda.prefix 313 | 314 | change prefix as you like, the default prefix is "soda-" 315 | 316 | ``` js 317 | soda.prefix('v:') 318 | 319 | var tpl = '\ 320 | ' 325 | 326 | 327 | var data = { 328 | list: [ 329 | {name: "Hello" ,show: true}, 330 | {name: "sodajs" ,show: true}, 331 | {name: "AlloyTeam"} 332 | ] 333 | }; 334 | 335 | document.body.innerHTML = soda(tpl, data); 336 | ``` 337 | ### soda.directive 338 | Custom your directive 339 | #### es 2015 340 | ```js 341 | soda.directive('name', { 342 | priority: 8, 343 | 344 | // how to compile el 345 | link({ scope, el, parseSodaExpression, expression, getValue, compileNode, document }) { 346 | 347 | } 348 | }); 349 | ``` 350 | * scope: current scope data 351 | * el: current node elment 352 | * expression: directive string value 353 | * getValue: get value from data 354 | ```js 355 | getValue({a: {b: 1}}, "a.b"); // ===> 1 356 | ``` 357 | * parseSodaExpression: parse soda expressions 358 | ```js 359 | parseSodaExpression('{{1 + 2 + a}}', {a: 1}); // ===> 4 360 | ``` 361 | * compileNode: compile new nodes 362 | * document: using document rather than window.document to run in node env; 363 | 364 | #### example 365 | ```js 366 | soda.directive('mydirective', { 367 | priority: 8, 368 | 369 | link({ scope, el, parseSodaExpression, expression, getValue, compileNode, document }) { 370 | var value = parseSodaExpression(expression); 371 | if(value){ 372 | var textNode = document.createTextNode(value); 373 | el.appendChild(textNode); 374 | } 375 | } 376 | } 377 | 378 | soda(` 379 |
380 | `, { 381 | tips: 'tips' 382 | }); 383 | 384 | // result ==>
add one tips: tips
385 | ``` 386 | 387 | 388 | ### soda.setDocument 389 | custom dom parsor for node running. 390 | 391 | soda.node version default document dom parsor is nodeWindow. 392 | 393 | ```js 394 | var document = require('document'); 395 | var soda = require('soda'); 396 | 397 | soda.setDocument(document); 398 | 399 | // ... run 400 | 401 | ``` 402 | 403 | 404 | ## Contribute 405 | ### Development 406 | 407 | git clone 408 | ``` shell 409 | git clone git://github.com/AlloyTeam/sodajs.git 410 | ``` 411 | install dependency 412 | 413 | ``` shell 414 | npm install 415 | ``` 416 | 417 | then run npm start 418 | 419 | ``` shell 420 | npm start 421 | ``` 422 | publish code to run test 423 | 424 | ``` shell 425 | npm run build 426 | ``` 427 | ### Auto-Test 428 | soda uses mocha to run test 429 | 430 | test unit is in test dir. 431 | ``` shell 432 | npm run test 433 | ``` 434 | 435 | #### online test result 436 | * [soda-mocha](http://alloyteam.github.io/sodajs/test/soda-mocha.html) 437 | * [soda.node-mocha](http://alloyteam.github.io/sodajs/test/soda.node-mocha.html) 438 | * [ie8 browser test](http://alloyteam.github.io/sodajs/test/soda-browser.html) 439 | 440 | 441 | ## Used projects 442 | QQ Tribes(兴趣部落), QQ Group(群) and other projects 443 | 444 | ## License 445 | 446 | [MIT](http://opensource.org/licenses/MIT) 447 | 448 | Copyright (c) 2015-present, AlloyTeam 449 | 450 | -------------------------------------------------------------------------------- /dist/soda.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(); 4 | else if(typeof define === 'function' && define.amd) 5 | define([], factory); 6 | else if(typeof exports === 'object') 7 | exports["soda"] = factory(); 8 | else 9 | root["soda"] = factory(); 10 | })(this, function() { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | /******/ 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | /******/ 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) { 20 | /******/ return installedModules[moduleId].exports; 21 | /******/ } 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ i: moduleId, 25 | /******/ l: false, 26 | /******/ exports: {} 27 | /******/ }; 28 | /******/ 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | /******/ 32 | /******/ // Flag the module as loaded 33 | /******/ module.l = true; 34 | /******/ 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | /******/ 39 | /******/ 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | /******/ 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | /******/ 46 | /******/ // define getter function for harmony exports 47 | /******/ __webpack_require__.d = function(exports, name, getter) { 48 | /******/ if(!__webpack_require__.o(exports, name)) { 49 | /******/ Object.defineProperty(exports, name, { 50 | /******/ configurable: false, 51 | /******/ enumerable: true, 52 | /******/ get: getter 53 | /******/ }); 54 | /******/ } 55 | /******/ }; 56 | /******/ 57 | /******/ // getDefaultExport function for compatibility with non-harmony modules 58 | /******/ __webpack_require__.n = function(module) { 59 | /******/ var getter = module && module.__esModule ? 60 | /******/ function getDefault() { return module['default']; } : 61 | /******/ function getModuleExports() { return module; }; 62 | /******/ __webpack_require__.d(getter, 'a', getter); 63 | /******/ return getter; 64 | /******/ }; 65 | /******/ 66 | /******/ // Object.prototype.hasOwnProperty.call 67 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 68 | /******/ 69 | /******/ // __webpack_public_path__ 70 | /******/ __webpack_require__.p = ""; 71 | /******/ 72 | /******/ // Load entry module and return exports 73 | /******/ return __webpack_require__(__webpack_require__.s = 3); 74 | /******/ }) 75 | /************************************************************************/ 76 | /******/ ([ 77 | /* 0 */ 78 | /***/ (function(module, exports, __webpack_require__) { 79 | 80 | "use strict"; 81 | 82 | 83 | Object.defineProperty(exports, "__esModule", { 84 | value: true 85 | }); 86 | 87 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 88 | 89 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 90 | 91 | var _const = __webpack_require__(1); 92 | 93 | var _util = __webpack_require__(2); 94 | 95 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 96 | 97 | var doc = typeof document !== 'undefined' ? document : {}; 98 | 99 | var Soda = function () { 100 | function Soda() { 101 | var prefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'soda-'; 102 | 103 | _classCallCheck(this, Soda); 104 | 105 | this._prefix = prefix; 106 | } 107 | 108 | _createClass(Soda, [{ 109 | key: 'setDocument', 110 | value: function setDocument(_doc) { 111 | doc = _doc; 112 | } 113 | }, { 114 | key: 'run', 115 | value: function run(str, data) { 116 | var _this = this; 117 | 118 | // 解析模板DOM 119 | var div = doc.createElement("div"); 120 | 121 | // 必须加入到body中去,不然自定义标签不生效 122 | if (doc.documentMode < 9) { 123 | div.style.display = 'none'; 124 | doc.body.appendChild(div); 125 | } 126 | 127 | div.innerHTML = str; 128 | 129 | (0, _util.nodes2Arr)(div.childNodes).map(function (child) { 130 | _this.compileNode(child, data); 131 | }); 132 | 133 | var innerHTML = div.innerHTML; 134 | 135 | if (doc.documentMode < 9) { 136 | doc.body.removeChild(div); 137 | } 138 | 139 | return innerHTML; 140 | } 141 | }, { 142 | key: 'prefix', 143 | value: function prefix(_prefix) { 144 | this._prefix = _prefix; 145 | } 146 | }, { 147 | key: '_getPrefixReg', 148 | value: function _getPrefixReg() { 149 | return new RegExp('^' + this._prefix); 150 | } 151 | }, { 152 | key: '_getPrefixedDirectiveMap', 153 | value: function _getPrefixedDirectiveMap() { 154 | var _this2 = this; 155 | 156 | var map = {}; 157 | Soda.sodaDirectives.map(function (item) { 158 | var prefixedName = _this2._prefix + item.name; 159 | 160 | map[prefixedName] = item; 161 | }); 162 | 163 | return map; 164 | } 165 | }, { 166 | key: '_removeSodaMark', 167 | value: function _removeSodaMark(node, name) { 168 | node.removeAttribute(name); 169 | } 170 | }, { 171 | key: 'compileNode', 172 | value: function compileNode(node, scope) { 173 | var _this3 = this; 174 | 175 | var prefixReg = this._getPrefixReg(); 176 | 177 | var sodaDirectives = Soda.sodaDirectives; 178 | 179 | 180 | var prefixedDirectiveMap = this._getPrefixedDirectiveMap(); 181 | 182 | var compile = function compile(node, scope) { 183 | 184 | // 如果只是文本 185 | // parseTextNode 186 | if (node.nodeType === (node.TEXT_NODE || 3)) { 187 | node.nodeValue = node.nodeValue.replace(_const.VALUE_OUT_REG, function (item, $1) { 188 | var value = _this3.parseSodaExpression($1, scope); 189 | if ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) === "object") { 190 | value = JSON.stringify(value, null, 2); 191 | } 192 | return value; 193 | }); 194 | } 195 | 196 | // parse Attributes 197 | if (node.attributes && node.attributes.length) { 198 | 199 | // 指令优先处理 200 | sodaDirectives.map(function (item) { 201 | var name = item.name, 202 | opt = item.opt; 203 | 204 | 205 | var prefixedName = _this3._prefix + name; 206 | 207 | // 这里移除了对parentNode的判断 208 | // 允许使用无值的指令 209 | if ((0, _util.exist)(node.getAttribute(prefixedName))) { 210 | var expression = node.getAttribute(prefixedName); 211 | 212 | opt.link.bind(_this3)({ 213 | expression: expression, 214 | scope: scope, 215 | el: node, 216 | parseSodaExpression: _this3.parseSodaExpression.bind(_this3), 217 | getValue: _this3.getValue.bind(_this3), 218 | compileNode: _this3.compileNode.bind(_this3), 219 | document: doc 220 | }); 221 | 222 | // 移除标签 223 | _this3._removeSodaMark(node, prefixedName); 224 | } 225 | }); 226 | 227 | // 处理输出 包含 prefix-* 228 | (0, _util.nodes2Arr)(node.attributes) 229 | // 过滤掉指令里包含的属性 230 | .filter(function (attr) { 231 | return !prefixedDirectiveMap[attr.name]; 232 | }).map(function (attr) { 233 | if (prefixReg.test(attr.name)) { 234 | var attrName = attr.name.replace(prefixReg, ''); 235 | 236 | if (attrName && (0, _util.exist)(attr.value)) { 237 | var attrValue = _this3.parseComplexExpression(attr.value, scope); 238 | 239 | if (attrValue !== false && (0, _util.exist)(attrValue)) { 240 | node.setAttribute(attrName, attrValue); 241 | } 242 | 243 | _this3._removeSodaMark(node, attr.name); 244 | } 245 | 246 | // 对其他属性里含expr 处理 247 | } else { 248 | if ((0, _util.exist)(attr.value)) { 249 | attr.value = _this3.parseComplexExpression(attr.value, scope); 250 | } 251 | } 252 | }); 253 | } 254 | 255 | // parse childNodes 256 | (0, _util.nodes2Arr)(node.childNodes).map(function (child) { 257 | compile(child, scope); 258 | }); 259 | }; 260 | 261 | compile(node, scope); 262 | } 263 | }, { 264 | key: 'getEvalFunc', 265 | value: function getEvalFunc(expr) { 266 | var evalFunc = new Function("getValue", "sodaFilterMap", "return function sodaExp(scope){ return " + expr + "}")(this.getValue, Soda.sodaFilterMap); 267 | 268 | return evalFunc; 269 | } 270 | }, { 271 | key: 'getValue', 272 | value: function getValue(_data, _attrStr) { 273 | _const.CONST_REGG.lastIndex = 0; 274 | var realAttrStr = _attrStr.replace(_const.CONST_REGG, function (r) { 275 | if (typeof _data[r] === "undefined") { 276 | return r; 277 | } else { 278 | return _data[r]; 279 | } 280 | }); 281 | 282 | if (_attrStr === 'true') { 283 | return true; 284 | } 285 | 286 | if (_attrStr === 'false') { 287 | return false; 288 | } 289 | 290 | var _getValue = function _getValue(data, attrStr) { 291 | var dotIndex = attrStr.indexOf("."); 292 | 293 | if (dotIndex > -1) { 294 | var attr = attrStr.substr(0, dotIndex); 295 | attrStr = attrStr.substr(dotIndex + 1); 296 | 297 | // 检查attrStr是否属于变量并转换 298 | if (typeof _data[attr] !== "undefined" && _const.CONST_REG.test(attr)) { 299 | attr = _data[attr]; 300 | } 301 | 302 | if (typeof data[attr] !== "undefined" && data[attr] !== null) { 303 | return _getValue(data[attr], attrStr); 304 | } else { 305 | var eventData = { 306 | name: realAttrStr, 307 | data: _data 308 | }; 309 | 310 | // 如果还有 311 | return ""; 312 | } 313 | } else { 314 | attrStr = attrStr.trim(); 315 | 316 | // 检查attrStr是否属于变量并转换 317 | if (typeof _data[attrStr] !== "undefined" && _const.CONST_REG.test(attrStr)) { 318 | attrStr = _data[attrStr]; 319 | } 320 | 321 | var rValue; 322 | if (typeof data[attrStr] !== "undefined") { 323 | rValue = data[attrStr]; 324 | } else { 325 | var eventData = { 326 | name: realAttrStr, 327 | data: _data 328 | }; 329 | 330 | rValue = ""; 331 | } 332 | 333 | return rValue; 334 | } 335 | }; 336 | 337 | return _getValue(_data, _attrStr); 338 | } 339 | 340 | // 解析混合表达式 341 | 342 | }, { 343 | key: 'parseComplexExpression', 344 | value: function parseComplexExpression(str, scope) { 345 | var _this4 = this; 346 | 347 | var onlyResult = _const.ONLY_VALUE_OUT_REG.exec(str); 348 | if (onlyResult) { 349 | var sodaExp = onlyResult[1]; 350 | 351 | return this.parseSodaExpression(sodaExp, scope); 352 | } 353 | 354 | return str.replace(_const.VALUE_OUT_REG, function (item, $1) { 355 | return _this4.parseSodaExpression($1, scope); 356 | }); 357 | } 358 | }, { 359 | key: 'parseSodaExpression', 360 | value: function parseSodaExpression(str, scope) { 361 | var _this5 = this; 362 | 363 | // 将字符常量保存下来 364 | str = str.replace(_const.STRING_REG, function (r, $1, $2) { 365 | var key = (0, _util.getRandom)(); 366 | scope[key] = $1 || $2; 367 | return key; 368 | }); 369 | 370 | // 对filter进行处理 371 | str = str.replace(_const.OR_REG, _const.OR_REPLACE).split("|"); 372 | 373 | for (var i = 0; i < str.length; i++) { 374 | str[i] = (str[i].replace(new RegExp(_const.OR_REPLACE, 'g'), "||") || '').trim(); 375 | } 376 | 377 | var expr = str[0] || ""; 378 | var filters = str.slice(1); 379 | 380 | while (_const.ATTR_REG_NG.test(expr)) { 381 | _const.ATTR_REG.lastIndex = 0; 382 | 383 | //对expr预处理 384 | expr = expr.replace(_const.ATTR_REG, function (r, $1) { 385 | var key = (0, _util.getAttrVarKey)(); 386 | // 属性名称为字符常量 387 | var attrName = _this5.parseSodaExpression($1, scope); 388 | 389 | // 给一个特殊的前缀 表示是属性变量 390 | 391 | scope[key] = attrName; 392 | 393 | return "." + key; 394 | }); 395 | } 396 | 397 | expr = expr.replace(_const.OBJECT_REG, function (value) { 398 | return "getValue(scope,'" + value.trim() + "')"; 399 | }); 400 | 401 | expr = this.parseFilter(filters, expr); 402 | 403 | return this.getEvalFunc(expr)(scope); 404 | } 405 | }, { 406 | key: 'parseFilter', 407 | value: function parseFilter(filters, expr) { 408 | var sodaFilterMap = Soda.sodaFilterMap; 409 | 410 | 411 | var parse = function parse() { 412 | var filterExpr = filters.shift(); 413 | 414 | if (!filterExpr) { 415 | return; 416 | } 417 | 418 | var filterExpr = filterExpr.split(":"); 419 | var args = filterExpr.slice(1) || []; 420 | var name = (filterExpr[0] || "").trim(); 421 | 422 | for (var i = 0; i < args.length; i++) { 423 | //这里根据类型进行判断 424 | if (_const.OBJECT_REG_NG.test(args[i])) { 425 | args[i] = "getValue(scope,'" + args[i] + "')"; 426 | } else {} 427 | } 428 | 429 | if (sodaFilterMap[name]) { 430 | args.unshift(expr); 431 | 432 | args = args.join(","); 433 | 434 | expr = "sodaFilterMap['" + name + "'](" + args + ")"; 435 | } 436 | 437 | parse(); 438 | }; 439 | 440 | parse(); 441 | 442 | return expr; 443 | } 444 | }], [{ 445 | key: 'filter', 446 | value: function filter(name, func) { 447 | this.sodaFilterMap[name] = func; 448 | } 449 | }, { 450 | key: 'getFilter', 451 | value: function getFilter(name) { 452 | return this.sodaFilterMap[name]; 453 | } 454 | }, { 455 | key: 'directive', 456 | value: function directive(name, opt) { 457 | // 按照顺序入 458 | var _opt$priority = opt.priority, 459 | priority = _opt$priority === undefined ? 0 : _opt$priority; 460 | 461 | var i = void 0; 462 | 463 | for (i = 0; i < this.sodaDirectives.length; i++) { 464 | var item = this.sodaDirectives[i]; 465 | var _item$opt$priority = item.opt.priority, 466 | itemPriority = _item$opt$priority === undefined ? 0 : _item$opt$priority; 467 | 468 | // 比他小 继续比下一个 469 | 470 | if (priority < itemPriority) { 471 | 472 | // 发现比它大或者相等 就插大他前面 473 | } else if (priority >= itemPriority) { 474 | break; 475 | } 476 | } 477 | 478 | this.sodaDirectives.splice(i, 0, { 479 | name: name, 480 | opt: opt 481 | }); 482 | } 483 | }, { 484 | key: 'discribe', 485 | value: function discribe(name, funcOrStr) { 486 | var option = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : { compile: true }; 487 | 488 | 489 | this.template[name] = { 490 | funcOrStr: funcOrStr, 491 | option: option 492 | }; 493 | } 494 | }, { 495 | key: 'getTmpl', 496 | value: function getTmpl(name, args) { 497 | var template = this.template[name]; 498 | var funcOrStr = template.funcOrStr, 499 | _template$option = template.option, 500 | option = _template$option === undefined ? {} : _template$option; 501 | 502 | 503 | var result = void 0; 504 | 505 | if (typeof funcOrStr === 'function') { 506 | result = funcOrStr.apply(null, args); 507 | } else { 508 | result = funcOrStr; 509 | } 510 | 511 | return { 512 | template: result, 513 | option: option 514 | }; 515 | } 516 | }]); 517 | 518 | return Soda; 519 | }(); 520 | 521 | Soda.sodaDirectives = []; 522 | Soda.sodaFilterMap = {}; 523 | Soda.template = {}; 524 | exports["default"] = Soda; 525 | 526 | /***/ }), 527 | /* 1 */ 528 | /***/ (function(module, exports, __webpack_require__) { 529 | 530 | "use strict"; 531 | 532 | 533 | Object.defineProperty(exports, "__esModule", { 534 | value: true 535 | }); 536 | // 标识符 537 | var IDENTOR_REG = exports.IDENTOR_REG = /[a-zA-Z_\$]+[\w\$]*/g; 538 | var STRING_REG = exports.STRING_REG = /"([^"]*)"|'([^']*)'/g; 539 | var NUMBER_REG = exports.NUMBER_REG = /\d+|\d*\.\d+/g; 540 | 541 | var OBJECT_REG = exports.OBJECT_REG = /[a-zA-Z_\$]+[\w\$]*(?:\s*\.\s*(?:[a-zA-Z_\$]+[\w\$]*|\d+))*/g; 542 | 543 | // 非global 做test用 544 | var OBJECT_REG_NG = exports.OBJECT_REG_NG = /[a-zA-Z_\$]+[\w\$]*(?:\s*\.\s*(?:[a-zA-Z_\$]+[\w\$]*|\d+))*/; 545 | 546 | var ATTR_REG = exports.ATTR_REG = /\[([^\[\]]*)\]/g; 547 | var ATTR_REG_NG = exports.ATTR_REG_NG = /\[([^\[\]]*)\]/; 548 | var ATTR_REG_DOT = exports.ATTR_REG_DOT = /\.([a-zA-Z_\$]+[\w\$]*)/g; 549 | 550 | var NOT_ATTR_REG = exports.NOT_ATTR_REG = /[^\.|]([a-zA-Z_\$]+[\w\$]*)/g; 551 | 552 | var OR_REG = exports.OR_REG = /\|\|/g; 553 | 554 | var OR_REPLACE = exports.OR_REPLACE = "OR_OPERATOR\x1E"; 555 | 556 | var CONST_PRIFIX = exports.CONST_PRIFIX = "_$C$_"; 557 | var CONST_REG = exports.CONST_REG = /^_\$C\$_/; 558 | var CONST_REGG = exports.CONST_REGG = /_\$C\$_[^\.]+/g; 559 | var VALUE_OUT_REG = exports.VALUE_OUT_REG = /\{\{([^\}]*)\}\}/g; 560 | var ONLY_VALUE_OUT_REG = exports.ONLY_VALUE_OUT_REG = /^\{\{([^\}]*)\}\}$/; 561 | 562 | /***/ }), 563 | /* 2 */ 564 | /***/ (function(module, exports, __webpack_require__) { 565 | 566 | "use strict"; 567 | 568 | 569 | Object.defineProperty(exports, "__esModule", { 570 | value: true 571 | }); 572 | exports.assign = exports.nodes2Arr = exports.exist = exports.getRandom = exports.getAttrVarKey = undefined; 573 | 574 | var _const = __webpack_require__(1); 575 | 576 | var getAttrVarKey = exports.getAttrVarKey = function getAttrVarKey() { 577 | return _const.CONST_PRIFIX + ~~(Math.random() * 1E6); 578 | }; 579 | 580 | var getRandom = exports.getRandom = function getRandom() { 581 | return "$$" + ~~(Math.random() * 1E6); 582 | }; 583 | 584 | var exist = exports.exist = function exist(value) { 585 | return value !== null && value !== undefined && value !== "" && typeof value !== 'undefined'; 586 | }; 587 | 588 | var nodes2Arr = exports.nodes2Arr = function nodes2Arr(nodes) { 589 | var arr = []; 590 | 591 | for (var i = 0; i < nodes.length; i++) { 592 | arr.push(nodes[i]); 593 | } 594 | 595 | return arr; 596 | }; 597 | 598 | var getOwnPropertySymbols = Object.getOwnPropertySymbols; 599 | var hasOwnProperty = Object.prototype.hasOwnProperty; 600 | var propIsEnumerable = Object.prototype.propertyIsEnumerable; 601 | 602 | var toObject = function toObject(val) { 603 | if (val === null || val === undefined) { 604 | throw new TypeError('Object.assign cannot be called with null or undefined'); 605 | } 606 | 607 | return Object(val); 608 | }; 609 | 610 | var assign = exports.assign = Object.assign || function (target, source) { 611 | var from; 612 | var to = toObject(target); 613 | var symbols; 614 | 615 | for (var s = 1; s < arguments.length; s++) { 616 | from = Object(arguments[s]); 617 | 618 | for (var key in from) { 619 | if (hasOwnProperty.call(from, key)) { 620 | to[key] = from[key]; 621 | } 622 | } 623 | 624 | if (getOwnPropertySymbols) { 625 | symbols = getOwnPropertySymbols(from); 626 | for (var i = 0; i < symbols.length; i++) { 627 | if (propIsEnumerable.call(from, symbols[i])) { 628 | to[symbols[i]] = from[symbols[i]]; 629 | } 630 | } 631 | } 632 | } 633 | 634 | return to; 635 | }; 636 | 637 | /***/ }), 638 | /* 3 */ 639 | /***/ (function(module, exports, __webpack_require__) { 640 | 641 | "use strict"; 642 | 643 | 644 | var _soda = __webpack_require__(0); 645 | 646 | var _soda2 = _interopRequireDefault(_soda); 647 | 648 | var _util = __webpack_require__(2); 649 | 650 | __webpack_require__(4); 651 | 652 | __webpack_require__(5); 653 | 654 | __webpack_require__(6); 655 | 656 | __webpack_require__(7); 657 | 658 | __webpack_require__(8); 659 | 660 | __webpack_require__(9); 661 | 662 | __webpack_require__(10); 663 | 664 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 665 | 666 | var sodaInstance = new _soda2["default"](); 667 | 668 | var init = function init(str, data) { 669 | return sodaInstance.run(str, data); 670 | }; 671 | 672 | var mock = { 673 | prefix: function prefix(_prefix) { 674 | sodaInstance.prefix(_prefix); 675 | }, 676 | filter: function filter(name, func) { 677 | _soda2["default"].filter(name, func); 678 | }, 679 | directive: function directive(name, opt) { 680 | _soda2["default"].directive(name, opt); 681 | }, 682 | setDocument: function setDocument(document) { 683 | sodaInstance.setDocument(document); 684 | }, 685 | discribe: function discribe(name, str, option) { 686 | _soda2["default"].discribe(name, str, option); 687 | }, 688 | 689 | 690 | Soda: _soda2["default"] 691 | }; 692 | 693 | var soda = (0, _util.assign)(init, mock); 694 | 695 | module.exports = soda; 696 | 697 | /***/ }), 698 | /* 4 */ 699 | /***/ (function(module, exports, __webpack_require__) { 700 | 701 | "use strict"; 702 | 703 | 704 | var _soda = __webpack_require__(0); 705 | 706 | var _soda2 = _interopRequireDefault(_soda); 707 | 708 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 709 | 710 | _soda2["default"].directive('repeat', { 711 | priority: 10, 712 | link: function link(_ref) { 713 | var _this = this; 714 | 715 | var scope = _ref.scope, 716 | el = _ref.el, 717 | expression = _ref.expression, 718 | getValue = _ref.getValue, 719 | parseSodaExpression = _ref.parseSodaExpression, 720 | compileNode = _ref.compileNode; 721 | 722 | var itemName; 723 | var valueName; 724 | 725 | var trackReg = /\s+by\s+([^\s]+)$/; 726 | 727 | var trackName; 728 | var opt = expression.replace(trackReg, function (item, $1) { 729 | if ($1) { 730 | trackName = ($1 || '').trim(); 731 | } 732 | 733 | return ''; 734 | }); 735 | 736 | var inReg = /([^\s]+)\s+in\s+([^\s]+)|\(([^,]+)\s*,\s*([^)]+)\)\s+in\s+([^\s]+)/; 737 | 738 | var r = inReg.exec(opt); 739 | if (r) { 740 | if (r[1] && r[2]) { 741 | itemName = (r[1] || '').trim(); 742 | valueName = (r[2] || '').trim(); 743 | 744 | if (!(itemName && valueName)) { 745 | return; 746 | } 747 | } else if (r[3] && r[4] && r[5]) { 748 | trackName = (r[3] || '').trim(); 749 | itemName = (r[4] || '').trim(); 750 | valueName = (r[5] || '').trim(); 751 | } 752 | } else { 753 | return; 754 | } 755 | 756 | trackName = trackName || '$index'; 757 | 758 | // 这里要处理一下 759 | var repeatObj = getValue(scope, valueName) || []; 760 | 761 | var repeatFunc = function repeatFunc(i) { 762 | var itemNode = el.cloneNode(true); 763 | 764 | // 这里创建一个新的scope 765 | var itemScope = Object.create(scope); 766 | itemScope[trackName] = i; 767 | 768 | itemScope[itemName] = repeatObj[i]; 769 | 770 | //itemScope.__proto__ = scope; 771 | 772 | // REMOVE cjd6568358 773 | itemNode.removeAttribute(_this._prefix + 'repeat'); 774 | 775 | el.parentNode.insertBefore(itemNode, el); 776 | 777 | // 这里是新加的dom, 要单独编译 778 | compileNode(itemNode, itemScope); 779 | }; 780 | 781 | if ('length' in repeatObj) { 782 | for (var i = 0; i < repeatObj.length; i++) { 783 | repeatFunc(i); 784 | } 785 | } else { 786 | for (var i in repeatObj) { 787 | if (repeatObj.hasOwnProperty(i)) { 788 | repeatFunc(i); 789 | } 790 | } 791 | } 792 | 793 | // el 清理 794 | el.parentNode.removeChild(el); 795 | 796 | if (el.childNodes && el.childNodes.length) { 797 | el.innerHTML = ''; 798 | } 799 | } 800 | }); 801 | 802 | /***/ }), 803 | /* 5 */ 804 | /***/ (function(module, exports, __webpack_require__) { 805 | 806 | "use strict"; 807 | 808 | 809 | var _soda = __webpack_require__(0); 810 | 811 | var _soda2 = _interopRequireDefault(_soda); 812 | 813 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 814 | 815 | _soda2["default"].directive('if', { 816 | priority: 9, 817 | link: function link(_ref) { 818 | var expression = _ref.expression, 819 | parseSodaExpression = _ref.parseSodaExpression, 820 | scope = _ref.scope, 821 | el = _ref.el; 822 | 823 | var expressFunc = parseSodaExpression(expression, scope); 824 | 825 | if (expressFunc) {} else { 826 | el.parentNode && el.parentNode.removeChild(el); 827 | el.innerHTML = ''; 828 | } 829 | } 830 | }); 831 | 832 | /***/ }), 833 | /* 6 */ 834 | /***/ (function(module, exports, __webpack_require__) { 835 | 836 | "use strict"; 837 | 838 | 839 | var _soda = __webpack_require__(0); 840 | 841 | var _soda2 = _interopRequireDefault(_soda); 842 | 843 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 844 | 845 | var classNameRegExp = function classNameRegExp(className) { 846 | return new RegExp('(^|\\s+)' + className + '(\\s+|$)', 'g'); 847 | }; 848 | 849 | var addClass = function addClass(el, className) { 850 | if (!el.className) { 851 | el.className = className; 852 | 853 | return; 854 | } 855 | 856 | if (el.className.match(classNameRegExp(className))) {} else { 857 | el.className += " " + className; 858 | } 859 | }; 860 | 861 | var removeClass = function removeClass(el, className) { 862 | el.className = el.className.replace(classNameRegExp(className), ""); 863 | }; 864 | 865 | _soda2["default"].directive('class', { 866 | link: function link(_ref) { 867 | var scope = _ref.scope, 868 | el = _ref.el, 869 | expression = _ref.expression, 870 | parseSodaExpression = _ref.parseSodaExpression; 871 | 872 | var expressFunc = parseSodaExpression(expression, scope); 873 | 874 | if (expressFunc) { 875 | addClass(el, expressFunc); 876 | } else {} 877 | } 878 | }); 879 | 880 | /***/ }), 881 | /* 7 */ 882 | /***/ (function(module, exports, __webpack_require__) { 883 | 884 | "use strict"; 885 | 886 | 887 | var _soda = __webpack_require__(0); 888 | 889 | var _soda2 = _interopRequireDefault(_soda); 890 | 891 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 892 | 893 | _soda2["default"].directive('html', { 894 | link: function link(_ref) { 895 | var expression = _ref.expression, 896 | scope = _ref.scope, 897 | el = _ref.el, 898 | parseSodaExpression = _ref.parseSodaExpression; 899 | 900 | var result = parseSodaExpression(expression, scope); 901 | 902 | if (result) { 903 | el.innerHTML = result; 904 | } 905 | } 906 | }); 907 | 908 | /***/ }), 909 | /* 8 */ 910 | /***/ (function(module, exports, __webpack_require__) { 911 | 912 | "use strict"; 913 | 914 | 915 | var _soda = __webpack_require__(0); 916 | 917 | var _soda2 = _interopRequireDefault(_soda); 918 | 919 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 920 | 921 | _soda2["default"].directive('replace', { 922 | link: function link(_ref) { 923 | var scope = _ref.scope, 924 | el = _ref.el, 925 | expression = _ref.expression, 926 | parseSodaExpression = _ref.parseSodaExpression, 927 | document = _ref.document; 928 | 929 | var result = parseSodaExpression(expression, scope); 930 | 931 | if (result) { 932 | var div = document.createElement('div'); 933 | div.innerHTML = result; 934 | 935 | if (el.parentNode) { 936 | while (div.childNodes[0]) { 937 | el.parentNode.insertBefore(div.childNodes[0], el); 938 | } 939 | } 940 | } 941 | 942 | el.parentNode && el.parentNode.removeChild(el); 943 | } 944 | }); 945 | 946 | /***/ }), 947 | /* 9 */ 948 | /***/ (function(module, exports, __webpack_require__) { 949 | 950 | "use strict"; 951 | 952 | 953 | var _soda = __webpack_require__(0); 954 | 955 | var _soda2 = _interopRequireDefault(_soda); 956 | 957 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 958 | 959 | _soda2["default"].directive('style', { 960 | link: function link(_ref) { 961 | var scope = _ref.scope, 962 | el = _ref.el, 963 | expression = _ref.expression, 964 | parseSodaExpression = _ref.parseSodaExpression; 965 | 966 | var expressFunc = parseSodaExpression(expression, scope); 967 | 968 | var getCssValue = function getCssValue(name, value) { 969 | var numberWithoutpx = /opacity|z-index/; 970 | if (numberWithoutpx.test(name)) { 971 | return parseFloat(value); 972 | } 973 | 974 | if (isNaN(value)) { 975 | return value; 976 | } else { 977 | return value + "px"; 978 | } 979 | }; 980 | 981 | if (expressFunc) { 982 | var stylelist = []; 983 | 984 | for (var i in expressFunc) { 985 | if (expressFunc.hasOwnProperty(i)) { 986 | var provalue = getCssValue(i, expressFunc[i]); 987 | 988 | stylelist.push([i, provalue].join(":")); 989 | } 990 | } 991 | 992 | var style = el.style; 993 | for (var i = 0; i < style.length; i++) { 994 | var name = style[i]; 995 | if (expressFunc[name]) {} else { 996 | stylelist.push([name, style[name]].join(":")); 997 | } 998 | } 999 | 1000 | var styleStr = stylelist.join(";"); 1001 | 1002 | el.setAttribute("style", styleStr); 1003 | } 1004 | } 1005 | }); 1006 | 1007 | /***/ }), 1008 | /* 10 */ 1009 | /***/ (function(module, exports, __webpack_require__) { 1010 | 1011 | "use strict"; 1012 | 1013 | 1014 | var _soda = __webpack_require__(0); 1015 | 1016 | var _soda2 = _interopRequireDefault(_soda); 1017 | 1018 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 1019 | 1020 | _soda2["default"].directive('include', { 1021 | priority: 8, 1022 | link: function link(_ref) { 1023 | var scope = _ref.scope, 1024 | el = _ref.el, 1025 | parseSodaExpression = _ref.parseSodaExpression, 1026 | expression = _ref.expression; 1027 | 1028 | var VALUE_OUT_REG = /\{\{([^\}]*)\}\}/g; 1029 | 1030 | var result = expression.replace(VALUE_OUT_REG, function (item, $1) { 1031 | return parseSodaExpression($1, scope); 1032 | }); 1033 | 1034 | result = result.split(":"); 1035 | 1036 | var name = result[0]; 1037 | 1038 | var args = result.slice(1); 1039 | 1040 | var templateOption = _soda2["default"].getTmpl(name, args); 1041 | 1042 | var template = templateOption.template, 1043 | _templateOption$optio = templateOption.option, 1044 | option = _templateOption$optio === undefined ? {} : _templateOption$optio; 1045 | 1046 | if (template) { 1047 | if (option.compile) { 1048 | el.outerHTML = this.run(template, scope); 1049 | } else { 1050 | el.outerHTML = template; 1051 | } 1052 | } 1053 | } 1054 | }); 1055 | 1056 | /***/ }) 1057 | /******/ ]); 1058 | }); -------------------------------------------------------------------------------- /dist/soda.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.soda=t():e.soda=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,t),i.l=!0,i.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e["default"]}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=3)}([function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},o=function(){function e(e,t){for(var n=0;n0&&arguments[0]!==undefined?arguments[0]:"soda-";r(this,e),this._prefix=t}return o(e,[{key:"setDocument",value:function(e){s=e}},{key:"run",value:function(e,t){var n=this,r=s.createElement("div");s.documentMode<9&&(r.style.display="none",s.body.appendChild(r)),r.innerHTML=e,(0,u.nodes2Arr)(r.childNodes).map(function(e){n.compileNode(e,t)});var i=r.innerHTML;return s.documentMode<9&&s.body.removeChild(r),i}},{key:"prefix",value:function(e){this._prefix=e}},{key:"_getPrefixReg",value:function(){return new RegExp("^"+this._prefix)}},{key:"_getPrefixedDirectiveMap",value:function(){var t=this,n={};return e.sodaDirectives.map(function(e){var r=t._prefix+e.name;n[r]=e}),n}},{key:"_removeSodaMark",value:function(e,t){e.removeAttribute(t)}},{key:"compileNode",value:function(t,n){var r=this,o=this._getPrefixReg(),l=e.sodaDirectives,f=this._getPrefixedDirectiveMap();!function c(e,t){e.nodeType===(e.TEXT_NODE||3)&&(e.nodeValue=e.nodeValue.replace(a.VALUE_OUT_REG,function(e,n){var o=r.parseSodaExpression(n,t);return"object"===(void 0===o?"undefined":i(o))&&(o=JSON.stringify(o,null,2)),o})),e.attributes&&e.attributes.length&&(l.map(function(n){var i=n.name,o=n.opt,a=r._prefix+i;if((0,u.exist)(e.getAttribute(a))){var l=e.getAttribute(a);o.link.bind(r)({expression:l,scope:t,el:e,parseSodaExpression:r.parseSodaExpression.bind(r),getValue:r.getValue.bind(r),compileNode:r.compileNode.bind(r),document:s}),r._removeSodaMark(e,a)}}),(0,u.nodes2Arr)(e.attributes).filter(function(e){return!f[e.name]}).map(function(n){if(o.test(n.name)){var i=n.name.replace(o,"");if(i&&(0,u.exist)(n.value)){var a=r.parseComplexExpression(n.value,t);!1!==a&&(0,u.exist)(a)&&e.setAttribute(i,a),r._removeSodaMark(e,n.name)}}else(0,u.exist)(n.value)&&(n.value=r.parseComplexExpression(n.value,t))})),(0,u.nodes2Arr)(e.childNodes).map(function(e){c(e,t)})}(t,n)}},{key:"getEvalFunc",value:function(t){return new Function("getValue","sodaFilterMap","return function sodaExp(scope){ return "+t+"}")(this.getValue,e.sodaFilterMap)}},{key:"getValue",value:function(e,t){a.CONST_REGG.lastIndex=0;t.replace(a.CONST_REGG,function(t){return"undefined"==typeof e[t]?t:e[t]});if("true"===t)return!0;if("false"===t)return!1;return function n(t,r){var i=r.indexOf(".");if(i>-1){var o=r.substr(0,i);if(r=r.substr(i+1),"undefined"!=typeof e[o]&&a.CONST_REG.test(o)&&(o=e[o]),"undefined"!=typeof t[o]&&null!==t[o])return n(t[o],r);return""}r=r.trim(),"undefined"!=typeof e[r]&&a.CONST_REG.test(r)&&(r=e[r]);var u;if("undefined"!=typeof t[r])u=t[r];else{u=""}return u}(e,t)}},{key:"parseComplexExpression",value:function(e,t){var n=this,r=a.ONLY_VALUE_OUT_REG.exec(e);if(r){var i=r[1];return this.parseSodaExpression(i,t)}return e.replace(a.VALUE_OUT_REG,function(e,r){return n.parseSodaExpression(r,t)})}},{key:"parseSodaExpression",value:function(e,t){var n=this;e=e.replace(a.STRING_REG,function(e,n,r){var i=(0,u.getRandom)();return t[i]=n||r,i}),e=e.replace(a.OR_REG,a.OR_REPLACE).split("|");for(var r=0;r=u)break}this.sodaDirectives.splice(i,0,{name:e,opt:t})}},{key:"discribe",value:function(e,t){var n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{compile:!0};this.template[e]={funcOrStr:t,option:n}}},{key:"getTmpl",value:function(e,t){var n=this.template[e],r=n.funcOrStr,i=n.option,o=i===undefined?{}:i,a=void 0;return a="function"==typeof r?r.apply(null,t):r,{template:a,option:o}}}]),e}();l.sodaDirectives=[],l.sodaFilterMap={},l.template={},t["default"]=l},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.IDENTOR_REG=/[a-zA-Z_\$]+[\w\$]*/g,t.STRING_REG=/"([^"]*)"|'([^']*)'/g,t.NUMBER_REG=/\d+|\d*\.\d+/g,t.OBJECT_REG=/[a-zA-Z_\$]+[\w\$]*(?:\s*\.\s*(?:[a-zA-Z_\$]+[\w\$]*|\d+))*/g,t.OBJECT_REG_NG=/[a-zA-Z_\$]+[\w\$]*(?:\s*\.\s*(?:[a-zA-Z_\$]+[\w\$]*|\d+))*/,t.ATTR_REG=/\[([^\[\]]*)\]/g,t.ATTR_REG_NG=/\[([^\[\]]*)\]/,t.ATTR_REG_DOT=/\.([a-zA-Z_\$]+[\w\$]*)/g,t.NOT_ATTR_REG=/[^\.|]([a-zA-Z_\$]+[\w\$]*)/g,t.OR_REG=/\|\|/g,t.OR_REPLACE="OR_OPERATOR",t.CONST_PRIFIX="_$C$_",t.CONST_REG=/^_\$C\$_/,t.CONST_REGG=/_\$C\$_[^\.]+/g,t.VALUE_OUT_REG=/\{\{([^\}]*)\}\}/g,t.ONLY_VALUE_OUT_REG=/^\{\{([^\}]*)\}\}$/},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.assign=t.nodes2Arr=t.exist=t.getRandom=t.getAttrVarKey=undefined;var r=n(1),i=(t.getAttrVarKey=function(){return r.CONST_PRIFIX+~~(1e6*Math.random())},t.getRandom=function(){return"$$"+~~(1e6*Math.random())},t.exist=function(e){return null!==e&&e!==undefined&&""!==e&&void 0!==e},t.nodes2Arr=function(e){for(var t=[],n=0;n"+this.innerHTML+"":"<"+n+" "+this._getAttributeString()+">",t.childNodes[0]}},{key:"getRootNode",value:function(){return function e(t){return t.parentNode?e(t.parentNode):t}(this)}},{key:"contains",value:function(e){if(e===this)return!0;for(var t=!1,n=0;n/g,">")}}]),t}(a);e.exports=u},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n/g,">")},p=/script|pre|code/,d=function(e){function t(e){r(this,t);var n=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this));return n.tagName=e,n.nodeType=n.ELEMENT_NODE,n.attributes=new u(n),n.classList=new c(n),n}return i(t,e),s(t,[{key:"_getAttributeString",value:function(){for(var e=[],t=0;t";else{if(r="<"+e+o+">",this.childNodes)for(var i=0;i"}return r}},{key:"scrollTop",get:function(){return this.__scrollTop||0},set:function(e){this.__scrollTop=e}}]),t}(a);e.exports=d},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n-1){delete this[t],this.length--;for(var n=t;n-1}},{key:"add",value:function(e){if(this.contains(e));else{var t=this.__class;t.push(e),this.ownElement.className=t.join(" ")}}},{key:"remove",value:function(e){var t=this.__class,n=t.indexOf(e);n>-1&&(t.splice(n,1),this.ownElement.className=t.join(" "))}},{key:"toggle",value:function(e){this.contains(e)?this.remove(e):this.add(e)}},{key:"keys",value:function(){return this.__class.keys()}},{key:"values",value:function(){return this.__class[Symbol.iterator]()}},{key:"toString",value:function(){return this.value}},{key:"__class",get:function(){if((this.ownElement.className||"").trim())return(this.ownElement.className||"").trim().split(/ +/);return[]}},{key:"length",get:function(){return this.__class.length}},{key:"value",get:function(){return this.__class.join(" ")}}]),e}();e.exports=i},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n"===m)n.getAttribute("type")&&"text/javascript"!==(n.getAttribute("type")+"").toLowerCase()||n.getAttribute("src")&&n.tagName,p.goNext&&u.goNext();else if("/>"===m);else{var b=p.attrName,g=p.attrValue;n.setAttribute(b,g)}}else if("textTag"===d){var _=p.nodeValue,y=o.createTextNode(_);if(u.push(y),n=y,p.checkParentScript&&"script"===y.parentNode.tagName)if(y.parentNode.getAttribute("type")&&"text/javascript"!==(y.parentNode.getAttribute("type")+"").toLowerCase());else try{s.runCode(_,t)}catch(e){}p.backUp&&u.backUp()}else if("commentTag"===d){var _=p.nodeValue,y=o.createComment(_);u.push(y),n=y,p.backUp&&u.backUp()}else if("endTag"===d){var y=o.createElement();u.push(y),n=y,p.backUp&&u.backUp()}}return{headNode:c,bodyNode:l,htmlNode:f,docTree:u}}},{key:"parseHTMLDocument",value:function(e,t){var n,r,o=t.document,s=this.parseHTMLFragment(e,t),a=s.docTree,u=s.headNode||o.createElement("head");if(s.bodyNode)n=s.bodyNode;else{var n=o.createElement("body");n.childNodes=a._tree.childNodes,n.childNodes.map(function(e){e.parentNode=n})}s.htmlNode?r=s.htmlNode:(r=o.createElement("html"),r.childNodes.push(u),r.childNodes.push(n));var c=new i;return c.push(o),c.goNext(),c.push(r),o.documentElement=r,o.head=u,o.body=n,o}},{key:"preParse",value:function(e){var t,n,r,o=[],i=/<\!\-\-|<([^\s\/>]+)|<(\/)([^\s>]+)>|([^<]+)/g,s=/>|\/>|([^\s=]+)=(?:"([^"]*)"|'([^']*)'|([^\s"']+))/g,a=/br|hr|img|link|meta/,u=/script|code|pre/,c=0,l=i;l.lastIndex=0;for(var f,h=function(e){c=l.lastIndex,l=e,l.lastIndex=c};n=l.exec(e);){var p,d=n[1]||n[3];if(l===i?p="\x3c!--"===n[0]?"commentTag":n[1]?"startTag":"/"===n[2]?"endTag":"textTag":l===s?p="attrs":"mixableTagCloseReg"===l.type?p="mixableTagCloseReg":"commentEndTag"===l.type&&(p="commentEndTag"),"startTag"===p)t={tagType:"startTag",tagName:d},f=t,{},o.push(t),h(s);else if("attrs"===p)if(">"===n[0])t={tagType:"attrs",match:">"},h(i),a.test(f.tagName)||(t.goNext=1),u.test(f.tagName)&&h(function(e){var t=new RegExp("","g");return t.type="mixableTagCloseReg",t}(f.tagName)),o.push(t);else if("/>"===n[0])h(i),o.push({tagType:"attrs",match:"/>"});else{var v=(n[1]+"").trim(),y=n[2]||n[3]||n[4];"class"===v&&(v="className"),o.push({tagType:"attrs",attrName:v,attrValue:y})}else if("textTag"===p){var r=n[4];o.push({tagType:"textTag",nodeValue:r||""}),f=void 0}else if("endTag"===p)t={tagType:"endTag"},a.test(d)||(t.backUp=1),o.push(t);else if("commentTag"===p){var m=/\-\->/g;m.type="commentEndTag",h(m)}else if("commentEndTag"===p){var b=c,g=l.lastIndex-n[0].length-b,r=e.substr(b,g);t={tagType:"commentTag",checkParentScript:!1,nodeValue:r,backUp:0},f=t,h(i),o.push(t)}else if("mixableTagCloseReg"===p){var b=c,g=l.lastIndex-n[0].length-b,r=e.substr(b,g);t={tagType:"textTag",checkParentScript:1,nodeValue:r,backUp:1},f=t,h(i),o.push(t)}}return o}}]),e}();e.exports=u},function(e,t){(function(t){e.exports=t}).call(t,{})},function(e,t,n){"use strict"},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:"soda-";r(this,e),this._prefix=t}return s(e,[{key:"setDocument",value:function(e){c=e}},{key:"run",value:function(e,t){var n=this,r=c.createElement("div");c.documentMode<9&&(r.style.display="none",c.body.appendChild(r)),r.innerHTML=e,(0,u.nodes2Arr)(r.childNodes).map(function(e){n.compileNode(e,t)});var o=r.innerHTML;return c.documentMode<9&&c.body.removeChild(r),o}},{key:"prefix",value:function(e){this._prefix=e}},{key:"_getPrefixReg",value:function(){return new RegExp("^"+this._prefix)}},{key:"_getPrefixedDirectiveMap",value:function(){var t=this,n={};return e.sodaDirectives.map(function(e){var r=t._prefix+e.name;n[r]=e}),n}},{key:"_removeSodaMark",value:function(e,t){e.removeAttribute(t)}},{key:"compileNode",value:function(t,n){var r=this,i=this._getPrefixReg(),s=e.sodaDirectives,l=this._getPrefixedDirectiveMap();!function e(t,n){t.nodeType===(t.TEXT_NODE||3)&&(t.nodeValue=t.nodeValue.replace(a.VALUE_OUT_REG,function(e,t){var i=r.parseSodaExpression(t,n);return"object"===(void 0===i?"undefined":o(i))&&(i=JSON.stringify(i,null,2)),i})),t.attributes&&t.attributes.length&&(s.map(function(e){var o=e.name,i=e.opt,s=r._prefix+o;if((0,u.exist)(t.getAttribute(s))){var a=t.getAttribute(s);i.link.bind(r)({expression:a,scope:n,el:t,parseSodaExpression:r.parseSodaExpression.bind(r),getValue:r.getValue.bind(r),compileNode:r.compileNode.bind(r),document:c}),r._removeSodaMark(t,s)}}),(0,u.nodes2Arr)(t.attributes).filter(function(e){return!l[e.name]}).map(function(e){if(i.test(e.name)){var o=e.name.replace(i,"");if(o&&(0,u.exist)(e.value)){var s=r.parseComplexExpression(e.value,n);!1!==s&&(0,u.exist)(s)&&t.setAttribute(o,s),r._removeSodaMark(t,e.name)}}else(0,u.exist)(e.value)&&(e.value=r.parseComplexExpression(e.value,n))})),(0,u.nodes2Arr)(t.childNodes).map(function(t){e(t,n)})}(t,n)}},{key:"getEvalFunc",value:function(t){return new Function("getValue","sodaFilterMap","return function sodaExp(scope){ return "+t+"}")(this.getValue,e.sodaFilterMap)}},{key:"getValue",value:function(e,t){a.CONST_REGG.lastIndex=0;t.replace(a.CONST_REGG,function(t){return void 0===e[t]?t:e[t]});if("true"===t)return!0;if("false"===t)return!1;return function t(n,r){var o=r.indexOf(".");if(o>-1){var i=r.substr(0,o);if(r=r.substr(o+1),void 0!==e[i]&&a.CONST_REG.test(i)&&(i=e[i]),void 0!==n[i]&&null!==n[i])return t(n[i],r);return""}r=r.trim(),void 0!==e[r]&&a.CONST_REG.test(r)&&(r=e[r]);var s;if(void 0!==n[r])s=n[r];else{s=""}return s}(e,t)}},{key:"parseComplexExpression",value:function(e,t){var n=this,r=a.ONLY_VALUE_OUT_REG.exec(e);if(r){var o=r[1];return this.parseSodaExpression(o,t)}return e.replace(a.VALUE_OUT_REG,function(e,r){return n.parseSodaExpression(r,t)})}},{key:"parseSodaExpression",value:function(e,t){var n=this;e=e.replace(a.STRING_REG,function(e,n,r){var o=(0,u.getRandom)();return t[o]=n||r,o}),e=e.replace(a.OR_REG,a.OR_REPLACE).split("|");for(var r=0;r=a)break}this.sodaDirectives.splice(o,0,{name:e,opt:t})}},{key:"discribe",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{compile:!0};this.template[e]={funcOrStr:t,option:n}}},{key:"getTmpl",value:function(e,t){var n=this.template[e],r=n.funcOrStr,o=n.option,i=void 0===o?{}:o,s=void 0;return s="function"==typeof r?r.apply(null,t):r,{template:s,option:i}}}]),e}();l.sodaDirectives=[],l.sodaFilterMap={},l.template={},t.default=l},function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0});t.IDENTOR_REG=/[a-zA-Z_\$]+[\w\$]*/g,t.STRING_REG=/"([^"]*)"|'([^']*)'/g,t.NUMBER_REG=/\d+|\d*\.\d+/g,t.OBJECT_REG=/[a-zA-Z_\$]+[\w\$]*(?:\s*\.\s*(?:[a-zA-Z_\$]+[\w\$]*|\d+))*/g,t.OBJECT_REG_NG=/[a-zA-Z_\$]+[\w\$]*(?:\s*\.\s*(?:[a-zA-Z_\$]+[\w\$]*|\d+))*/,t.ATTR_REG=/\[([^\[\]]*)\]/g,t.ATTR_REG_NG=/\[([^\[\]]*)\]/,t.ATTR_REG_DOT=/\.([a-zA-Z_\$]+[\w\$]*)/g,t.NOT_ATTR_REG=/[^\.|]([a-zA-Z_\$]+[\w\$]*)/g,t.OR_REG=/\|\|/g,t.OR_REPLACE="OR_OPERATOR",t.CONST_PRIFIX="_$C$_",t.CONST_REG=/^_\$C\$_/,t.CONST_REGG=/_\$C\$_[^\.]+/g,t.VALUE_OUT_REG=/\{\{([^\}]*)\}\}/g,t.ONLY_VALUE_OUT_REG=/^\{\{([^\}]*)\}\}$/},function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.assign=t.nodes2Arr=t.exist=t.getRandom=t.getAttrVarKey=void 0;var r=n(1),o=(t.getAttrVarKey=function(){return r.CONST_PRIFIX+~~(1e6*Math.random())},t.getRandom=function(){return"$$"+~~(1e6*Math.random())},t.exist=function(e){return null!==e&&void 0!==e&&""!==e&&void 0!==e},t.nodes2Arr=function(e){for(var t=[],n=0;n0&&this._events[e].length>n&&(this._events[e].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[e].length),"function"==typeof console.trace&&console.trace()),this},r.prototype.on=r.prototype.addListener,r.prototype.once=function(e,t){function n(){this.removeListener(e,n),r||(r=!0,t.apply(this,arguments))}if(!o(t))throw TypeError("listener must be a function");var r=!1;return n.listener=t,this.on(e,n),this},r.prototype.removeListener=function(e,t){var n,r,i,a;if(!o(t))throw TypeError("listener must be a function");if(!this._events||!this._events[e])return this;if(n=this._events[e],i=n.length,r=-1,n===t||o(n.listener)&&n.listener===t)delete this._events[e],this._events.removeListener&&this.emit("removeListener",e,t);else if(s(n)){for(a=i;a-- >0;)if(n[a]===t||n[a].listener&&n[a].listener===t){r=a;break}if(r<0)return this;1===n.length?(n.length=0,delete this._events[e]):n.splice(r,1),this._events.removeListener&&this.emit("removeListener",e,t)}return this},r.prototype.removeAllListeners=function(e){var t,n;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[e]&&delete this._events[e],this;if(0===arguments.length){for(t in this._events)"removeListener"!==t&&this.removeAllListeners(t);return this.removeAllListeners("removeListener"),this._events={},this}if(n=this._events[e],o(n))this.removeListener(e,n);else if(n)for(;n.length;)this.removeListener(e,n[n.length-1]);return delete this._events[e],this},r.prototype.listeners=function(e){return this._events&&this._events[e]?o(this._events[e])?[this._events[e]]:this._events[e].slice():[]},r.prototype.listenerCount=function(e){if(this._events){var t=this._events[e];if(o(t))return 1;if(t)return t.length}return 0},r.listenerCount=function(e,t){return e.listenerCount(t)}},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n",'"',"`"," ","\r","\n","\t"],v=["{","}","|","\\","^","`"].concat(d),y=["'"].concat(v),m=["%","/","?",";","#"].concat(y),b=["/","?","#"],g=/^[+a-z0-9A-Z_-]{0,63}$/,_=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,N={javascript:!0,"javascript:":!0},E={javascript:!0,"javascript:":!0},T={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0},w=n(29);r.prototype.parse=function(e,t,n){if(!l.isString(e))throw new TypeError("Parameter 'url' must be a string, not "+(void 0===e?"undefined":u(e)));var r=e.indexOf("?"),o=-1!==r&&r127?M+="x":M+=L[I];if(!M.match(g)){var U=R.slice(0,k),V=R.slice(k+1),G=L.match(_);G&&(U.push(G[1]),V.unshift(G[2])),V.length&&(a="/"+V.join(".")+a),this.hostname=U.join(".");break}}}this.hostname.length>255?this.hostname="":this.hostname=this.hostname.toLowerCase(),A||(this.hostname=c.toASCII(this.hostname));var F=this.port?":"+this.port:"",H=this.hostname||"";this.host=H+F,this.href+=this.host,A&&(this.hostname=this.hostname.substr(1,this.hostname.length-2),"/"!==a[0]&&(a="/"+a))}if(!N[v])for(var k=0,P=y.length;k0)&&n.host.split("@");x&&(n.auth=x.shift(),n.host=n.hostname=x.shift())}return n.search=e.search,n.query=e.query,l.isNull(n.pathname)&&l.isNull(n.search)||(n.path=(n.pathname?n.pathname:"")+(n.search?n.search:"")),n.href=n.format(),n}if(!N.length)return n.pathname=null,n.search?n.path="/"+n.search:n.path=null,n.href=n.format(),n;for(var O=N.slice(-1)[0],k=(n.host||e.host||N.length>1)&&("."===O||".."===O)||""===O,j=0,C=N.length;C>=0;C--)O=N[C],"."===O?N.splice(C,1):".."===O?(N.splice(C,1),j++):j&&(N.splice(C,1),j--);if(!g&&!_)for(;j--;j)N.unshift("..");!g||""===N[0]||N[0]&&"/"===N[0].charAt(0)||N.unshift(""),k&&"/"!==N.join("/").substr(-1)&&N.push("");var S=""===N[0]||N[0]&&"/"===N[0].charAt(0);if(w){n.hostname=n.host=S?"":N.length?N.shift():"";var x=!!(n.host&&n.host.indexOf("@")>0)&&n.host.split("@");x&&(n.auth=x.shift(),n.host=n.hostname=x.shift())}return g=g||n.host&&N.length,g&&!S&&N.unshift(""),N.length?n.pathname=N.join("/"):(n.pathname=null,n.path=null),l.isNull(n.pathname)&&l.isNull(n.search)||(n.path=(n.pathname?n.pathname:"")+(n.search?n.search:"")),n.auth=e.auth||n.auth,n.slashes=n.slashes||e.slashes,n.href=n.format(),n},r.prototype.parseHost=function(){var e=this.host,t=h.exec(e);t&&(t=t[0],":"!==t&&(this.port=t.substr(1)),e=e.substr(0,e.length-t.length)),e&&(this.hostname=e)}},function(e,t,n){"use strict";(function(e,r){var o,i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};!function(s){function a(e){throw new RangeError(I[e])}function u(e,t){for(var n=e.length,r=[];n--;)r[n]=t(e[n]);return r}function c(e,t){var n=e.split("@"),r="";return n.length>1&&(r=n[0]+"@",e=n[1]),e=e.replace(M,"."),r+u(e.split("."),t).join(".")}function l(e){for(var t,n,r=[],o=0,i=e.length;o=55296&&t<=56319&&o65535&&(e-=65536,t+=V(e>>>10&1023|55296),e=56320|1023&e),t+=V(e)}).join("")}function h(e){return e-48<10?e-22:e-65<26?e-65:e-97<26?e-97:x}function p(e,t){return e+22+75*(e<26)-((0!=t)<<5)}function d(e,t,n){var r=0;for(e=n?U(e/C):e>>1,e+=U(e/t);e>D*k>>1;r+=x)e=U(e/D);return U(r+(D+1)*e/(e+j))}function v(e){var t,n,r,o,i,s,u,c,l,p,v=[],y=e.length,m=0,b=A,g=S;for(n=e.lastIndexOf(R),n<0&&(n=0),r=0;r=128&&a("not-basic"),v.push(e.charCodeAt(r));for(o=n>0?n+1:0;o=y&&a("invalid-input"),c=h(e.charCodeAt(o++)),(c>=x||c>U((w-m)/s))&&a("overflow"),m+=c*s,l=u<=g?O:u>=g+k?k:u-g,!(cU(w/p)&&a("overflow"),s*=p;t=v.length+1,g=d(m-i,t,0==i),U(m/t)>w-b&&a("overflow"),b+=U(m/t),m%=t,v.splice(m++,0,b)}return f(v)}function y(e){var t,n,r,o,i,s,u,c,f,h,v,y,m,b,g,_=[];for(e=l(e),y=e.length,t=A,n=0,i=S,s=0;s=t&&vU((w-n)/m)&&a("overflow"),n+=(u-t)*m,t=u,s=0;sw&&a("overflow"),v==t){for(c=n,f=x;h=f<=i?O:f>=i+k?k:f-i,!(c= 0x80 (not a basic code point)","invalid-input":"Invalid input"},D=x-O,U=Math.floor,V=String.fromCharCode;if(E={version:"1.4.1",ucs2:{decode:l,encode:f},decode:v,encode:y,toASCII:b,toUnicode:m},"object"==i(n(13))&&n(13))void 0!==(o=function(){return E}.call(t,n,t,e))&&(e.exports=o);else if(g&&_)if(e.exports==g)_.exports=E;else for(T in E)E.hasOwnProperty(T)&&(g[T]=E[T]);else s.punycode=E}(void 0)}).call(t,n(11)(e),n(27))},function(e,t,n){"use strict";var r,o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};r=function(){return this}();try{r=r||Function("return this")()||(0,eval)("this")}catch(e){"object"===("undefined"==typeof window?"undefined":o(window))&&(r=window)}e.exports=r},function(e,t,n){"use strict";var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};e.exports={isString:function(e){return"string"==typeof e},isObject:function(e){return"object"===(void 0===e?"undefined":r(e))&&null!==e},isNull:function(e){return null===e},isNullOrUndefined:function(e){return null==e}}},function(e,t,n){"use strict";t.decode=t.parse=n(30),t.encode=t.stringify=n(31)},function(e,t,n){"use strict";function r(e,t){return Object.prototype.hasOwnProperty.call(e,t)}e.exports=function(e,t,n,i){t=t||"&",n=n||"=";var s={};if("string"!=typeof e||0===e.length)return s;var a=/\+/g;e=e.split(t);var u=1e3;i&&"number"==typeof i.maxKeys&&(u=i.maxKeys);var c=e.length;u>0&&c>u&&(c=u);for(var l=0;l=0?(f=v.substr(0,y),h=v.substr(y+1)):(f=v,h=""),p=decodeURIComponent(f),d=decodeURIComponent(h),r(s,p)?o(s[p])?s[p].push(d):s[p]=[s[p],d]:s[p]=d}return s};var o=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)}},function(e,t,n){"use strict";function r(e,t){if(e.map)return e.map(t);for(var n=[],r=0;r=0;r--){var o=e[r];"."===o?e.splice(r,1):".."===o?(e.splice(r,1),n++):n&&(e.splice(r,1),n--)}if(t)for(;n--;n)e.unshift("..");return e}function r(e,t){if(e.filter)return e.filter(t);for(var n=[],r=0;r=-1&&!o;i--){var s=i>=0?arguments[i]:e.cwd();if("string"!=typeof s)throw new TypeError("Arguments to path.resolve must be strings");s&&(t=s+"/"+t,o="/"===s.charAt(0))}return t=n(r(t.split("/"),function(e){return!!e}),!o).join("/"),(o?"/":"")+t||"."},t.normalize=function(e){var o=t.isAbsolute(e),i="/"===s(e,-1);return e=n(r(e.split("/"),function(e){return!!e}),!o).join("/"),e||o||(e="."),e&&i&&(e+="/"),(o?"/":"")+e},t.isAbsolute=function(e){return"/"===e.charAt(0)},t.join=function(){var e=Array.prototype.slice.call(arguments,0);return t.normalize(r(e,function(e,t){if("string"!=typeof e)throw new TypeError("Arguments to path.join must be strings");return e}).join("/"))},t.relative=function(e,n){function r(e){for(var t=0;t=0&&""===e[n];n--);return t>n?[]:e.slice(t,n-t+1)}e=t.resolve(e).substr(1),n=t.resolve(n).substr(1);for(var o=r(e.split("/")),i=r(n.split("/")),s=Math.min(o.length,i.length),a=s,u=0;u1)for(var n=1;n 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/filter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /example/html.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /example/if.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /example/prefix.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /example/repeat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /example/replace.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /example/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /node/index.js: -------------------------------------------------------------------------------- 1 | var soda = require('./../dist/soda'); 2 | 3 | if(typeof document === 'undefined'){ 4 | var NodeWindow = require('nodewindow'); 5 | var nodeWindow = new NodeWindow(); 6 | 7 | var win = nodeWindow.runHTML("", {}, {}); 8 | 9 | var document = win.document; 10 | 11 | soda.setDocument(document); 12 | } 13 | 14 | module.exports = soda; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sodajs", 3 | "version": "0.4.10", 4 | "description": "Light weight but powerful template engine for JavaScript.", 5 | "main": "dist/soda.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/AlloyTeam/sodajs.git" 9 | }, 10 | "scripts": { 11 | "start": "webpack -w", 12 | "build": "npm run build-uncom && npm run build-min && npm run build-node-uncom && npm run build-node-min && npm run build-test && npm run build-node-test", 13 | "build-uncom": "webpack", 14 | "build-min": "webpack", 15 | "build-test": "webpack", 16 | "build-node-test": "webpack", 17 | "build-lite-uncom": "webpack", 18 | "build-lite-min": "webpack", 19 | "build-node-uncom": "webpack", 20 | "build-node-min": "webpack", 21 | "test": "mocha test/index.js" 22 | }, 23 | "keywords": [ 24 | "sodajs", 25 | "soda", 26 | "template" 27 | ], 28 | "author": "dorsywang", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/AlloyTeam/sodajs/issues/new" 32 | }, 33 | "homepage": "http://alloyteam.github.io/sodajs", 34 | "dependencies": { 35 | "nodewindow": "^1.0.11" 36 | }, 37 | "devDependencies": { 38 | "babel-core": "^6.25.0", 39 | "babel-loader": "^7.0.0", 40 | "babel-plugin-transform-class-properties": "^6.24.1", 41 | "babel-preset-es2015": "^6.24.1", 42 | "chai": "^4.0.2", 43 | "es3ify-webpack-plugin": "0.0.1", 44 | "mocha": "^3.4.2", 45 | "uglifyjs-webpack-plugin": "^0.4.6", 46 | "webpack": "^3.0.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /pg/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Sodajs Playground 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 | 21 |
22 |
23 | 41 |
42 |
43 | 44 |
45 |

46 |     
47 |
48 | 49 | 50 | 51 | 52 | 53 | 54 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /pg/preview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /pg/rd.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /plugin/koa-view-ng.js: -------------------------------------------------------------------------------- 1 | // app.js 2 | // let ngViews = require('./server/middleware/koa-view-ng'); 3 | 4 | // 加载模板引擎 5 | // ngViews(app, { 6 | // extension: ".html", 7 | // filters: ngTemplateFilter, 8 | // templateDir: path.join(__dirname, './common/templates/views'), 9 | // cache: LRU({ 10 | // max: 500, // The maximum number of items allowed in the cache 11 | // max_age: 1000 * 60 * 60 * 24 // The maximum life of a cached item in milliseconds 12 | // }), 13 | // beforeRender: (viewName, scope, ctx, setting) => { 14 | // let { prefix, extension } = setting; 15 | // let combScope = Object.assign({ 16 | // HMC: Object.assign({}, ctx.state, { 17 | // tplSettings: { prefix, extension }, 18 | // currPage: viewName, 19 | // jsRev: ctx.state.jsRevList[viewName + ".min.js"], 20 | // pointList: ctx.state.pointData.common.concat(ctx.state.pointData.pages[viewName] || []) 21 | // }) 22 | // }, scope, { 23 | // templates: scope && scope.templates || {} 24 | // }) 25 | // delete combScope.HMC.jsRevList; 26 | // delete combScope.HMC.pointData; 27 | // return { viewName, combScope, setting } 28 | // }, 29 | // disabledCache: process.env.NODE_ENV === 'development' 30 | // }); 31 | 32 | // router.get('/', async (ctx, next) => { 33 | // let scope = { 34 | // state, 35 | // page: "index" 36 | // } 37 | // let setting = { 38 | // disabledCache: false, 39 | // serverCacheKey: scope 40 | // } 41 | // await ctx.render("index", scope, setting||null) 42 | // }) 43 | 'use strict'; 44 | 45 | /** 46 | * Module dependencies. 47 | */ 48 | const fs = require('fs'); 49 | const path = require('path'); 50 | const ngTemplate = require('sodajs/node'); 51 | const crypto = require('crypto'); 52 | 53 | /** 54 | * default render options 55 | * @type {Object} 56 | */ 57 | const defaultSettings = { 58 | prefix: 'ng', 59 | templateDir: __dirname 60 | }; 61 | 62 | let tplCache = new Map() 63 | 64 | /** 65 | * set app.context.render 66 | * 67 | * usage: 68 | * ``` 69 | * await ctx.render('user', {name: 'dead_horse'}); 70 | * ``` 71 | * @param {Application} app koa application instance 72 | * @param {Object} settings user settings 73 | */ 74 | exports = module.exports = function (app, settings) { 75 | if (app.context.render) { 76 | return; 77 | } 78 | 79 | if (!settings || !settings.templateDir) { 80 | throw new Error('settings.templateDir required'); 81 | } 82 | 83 | settings = Object.assign({}, defaultSettings, settings); 84 | 85 | if (settings.cache) { 86 | tplCache = settings.cache; 87 | settings.cache = null; 88 | } 89 | 90 | 91 | // 初始化ngTemplate设置 92 | ngTemplate.prefix(settings.prefix); 93 | //ngTemplate.templateDir = settings.templateDir; 94 | 95 | for (var key in settings.filters) { 96 | ngTemplate.filter(key, settings.filters[key]) 97 | } 98 | 99 | /** 100 | * generate html with view name and options 101 | * @param {String} view 102 | * @param {Object} options 103 | * @return {String} html 104 | */ 105 | async function render(view, options, ctx, setting) { 106 | const viewPath = path.join(setting.templateDir, view + setting.extension); 107 | const tpl = await fs.readFileSync(viewPath, 'utf-8'); 108 | let hashKey = ""; 109 | let hash = crypto.createHmac('sha256', "hmc_secret"); 110 | if (process.env.NODE_ENV === 'development') { 111 | hashKey = hash.update(tpl + JSON.stringify(setting.serverCacheKey || options)).digest('hex'); 112 | } 113 | else { 114 | hashKey = hash.update(viewPath + JSON.stringify(setting.serverCacheKey || options)).digest('hex'); 115 | } 116 | // 从缓存获取模版 117 | if (!setting.disabledCache && tplCache && tplCache.get(hashKey)) { 118 | ctx.set("fromCache", true); 119 | return tplCache.get(hashKey); 120 | } 121 | 122 | let template = ngTemplate(tpl, options) 123 | // 加入缓存 124 | if (!setting.disabledCache && tplCache) { 125 | tplCache.set(hashKey, template); 126 | } 127 | return template; 128 | } 129 | 130 | app.context.render = async function (view, _context, setting) { 131 | const ctx = this; 132 | setting = Object.assign({}, settings, setting); 133 | let { prefix, extension } = setting; 134 | Object.assign(ctx.state, { 135 | tplSettings: { prefix, extension } 136 | }); 137 | const context = Object.assign({}, ctx.state, _context); 138 | 139 | let html = await render(view, context, ctx, setting); 140 | ctx.type = 'html'; 141 | ctx.body = html.replace("", "").replace("", ""); 142 | }; 143 | }; 144 | 145 | exports.ngTemplate = ngTemplate; 146 | -------------------------------------------------------------------------------- /readme_zh.md: -------------------------------------------------------------------------------- 1 | # sodajs 2 | 超好用的指令模板引擎 3 | 4 | ## 特性 5 | * 超小体积(gzip之后只有4K) 6 | * 支持dom指令系统 7 | * 良好的兼容性,兼容ie8及现代浏览器,兼容node环境 8 | * 避免输出的xss漏洞 9 | * 高性能dom渲染引擎 10 | * 与AngularJS指令兼容 11 | * 自定义指令和前缀 12 | 13 | 14 | ## 安装 15 | ### npm 16 | ``` js 17 | npm install --save sodajs 18 | ``` 19 | 20 | ### CDN 21 | * [https://unpkg.com/sodajs@0.4.10/dist/soda.min.js](https://unpkg.com/sodajs@0.4.10/dist/soda.min.js) 22 | * [https://unpkg.com/sodajs@0.4.10/dist/soda.js](https://unpkg.com/sodajs@0.4.10/dist/soda.js) 23 | * [https://unpkg.com/sodajs@0.4.10/dist/soda.node.min.js](https://unpkg.com/sodajs@0.4.10/dist/soda.node.min.js) 24 | * [https://unpkg.com/sodajs@0.4.10/dist/soda.node.js](https://unpkg.com/sodajs@0.4.10/dist/soda.node.js) 25 | 26 | 27 | ## 使用 28 | ### soda & soda.node的不同 29 | | version | soda | soda.node | 30 | | ------------ | ------------ | ------------ | 31 | | Mordern Browsers | ✓| ✓| 32 | | Mobile Browsers | ✓ | ✓ | 33 | | ie | ≥8 | ≥9| 34 | | node | ✗ | ✓| 35 | | dom解析引擎| 原生| 自带nodeWindow引擎| 36 | 37 | 提示: ie 8兼容需要自行引入es5-shim 或es5-sham 和console-polyfill 38 | 39 | 查看这里的ie8的兼容测试 40 | * [ie8 browser test](http://alloyteam.github.io/sodajs/test/soda-browser.html) 41 | 42 | ### 浏览器端 43 | * script标签 44 | 45 | ```html 46 | 47 | ``` 48 | * 使用webpack 49 | 50 | ```javascript 51 | import soda from "sodajs" 52 | ``` 53 | 54 | ### Node端 55 | ```js 56 | let soda = require('sodajs/node'); 57 | ``` 58 | 低版本node可以使用dist版本 59 | ```js 60 | let soda = require('sodajs/dist/soda.node') 61 | ``` 62 | ## API 63 | ### 输出 64 | 65 | #### 简单输出 66 | 67 | ```js 68 | var tpl = '
{{name}}
'; 69 | 70 | document.body.innerHTML = soda(tpl,{ name : 'soda' }) 71 | 72 | ``` 73 | ➜ [示例](http://alloyteam.github.io/sodajs/pg/rd.html?type=simple) 74 | 75 | 76 | #### 安全的链式输出 77 | ```js 78 | var data = { 79 | name: 'soda', 80 | info: { 81 | version: '2.0' 82 | } 83 | } 84 | 85 | soda("{{info.version}}", data); 86 | // result => "2.0" 87 | 88 | 89 | soda("{{info.foo.foo1}}", data) 90 | // result => "" without errors 91 | 92 | soda("{{info['name']}}", data) 93 | // result => "2.0" 94 | 95 | ``` 96 | 97 | #### 表达式 98 | 99 | ```js 100 | var data = {} 101 | 102 | soda("{{1 + 2}}", data); 103 | // result => 2 104 | 105 | 106 | soda("{{true ? 'soda' : 'foo'}}", data) 107 | // result => "soda" 108 | 109 | soda("{{1 < 3 && 'soda'}}", data) 110 | // result => "soda" 111 | 112 | ``` 113 | ➜ [示例](http://alloyteam.github.io/sodajs/pg/rd.html?type=expression) 114 | 115 | #### 复杂的表达式 116 | ```js 117 | var data = { 118 | list: [ 119 | {list: [{'title': '<>aa'}, {'title': 'bb'}], name: 0, show: 1}, 120 | {list: [{'title': 0 }, {'title': 'bb'}], name: 'b'} 121 | ] 122 | 123 | }; 124 | 125 | soda('{{list[list[0].show === 1 ? list[0].name : 1].list[0].title}}', data) 126 | // result => '<>aa' 127 | ``` 128 | 129 | ### 指令 130 | 131 | #### if 132 | 133 | ``` js 134 | var data = { name : 'soda',show: true }; 135 | soda(`
Hello, {{name}}
136 |
I\'m hidden!
`, 137 | data 138 | ) 139 | // result =>
Hello, soda
140 | ``` 141 | 142 | ➜ [示例](http://alloyteam.github.io/sodajs/pg/rd.html?type=if) 143 | 144 | 145 | ### repeat 146 | 147 | > soda-repeat="item in array" 148 | 149 | > soda-repeat="item in object" 150 | 151 | > soda-repeat="item in array by index" 152 | 153 | > soda-repeat="item in object by key" 154 | 155 | > soda-repeat="(index, value) in array" 156 | 157 | > soda-repeat="(key, value) in object" 158 | 159 | 默认的下标是$index 160 | 161 | 162 | ``` js 163 | var tpl = '\ 164 |
    \ 165 |
  • \ 166 | {{item.name}}\ 167 | {{$index}}\ 168 |
  • \ 169 |
' 170 | 171 | var data = { 172 | list: [ 173 | {name: "Hello" ,show: true}, 174 | {name: "sodajs" ,show: true}, 175 | {name: "AlloyTeam"} 176 | ] 177 | }; 178 | 179 | document.body.innerHTML = soda(tpl, data); 180 | ``` 181 | 182 | ➜ [示例](http://alloyteam.github.io/sodajs/pg/rd.html?type=repeat) 183 | 184 | 185 | ### filter 186 | 187 | > soda.filter(String filterName, Function func(input, args...)) 188 | > {{input|filte1:args1:args2...|filter2:args...}} 189 | 190 | example: 191 | 192 | ``` js 193 | soda.filter('shortTitle', function(input, length){ 194 | return (input || '').substr(0, length); 195 | }); 196 | 197 | var tpl = '\ 198 |
    \ 199 |
  • \ 200 | {{item.title|shortTitle:10}}\ 201 |
  • \ 202 |
' 203 | 204 | 205 | document.body.innerHTML = soda(tpl,{ list : [ 206 | {title:'short'}, 207 | {title:'i am too long!'} 208 | ] }) 209 | ``` 210 | 211 | ➜ [示例](http://alloyteam.github.io/sodajs/pg/rd.html?type=filter) 212 | 213 | ### html 214 | 输出原始的html,不做完全转换 215 | 216 | ```js 217 | var tpl = '
' 218 | document.body.innerHTML = soda(tpl,{ html : 'test soda-html' }) 219 | ``` 220 | 221 | ➜ [html example](http://alloyteam.github.io/sodajs/pg/rd.html?type=html) 222 | 223 | ### replace 224 | 用html替换当前结点 225 | 226 | ```js 227 | var tpl = '
' 228 | document.body.innerHTML = soda(tpl,{ html : 'test soda-html' }) 229 | ``` 230 | 231 | ➜ [replace example](http://alloyteam.github.io/sodajs/pg/rd.html?type=replace) 232 | 233 | div will be replaced with given html 234 | div会被html替换 235 | 236 | #### include 237 | 嵌套模板 238 | 239 | soda-include="tmplateName:arg1:arg2:..." 240 | 和soda.discribe一起使用 241 | 242 | ```js 243 | var data = { 244 | name: "soda" 245 | }; 246 | 247 | // define sub template named tmpl1 248 | soda.discribe('tmpl1', `

{{name}}

`); 249 | 250 | 251 | // use template tmpl1 by soda-include 252 | soda(`1`, data); 253 | // result =>

dorsy

254 | 255 | // set compile false not to compile sub template 256 | soda.discribe('tmpl1', `

{{name}}

`, { 257 | compile: false 258 | }); 259 | 260 | // show origin template 261 | soda(`1`, data); 262 | // result =>

{{name}}

263 | 264 | soda.discribe('tmpl2', function(path){ 265 | return `

{{name}}_${path}

`; 266 | }); 267 | 268 | soda(`1`, data); 269 | // result =>

soda_subpath1

270 | 271 | 272 | // In node env 273 | soda.discribe('tmplNode', function(path){ 274 | return fs.readFileSync(path, 'utf-8'); 275 | }); 276 | 277 | soda(`1`, data); 278 | // result => view.html Tmplate 279 | 280 | 281 | ``` 282 | 283 | ### 其他 284 | 285 | #### soda-class 286 | > soda-class="currItem === 'list1' ? 'active' : ''" 287 | 288 | 289 | #### soda-src 290 | > soda-src="hello{{index}}.png" 291 | 292 | #### soda-style 293 | > soda-style="style" 294 | 295 | data example: 296 | 297 | ```js 298 | var data = { style : { width : '100px', height : '100px' } }; 299 | ``` 300 | 301 | 302 | #### soda-* 303 | > soda-rx="{{rx}}%" 304 | 305 | > soda-checked="{{false}}" 306 | 307 | 如果值为false 或者 "", 该属性就会被移除,否则,会被添加上去 308 | 309 | ## 自定义 310 | 311 | ### soda.prefix 312 | 313 | 改变指令的前缀,默认的前缀是soda- 314 | 315 | ``` js 316 | soda.prefix('v:') 317 | 318 | var tpl = '\ 319 |
    \ 320 |
  • \ 321 | {{item.name}}\ 322 |
  • \ 323 |
' 324 | 325 | 326 | var data = { 327 | list: [ 328 | {name: "Hello" ,show: true}, 329 | {name: "sodajs" ,show: true}, 330 | {name: "AlloyTeam"} 331 | ] 332 | }; 333 | 334 | document.body.innerHTML = soda(tpl, data); 335 | ``` 336 | ### soda.directive 337 | 自定义指令 338 | #### es 2015写法 339 | ```js 340 | soda.directive('name', { 341 | priority: 8, 342 | 343 | // how to compile el 344 | link({ scope, el, parseSodaExpression, expression, getValue, compileNode, document }) { 345 | 346 | } 347 | }); 348 | ``` 349 | * scope: 当前的scope数据 350 | * el: 当前节点 351 | * expression: 指令的表达式原始字符串 352 | * getValue: 从data链式获取值 353 | ```js 354 | getValue({a: {b: 1}}, "a.b"); // ===> 1 355 | ``` 356 | * parseSodaExpression: 解析soda表达式 357 | ```js 358 | parseSodaExpression('{{1 + 2 + a}}', {a: 1}); // ===> 4 359 | ``` 360 | * compileNode: 继续编译节点 361 | * document: 使用document参数而不是使用window.document, 这样可以在node环境下去用 362 | 363 | #### 示例 364 | ```js 365 | soda.directive('mydirective', { 366 | priority: 8, 367 | 368 | link({ scope, el, parseSodaExpression, expression, getValue, compileNode, document }) { 369 | var value = parseSodaExpression(expression); 370 | if(value){ 371 | var textNode = document.createTextNode(value); 372 | el.appendChild(textNode); 373 | } 374 | } 375 | } 376 | 377 | soda(` 378 |
379 | `, { 380 | tips: 'tips' 381 | }); 382 | 383 | // result ==>
add one tips: tips
384 | ``` 385 | 386 | 387 | ### soda.setDocument 388 | 自定义node端的dom解析引擎 389 | 390 | soda.node版本的默认dom解析引擎是nodeWindow,你可以用这个方法替换为jsdom等 391 | 392 | 393 | ```js 394 | var document = require('document'); 395 | var soda = require('soda'); 396 | 397 | soda.setDocument(document); 398 | 399 | // ... run 400 | 401 | ``` 402 | 403 | 404 | ## 贡献代码 405 | ### 开发 406 | 407 | git clone 408 | ``` shell 409 | git clone git://github.com/AlloyTeam/sodajs.git 410 | ``` 411 | 412 | 安装依赖 413 | ``` shell 414 | npm install 415 | ``` 416 | 417 | 然后执行npm start 418 | 419 | ``` shell 420 | npm start 421 | ``` 422 | 执行run build构建代码 423 | 424 | ``` shell 425 | npm run build 426 | ``` 427 | ### 自动化测试 428 | soda使用mocha来做自动化测试 429 | 430 | 测试单元在test目录 431 | ``` shell 432 | npm run test 433 | ``` 434 | 435 | #### 在线测试页面 436 | * [soda-mocha](http://alloyteam.github.io/sodajs/test/soda-mocha.html) 437 | * [soda.node-mocha](http://alloyteam.github.io/sodajs/test/soda.node-mocha.html) 438 | * [ie8 browser test](http://alloyteam.github.io/sodajs/test/soda-browser.html) 439 | 440 | 441 | ## 使用项目 442 | 兴趣部落, QQ群, 群活动 443 | 444 | ## 协议 445 | 446 | [MIT](http://opensource.org/licenses/MIT) 447 | 448 | Copyright (c) 2015-present, AlloyTeam 449 | -------------------------------------------------------------------------------- /src/const.js: -------------------------------------------------------------------------------- 1 | // 标识符 2 | export const IDENTOR_REG = /[a-zA-Z_\$]+[\w\$]*/g; 3 | export const STRING_REG = /"([^"]*)"|'([^']*)'/g 4 | export const NUMBER_REG = /\d+|\d*\.\d+/g; 5 | 6 | export const OBJECT_REG = /[a-zA-Z_\$]+[\w\$]*(?:\s*\.\s*(?:[a-zA-Z_\$]+[\w\$]*|\d+))*/g; 7 | 8 | // 非global 做test用 9 | export const OBJECT_REG_NG = /[a-zA-Z_\$]+[\w\$]*(?:\s*\.\s*(?:[a-zA-Z_\$]+[\w\$]*|\d+))*/; 10 | 11 | export const ATTR_REG = /\[([^\[\]]*)\]/g; 12 | export const ATTR_REG_NG = /\[([^\[\]]*)\]/; 13 | export const ATTR_REG_DOT = /\.([a-zA-Z_\$]+[\w\$]*)/g; 14 | 15 | export const NOT_ATTR_REG = /[^\.|]([a-zA-Z_\$]+[\w\$]*)/g; 16 | 17 | export const OR_REG = /\|\|/g; 18 | 19 | export const OR_REPLACE = "OR_OPERATOR\x1E"; 20 | 21 | export const CONST_PRIFIX = "_$C$_"; 22 | export const CONST_REG = /^_\$C\$_/; 23 | export const CONST_REGG = /_\$C\$_[^\.]+/g; 24 | export const VALUE_OUT_REG = /\{\{([^\}]*)\}\}/g; 25 | export const ONLY_VALUE_OUT_REG = /^\{\{([^\}]*)\}\}$/; 26 | -------------------------------------------------------------------------------- /src/directive/class.js: -------------------------------------------------------------------------------- 1 | import Soda from './../soda'; 2 | 3 | var classNameRegExp = function(className) { 4 | return new RegExp('(^|\\s+)' + className + '(\\s+|$)', 'g'); 5 | }; 6 | 7 | var addClass = function(el, className){ 8 | if(! el.className){ 9 | el.className = className; 10 | 11 | return; 12 | } 13 | 14 | if(el.className.match(classNameRegExp(className))){ 15 | }else{ 16 | el.className += " " + className; 17 | } 18 | }; 19 | 20 | var removeClass = function(el, className){ 21 | el.className = el.className.replace(classNameRegExp(className), ""); 22 | }; 23 | 24 | Soda.directive('class', { 25 | link: function({scope, el, expression, parseSodaExpression}){ 26 | var expressFunc = parseSodaExpression(expression, scope); 27 | 28 | if(expressFunc){ 29 | addClass(el, expressFunc); 30 | }else{ 31 | } 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /src/directive/html.js: -------------------------------------------------------------------------------- 1 | import Soda from './../soda'; 2 | 3 | Soda.directive('html',{ 4 | link({expression, scope, el, parseSodaExpression}) { 5 | var result = parseSodaExpression(expression, scope); 6 | 7 | if (result) { 8 | el.innerHTML = result; 9 | } 10 | } 11 | }); 12 | 13 | -------------------------------------------------------------------------------- /src/directive/if.js: -------------------------------------------------------------------------------- 1 | import Soda from './../soda'; 2 | 3 | Soda.directive('if', { 4 | priority: 9, 5 | link: function({expression, parseSodaExpression, scope, el}){ 6 | var expressFunc = parseSodaExpression(expression, scope); 7 | 8 | if(expressFunc){ 9 | }else{ 10 | el.parentNode && el.parentNode.removeChild(el); 11 | el.innerHTML = ''; 12 | } 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /src/directive/include.js: -------------------------------------------------------------------------------- 1 | import Soda from './../soda'; 2 | 3 | Soda.directive('include', { 4 | priority: 8, 5 | link({scope, el, parseSodaExpression, expression}) { 6 | const VALUE_OUT_REG = /\{\{([^\}]*)\}\}/g; 7 | 8 | var result = expression.replace(VALUE_OUT_REG, function(item, $1){ 9 | return parseSodaExpression($1, scope); 10 | }); 11 | 12 | result = result.split(":") 13 | 14 | var name = result[0]; 15 | 16 | var args = result.slice(1); 17 | 18 | var templateOption = Soda.getTmpl(name, args); 19 | 20 | let { template, option = {} } = templateOption; 21 | if (template) { 22 | if(option.compile){ 23 | el.outerHTML = this.run(template, scope); 24 | }else{ 25 | el.outerHTML = template; 26 | } 27 | } 28 | 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /src/directive/repeat.js: -------------------------------------------------------------------------------- 1 | import Soda from './../soda'; 2 | 3 | Soda.directive('repeat', { 4 | priority: 10, 5 | link: function({scope, el, expression, getValue, parseSodaExpression, compileNode}){ 6 | var itemName; 7 | var valueName; 8 | 9 | var trackReg = /\s+by\s+([^\s]+)$/; 10 | 11 | var trackName; 12 | var opt = expression.replace(trackReg, function(item, $1) { 13 | if ($1) { 14 | trackName = ($1 || '').trim(); 15 | } 16 | 17 | return ''; 18 | }); 19 | 20 | 21 | var inReg = /([^\s]+)\s+in\s+([^\s]+)|\(([^,]+)\s*,\s*([^)]+)\)\s+in\s+([^\s]+)/; 22 | 23 | var r = inReg.exec(opt); 24 | if (r) { 25 | if (r[1] && r[2]) { 26 | itemName = (r[1] || '').trim(); 27 | valueName = (r[2] || '').trim(); 28 | 29 | if (!(itemName && valueName)) { 30 | return; 31 | } 32 | } else if (r[3] && r[4] && r[5]) { 33 | trackName = (r[3] || '').trim(); 34 | itemName = (r[4] || '').trim(); 35 | valueName = (r[5] || '').trim(); 36 | } 37 | } else { 38 | return; 39 | } 40 | 41 | trackName = trackName || '$index'; 42 | 43 | // 这里要处理一下 44 | var repeatObj = getValue(scope, valueName) || []; 45 | 46 | var repeatFunc = (i) => { 47 | var itemNode = el.cloneNode(true); 48 | 49 | // 这里创建一个新的scope 50 | var itemScope = Object.create(scope); 51 | itemScope[trackName] = i; 52 | 53 | itemScope[itemName] = repeatObj[i]; 54 | 55 | //itemScope.__proto__ = scope; 56 | 57 | // REMOVE cjd6568358 58 | itemNode.removeAttribute(this._prefix + 'repeat'); 59 | 60 | el.parentNode.insertBefore(itemNode, el); 61 | 62 | // 这里是新加的dom, 要单独编译 63 | compileNode(itemNode, itemScope); 64 | 65 | }; 66 | 67 | if ('length' in repeatObj) { 68 | for (var i = 0; i < repeatObj.length; i++) { 69 | repeatFunc(i); 70 | } 71 | } else { 72 | for (var i in repeatObj) { 73 | if (repeatObj.hasOwnProperty(i)) { 74 | repeatFunc(i); 75 | } 76 | } 77 | } 78 | 79 | // el 清理 80 | el.parentNode.removeChild(el); 81 | 82 | if(el.childNodes && el.childNodes.length){ 83 | el.innerHTML = ''; 84 | } 85 | } 86 | }); 87 | -------------------------------------------------------------------------------- /src/directive/replace.js: -------------------------------------------------------------------------------- 1 | import Soda from './../soda'; 2 | 3 | Soda.directive('replace', { 4 | link({scope, el, expression, parseSodaExpression, document}) { 5 | var result = parseSodaExpression(expression, scope); 6 | 7 | if (result) { 8 | var div = document.createElement('div'); 9 | div.innerHTML = result; 10 | 11 | if (el.parentNode) { 12 | while (div.childNodes[0]) { 13 | el.parentNode.insertBefore(div.childNodes[0], el); 14 | } 15 | } 16 | } 17 | 18 | el.parentNode && el.parentNode.removeChild(el); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /src/directive/src.js: -------------------------------------------------------------------------------- 1 | import Soda from './../soda'; 2 | 3 | Soda.directive('src', { 4 | link: function({scope, el, expression, parseSodaExpression}){ 5 | const VALUE_OUT_REG = /\{\{([^\}]*)\}\}/g; 6 | 7 | var expressFunc = expression.replace(VALUE_OUT_REG, function(item, $1){ 8 | return parseSodaExpression($1, scope); 9 | }); 10 | 11 | if(expressFunc){ 12 | el.setAttribute("src", expressFunc); 13 | }else{ 14 | } 15 | } 16 | }); 17 | 18 | -------------------------------------------------------------------------------- /src/directive/style.js: -------------------------------------------------------------------------------- 1 | import Soda from './../soda'; 2 | 3 | Soda.directive('style', { 4 | link({scope, el, expression, parseSodaExpression}) { 5 | var expressFunc = parseSodaExpression(expression, scope); 6 | 7 | var getCssValue = function(name, value) { 8 | var numberWithoutpx = /opacity|z-index/; 9 | if (numberWithoutpx.test(name)) { 10 | return parseFloat(value); 11 | } 12 | 13 | if (isNaN(value)) { 14 | return value; 15 | } else { 16 | return value + "px"; 17 | } 18 | }; 19 | 20 | if (expressFunc) { 21 | var stylelist = []; 22 | 23 | for (var i in expressFunc) { 24 | if (expressFunc.hasOwnProperty(i)) { 25 | var provalue = getCssValue(i, expressFunc[i]); 26 | 27 | stylelist.push([i, provalue].join(":")); 28 | } 29 | } 30 | 31 | var style = el.style; 32 | for (var i = 0; i < style.length; i++) { 33 | var name = style[i]; 34 | if (expressFunc[name]) {} else { 35 | stylelist.push([name, style[name]].join(":")); 36 | } 37 | } 38 | 39 | var styleStr = stylelist.join(";"); 40 | 41 | el.setAttribute("style", styleStr); 42 | } 43 | } 44 | }); 45 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Soda from './soda'; 2 | import { assign } from './util'; 3 | 4 | import './directive/repeat' 5 | import './directive/if' 6 | import './directive/class' 7 | import './directive/html' 8 | import './directive/replace' 9 | import './directive/style' 10 | import './directive/include' 11 | 12 | 13 | let sodaInstance = new Soda(); 14 | 15 | let init = function(str, data){ 16 | return sodaInstance.run(str, data); 17 | }; 18 | 19 | let mock = { 20 | prefix(prefix){ 21 | sodaInstance.prefix(prefix); 22 | }, 23 | 24 | filter(name, func){ 25 | Soda.filter(name, func); 26 | }, 27 | 28 | directive(name, opt){ 29 | Soda.directive(name, opt); 30 | }, 31 | 32 | setDocument(document){ 33 | sodaInstance.setDocument(document); 34 | }, 35 | 36 | discribe(name, str, option){ 37 | Soda.discribe(name, str, option); 38 | }, 39 | 40 | Soda 41 | }; 42 | 43 | let soda = assign(init, mock); 44 | 45 | module.exports = soda; 46 | -------------------------------------------------------------------------------- /src/soda.js: -------------------------------------------------------------------------------- 1 | import { 2 | IDENTOR_REG, 3 | STRING_REG, 4 | NUMBER_REG, 5 | OBJECT_REG, 6 | OBJECT_REG_NG, 7 | ATTR_REG, 8 | ATTR_REG_NG, 9 | ATTR_REG_DOT, 10 | NOT_ATTR_REG, 11 | OR_REG, 12 | OR_REPLACE, 13 | CONST_PRIFIX, 14 | CONST_REG, 15 | CONST_REGG, 16 | VALUE_OUT_REG, 17 | ONLY_VALUE_OUT_REG 18 | } from './const'; 19 | 20 | import { 21 | getAttrVarKey, 22 | getRandom, 23 | exist, 24 | nodes2Arr 25 | } from './util'; 26 | 27 | var doc = typeof document !== 'undefined' ? document : {}; 28 | 29 | export default class Soda{ 30 | static sodaDirectives = []; 31 | static sodaFilterMap = {}; 32 | static template = {}; 33 | 34 | constructor(prefix = 'soda-'){ 35 | this._prefix = prefix; 36 | } 37 | 38 | setDocument(_doc){ 39 | doc = _doc; 40 | } 41 | 42 | 43 | run(str, data){ 44 | // 解析模板DOM 45 | var div = doc.createElement("div"); 46 | 47 | // 必须加入到body中去,不然自定义标签不生效 48 | if (doc.documentMode < 9) { 49 | div.style.display = 'none'; 50 | doc.body.appendChild(div); 51 | } 52 | 53 | div.innerHTML = str; 54 | 55 | nodes2Arr(div.childNodes).map(child => { 56 | this.compileNode(child, data); 57 | }); 58 | 59 | var innerHTML = div.innerHTML; 60 | 61 | 62 | if (doc.documentMode < 9) { 63 | doc.body.removeChild(div); 64 | } 65 | 66 | return innerHTML; 67 | } 68 | 69 | prefix(prefix){ 70 | this._prefix = prefix; 71 | } 72 | 73 | _getPrefixReg(){ 74 | return new RegExp('^' + this._prefix); 75 | } 76 | 77 | _getPrefixedDirectiveMap(){ 78 | var map = {}; 79 | Soda.sodaDirectives.map(item => { 80 | var prefixedName = this._prefix + item.name; 81 | 82 | map[prefixedName] = item; 83 | }); 84 | 85 | return map; 86 | } 87 | 88 | _removeSodaMark(node, name){ 89 | node.removeAttribute(name); 90 | } 91 | 92 | compileNode(node, scope){ 93 | let prefixReg = this._getPrefixReg(); 94 | 95 | let { 96 | sodaDirectives 97 | } = Soda; 98 | 99 | let prefixedDirectiveMap = this._getPrefixedDirectiveMap(); 100 | 101 | let compile = (node, scope) => { 102 | 103 | // 如果只是文本 104 | // parseTextNode 105 | if (node.nodeType === (node.TEXT_NODE || 3)) { 106 | node.nodeValue = node.nodeValue.replace(VALUE_OUT_REG, (item, $1) => { 107 | var value = this.parseSodaExpression($1, scope); 108 | if (typeof value === "object") { 109 | value = JSON.stringify(value, null, 2) 110 | } 111 | return value; 112 | }); 113 | } 114 | 115 | // parse Attributes 116 | if (node.attributes && node.attributes.length) { 117 | 118 | // 指令优先处理 119 | sodaDirectives.map(item => { 120 | let { 121 | name, 122 | opt 123 | } = item; 124 | 125 | let prefixedName = this._prefix + name; 126 | 127 | // 这里移除了对parentNode的判断 128 | // 允许使用无值的指令 129 | if (exist(node.getAttribute(prefixedName))) { 130 | let expression = node.getAttribute(prefixedName); 131 | 132 | opt.link.bind(this)({ 133 | expression, 134 | scope, 135 | el: node, 136 | parseSodaExpression: this.parseSodaExpression.bind(this), 137 | getValue: this.getValue.bind(this), 138 | compileNode: this.compileNode.bind(this), 139 | document: doc 140 | }); 141 | 142 | // 移除标签 143 | this._removeSodaMark(node, prefixedName); 144 | 145 | } 146 | }); 147 | 148 | // 处理输出 包含 prefix-* 149 | nodes2Arr(node.attributes) 150 | // 过滤掉指令里包含的属性 151 | .filter( 152 | attr => 153 | ! prefixedDirectiveMap[attr.name] 154 | ) 155 | .map(attr => { 156 | if (prefixReg.test(attr.name)) { 157 | var attrName = attr.name.replace(prefixReg, ''); 158 | 159 | if (attrName && exist(attr.value)) { 160 | var attrValue = this.parseComplexExpression(attr.value, scope); 161 | 162 | if(attrValue !== false && exist(attrValue)){ 163 | node.setAttribute(attrName, attrValue); 164 | } 165 | 166 | this._removeSodaMark(node, attr.name); 167 | } 168 | 169 | // 对其他属性里含expr 处理 170 | } else { 171 | if (exist(attr.value)) { 172 | attr.value = this.parseComplexExpression(attr.value, scope); 173 | } 174 | } 175 | }); 176 | 177 | } 178 | 179 | // parse childNodes 180 | nodes2Arr(node.childNodes).map(child => { 181 | compile(child, scope); 182 | }); 183 | }; 184 | 185 | compile(node, scope); 186 | 187 | } 188 | 189 | getEvalFunc(expr){ 190 | var evalFunc = new Function("getValue", "sodaFilterMap", "return function sodaExp(scope){ return " + expr + "}")(this.getValue, Soda.sodaFilterMap); 191 | 192 | return evalFunc; 193 | } 194 | 195 | getValue(_data, _attrStr) { 196 | CONST_REGG.lastIndex = 0; 197 | var realAttrStr = _attrStr.replace(CONST_REGG, function(r) { 198 | if (typeof _data[r] === "undefined") { 199 | return r; 200 | } else { 201 | return _data[r]; 202 | } 203 | }); 204 | 205 | if (_attrStr === 'true') { 206 | return true; 207 | } 208 | 209 | if (_attrStr === 'false') { 210 | return false; 211 | } 212 | 213 | var _getValue = function(data, attrStr) { 214 | var dotIndex = attrStr.indexOf("."); 215 | 216 | if (dotIndex > -1) { 217 | var attr = attrStr.substr(0, dotIndex); 218 | attrStr = attrStr.substr(dotIndex + 1); 219 | 220 | // 检查attrStr是否属于变量并转换 221 | if (typeof _data[attr] !== "undefined" && CONST_REG.test(attr)) { 222 | attr = _data[attr]; 223 | } 224 | 225 | if (typeof data[attr] !== "undefined" && data[attr] !== null) { 226 | return _getValue(data[attr], attrStr); 227 | } else { 228 | var eventData = { 229 | name: realAttrStr, 230 | data: _data 231 | }; 232 | 233 | 234 | // 如果还有 235 | return ""; 236 | } 237 | } else { 238 | attrStr = attrStr.trim(); 239 | 240 | // 检查attrStr是否属于变量并转换 241 | if (typeof _data[attrStr] !== "undefined" && CONST_REG.test(attrStr)) { 242 | attrStr = _data[attrStr]; 243 | } 244 | 245 | var rValue; 246 | if (typeof data[attrStr] !== "undefined") { 247 | rValue = data[attrStr]; 248 | } else { 249 | var eventData = { 250 | name: realAttrStr, 251 | data: _data 252 | }; 253 | 254 | rValue = ""; 255 | } 256 | 257 | return rValue; 258 | } 259 | }; 260 | 261 | return _getValue(_data, _attrStr); 262 | } 263 | 264 | // 解析混合表达式 265 | parseComplexExpression(str, scope){ 266 | var onlyResult = ONLY_VALUE_OUT_REG.exec(str); 267 | if(onlyResult){ 268 | var sodaExp = onlyResult[1]; 269 | 270 | return this.parseSodaExpression(sodaExp, scope); 271 | } 272 | 273 | return str.replace(VALUE_OUT_REG, (item, $1) => { 274 | return this.parseSodaExpression($1, scope); 275 | }); 276 | } 277 | 278 | 279 | parseSodaExpression(str, scope) { 280 | // 将字符常量保存下来 281 | str = str.replace(STRING_REG, function(r, $1, $2) { 282 | var key = getRandom(); 283 | scope[key] = $1 || $2; 284 | return key; 285 | }); 286 | 287 | // 对filter进行处理 288 | str = str.replace(OR_REG, OR_REPLACE).split("|"); 289 | 290 | for (var i = 0; i < str.length; i++) { 291 | str[i] = (str[i].replace(new RegExp(OR_REPLACE, 'g'), "||") || '').trim(); 292 | } 293 | 294 | 295 | var expr = str[0] || ""; 296 | var filters = str.slice(1); 297 | 298 | 299 | while (ATTR_REG_NG.test(expr)) { 300 | ATTR_REG.lastIndex = 0; 301 | 302 | //对expr预处理 303 | expr = expr.replace(ATTR_REG, (r, $1) => { 304 | var key = getAttrVarKey(); 305 | // 属性名称为字符常量 306 | var attrName = this.parseSodaExpression($1, scope); 307 | 308 | // 给一个特殊的前缀 表示是属性变量 309 | 310 | scope[key] = attrName; 311 | 312 | return "." + key; 313 | }); 314 | } 315 | 316 | expr = expr.replace(OBJECT_REG, function(value) { 317 | return "getValue(scope,'" + value.trim() + "')"; 318 | }); 319 | 320 | expr = this.parseFilter(filters, expr); 321 | 322 | return this.getEvalFunc(expr)(scope); 323 | } 324 | 325 | parseFilter(filters, expr) { 326 | let {sodaFilterMap} = Soda; 327 | 328 | var parse = () => { 329 | var filterExpr = filters.shift(); 330 | 331 | if (!filterExpr) { 332 | return; 333 | } 334 | 335 | 336 | var filterExpr = filterExpr.split(":"); 337 | var args = filterExpr.slice(1) || []; 338 | var name = (filterExpr[0] || "").trim(); 339 | 340 | for (var i = 0; i < args.length; i++) { 341 | //这里根据类型进行判断 342 | if (OBJECT_REG_NG.test(args[i])) { 343 | args[i] = "getValue(scope,'" + args[i] + "')"; 344 | } else {} 345 | } 346 | 347 | if (sodaFilterMap[name]) { 348 | args.unshift(expr); 349 | 350 | args = args.join(","); 351 | 352 | expr = "sodaFilterMap['" + name + "'](" + args + ")"; 353 | } 354 | 355 | parse(); 356 | } 357 | 358 | parse(); 359 | 360 | return expr; 361 | } 362 | 363 | static filter(name, func) { 364 | this.sodaFilterMap[name] = func; 365 | } 366 | 367 | static getFilter(name){ 368 | return this.sodaFilterMap[name]; 369 | } 370 | 371 | static directive(name, opt) { 372 | // 按照顺序入 373 | let {priority = 0} = opt; 374 | let i; 375 | 376 | for(i = 0; i < this.sodaDirectives.length; i ++){ 377 | let item = this.sodaDirectives[i]; 378 | let {priority: itemPriority = 0} = item.opt; 379 | 380 | // 比他小 继续比下一个 381 | if(priority < itemPriority){ 382 | 383 | // 发现比它大或者相等 就插大他前面 384 | }else if(priority >= itemPriority){ 385 | break; 386 | } 387 | } 388 | 389 | this.sodaDirectives.splice(i, 0, { 390 | name, 391 | opt 392 | }); 393 | } 394 | 395 | static discribe(name, funcOrStr, option = { compile: true}){ 396 | 397 | this.template[name] = { 398 | funcOrStr, 399 | option 400 | }; 401 | } 402 | 403 | static getTmpl(name, args){ 404 | let template = this.template[name]; 405 | let { funcOrStr, option = {} } = template; 406 | 407 | let result; 408 | 409 | if(typeof funcOrStr === 'function'){ 410 | result = funcOrStr.apply(null, args); 411 | }else{ 412 | result = funcOrStr; 413 | } 414 | 415 | return { 416 | template: result, 417 | option 418 | } 419 | } 420 | } 421 | -------------------------------------------------------------------------------- /src/soda.old.js: -------------------------------------------------------------------------------- 1 | /** 2 | * sodajs v0.4.4 by dorsywang 3 | * Light weight but powerful template engine for JavaScript 4 | * Github: https://github.com/AlloyTeam/sodajs 5 | * MIT License 6 | */ 7 | 8 | ; 9 | (function() { 10 | var document, isBrowser; 11 | if (typeof require === "function" && typeof window === 'undefined') { 12 | var NodeWindow = require('nodewindow'); 13 | var nodeWindow = new NodeWindow(); 14 | 15 | var win = nodeWindow.runHTML("", {}, {}); 16 | 17 | document = win.document; 18 | isBrowser = false; 19 | } else { 20 | document = window.document; 21 | isBrowser = true; 22 | } 23 | 24 | if (!Array.prototype.map) { 25 | Array.prototype.map = function(func) { 26 | var arr = []; 27 | for (var i = 0; i < this.length; i++) { 28 | var item = this[i]; 29 | 30 | [].push(func && func.call(item, item, i)); 31 | } 32 | 33 | return arr; 34 | }; 35 | } 36 | 37 | if (!Array.prototype.forEach) { 38 | Array.prototype.forEach = function(callback) { 39 | var T, k; 40 | if (this == null) { 41 | throw new TypeError('this is null or not defined'); 42 | } 43 | var O = Object(this); 44 | var len = O.length >>> 0; 45 | if (typeof callback !== 'function') { 46 | throw new TypeError(callback + ' is not a function'); 47 | } 48 | if (arguments.length > 1) { 49 | T = arguments[1]; 50 | } 51 | k = 0; 52 | while (k < len) { 53 | var kValue; 54 | if (k in O) { 55 | kValue = O[k]; 56 | callback.call(T, kValue, k, O); 57 | } 58 | k++; 59 | } 60 | }; 61 | } 62 | 63 | if (!String.prototype.trim) { 64 | String.prototype.trim = function() { 65 | return this.replace(/^\s*|\s*$/g, ''); 66 | }; 67 | } 68 | 69 | 70 | var nodes2Arr = function(nodes) { 71 | var arr = []; 72 | 73 | for (var i = 0; i < nodes.length; i++) { 74 | arr.push(nodes[i]); 75 | } 76 | 77 | return arr; 78 | }; 79 | 80 | var valueoutReg = /\{\{([^\}]*)\}\}/g; 81 | 82 | var prefix = 'soda'; 83 | var prefixReg = new RegExp('^' + prefix + '-'); 84 | 85 | var classNameRegExp = function(className) { 86 | return new RegExp('(^|\\s+)' + className + '(\\s+|$)', 'g'); 87 | }; 88 | 89 | var addClass = function(el, className) { 90 | if (!el.className) { 91 | el.className = className; 92 | 93 | return; 94 | } 95 | 96 | if (el.className.match(classNameRegExp(className))) {} else { 97 | el.className += " " + className; 98 | } 99 | }; 100 | 101 | var removeClass = function(el, className) { 102 | el.className = el.className.replace(classNameRegExp(className), ""); 103 | }; 104 | 105 | var getValue = function(_data, _attrStr) { 106 | CONST_REGG.lastIndex = 0; 107 | var realAttrStr = _attrStr.replace(CONST_REGG, function(r) { 108 | if (typeof _data[r] === "undefined") { 109 | return r; 110 | } else { 111 | return _data[r]; 112 | } 113 | }); 114 | 115 | if (_attrStr === 'true') { 116 | return true; 117 | } 118 | 119 | if (_attrStr === 'false') { 120 | return false; 121 | } 122 | 123 | var _getValue = function(data, attrStr) { 124 | var dotIndex = attrStr.indexOf("."); 125 | 126 | if (dotIndex > -1) { 127 | var attr = attrStr.substr(0, dotIndex); 128 | attrStr = attrStr.substr(dotIndex + 1); 129 | 130 | // 检查attrStr是否属于变量并转换 131 | if (typeof _data[attr] !== "undefined" && CONST_REG.test(attr)) { 132 | attr = _data[attr]; 133 | } 134 | 135 | if (typeof data[attr] !== "undefined") { 136 | return _getValue(data[attr], attrStr); 137 | } else { 138 | var eventData = { 139 | name: realAttrStr, 140 | data: _data 141 | }; 142 | 143 | triggerEvent("nullvalue", { 144 | type: "nullattr", 145 | data: eventData 146 | }, eventData); 147 | 148 | // 如果还有 149 | return ""; 150 | } 151 | } else { 152 | 153 | // 检查attrStr是否属于变量并转换 154 | if (typeof _data[attrStr] !== "undefined" && CONST_REG.test(attrStr)) { 155 | attrStr = _data[attrStr]; 156 | } 157 | 158 | var rValue; 159 | if (typeof data[attrStr] !== "undefined") { 160 | rValue = data[attrStr]; 161 | } else { 162 | var eventData = { 163 | name: realAttrStr, 164 | data: _data 165 | }; 166 | 167 | triggerEvent("nullvalue", { 168 | type: "nullvalue", 169 | data: eventData 170 | }, eventData); 171 | 172 | rValue = attrStr; 173 | } 174 | 175 | return rValue; 176 | } 177 | }; 178 | 179 | return _getValue(_data, _attrStr); 180 | }; 181 | 182 | // 注释node 183 | var commentNode = function(node) {}; 184 | 185 | // 标识符 186 | var IDENTOR_REG = /[a-zA-Z_\$]+[\w\$]*/g; 187 | var STRING_REG = /"([^"]*)"|'([^']*)'/g 188 | var NUMBER_REG = /\d+|\d*\.\d+/g; 189 | 190 | var OBJECT_REG = /[a-zA-Z_\$]+[\w\$]*(?:\s*\.\s*(?:[a-zA-Z_\$]+[\w\$]*|\d+))*/g; 191 | // 非global 做test用 192 | var OBJECT_REG_NG = /[a-zA-Z_\$]+[\w\$]*(?:\s*\.\s*(?:[a-zA-Z_\$]+[\w\$]*|\d+))*/; 193 | 194 | var ATTR_REG = /\[([^\[\]]*)\]/g; 195 | var ATTR_REG_NG = /\[([^\[\]]*)\]/; 196 | var ATTR_REG_DOT = /\.([a-zA-Z_\$]+[\w\$]*)/g; 197 | 198 | var NOT_ATTR_REG = /[^\.|]([a-zA-Z_\$]+[\w\$]*)/g; 199 | 200 | var OR_REG = /\|\|/g; 201 | 202 | var OR_REPLACE = "OR_OPERATOR\x1E"; 203 | 204 | var getRandom = function() { 205 | return "$$" + ~~(Math.random() * 1E6); 206 | }; 207 | 208 | var CONST_PRIFIX = "_$C$_"; 209 | var CONST_REG = /^_\$C\$_/; 210 | var CONST_REGG = /_\$C\$_[^\.]+/g; 211 | 212 | var getAttrVarKey = function() { 213 | return CONST_PRIFIX + ~~(Math.random() * 1E6); 214 | }; 215 | 216 | var parseSodaExpression = function(str, scope) { 217 | // 对filter进行处理 218 | str = str.replace(OR_REG, OR_REPLACE).split("|"); 219 | 220 | for (var i = 0; i < str.length; i++) { 221 | str[i] = (str[i].replace(new RegExp(OR_REPLACE, 'g'), "||") || '').trim(); 222 | } 223 | 224 | var expr = str[0] || ""; 225 | var filters = str.slice(1); 226 | 227 | // 将字符常量保存下来 228 | expr = expr.replace(STRING_REG, function(r, $1, $2) { 229 | var key = getRandom(); 230 | scope[key] = $1 || $2; 231 | return key; 232 | }); 233 | 234 | while (ATTR_REG_NG.test(expr)) { 235 | ATTR_REG.lastIndex = 0; 236 | 237 | //对expr预处理 238 | expr = expr.replace(ATTR_REG, function(r, $1) { 239 | var key = getAttrVarKey(); 240 | // 属性名称为字符常量 241 | var attrName = parseSodaExpression($1, scope); 242 | 243 | // 给一个特殊的前缀 表示是属性变量 244 | 245 | scope[key] = attrName; 246 | 247 | return "." + key; 248 | }); 249 | } 250 | 251 | expr = expr.replace(OBJECT_REG, function(value) { 252 | return "getValue(scope,'" + value.trim() + "')"; 253 | }); 254 | 255 | 256 | var parseFilter = function() { 257 | var filterExpr = filters.shift(); 258 | 259 | if (!filterExpr) { 260 | return; 261 | } 262 | 263 | var filterExpr = filterExpr.split(":"); 264 | var args = filterExpr.slice(1) || []; 265 | var name = filterExpr[0] || ""; 266 | 267 | var stringReg = /^'.*'$|^".*"$/; 268 | for (var i = 0; i < args.length; i++) { 269 | //这里根据类型进行判断 270 | if (OBJECT_REG_NG.test(args[i])) { 271 | args[i] = "getValue(scope,'" + args[i].replace(/^'(.*)'$|^"(.*)"$/g, "$1") + "')"; 272 | } else {} 273 | } 274 | 275 | if (sodaFilterMap[name]) { 276 | args.unshift(expr); 277 | 278 | args = args.join(","); 279 | 280 | expr = "sodaFilterMap['" + name + "'](" + args + ")"; 281 | } 282 | 283 | parseFilter(); 284 | }; 285 | 286 | parseFilter(); 287 | 288 | var evalFunc = new Function("getValue", "sodaFilterMap", "return function sodaExp(scope){ return " + expr + "}")(getValue, sodaFilterMap); 289 | 290 | return evalFunc(scope); 291 | }; 292 | 293 | var hashTable = { 294 | id2Expression: {}, 295 | 296 | expression2id: {}, 297 | 298 | getRandId: function() { 299 | return 'soda' + ~~(Math.random() * 1E5); 300 | } 301 | }; 302 | 303 | // 解析指令 304 | // 解析attr 305 | var compileNode = function(node, scope) { 306 | // 如果只是文本 307 | if (node.nodeType === 3) { 308 | node.nodeValue = node.nodeValue.replace(valueoutReg, function(item, $1) { 309 | /* 310 | var id = hashTable.getRandId(); 311 | 312 | hashTable.id2Expression[id] = { 313 | expression: $1, 314 | el: child 315 | }; 316 | 317 | hashTable.expression2id[$1] = { 318 | id: id, 319 | el: child 320 | }; 321 | */ 322 | 323 | var value = parseSodaExpression($1, scope); 324 | if (typeof value === "object") { 325 | value = JSON.stringify(value, null, 2) 326 | } 327 | return value; 328 | }); 329 | } 330 | 331 | if (node.attributes) { 332 | // 指令处理 333 | sodaDirectiveArr.map(function(item) { 334 | var name = item.name; 335 | 336 | var opt = item.opt; 337 | 338 | if (node.getAttribute(name) && node.parentNode) { 339 | opt.link(scope, node, node.attributes); 340 | } 341 | }); 342 | 343 | // 处理输出 包含 prefix-* 344 | [].map.call(node.attributes, function(attr) { 345 | // 如果dirctiveMap有的就跳过不再处理 346 | if (!sodaDirectiveMap[attr.name]) { 347 | if (prefixReg.test(attr.name)) { 348 | var attrName = attr.name.replace(prefixReg, ''); 349 | 350 | if (attrName) { 351 | if (attr.value) { 352 | var attrValue = attr.value.replace(valueoutReg, function(item, $1) { 353 | return parseSodaExpression($1, scope); 354 | }); 355 | 356 | node.setAttribute(attrName, attrValue); 357 | } 358 | } 359 | 360 | // 对其他属性里含expr 处理 361 | } else { 362 | if (attr.value) { 363 | attr.value = attr.value.replace(valueoutReg, function(item, $1) { 364 | return parseSodaExpression($1, scope); 365 | }); 366 | } 367 | } 368 | } 369 | }); 370 | 371 | } 372 | 373 | nodes2Arr(node.childNodes).map(function(child) { 374 | compileNode(child, scope); 375 | }); 376 | }; 377 | 378 | var sodaDirectiveMap = {}; 379 | 380 | var sodaFilterMap = {}; 381 | 382 | var sodaDirectiveArr = []; 383 | 384 | var sodaDirective = function(name, func) { 385 | var name = prefix + '-' + name; 386 | sodaDirectiveMap[name] = func(); 387 | 388 | sodaDirectiveArr.push({ 389 | name: name, 390 | opt: sodaDirectiveMap[name] 391 | }); 392 | }; 393 | 394 | var sodaFilter = function(name, func) { 395 | sodaFilterMap[name] = func; 396 | }; 397 | 398 | sodaFilter.get = function(name) { 399 | return sodaFilterMap[name]; 400 | }; 401 | 402 | sodaFilter("date", function(input, lenth) { 403 | return lenth; 404 | }); 405 | 406 | sodaDirective('repeat', function() { 407 | return { 408 | priority: 10, 409 | compile: function(scope, el, attrs) { 410 | 411 | }, 412 | link: function(scope, el, attrs) { 413 | var opt = el.getAttribute(prefix + '-repeat'); 414 | var itemName; 415 | var valueName; 416 | 417 | var trackReg = /\s+by\s+([^\s]+)$/; 418 | 419 | var trackName; 420 | opt = opt.replace(trackReg, function(item, $1) { 421 | if ($1) { 422 | trackName = ($1 || '').trim(); 423 | } 424 | 425 | return ''; 426 | }); 427 | 428 | 429 | var inReg = /([^\s]+)\s+in\s+([^\s]+)|\(([^,]+)\s*,\s*([^)]+)\)\s+in\s+([^\s]+)/; 430 | 431 | var r = inReg.exec(opt); 432 | if (r) { 433 | if (r[1] && r[2]) { 434 | itemName = (r[1] || '').trim(); 435 | valueName = (r[2] || '').trim(); 436 | 437 | if (!(itemName && valueName)) { 438 | return; 439 | } 440 | } else if (r[3] && r[4] && r[5]) { 441 | trackName = (r[3] || '').trim(); 442 | itemName = (r[4] || '').trim(); 443 | valueName = (r[5] || '').trim(); 444 | } 445 | } else { 446 | return; 447 | } 448 | 449 | trackName = trackName || '$index'; 450 | 451 | // 这里要处理一下 452 | var repeatObj = getValue(scope, valueName) || []; 453 | 454 | var repeatFunc = function(i) { 455 | var itemNode = el.cloneNode(true); 456 | 457 | // 这里创建一个新的scope 458 | var itemScope = {}; 459 | itemScope[trackName] = i; 460 | 461 | itemScope[itemName] = repeatObj[i]; 462 | 463 | itemScope.__proto__ = scope; 464 | 465 | // REMOVE cjd6568358 466 | // itemNode.removeAttribute(prefix + '-repeat'); 467 | 468 | el.parentNode.insertBefore(itemNode, el); 469 | 470 | // 这里是新加的dom, 要单独编译 471 | compileNode(itemNode, itemScope); 472 | 473 | }; 474 | 475 | // ADD cjd6568358 476 | // 提前移除当前节点指令,否则Node端会重复渲染 477 | el.removeAttribute(prefix + '-repeat'); 478 | 479 | if ('length' in repeatObj) { 480 | for (var i = 0; i < repeatObj.length; i++) { 481 | repeatFunc(i); 482 | } 483 | } else { 484 | for (var i in repeatObj) { 485 | if (repeatObj.hasOwnProperty(i)) { 486 | repeatFunc(i); 487 | } 488 | } 489 | } 490 | 491 | el.parentNode.removeChild(el); 492 | 493 | } 494 | }; 495 | }); 496 | 497 | sodaDirective('if', function() { 498 | return { 499 | priority: 9, 500 | link: function(scope, el, attrs) { 501 | var opt = el.getAttribute(prefix + '-if'); 502 | 503 | var expressFunc = parseSodaExpression(opt, scope); 504 | 505 | if (expressFunc) {} else { 506 | el.parentNode && el.parentNode.removeChild(el); 507 | } 508 | } 509 | }; 510 | }); 511 | 512 | sodaDirective('class', function() { 513 | return { 514 | link: function(scope, el, attrs) { 515 | var opt = el.getAttribute(prefix + "-class"); 516 | 517 | var expressFunc = parseSodaExpression(opt, scope); 518 | 519 | if (expressFunc) { 520 | addClass(el, expressFunc); 521 | } else {} 522 | } 523 | }; 524 | }); 525 | 526 | sodaDirective('src', function() { 527 | return { 528 | link: function(scope, el, attrs) { 529 | var opt = el.getAttribute(prefix + "-src"); 530 | 531 | var expressFunc = opt.replace(valueoutReg, function(item, $1) { 532 | return parseSodaExpression($1, scope); 533 | }); 534 | 535 | if (expressFunc) { 536 | el.setAttribute("src", expressFunc); 537 | } else {} 538 | } 539 | }; 540 | }); 541 | 542 | sodaDirective('bind-html', function() { 543 | return { 544 | link: function(scope, el, attrs) { 545 | var opt = el.getAttribute(prefix + "-bind-html"); 546 | var expressFunc = parseSodaExpression(opt, scope); 547 | 548 | if (expressFunc) { 549 | el.innerHTML = expressFunc; 550 | 551 | return { 552 | command: "childDone" 553 | }; 554 | } 555 | } 556 | }; 557 | }); 558 | 559 | sodaDirective('html', function() { 560 | return { 561 | link: function(scope, el, attrs) { 562 | var opt = el.getAttribute(prefix + "-html"); 563 | var expressFunc = parseSodaExpression(opt, scope); 564 | 565 | if (expressFunc) { 566 | el.innerHTML = expressFunc; 567 | 568 | return { 569 | command: "childDone" 570 | }; 571 | } 572 | } 573 | }; 574 | }); 575 | 576 | sodaDirective('replace', function() { 577 | return { 578 | link: function(scope, el, attrs) { 579 | var opt = el.getAttribute(prefix + "-replace"); 580 | var expressFunc = parseSodaExpression(opt, scope); 581 | 582 | if (expressFunc) { 583 | var div = document.createElement('div'); 584 | div.innerHTML = expressFunc; 585 | 586 | if (el.parentNode) { 587 | while (div.childNodes[0]) { 588 | el.parentNode.insertBefore(div.childNodes[0], el); 589 | } 590 | } 591 | } 592 | 593 | el.parentNode.removeChild(el); 594 | } 595 | }; 596 | }); 597 | 598 | sodaDirective('include', function() { 599 | return { 600 | priority: 8, 601 | link: function(scope, el, attrs) { 602 | var opt = el.getAttribute(prefix + "-include"); 603 | var template = ""; 604 | if (isBrowser) { 605 | // browser 606 | template = sodaRender.browserTemplates[opt] || window.templates[opt] || ""; 607 | // el.outerHTML = sodaRender(template, scope); 608 | } else { 609 | // Node 610 | template = require("fs").readFileSync(require("path").resolve(sodaRender.templateDir, opt), "utf-8"); 611 | // el.innerHTML = sodaRender(template, scope); 612 | // // 修复head标签中使用include,include代码中ng-if指令失效BUG 613 | // [].forEach.call(el.childNodes, function (item, index) { 614 | // el.parentNode.insertBefore(item, el); 615 | // }) 616 | // el.parentNode.removeChild(el); 617 | } 618 | el.outerHTML = sodaRender(template, scope); 619 | return { 620 | command: "childDone" 621 | }; 622 | } 623 | }; 624 | }); 625 | 626 | sodaDirective("style", function() { 627 | return { 628 | link: function(scope, el, attrs) { 629 | var opt = el.getAttribute(prefix + "-style"); 630 | var expressFunc = parseSodaExpression(opt, scope); 631 | 632 | var getCssValue = function(name, value) { 633 | var numberWithoutpx = /opacity|z-index/; 634 | if (numberWithoutpx.test(name)) { 635 | return parseFloat(value); 636 | } 637 | 638 | if (isNaN(value)) { 639 | return value; 640 | } else { 641 | return value + "px"; 642 | } 643 | }; 644 | 645 | if (expressFunc) { 646 | var stylelist = []; 647 | 648 | for (var i in expressFunc) { 649 | if (expressFunc.hasOwnProperty(i)) { 650 | var provalue = getCssValue(i, expressFunc[i]); 651 | 652 | stylelist.push([i, provalue].join(":")); 653 | } 654 | } 655 | 656 | var style = el.style; 657 | for (var i = 0; i < style.length; i++) { 658 | var name = style[i]; 659 | if (expressFunc[name]) {} else { 660 | stylelist.push([name, style[name]].join(":")); 661 | } 662 | } 663 | 664 | var styleStr = stylelist.join(";"); 665 | 666 | el.setAttribute("style", styleStr); 667 | } 668 | } 669 | }; 670 | }); 671 | 672 | var sodaRender = function(str, data) { 673 | //console.log( + new Date() - data.t1); 674 | // 对directive进行排序 675 | sodaDirectiveArr.sort(function(b, a) { 676 | return (Number(a.opt.priority || 0) - Number(b.opt.priority || 0)); 677 | }); 678 | 679 | //console.log(sodaDirectiveArr); 680 | 681 | // 解析模板DOM 682 | var div = document.createElement("div"); 683 | 684 | // 必须加入到body中去,不然自定义标签不生效 685 | if (document.documentMode < 9) { 686 | div.style.display = 'none'; 687 | document.body.appendChild(div); 688 | } 689 | div.innerHTML = str; 690 | 691 | nodes2Arr(div.childNodes).map(function(child) { 692 | compileNode(child, data); 693 | }); 694 | 695 | var innerHTML = div.innerHTML; 696 | if (document.documentMode < 9) { 697 | document.body.removeChild(div); 698 | } 699 | 700 | return innerHTML; 701 | 702 | // var frament = document.createDocumentFragment(); 703 | // frament.innerHTML = div.innerHTML; 704 | 705 | /* 706 | frament.update = function(newData){ 707 | //checkingDirtyData(data, d); 708 | var diff = DeepDiff.noConflict(); 709 | 710 | var diffResult = diff(data, newData); 711 | 712 | console.log(diffResult); 713 | 714 | var dirtyData = ['a']; 715 | 716 | for(var i = 0; i < dirtyData.length; i ++){ 717 | var item = dirtyData[i]; 718 | 719 | var id = hashTable.expression2id[item]; 720 | 721 | var nowValue = parseSodaExpression(item, newData); 722 | //console.log(nowValue); 723 | 724 | if(id.el){ 725 | id.el.nodeValue = nowValue; 726 | } 727 | } 728 | 729 | console.log(hashTable); 730 | 731 | 732 | }; 733 | */ 734 | 735 | var child; 736 | while (child = div.childNodes[0]) { 737 | frament.appendChild(child); 738 | } 739 | 740 | 741 | return frament; 742 | }; 743 | 744 | var eventPool = {}; 745 | sodaRender.addEventListener = function(type, func) { 746 | if (eventPool[type]) {} else { 747 | eventPool[type] = []; 748 | } 749 | 750 | eventPool[type].push(func); 751 | }; 752 | 753 | var triggerEvent = function(type, e, data) { 754 | var events = eventPool[type] || []; 755 | 756 | for (var i = 0; i < events.length; i++) { 757 | var eventFunc = events[i]; 758 | eventFunc && eventFunc(e, data); 759 | } 760 | }; 761 | 762 | sodaRender.filter = sodaFilter; 763 | // ADD cjd6568358 764 | sodaRender.templateDir = ""; //服务端模版目录(用于include指令) 765 | sodaRender.browserTemplates = null; //浏览器端模版缓存对象(用于include指令) 766 | 767 | sodaRender.prefix = function(newPrefix) { 768 | for (var key in sodaDirectiveMap) { 769 | if (sodaDirectiveMap.hasOwnProperty(key)) { 770 | sodaDirectiveMap[key.replace(prefix, newPrefix)] = sodaDirectiveMap[key]; 771 | delete sodaDirectiveMap[key]; 772 | } 773 | } 774 | 775 | var i = 0, 776 | len = sodaDirectiveArr.length; 777 | for (; i < len; i++) { 778 | sodaDirectiveArr[i].name = sodaDirectiveArr[i].name.replace(prefix, newPrefix); 779 | } 780 | 781 | prefix = newPrefix 782 | prefixReg = new RegExp('^' + prefix + '-') 783 | } 784 | 785 | if (typeof exports === 'object' && typeof module === 'object') 786 | module.exports = sodaRender; 787 | else if (typeof define === 'function' && define.amd) 788 | define([], function() { 789 | return sodaRender; 790 | }); 791 | else if (typeof exports === 'object') 792 | exports["soda"] = sodaRender; 793 | else 794 | window.soda = sodaRender; 795 | 796 | // 监听数据异常情况 797 | })(); 798 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | import {CONST_PRIFIX} from "./const"; 2 | export let getAttrVarKey = function() { 3 | return CONST_PRIFIX + ~~(Math.random() * 1E6); 4 | }; 5 | 6 | export let getRandom = function() { 7 | return "$$" + ~~(Math.random() * 1E6); 8 | }; 9 | 10 | export let exist = function(value){ 11 | return value !== null && value !== undefined && value !== "" && typeof value !== 'undefined'; 12 | }; 13 | 14 | export let nodes2Arr = function(nodes) { 15 | var arr = []; 16 | 17 | for (var i = 0; i < nodes.length; i++) { 18 | arr.push(nodes[i]); 19 | } 20 | 21 | return arr; 22 | }; 23 | 24 | let getOwnPropertySymbols = Object.getOwnPropertySymbols; 25 | let hasOwnProperty = Object.prototype.hasOwnProperty; 26 | let propIsEnumerable = Object.prototype.propertyIsEnumerable; 27 | 28 | let toObject = function(val) { 29 | if (val === null || val === undefined) { 30 | throw new TypeError('Object.assign cannot be called with null or undefined'); 31 | } 32 | 33 | return Object(val); 34 | } 35 | 36 | 37 | export let assign = Object.assign || function (target, source) { 38 | var from; 39 | var to = toObject(target); 40 | var symbols; 41 | 42 | for (var s = 1; s < arguments.length; s++) { 43 | from = Object(arguments[s]); 44 | 45 | for (var key in from) { 46 | if (hasOwnProperty.call(from, key)) { 47 | to[key] = from[key]; 48 | } 49 | } 50 | 51 | if (getOwnPropertySymbols) { 52 | symbols = getOwnPropertySymbols(from); 53 | for (var i = 0; i < symbols.length; i++) { 54 | if (propIsEnumerable.call(from, symbols[i])) { 55 | to[symbols[i]] = from[symbols[i]]; 56 | } 57 | } 58 | } 59 | } 60 | 61 | return to; 62 | }; 63 | 64 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var expect = chai.expect; 3 | var assert = chai.assert; 4 | 5 | //var soda = require('./../node'); 6 | var soda = require('./../node'); 7 | //} 8 | /* 9 | if(typeof describe === 'undefined'){ 10 | var describe = function(name, func){ 11 | console.log(name); 12 | 13 | func && func(); 14 | }; 15 | 16 | var it = function(name, func){ 17 | console.log(name); 18 | func && func(); 19 | }; 20 | } 21 | */ 22 | 23 | 24 | describe('Output', function() { 25 | it('recovery', function(){ 26 | var html1 = ` 27 |
asdfasdf
28 | `; 29 | 30 | var html2 = `1`; 31 | 32 | 33 | assert.equal(soda(html1), html1); 34 | assert.equal(soda(html2), html2); 35 | }); 36 | 37 | it('script tag output', function(){ 38 | var html1 = `
{{a}}
`; 39 | 40 | assert.equal(soda(html1, { 41 | a: '1>2' 42 | }), `
1>2
`); 43 | }); 44 | 45 | 46 | it('plain', function() { 47 | assert.equal( 48 | soda('{{a}}', {a: 1}), 49 | '1' 50 | ); 51 | 52 | assert.equal( 53 | soda('{{a + 1}}', {a: 1}), 54 | '2' 55 | ); 56 | 57 | assert.equal( 58 | soda('{{1}}', {}), 59 | '1' 60 | ); 61 | 62 | assert.equal( 63 | soda('{{1 + 3}}', {}), 64 | '4' 65 | ); 66 | 67 | assert.equal( 68 | soda('{{1 + "1"}}', {}), 69 | '11' 70 | ); 71 | 72 | assert.equal( 73 | soda('{{true}}', {}), 74 | 'true' 75 | ); 76 | 77 | assert.equal( 78 | soda('{{false}}', {}), 79 | 'false' 80 | ); 81 | 82 | assert.equal( 83 | soda('{{true ? 1 : 2}}', {}), 84 | '1' 85 | ); 86 | 87 | assert.equal( 88 | soda('{{false ? 1 : 2}}', {}), 89 | '2' 90 | ); 91 | 92 | assert.equal( 93 | soda('{{0 ? 1 : 2}}', {}), 94 | '2' 95 | ); 96 | 97 | assert.equal( 98 | soda("{{'0' ? 1 : 2}}", {}), 99 | '1' 100 | ); 101 | 102 | assert.equal( 103 | soda('{{3 > 2 && "foo"}}', {}), 104 | 'foo' 105 | ); 106 | 107 | }); 108 | 109 | 110 | it('property', function() { 111 | var data = { 112 | a: { 113 | b: '1' 114 | }, 115 | 116 | list: [ 117 | { title: 1} 118 | ] 119 | }; 120 | 121 | assert.equal( 122 | soda('{{a.b}}', data), 123 | '1' 124 | ); 125 | 126 | assert.equal( 127 | soda('{{a.c}}', data), 128 | '' 129 | ); 130 | 131 | assert.equal( 132 | soda('{{list[0].title}}', data), 133 | '1' 134 | ); 135 | 136 | assert.equal( 137 | soda('{{list[0]["title"]}}', data), 138 | '1' 139 | ); 140 | 141 | }); 142 | 143 | it('safe output', function() { 144 | var data = { 145 | b: null, 146 | c: undefined, 147 | d: 0, 148 | e: '' 149 | }; 150 | 151 | assert.equal( 152 | soda(`{{b.a}}`, data), 153 | '' 154 | ); 155 | 156 | assert.equal( 157 | soda(`{{c.a}}`, data), 158 | '' 159 | ); 160 | 161 | assert.equal( 162 | soda(`{{d.a}}`, data), 163 | '' 164 | ); 165 | 166 | assert.equal( 167 | soda(`{{e.a}}`, data), 168 | '' 169 | ); 170 | 171 | 172 | }); 173 | 174 | it('prototype output', function() { 175 | var data = { 176 | test: 'test', 177 | list: [ 178 | {} 179 | ] 180 | }; 181 | 182 | 183 | assert.equal( 184 | soda(`{{test}}`, data), 185 | 'test' 186 | ); 187 | }); 188 | 189 | it('complex output', function(){ 190 | var data = { 191 | list: [ 192 | {list: [{'title': '<>aa'}, {'title': 'bb'}], name: 0, show: 1}, 193 | {list: [{'title': 0 }, {'title': 'bb'}], name: 'b'}, 194 | {list: [{'title': 'aa'}, {'title': 'bb'}], name: 'b'}, 195 | {list: [{'title': 'aa'}, {'title': 'bb'}], name: 'b'}, 196 | {list: [{'title': 'aa'}, {'title': 'bb'}], name: 'b'}, 197 | {list: [{'title': 'aa'}, {'title': 'bb'}], name: 'b'}, 198 | {list: [{'title': 'aa'}, {'title': 'bb'}], name: 'c'} 199 | ] 200 | }; 201 | 202 | assert.equal( 203 | soda('{{list[list[0].show === 1 ? list[0].name : 1].list[0].title}}', data), 204 | '<>aa</h1>' 205 | ); 206 | }); 207 | 208 | it('attr output', function(){ 209 | var data = { 210 | a: 1, 211 | b: 2 212 | }; 213 | 214 | assert.equal( 215 | soda('
', data), 216 | '
' 217 | ); 218 | 219 | assert.equal( 220 | soda(`a`, data), 221 | 'a' 222 | ); 223 | 224 | assert.equal( 225 | soda(``, data), 226 | '' 227 | ); 228 | 229 | assert.equal( 230 | soda(``, data), 231 | '' 232 | ); 233 | 234 | assert.equal( 235 | soda(``, data), 236 | '' 237 | ); 238 | 239 | assert.equal( 240 | soda(``, data), 241 | '' 242 | ); 243 | 244 | assert.equal( 245 | soda(``, data), 246 | '' 247 | ); 248 | 249 | }); 250 | 251 | 252 | }); 253 | 254 | 255 | describe('Directives', function() { 256 | it('repeat', function(){ 257 | var data = { 258 | list: [ 259 | {name: 'a'}, 260 | {name: 'b'} 261 | ], 262 | 263 | trackObject: { 264 | a: 1, 265 | b: '2' 266 | } 267 | }; 268 | 269 | 270 | assert.equal( 271 | soda(`{{$index}}{{item.name}}`, data), 272 | '0a1b' 273 | ); 274 | 275 | assert.equal( 276 | soda(`{{i}}{{item.name}}`, data), 277 | '0a1b' 278 | ); 279 | 280 | assert.equal( 281 | soda(`{{i}}{{item.name}}`, data), 282 | '0a1b' 283 | ); 284 | 285 | assert.equal( 286 | soda(`{{key}}{{value}}`, data), 287 | 'a1b2' 288 | ); 289 | 290 | assert.equal( 291 | soda(`{{$index}}{{item}}`, data), 292 | 'a1b2' 293 | ); 294 | 295 | }); 296 | 297 | it('if', function(){ 298 | assert.equal( 299 | soda(`12`, {}), 300 | '1' 301 | ); 302 | 303 | assert.equal( 304 | soda(`34`, {}), 305 | '3' 306 | ); 307 | }); 308 | 309 | 310 | it('html', function(){ 311 | var data = { 312 | html: '
a
' 313 | }; 314 | 315 | assert.equal( 316 | soda(`1`, data), 317 | "" + data.html + "" 318 | ); 319 | }); 320 | 321 | 322 | it('style', function(){ 323 | var data = { 324 | style: { 325 | width: 100, 326 | height: 100, 327 | opacity: 0.4 328 | } 329 | }; 330 | 331 | assert.equal( 332 | soda(`1`, data), 333 | `1` 334 | ); 335 | }); 336 | 337 | it('replace', function(){ 338 | var data = { 339 | html: '
a
' 340 | }; 341 | 342 | assert.equal( 343 | soda(`1`, data), 344 | data.html 345 | ); 346 | }); 347 | 348 | it('include', function(){ 349 | var data = { 350 | name: "dorsy" 351 | }; 352 | 353 | soda.discribe('list', `

{{name}}

`); 354 | 355 | assert.equal( 356 | soda(`1`, data), 357 | `

dorsy

` 358 | ); 359 | 360 | // static 361 | soda.discribe('list-static', `

{{name}}

`, { compile: false }); 362 | 363 | assert.equal( 364 | soda(`1`, data), 365 | `

{{name}}

` 366 | ); 367 | 368 | soda.discribe('list2', function(arg){ 369 | return `

{{name}}

`; 370 | }); 371 | 372 | 373 | assert.equal( 374 | soda(`1`, data), 375 | `

dorsy

` 376 | ); 377 | 378 | 379 | soda.discribe('list3', function(arg){ 380 | return `

{{name}}${arg}

`; 381 | }); 382 | 383 | assert.equal( 384 | soda(`1`, data), 385 | `

dorsysubnamename1

` 386 | ); 387 | }); 388 | }); 389 | 390 | describe('filter', function() { 391 | it('no arguments', function(){ 392 | soda.filter('add', function(input){ 393 | return input + 2; 394 | }); 395 | 396 | assert.equal( 397 | soda(`{{2|add}}`, {}), 398 | 4 399 | ); 400 | 401 | assert.equal( 402 | soda(`{{2 | add}}`, {}), 403 | 4 404 | ); 405 | }); 406 | 407 | 408 | it('with arguments', function(){ 409 | soda.filter('addnum', function(input, num){ 410 | return input + num; 411 | }); 412 | 413 | soda.filter('addargs', function(input, one, two){ 414 | return input + one + two; 415 | }); 416 | 417 | 418 | assert.equal( 419 | soda(`{{2|addnum:2}}`, {}), 420 | 4 421 | ); 422 | 423 | assert.equal( 424 | soda(`{{2 | addnum : 3}}`, {}), 425 | 5 426 | ); 427 | 428 | assert.equal( 429 | soda(`{{2 | addnum : '3'}}`, {}), 430 | '23' 431 | ); 432 | 433 | assert.equal( 434 | soda(`{{2 | addargs : '3' : 'YYYY-MMMM-dd'}}`, {}), 435 | '23YYYY-MMMM-dd' 436 | ); 437 | 438 | 439 | assert.equal( 440 | soda(`{{2 | addnum : 3 | addargs : '3' : 'YYYY-MMMM-dd'}}`, {}), 441 | '53YYYY-MMMM-dd' 442 | ); 443 | }); 444 | }); 445 | 446 | describe('prefix', function() { 447 | it('change prefix to v:', function(){ 448 | soda.prefix('v:'); 449 | 450 | assert.equal( 451 | soda(`12`, {}), 452 | '1' 453 | ); 454 | 455 | assert.equal( 456 | soda(`34`, {}), 457 | '3' 458 | ); 459 | }); 460 | 461 | 462 | }); 463 | -------------------------------------------------------------------------------- /test/soda-browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 16 | 17 | 18 | 66 | 67 |
68 | 69 | 70 | 71 | 72 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /test/soda-mocha.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/soda.node-mocha.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var UglifyJSPlugin = require('uglifyjs-webpack-plugin'); 3 | var es3ifyPlugin = require('es3ify-webpack-plugin'); 4 | 5 | var ENV = process.env.npm_lifecycle_event; 6 | 7 | var config = { 8 | entry: { 9 | 'soda': './src/index.js' 10 | }, 11 | output: { 12 | filename: '[name].js', 13 | library: 'soda', 14 | path: path.resolve('./dist'), 15 | libraryTarget: 'umd' 16 | }, 17 | 18 | module: { 19 | loaders: [ 20 | { test: /\.js$/, loader: "babel-loader" } 21 | ] 22 | }, 23 | 24 | node: { 25 | fs: "empty" 26 | }, 27 | 28 | resolve:{ 29 | alias: { 30 | } 31 | } 32 | }; 33 | 34 | switch(ENV){ 35 | // soda 36 | case 'build-uncom': 37 | config = Object.assign(config, { 38 | plugins: [ 39 | new es3ifyPlugin() 40 | ] 41 | 42 | }); 43 | break; 44 | 45 | //soda.min 46 | case 'build-min': 47 | config = Object.assign(config, { 48 | entry: { 49 | 'soda.min': './src/index.js' 50 | }, 51 | 52 | plugins: [ 53 | new es3ifyPlugin(), 54 | new UglifyJSPlugin({ 55 | mangle: { 56 | screw_ie8: false 57 | }, 58 | mangleProperties: { 59 | screw_ie8: false, 60 | //ignore_quoted: true // do not mangle quoted properties and object keys 61 | }, 62 | compress: { 63 | screw_ie8: false, 64 | //properties: false // optional: don't convert foo["bar"] to foo.bar 65 | }, 66 | output: { 67 | screw_ie8: false 68 | } 69 | 70 | }) 71 | ] 72 | 73 | }); 74 | 75 | break; 76 | 77 | 78 | // soda-all.js 79 | case 'build-node-uncom': 80 | config = Object.assign(config, { 81 | entry: { 82 | 'soda.node': './node/index.js' 83 | }, 84 | 85 | plugins: [ 86 | ] 87 | 88 | }); 89 | 90 | break; 91 | 92 | // soda-all.min 93 | case 'build-node-min': 94 | config = Object.assign(config, { 95 | entry: { 96 | 'soda.node.min': './node/index.js' 97 | }, 98 | plugins: [ 99 | new UglifyJSPlugin() 100 | ] 101 | }); 102 | 103 | break; 104 | 105 | 106 | case 'build-test': 107 | config = Object.assign(config, { 108 | entry: { 109 | 'test.soda': './test/index.js' 110 | }, 111 | resolve:{ 112 | alias: { 113 | './../node' : path.resolve(__dirname, "./dist/soda.js") 114 | } 115 | } 116 | }); 117 | 118 | config.output.path = path.resolve('./test'); 119 | 120 | break; 121 | 122 | case 'build-node-test': 123 | config = Object.assign(config, { 124 | entry: { 125 | 'test.soda.node': './test/index.js' 126 | }, 127 | resolve:{ 128 | /* 129 | alias: { 130 | './../node' : path.resolve(__dirname, "./dist/soda.js") 131 | } 132 | */ 133 | } 134 | }); 135 | 136 | config.output.path = path.resolve('./test'); 137 | 138 | break; 139 | 140 | } 141 | 142 | module.exports = config; 143 | --------------------------------------------------------------------------------