├── .gitignore ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── dist ├── zect.js └── zect.min.js ├── gulpfile.js ├── index.js ├── lib ├── compile.js ├── compiler.js ├── conf.js ├── directives.js ├── dm.js ├── elements.js ├── execute.js ├── expression.js ├── is.js ├── scope.js ├── util.js └── zect.js ├── package.json └── test ├── array.html ├── debug.html ├── index.html ├── runner.html ├── spec.directive.if.js ├── spec.directive.js ├── spec.directive.repeat.js ├── spec.js └── tools.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /test/node_modules 3 | .DS_Store 4 | Thumbs.db 5 | *.bak 6 | *.tmp 7 | *.log -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": false, 3 | "strict": true, 4 | "browser": true, 5 | "node": true, 6 | "strict": true, 7 | "expr": true, 8 | "globals": { 9 | "require": true, 10 | "module": true, 11 | "Zect": true, 12 | "it": true, 13 | "describe": true, 14 | "assert": true, 15 | "expect": true 16 | }, 17 | "asi": true //makes it an error to leave a trailing whitespace in your code 18 | } 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '0.12' -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 guankaishe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](http://switer.qiniudn.com/zect.png?imageView2/0/w/160/) 2 | Zect , component & mvvm 3 | ==== 4 | [![Build Status](https://travis-ci.org/switer/Zect.svg)](https://travis-ci.org/switer/Zect) 5 | [![npm version](https://badge.fury.io/js/zect.svg)](https://badge.fury.io/js/zect) 6 | 7 | A lightweight Web components and MVVM framework. 8 | **Zect**'s state observer is power by [muxjs](https://github.com/switer/muxjs) 9 | 10 | ## Examples 11 | 12 | * Todo MVC: http://zectjs.github.io/zect-todo 13 | * Production: 14 | - http://corner.ucweb.com 15 | - http://m.v.qq.com/gift/bigbang/ 16 | 17 | ## Downloads 18 | - [zect.js](https://raw.githubusercontent.com/switer/zect/master/dist/zect.js) 19 | - [zect.min.js](https://raw.githubusercontent.com/switer/zect/master/dist/zect.min.js) 20 | 21 | ## Usage 22 | 23 | ```html 24 | 25 | 26 |
27 | {title} 28 | 29 |
30 | ``` 31 | Define and instance 32 | 33 | ```js 34 | var app = new Zect({ 35 | el: '#app', 36 | data: function () { 37 | return { 38 | title: 'Hello, Zect' 39 | } 40 | } 41 | }) 42 | ``` 43 | 44 | ## API Reference 45 | - Global API 46 | * [Zect()](https://github.com/switer/Zect/wiki/Global-API#zectoptions) 47 | * [Zect.extend()](https://github.com/switer/Zect/wiki/Global-API#zectextendoptions) 48 | * [Zect.component()](https://github.com/switer/Zect/wiki/Global-API#zectcomponentid-definition) 49 | * [Zect.namespace(namespace)](https://github.com/switer/Zect/wiki/Global-API#zectnamespacenamespace) 50 | * [Zect.directive(id, definition)](https://github.com/switer/Zect/wiki/Global-API#zectdirectiveid-definition) 51 | 52 | - Instance Options 53 | * [el](https://github.com/switer/Zect/wiki/Instance-Options#el) 54 | * [data](https://github.com/switer/Zect/wiki/Instance-Options#data) 55 | * [mixins](https://github.com/switer/Zect/wiki/Instance-Options#mixins) 56 | * [replace](https://github.com/switer/Zect/wiki/Instance-Options#replace) 57 | * [methods](https://github.com/switer/Zect/wiki/Instance-Options#methods) 58 | * [template](https://github.com/switer/Zect/wiki/Instance-Options#template) 59 | * [computed](https://github.com/switer/Zect/wiki/Instance-Options#computed) 60 | * [directives](https://github.com/switer/Zect/wiki/Instance-Options#directives) 61 | * [components](https://github.com/switer/Zect/wiki/Instance-Options#components) 62 | 63 | - Lifecyle Methods 64 | * [created](https://github.com/switer/Zect/wiki/Lifecyle-Methods#created) 65 | * [ready](https://github.com/switer/Zect/wiki/Lifecyle-Methods#ready) 66 | * [destroy](https://github.com/switer/Zect/wiki/Lifecyle-Methods#destroy) 67 | 68 | - Instance Methods 69 | * [$set](https://github.com/switer/Zect/wiki/Instance-Methods#setkeypath-value) 70 | * [$get](https://github.com/switer/Zect/wiki/Instance-Methods#getkeypath) 71 | * [$watch](https://github.com/switer/Zect/wiki/Instance-Methods#watchkeypath-callback) 72 | * [$unwatch](https://github.com/switer/Zect/wiki/Instance-Methods#unwatchcallback) 73 | * [$compile](https://github.com/switer/Zect/wiki/Instance-Methods#compileel-scope) 74 | * [$component](https://github.com/switer/Zect/wiki/Instance-Methods#componentid) 75 | * [$destroy](https://github.com/switer/Zect/wiki/Instance-Methods#destroy) 76 | 77 | - Instance Properties 78 | * [$el](https://github.com/switer/Zect/wiki/Instance-Properties#el) 79 | * [$refs](https://github.com/switer/Zect/wiki/Instance-Properties#refs) 80 | * [$methods](https://github.com/switer/Zect/wiki/Instance-Properties#methods) 81 | * [$destroyed](https://github.com/switer/Zect/wiki/Instance-Properties#destroyed) 82 | 83 | - Template Syntax 84 | * [if](https://github.com/switer/Zect/wiki/Template-Syntax#if) 85 | * [repeat](https://github.com/switer/Zect/wiki/Template-Syntax#repeat) 86 | * [template](https://github.com/switer/Zect/wiki/Template-Syntax#template) 87 | * [{expression}](https://github.com/switer/Zect/wiki/Template-Syntax#expression) 88 | * [{- expression}](https://github.com/switer/Zect/wiki/Template-Syntax#--expression) 89 | 90 | - Direcitves 91 | * [on](https://github.com/switer/Zect/wiki/Directives#z-on) 92 | * [show](https://github.com/switer/Zect/wiki/Directives#z-show) 93 | * [html](https://github.com/switer/Zect/wiki/Directives#z-html) 94 | * [attr](https://github.com/switer/Zect/wiki/Directives#z-attr) 95 | * [class](https://github.com/switer/Zect/wiki/Directives#z-class) 96 | * [style](https://github.com/switer/Zect/wiki/Directives#z-style) 97 | * [component](https://github.com/switer/Zect/wiki/Directives#z-component) 98 | * [static](https://github.com/switer/Zect/wiki/Directives#z-static) 99 | 100 | ## Guide 101 | ### Custom directive 102 | 103 | Options's Methods: 104 | * **bind** Call only once when directive is binding. 105 | * **update** Call every time when expression's value has been changed. 106 | * **unbind** Call only once when directive is unbinded. 107 | 108 | Directive instance properties: 109 | * **$vm** Mounted VM of the directive 110 | * **$el** Mounted target Node of the directive 111 | * **$id** Current directive instance id 112 | * **$scope** Repeat directive will create a scope for each item when compiling, 113 | so your can access "$index", "$value" through "$scope". 114 | 115 | **Example below:** 116 | 117 | ```html 118 |
119 | ``` 120 | 121 | ```js 122 | Zect.directive('tap', { 123 | bind: function (result, expression) { 124 | // do something when init 125 | }, 126 | update: function (result) { 127 | // do something when state change or after init 128 | }, 129 | unbind: function () { 130 | // do something before destroy the directive instance 131 | } 132 | }) 133 | ``` 134 | 135 | ### Two way binding 136 | 137 | ```html 138 |
139 | 140 | 141 |
142 | ``` 143 | 144 | ```js 145 | new Zect({ 146 | el: '#con', 147 | data: { 148 | search: 'Please input' 149 | }, 150 | methods: { 151 | onSubmit: function () { 152 | this.$data.search // input-value 153 | } 154 | } 155 | }) 156 | ``` 157 | 158 | ### Filter Expression 159 | Filters actually are function call using in template's expression. 160 | 161 | ```html 162 | 167 | ``` 168 | 169 | ```js 170 | new Zect({ 171 | el: '#con', 172 | data: function () { 173 | return [1,2,3,4,5] 174 | }, 175 | methods: { 176 | lessThanFour: function (items) { 177 | return items.filter(function (item) { 178 | if (item < 4) return true 179 | }) 180 | } 181 | } 182 | }) 183 | ``` 184 | 185 | * **Render result:** 186 | 187 | ```html 188 | 193 | ``` 194 | 195 | ### Template syntax 196 | 197 | * **Content Render:** 198 | 199 | ```html 200 | 201 |

{title}

202 | 203 | 204 |

{- title}

205 | ``` 206 | 207 | * **Javascript Syntax In Expression:** 208 | 209 | ```html 210 | 211 |

{'Current time is: ' + new Date()}

212 | 213 | 214 |

{- 'Current Page: ' + page}

215 | ``` 216 | 217 | * **Condition Statements:** 218 | `"is"` is a keyword-attribute for the "if" directive. 219 | If value is truly, the template that is included by "if" directive element will be compiled and insert into to parent's DOM tree. 220 | Otherwise template will be removed from parent's DOM tree. 221 | 222 | ```html 223 | 224 | 225 |
{title}
226 |
227 | ``` 228 | 229 | * **Array Iterator:** 230 | `"items"` is a keyword-attribute for the "repeat" directive. 231 | The value of items's expression should be an Array object. 232 | 233 | ```html 234 | 235 | 236 |
{- $value}
237 |
238 | ``` 239 | 240 | ### Reusable Component 241 | 242 | Zect support reusable component that are conceptually similar to Web Components. 243 | 244 | * **define:** 245 | 246 | ```html 247 | 252 | ``` 253 | 254 | ```js 255 | Zect.component('c-header', { 256 | template: document.querySelector('#tpl-header').innerHTML, 257 | data: { 258 | title: 'index' 259 | }, 260 | ready: function () { 261 | 262 | } 263 | }) 264 | ``` 265 | * **use:** 266 | 267 | ```html 268 | 269 |
270 | 271 |
272 |
273 | 278 | 279 | ``` 280 | 281 | * **render result:** 282 | 283 | ```html 284 |
285 | 286 |
index
287 |
288 |
289 |
index
290 |
291 |
292 | ``` 293 | 294 | ## Component Template 295 | 296 | Zect will copy all attributes for "template" element to instance component element. 297 | 298 | Component's HTML template: 299 | 300 | ```html 301 | 307 | ``` 308 | 309 | Define component: 310 | ```javascript 311 | Zect.component('c-header', { 312 | template: document.querySelector('#tpl-header').innerHTML 313 | }) 314 | ``` 315 | 316 | 317 | ## Component Attributes 318 | 319 | * **data** 320 | "data" property is used to declare binding data from the parent ViewModel. 321 | Just like your instance a component and pass data option. When those binding variables of expression change, 322 | `Zect` will be re-excute the expression and call component instance's "$set" method automatically for updating child component. 323 | 324 | > Notice: r-data has multiple keys must using ';' as separator, otherwise can't create binding for each keys. 325 | 326 | ```html 327 |
328 | 334 | 335 |
336 | ``` 337 | 338 | * **methods** 339 | Just like your instance a component and pass method option. Methods only set once, so when those binding variables of expression change, it will do nothing. 340 | 341 | ```html 342 |
343 | 349 |
350 | ``` 351 | 352 | * **ref** 353 | This option is used to save ref to parent ViewModel, so that access it's instance with the specified name by "$refs". 354 | 355 | ```html 356 |
357 | 358 |
359 | ``` 360 | 361 | ```js 362 | this.$refs.header // access child component instance. 363 | ``` 364 | 365 | * **replace** 366 | This option is uesed to reduce one level document structure. if attribute value equal "true", 367 | will replace component's element with it's main child element. 368 | 369 | Component template: 370 | ```html 371 |
372 |

Header

373 |
374 | ``` 375 | 376 | Usage: 377 | ```html 378 |
379 | 380 |
381 | ``` 382 | 383 | Render result: 384 | ```html 385 |
386 |
387 |

Header

388 |
389 |
390 | ``` 391 | 392 | 393 | ## Computed Properties 394 | For those complicated logic, you should use computed properties to replace inline expressions. 395 | 396 | ```js 397 | var demo = new Zect({ 398 | data: { 399 | host: 'https://github.com', 400 | user: 'switer', 401 | repos: 'zect' 402 | }, 403 | computed: { 404 | link: { 405 | // property dependencies of getter 406 | deps: ['host', 'user', 'repos'], 407 | // property getter 408 | get: function () { 409 | var $data = this.$data 410 | return [$data.host, $data.user, $data.repos].join('/') // https://github.com/switer/zect 411 | }, 412 | // setter is optional 413 | set: function (link) { 414 | // input: https://github.com/zectjs/zect.github.io 415 | var $data = this.$data 416 | var parts = link.replace(/\/$/, '').split('\/') 417 | $data.repos = parts.pop() 418 | $data.user = parts.pop() 419 | $data.host = parts.join('/') 420 | } 421 | } 422 | } 423 | }) 424 | 425 | ``` 426 | ## List operate 427 | 428 | - **Display List** 429 | 430 | Use `z-repeat` block element to repeat display template. 431 | 432 | ```html 433 |
434 | 439 |
440 | ``` 441 | ```js 442 | new Zect({ 443 | data: { 444 | items: ["Switer", "Zect", "Xiaokai"] 445 | } 446 | }) 447 | ``` 448 | Result: 449 | ``` 450 | * Switer 451 | * Zect 452 | * Xiaokai 453 | ``` 454 | 455 | - **Append More** 456 | 457 | ```js 458 | vm.$data.items.$concat(['Web Component']) 459 | ``` 460 | Will delta update: 461 | ``` 462 | * Switer 463 | * Zect 464 | * Xiaokai 465 | + Web Component 466 | ``` 467 | 468 | - **Append Before** 469 | 470 | ```js 471 | vm.$data.items.splice(0, 0, 'Web Component', 'MVVM') 472 | ``` 473 | Result: 474 | ``` 475 | + Web Component 476 | + MVVM 477 | * Switer 478 | * Zect 479 | * Xiaokai 480 | ``` 481 | 482 | - **Remove** 483 | 484 | ```js 485 | vm.$data.items.splice(1, 1) 486 | ``` 487 | Result: 488 | ``` 489 | + Web Component 490 | - MVVM 491 | * Switer 492 | * Zect 493 | * Xiaokai 494 | ``` 495 | 496 | - **Push** 497 | 498 | ```js 499 | vm.$data.items.push('Web Component') 500 | ``` 501 | Result: 502 | ``` 503 | * Switer 504 | * Zect 505 | * Xiaokai 506 | + Web Component 507 | ``` 508 | 509 | and `shift`, `unshift`, `sort`, `reverse`, `pop`. `shift`, `unshift`, `pop` whill be Update in delta (includes `splice` and `concat`). 510 | 511 | 512 | ![Footer](http://switer.qiniudn.com/red-brick.jpg) 513 | 514 | ## License 515 | 516 | MIT 517 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zect", 3 | "main": "index.js", 4 | "version": "0.0.0", 5 | "homepage": "https://github.com/switer/Zect", 6 | "authors": [ 7 | "switer " 8 | ], 9 | "keywords": [ 10 | "web", 11 | "component", 12 | "mvvm", 13 | "data-binding", 14 | "lightweight", 15 | "directive" 16 | ], 17 | "license": "MIT", 18 | "ignore": [ 19 | "**/.*", 20 | "node_modules", 21 | "bower_components", 22 | "test" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /dist/zect.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Zect v1.2.26 3 | * (c) 2015 guankaishe 4 | * Released under the MIT License. 5 | */ 6 | !function(a,b){"object"==typeof exports&&"object"==typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):"object"==typeof exports?exports.Zect=b():a.Zect=b()}(this,function(){return function(a){function b(d){if(c[d])return c[d].exports;var e=c[d]={exports:{},id:d,loaded:!1};return a[d].call(e.exports,e,e.exports,b),e.loaded=!0,e.exports}var c={};return b.m=a,b.c=c,b.p="",b(0)}([function(a,b,c){a.exports=c(1)},function(a,b,c){"use strict";function d(a){var b=k([a]);return e.call(this,b)}function e(a){function b(){var a={};return G.forEach(function(b){p.objEach(b,function(b,c){a[J+b]=c})}),a}function c(a,b){a&&a.bindings&&a.bindings.push(b)}function d(a,b,c){var d,f=!0;switch(a.nodeType){case 1:if(a.hasAttribute(J+"static")){f=!1;break}if(a=e(a),d=i(a,b,c)){f=!1;break}if(k(a,b),d=j(a,B,b,c)){f=!1;break}break;case 3:a.nodeValue.trim()&&(d=y(a,B,b,c)),f=!1;break;case 11:break;default:f=!1}return{into:!!f,inst:!d&&c?new s(a):d}}function e(a){var b,c,d=J+"repeat",f=J+"if";if(a.hasAttribute(f))c=f,b="is";else{if(!a.hasAttribute(d))return a;c=d,b="items"}var g=a.getAttribute(c),h=document.createElement(c);for(a.removeAttribute(c),a.parentNode&&a.parentNode.replaceChild(h,a),h.appendChild(a),h.setAttribute(b,g);a.hasAttribute(f)||a.hasAttribute(d);)e(a);return h}function h(a){var b,c=a.toLowerCase();return F.some(function(a){return a.hasOwnProperty(c)?(b=a[c],!0):void 0}),b}function i(a,b,d){var e,g=a.tagName;switch(!0){case n.IfElement(g):var h=f(a.children),i=[m(a).attr("is")];return h.forEach(function(a){n.ElseElement(a)&&i.push(m(a).attr(q.namespace+"else")||"")}),e=new x(B,b,a,z["if"],J+"if",i),d||e.$mount(a),H.push(e),c(b,e),e;case n.RepeatElement(g):return e=new x(B,b,a,z.repeat,J+"repeat",m(a).attr("items")),d||e.$mount(a),H.push(e),c(b,e),e}}function j(a,b,d){function e(a){var b;return a=a.replace(/^[^:]+:/,function(a){return b=a.replace(/:$/,"").trim(),""}).trim(),{name:b,expr:a,vars:t.extract(a)}}function g(a){var b=e(a);F[b.name]=b,(b.vars||[]).forEach(function(a){!G[a]&&(G[a]=[]),!~G[a].indexOf(b.name)&&G[a].push(b.name)})}var i=J+"component",j=a.getAttribute(i),k=a.tagName;if(j||"DIV"!=k&&"SPAN"!=k&&"A"!=k&&"IMG"!=k){j=j||k;var l=h(j);if(l){var n=m(a);if(n.removeAttr(i),a!==b.$el){var o=J+"ref",q=J+"data",s=J+"methods",u=J+"replace",v=n.attr(o),w=n.attr(q),x=n.attr(s),y=n.attr(u);n.removeAttr(o).removeAttr(q).removeAttr(s).removeAttr(u);var z,A,B,C=D(w),E=t.execLiteral,F={},G={};z=E(w,b,d),A=E(x,b,d),B=new l({el:a,data:z,methods:A,$parent:b,$childrens:f(a.childNodes),replace:"true"==y});var H=C?t.strip(w):"",K=t.sep;return H&&(H.match(K)?H.replace(new RegExp(K+"\\s*$"),"").split(K).forEach(g):g(H)),C&&b.$data.$watch(function(a){var c;p.objEach(G,function(e,f){0===a.indexOf(e)&&(!c&&(c={}),f.forEach(function(a){c[a]=r(b,d,F[a].expr)}))}),c&&B.$set(c)}),v&&(b.$refs[v]=B),I.push(B),c(d,B),B.$update=function(){C&&B.$set(E(w,b,d))},B}}}}function k(a,d){var e={attrs:{},dires:{}},g=b();f(a.attributes).forEach(function(b){var c=b.name,d=b.value;if(!~K.indexOf(c)){if(D(c))e.attrs[c]=d;else if(0===c.indexOf(J)){var f=g[c];if(!f)return;e.dires[c]={def:f,expr:d}}else{if(!D(d.trim()))return;e.attrs[c]=d}a.removeAttribute(c)}}),p.objEach(e.attrs,function(b,e){var f=new v(B,d,a,b,e);H.push(f),c(d,f)}),p.objEach(e.dires,function(b,e){var f,g=e.def,h=e.expr,i=";";g.multi&&h.match(i)?t.strip(h).split(i).forEach(function(e){e.trim()&&(f=new u(B,d,a,g,b,"{"+e+"}"),H.push(f),c(d,f))}):(f=new u(B,d,a,g,b,h),H.push(f),c(d,f))})}function y(a,b,d){var e,f=a.nodeValue,g=t.veil(f),h=t.exprRegexp,i=g.split(h),j=g.match(h);return j&&j.length&&(e=new w(b,d,a,f,i,j),H.push(e),c(d,e)),e}var B=this,E=a.el,F=[C,a.components||{}],G=A.concat([a.directives||{}]),H=[],I=[],J=q.namespace,K=[J+"component",J+"data",J+"methods",J+"ref",J+"replace"],L=a.$childrens;if(B.$parent=a.$parent||null,"string"==p.type(E)&&(E=document.querySelector(E)),E&&a.template)E.innerHTML=a.template;else if(a.template)E=document.createElement("div"),E.innerHTML=a.template;else if(!n.Element(E))throw new Error("Unmatch el option");if(1==E.children.length&&E.firstElementChild.tagName.toLowerCase()==J+"template"){var M=E.firstElementChild,N=f(M.childNodes),O=f(M.attributes);E.removeChild(M),m(N).appendTo(E),O.forEach(function(a){var b;if("class"==a.name)b=a.value+(E.className?" "+E.className:"");else{if(E.hasAttribute(a.name))return;b=a.value}E.setAttribute(a.name,b)})}var P=f(E.querySelectorAll("content"));if(P){var Q;L&&L.length&&(Q=document.createDocumentFragment(),f(L).forEach(function(a){Q.appendChild(a)})),P.forEach(function(a){if(!L||!L.length)return m(a).remove();var b,c,d=m(a),e=d.attr("select");e&&(b=Q.querySelector(e))&&~(c=L.indexOf(b))?(d.replace(b),L.splice(c,1)):e||(d.replace(Q),L=null)})}if(a.replace)if(1!==E.children.length)console.warn("Can't using '"+J+"replace=true' for a component that has no or multiple child-elements.");else if(E.parentNode){var R=E.firstElementChild;l(R,E),E.parentNode.replaceChild(R,E),E=R}else E=E.firstElementChild;B.$el=E,B.$component=h,B.$refs={};var S={};p.objEach(a.methods,function(a,b){return"function"!==p.type(b)?console.warn(a+" is not a function."):void(B[a]=S[a]=p.bind(b,B))}),B.$methods=S;var T,U={};Object.defineProperty(B,"$data",{enumerable:!0,get:function(){return T||U},set:function(a){return T?(T.$set(a),T):p.merge(U,a)}}),B.$set=function(){T.$set.apply(T,arguments)},B.$get=function(){return T.$get.apply(T,arguments)},B.$watch=function(){return T.$watch.apply(T,arguments)},B.$unwatch=function(){return T.$unwatch.apply(T,arguments)};var V=a.created;if(a.$data)T=a.$data,V&&V.call(B);else{p.merge(U,g(a,"data")),V&&V.call(B);var W={props:U,computed:a.computed,computedContext:B};T=new o(W)}B.$compile=function(a,b){var c;return p.walk(a,function(e){var f=e===a,g=d(e,b,f);return f&&(c=g.inst),g.into}),c};var X=a.destroy;B.$destroy=function(){B.$destroyed||(X&&X.call(B),[I,H].forEach(function(a){a.forEach(function(a){a.$destroy()})}),T.$destroy(),B.$el=null,B.$get=null,B.$set=null,B.$refs=null,B.$watch=null,B.$unwatch=null,B.$compile=null,B.$component=null,G=null,F=null,H=null,I=null,B.$destroyed=!0)},B.$compiler=B.$compile(E),a.ready&&a.ready.call(B)}function f(a){return a?[].slice.call(a):[]}function g(a,b){var c=a[b];return"function"==p.type(c)?c.call(a):c}function h(a){return p.extend.apply(p,a)}function i(a,b){var c=a.prototype;return a.prototype=Object.create(b.prototype),c}function j(a){var b={};return h([b].concat(a)),["data","methods","directives","components"].forEach(function(c){b[c]=h([{}].concat(a.map(function(a){return g(a,c)})))}),b}function k(a){var b=[];a.forEach(function(a){a&&(b.push(a),a.mixins&&(b=b.concat(a.mixins)))});var c=j(b),d=c.methods=c.methods||{};return a.forEach(function(a){var b=a&&a.methods&&a.methods.mixins;b&&b.forEach(function(a){p.objEach(a,function(a,b){"mixins"!==a&&(d[a]=b)})})}),delete c.methods.mixins,c}function l(a,b){var c=m(b);return f(a.attributes).forEach(function(a){"class"==a.name?c.addClass(a.value):c.attr(a.name,a.value)}),b}var m=c(2),n=c(3),o=c(11),p=c(4),q=c(5),r=c(6),s=c(7),t=c(8),u=s.Directive,v=s.Attribute,w=s.Text,x=s.Element,y=c(9)(d),z=c(10)(d),A=[y,{}],B=A[1],C={},D=t.isExpr;d.create=d.extend=function(a){function b(b){var c=k([a,b]);return e.call(this,c)}return i(b,d),b},d.component=function(a,b){var c=d.extend(b);return C[a.toLowerCase()]=c,c},d.directive=function(a,b){B[a]=b},d.namespace=function(a){q.namespace=a},d.$=m,i(d,s),a.exports=d},function(a,b,c){"use strict";function d(a){if("string"==j.type(a))return e(j.copyArray(document.querySelectorAll(a)));if("array"==j.type(a))return e(a);if(a instanceof e)return a;if(k.DOM(a))return e(new f(a));throw new Error("Unexpect selector !")}function e(a){if(a instanceof e)return a;var b=new f;return a.forEach(function(a){b.push(a)}),b}function f(){this.push=function(){Array.prototype.push.apply(this,arguments)},this.forEach=function(){Array.prototype.forEach.apply(this,arguments)},this.push.apply(this,arguments)}function g(a){return a&&a.parentNode}function h(){return document.createDocumentFragment()}function i(a,b){return a.appendChild(b)}var j=c(4),k=c(3);f.prototype=Object.create(e.prototype);var l=e.prototype;l.find=function(a){var b=[];return this.forEach(function(c){b=b.concat(j.copyArray(c.querySelectorAll(a)))}),e(b)},l.attr=function(a,b){var c=arguments.length,d=this[0];if(c>1)d.setAttribute(a,b);else if(1==c)return(d.getAttribute(a)||"").toString();return this},l.removeAttr=function(a){return this.forEach(function(b){b.removeAttribute(a)}),this},l.addClass=function(a){return this.forEach(function(b){var c=b.className.split(" ");~c.indexOf(a)||c.push(a),b.className=c.join(" ")}),this},l.removeClass=function(a){return this.forEach(function(b){var c=b.className.split(" "),d=c.indexOf(a);~d&&c.splice(d,1),b.className=c.join(" ")}),this},l.each=function(a){return this.forEach(a),this},l.on=function(a,b,c){return this.forEach(function(d){d.addEventListener(a,b,c)}),this},l.off=function(a,b){return this.forEach(function(c){c.removeEventListener(a,b)}),this},l.html=function(a){var b=arguments.length;if(b>=1)this.forEach(function(b){b.innerHTML=a});else if(this.length)return this[0].innerHTML;return this},l.parent=function(){return this.length?e([g(this[0])]):null},l.remove=function(){return this.forEach(function(a){var b=g(a);b&&b.removeChild(a)}),this},l.insertBefore=function(a){var b;return this.length?(1==this.length?b=this[0]:(b=h(),this.forEach(function(a){i(b,a)})),g(a).insertBefore(b,a),this):this},l.insertAfter=function(a){var b;return this.length?(1==this.length?b=this[0]:(b=h(),this.forEach(function(a){i(b,a)})),g(a).insertBefore(b,a.nextSibling),this):this},l.get=function(a){return this[a]},l.append=function(a){return this.length&&i(this[0],a),this},l.appendTo=function(a){if(1==this.length)i(a,this[0]);else if(this.length>1){var b=h();this.forEach(function(a){i(b,a)}),i(a,b)}},l.replace=function(a){var b=this[0],c=g(b);return c&&c.replaceChild(a,b),this},a.exports=d},function(a,b,c){"use strict";var d=c(5);a.exports={Element:function(a){return 1==a.nodeType||11==a.nodeType},DOM:function(a){return this.Element(a)||8==a.nodeType},IfElement:function(a){return a==(d.namespace+"if").toUpperCase()},ElseElement:function(a){return a.hasAttribute&&a.hasAttribute(d.namespace+"else")},RepeatElement:function(a){return a==(d.namespace+"repeat").toUpperCase()}}},function(a,b,c){"use strict";function d(a){return Object.keys(a)}function e(a,b){for(var c=a.length||0,d=0;c>d&&!b(a[d],d);d++);}function f(a){return a?[].slice.call(a):[]}var g=c(11),h=g.utils,i=g.keyPath.normalize,j=g.keyPath.digest,k={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"},l=new RegExp(d(k).join("|"),"g");a.exports={type:h.type,diff:h.diff,merge:h.merge,objEach:h.objEach,copyArray:h.copyArray,copyObject:h.copyObject,extend:function(a){if("object"!=this.type(a))return a;for(var b,c,d=1,e=arguments.length;e>d;d++){b=arguments[d];for(c in b)a[c]=b[c]}return a},valueDiff:function(a,b){return a!==b||a instanceof Object},walk:function(a,b){var c=b(a)!==!1,d=this;c&&f(a.childNodes).forEach(function(a){d.walk(a,b)})},domRange:function(a,b,c){for(var d=[],e=a.childNodes,f=!1,g=0;g]+?>/),d=b.match(/<\/[^<]+?>$/);return[c?c[0]:"",d?d[0]:""]},relative:function(a,b){if(a=i(a),b=i(b),a==b)return!0;var c=0===a.indexOf(b),d=a.replace(b,"").match(/^[\.\[]/);return c&&d},escape:function(a){return"string"==!this.type(a)?a:a.replace(l,function(a){return k[a]})},isUndef:function(a){return void 0===a},isNon:function(a){var b=this.type(a);return"undefined"===b||"null"===b},bind:function(a,b){var c=function(){return a.apply(b,arguments)};return c.toString=function(){return a.toString.apply(a,arguments)},c},forEach:e,normalize:i,digest:j}},function(a,b,c){"use strict";var d="z-";a.exports={set namespace(a){d=a+"-"},get namespace(){return d}}},function(a,b,c){function d(a,b){b&&b.$parent&&(b.data.$parent=b.$parent.data);var c=arguments[2],d=g[c];b=b||{},b=e.extend({},a.$methods,a.$data,b.methods,b.data);try{return d||(d=g[c]=f(c)),e.immutable(d(b))}catch(h){switch(c=/^\{/.test(c)?". "+c:". {"+c+"}",h.name){case"ReferenceError":console.warn(h.message+c);break;default:console.error(arguments[3]?"'"+arguments[3]+"': ":"",h.message+c,arguments[4]||"")}return""}}var e=c(4),f=c(12),g={};a.exports=d},function(a,b,c){"use strict";function d(){}function e(a,b,c){function e(a){f.some(function(b){return r(a,b)?!0:void 0})&&c.apply(null,arguments)}var f=[];return b&&b.length?(b.forEach(function(a){if(!~u.indexOf(a))for(;a;)~f.indexOf(a)||f.push(a),a=o.digest(a)}),f.length?a.$watch(e):d):d}function f(a){this.$el=a}function g(a,b){for(var c=b.nextSibling;c;){if(c===a)return!0;{if(3!=c.nodeType||!/^\s*$/m.test(c.nodeValue))break;c=c.nextSibling}}return!1}function h(a,b){return a.appendChild(b)}function i(a){return document.createComment(a)}function j(a,b,c){return a.insertBefore(b,c)}function k(a){return a&&a.parentNode}function l(a){return a&&a.nextSibling}function m(a,b){return b&&b.parentNode===a}var n=c(2),o=c(4),p=c(8),q=c(6),r=o.relative,s=p.isExpr,t=p.extract,u=["$index","$value","$parent","$vm","$scope"],v=p.strip,w=f.prototype;f.inherit=function(a){return a.prototype=Object.create(f.prototype),a},w.$bundle=function(){return this.$el},w.$floor=function(){return this.$el},w.$ceil=function(){return this.$el},w.$mount=function(a){return n(a).replace(this.$bundle()),this},w.$remove=function(){var a=this.$bundle();return k(a)&&n(a).remove(),this},w.$appendTo=function(a){return h(a,this.$bundle()),this},w.$insertBefore=function(a){return j(k(a),this.$bundle(),a),this},w.$insertAfter=function(a){return j(k(a),this.$bundle(),l(a)),this},w.$destroy=function(){return this.$el=null,this},w.$nextTo=function(a){return a=a instanceof f?a.$ceil():a,g(a,this.$floor())},w.$preTo=function(a){return a=a instanceof f?a.$floor():a,g(this.$ceil(),a)},w.$update=d;var x=0;f.Directive=f.inherit(function(a,b,c,d,f,g){function h(c){return q(a,b,c,f)}function i(a){if(!j.$destroyed){var b=h(g);if(o.diff(b,n)){var c=n;n=b,w&&w.call(j,b,c,a)}}}var j=this,k=[],l=!!s(g);if(j.$expr=g,l&&(g=v(g)),d.multi){var m;g=g.replace(/^[^:]+:/,function(a){return m=a.replace(/:$/,"").replace(/(^\s*['"]?|['"]?\s*$)/g,""),""}).trim(),k.push(m)}j.$id="d"+x++,j.$name=f,j.$el=c,j.$vm=a,j.$scope=b||null;var n,p,r=d.bind,u=d.unbind,w=d.update;o.objEach(d,function(a,b){j[a]=b}),n=l?h(g):g,k.push(n),k.push(g),d.watch!==!1&&l&&(p=e(a,t(g),i)),j.$destroy=function(){u&&u.call(j),p&&p(),j.$el=null,j.$vm=null,j.$scope=null,j.$destroyed=!0},j.$update=i,r&&r.apply(j,k,g),w&&w.call(j,n)});var y=0;f.Element=f.inherit(function(a,b,c,d,f,g){function l(a,b,c,d,e,f){if(!w.$destroyed){var h;if(D){var i;h=g.map(function(a,b){return p[b][0]?i&&E?!1:i=n(p[b][1]):a})}else h=n(g);var j;if(B&&(j=B.call(w,h,r,a)))return C&&C.call(w,h,r,a,j);if(o.diff(h,r)){var k=r;r=h,A&&A.call(w,h,k,a,d,e,f)}}}function n(c){return q(a,b,c,f)}var p,r,u,w=this,x=d.bind,z=d.unbind,A=d.update,B=d.delta,C=d.deltaUpdate,D=d.multiExpr&&"array"==o.type(g),E="exclusion"==d.multiExpr;w.$expr=g,D&&(p=g.map(function(a){var b=s(a);return[!!b,b?v(a):a]})),w.$id="e"+y++,w.$name=f,w.$vm=a,w.$el=c,w.$scope=b;var F=o.tagHTML(c);if(w.$before=i(F[0]),w.$after=i(F[1]),w.$container=document.createDocumentFragment(),h(w.$container,w.$before),h(w.$container,w.$after),o.objEach(d,function(a,b){w[a]=b}),w.$bundle=function(){var a=this.$ceil(),b=this.$floor(),c=this.$container,d=this;return m(c,a)||(o.domRange(k(a),a,b).forEach(function(a){h(d.$container,a)}),j(c,a,c.firstChild),h(c,b)),c},w.$floor=function(){return this.$after},w.$ceil=function(){return this.$before},w.$destroy=function(){z&&z.call(w),u&&u(),w.$el=null,w.$vm=null,w.$scope=null,w.$destroyed=!0},w.$update=l,D){var G,H=[];r=g.map(function(a,b){return p[b][0]?(a=p[b][1],H=H.concat(t(a)),G&&E?!1:G=n(a)):a}),H.length&&(u=e(a,H,l))}else{var I=!!s(g);I&&(g=v(g)),r=I?n(g):g,d.watch!==!1&&I&&(u=e(a,t(g),l))}x&&x.call(w,r,g),A&&A.call(w,r)});var z=0;f.Text=f.inherit(function(a,b,c,d,f,g){function m(c){return q(a,b,c,null)}function n(){var a=[];f.forEach(function(b,c){a.push(b),c1&&!this.$expr[c]&&(d=c,e=!0),this._lIndex=d,b!=d&&~b&&this._unmount(b),~d&&(e?this.compileds[d]?this._mount(d):(this.compileds[d]=!0,this.$vm.$compile(this._tmpCons[d],this.$scope),this._mount(d)):this._unmount(d))},unbind:function(){this.$update=this._mount=this._unmount=i,this.compileds=this._tmpCons=null}},repeat:{bind:function(a,b){var c=this.$el.getAttribute("ref"),d=this;return c&&(this.$vm.$refs[c]=this),this.$items=function(){return this.$vms},this.$itemBindings=function(a){if(!d.$vms||!d.$vms.length)return[];var b=d.$vms[a];return b?b.$scope.bindings:[]},this.child=this.$el.firstElementChild,this.expr=b,this.child?void(this._noArrayFilter=o.notFunctionCall(b)):console.warn('"'+k.namespace+"repeat\"'s childNode must has a HTMLElement node. {"+b+"}")},unbind:function(){this.$vms&&this.$vms.forEach(function(a){h(a)}),this.child=this.$vms=this._lastItems=null,this.$items=this.$itemBindings=i},delta:function(a,b,c){if(!c)return!1;var d,e,f=l.normalize(o.strip(this.$expr).trim()),g=c.replace(f,"");if("$value"==f&&(e=g.match(/^\d+\.(\d+)(\.|$)/)))g=g.replace(/\d+\.?/,"");else{if(!(e=g.match(/^\.(\d+)(\.|$)/)))return!1;g=g.replace(/^\./,"")}return d=Number(e[1]),this.$vms&&dc?c:a,i.length>2){var d=[].slice.call(i,2).map(function(b,c){return e.call(m,b,a+c)});this.$vms.splice.apply(this.$vms,[a,b].concat(d)),j(d.map(function(a){return a.$compiler.$bundle()})).insertAfter(0===a?q:this.$vms[a-1].$compiler.$bundle());var f=a+d.length;this.$vms.forEach(function(a,b){b>=f&&g(a,b)})}else this.$vms.splice.apply(this.$vms,i).forEach(function(a){h(a)}),this.$vms.forEach(function(b,c){c>=a&&g(b,c)})},push:function(){var b=a.length-1,c=e.call(m,a[b],b);this.$vms.push(c),c.$compiler.$insertBefore(p)},pop:function(){var a=this.$vms.pop();h(a)},shift:function(){var a=this.$vms.shift();h(a),this.$vms.forEach(function(a,b){g(a,b)})},unshift:function(){var b=e.call(m,a[0],0);this.$vms.unshift(b),b.$compiler.$insertAfter(q),this.$vms.forEach(function(a,b){0!==b&&g(a,b)})},$concat:function(){var b=this.$vms.length;j(a.slice(b).map(function(a,c){var d=e.call(m,a,c+b);return m.$vms.push(d),d.$compiler.$bundle()})).insertBefore(p)}},s=r[d];if(n&&this._noArrayFilter&&s)return s.call(this),void(this._lastItems=l.copyArray(a));var t=this._lastItems?this._lastItems.map(function(a){return{data:a}}):null,u=a.map(function(a,b){var c={data:a};if(t){var d,e=-1;t.some(function(c,f){if(!c.used){var g=l.diff(c.data,a);if(!g){if(e=f,b===e)return d=!0;~e||(e=f)}}}),~e&&d?(t[e].used=!0,c.status="reused"):~e?(t[e].used=!0,c.status="moved",c.from=e):(t.some(function(a,c){return a.used||b!=c?void 0:(e=c,!0)}),~e?(t[e].used=!0,c.status="updated",c.from=e):c.status="created")}else c.status="created";return c}),v=(t||[]).reduce(function(a,b,c){return b.used||a.push(c),a},[]);u.some(function(a){return v.length?void("created"==a.status&&(a.from=v.pop(),a.status="recycled")):!0}),v.forEach(function(a){h(m.$vms[a])});var w=q;this.$vms=u.map(function(a,b){var d;switch(a.status){case"created":d=e.call(m,a.data,b);break;case"updated":d=m.$vms[b],f(d,a.data,b,c);break;case"moved":d=m.$vms[a.from],g(d,b);break;case"reused":d=m.$vms[b];break;case"recycled":d=m.$vms[a.from],f(d,a.data,a.from,c)}var h=d.$compiler;return h.$preTo(w)||d.$compiler.$insertAfter(w),w=h.$floor(),d}),this._lastItems=l.copyArray(a)}}}}}},function(a,b,c){"use strict";a.exports=c(14)},function(a,b,c){a.exports=function(a){return/^[_$][\w$]*$/.test(a)?function(b){return b[a]}:new Function("$scope","with($scope){return ("+a+")}")}},function(a,b,c){"use strict";function d(a,b){this.data=a,this.bindings=[],this.children=[],this.$parent=b||null}d.prototype.$update=function(){var a=arguments;this.bindings.forEach(function(b){b.$update.apply(b,a)}),this.children.forEach(function(b){b.$update.apply(b,a)})},d.prototype.$removeChild=function(a){var b=this.children.indexOf(a);return~b&&(a.$parent=null,this.children.splice(b,1)),this},d.prototype.$addChild=function(a){return~this.children.indexOf(a)||this.children.push(a),this},a.exports=d},function(a,b,c){"use strict";function d(){return A++}function e(a){a=a||{},g.call(this,a)}function f(a){function b(b){g.call(this,a,b)}return b.prototype=Object.create(e.prototype),b}function g(a,b){function c(){return N||""}function f(a,b){j(b,Function)&&(b=b.bind(J)),R[a]=b,o.def(J,a,{enumerable:!1,value:b})}function g(){return u("Instance already has bean destroyed"),I}function n(a){var b=arguments,d=p(q(c(),a));b[0]=z+":"+d,L.emit(z,d),K.emit.apply(K,b),b=o.copyArray(b),b[0]=d,b.unshift("*"),K.emit.apply(K,b)}function A(a,b){o.patch(Y,b,[]);var c=Y[b];s(c,a)||c.push(a)}function B(a,b,c){var d,f=a.__mux__;return f&&f.__kp__===c&&f.__root__===O?(d=a,d._$emitter(K),d._$_emitter(L)):d=new e({props:b,emitter:K,_emitter:L,__kp__:c}),d.__root__||o.def(d,"__root__",{enumerable:!1,value:O}),d}function C(a,b,d){var f=r(b),g=d?d:q(c(),a);switch(f==w&&m(b,function(b,c,d,e){var f=o.copyArray(b),h=d.apply(b,e);return _[a]=C(a,b,g),"splice"==c?n(g,b,f,c,e):n(g,b,f,c),h}),f){case x:var h={},i=b;return j(b,e)&&(i=b.$props()),o.objEach(i,function(a,b){h[a]=C(a,b,q(g,a))}),B(b,h,g);case w:return b.forEach(function(a,c){b[c]=C(c,a,q(g,c))}),b;default:return b}}function D(a,b,d){var f=p(a).split("."),g=f[0];if(s(X,g))return void(J[g]=b);if(!s($,g))return void u('Property "'+g+'" has not been observed');var h,i=l.get(_,a),k=j(b,Object),m=f.join("."),r=f.pop(),v=f.join("."),w=l.get(_,v),x=j(w,e);return x?t(w,r)?h=w._$set(r,b,d):(w._$add(r,b,d),h=[l.join(c(),a),b]):(l.set(_,a,k?C(r,b,q(c(),m)):b),o.diff(b,i)&&(d?h=[l.join(c(),a),b,i]:n(a,b,i))),h}function E(a,b,c){return I?g():D(a,b,c)}function F(a){if(I)return g();if(a&&r(a)==x){var b=[];o.objEach(a,function(a,c){var d=E(a,c,!0);d&&b.push(d)}),b.forEach(function(a){n.apply(null,a)})}}function G(a,b,c){if(a.match(/[\.\[\]]/))throw new Error('Propname shoudn\'t contains "." or "[" or "]"');return s($,a)?arguments.length>1?!0:!1:(_[a]=C(a,o.copyValue(b)),$.push(a),o.def(J,a,{enumerable:!0,get:function(){return _[a]},set:function(b){E(a,b)}}),c?{kp:a,vl:b}:void n(a,b))}function H(a,b,c,d,e){if(!s(X,a)){X.push(a),W[a]={deps:b,get:c,set:d},(b||[]).forEach(function(b){for(;b;)A(a,b),b=l.digest(b)}),o.patch(Z,a,{});var f=Z[a];f.cur=c?c.call(M,J):void 0,o.def(J,a,{enumerable:void 0===e?!0:!!e,get:function(){return f.cur},set:function(){d&&d.apply(M,arguments)}}),n(a,f.cur)}}var I,J=this,K=a.emitter||new k(J),L=a._emitter||new k(J),M=t(a,"computedContext")?a.computedContext:J,N=l.normalize(a.__kp__||""),O=d(),P=!!a.emitter,Q=!!a._emitter,R={};f("__muxid__",O),f("__kp__",N);var S=a.props,T={},U=r(S);U==y?T=S():U==x&&(T=S),S=null;var V=a.computed,W={},X=[],Y={},Z={},$=[],_={};o.objEach(T,function(a,b){G(a,b,!0)}),T=null,o.objEach(V,function(a,b){H(a,b.deps,b.get,b.set,b["enum"])}),V=null,L.on(z,function(a){var b=[],c=[];if(Object.keys(Y).length){for(;a;)Y[a]&&(c=c.concat(Y[a])),a=l.digest(a);c.length&&(c.reduce(function(a,b){return s(a,b)||a.push(b),a},b),b.forEach(function(a){o.patch(Z,a,{});var b=Z[a],c=b.pre=b.cur,d=b.cur=(W[a].get||i).call(M,J);o.diff(d,c)&&n(a,d,c)}))}},O),f("$add",function(){var a,b,c=arguments,d=c[0];switch(r(d)){case v:a=d,c.length>1?(b=c[1],G(a,b)&&E(a,b)):G(a);break;case w:d.forEach(function(a){G(a)});break;case x:var e;o.objEach(d,function(a,b){G(a,b)&&(!e&&(e={}),e[a]=b)}),e&&F(e);break;default:u("Unexpect params")}return this}),f("_$add",function(a,b,c){var d=G(a,b,!!c);return d===!0?E(a,b,!!c):d}),f("$computed",function(a){return r(a)==v?H.apply(null,arguments):r(a)==x?o.objEach(arguments[0],function(a,b){H(a,b.deps,b.get,b.set,b["enum"])}):u('$computed params show be "(String, Array, Function, Function)" or "(Object)"'),this}),f("$set",function(){var a=arguments,b=a.length;return b>=2||1==b&&r(a[0])==v?E(a[0],a[1]):1==b&&r(a[0])==x?F(a[0]):void u("Unexpect $set params")}),f("_$set",function(a,b,c){return E(a,b,!!c)}),f("$get",function(a){if(s($,a))return _[a];if(s(X,a))return(W[a].get||i).call(M,J);var b=p(a),c=b.split(".");return s($,c[0])?l.get(_,b):void 0}),f("$watch",function(){var a,b,d=arguments,e=d.length,f=d[0];if(e>=2)a=z+":"+p(q(c(),f)),b=d[1];else{if(1!=e||r(f)!=y)return u("Unexpect $watch params"),i;a="*",b=f}K.on(a,b,O);var g=this;return function(){g.$unwatch.apply(g,d)}}),f("$unwatch",function(){var a,b,d=arguments,e=d.length,f=d[0];switch(!0){case e>=2:a=[d[1]];case 1==e&&r(f)==v:!a&&(a=[]), 7 | b=z+":"+p(q(c(),f)),a.unshift(b);break;case 1==e&&r(f)==y:a=["*",f];break;case 0===e:a=[];break;default:u("Unexpect param type of "+f)}return a&&(a.push(O),K.off.apply(K,a)),this}),f("$props",function(){return o.copyObject(_)}),f("$emitter",function(a,b){return 0===arguments.length?K:(K=a,h(this.$props(),a,b),this)}),f("_$emitter",function(a){K=a}),f("_$_emitter",function(a){j(a,k)&&(L=a)}),f("$destroy",function(){o.objEach(R,function(a,b){r(b)==y&&"$destroyed"!=a&&(R[a]=g)}),P?K.off(O):K.off(),Q?L.off(O):L.off(),K=null,L=null,W=null,X=null,Y=null,Z=null,$=null,_=null,I=!0}),f("$destroyed",function(){return I}),F(b)}function h(a,b,c){if(r(a)==x){var d=a;j(a,e)&&(a._$emitter(b,c),d=a.$props()),o.objEach(d,function(a,d){h(d,b,c)})}else r(a)==w&&a.forEach(function(a){h(a,b,c)})}function i(){}function j(a,b){return a instanceof b}var k=c(15),l=c(16),m=c(17),n=c(18),o=c(19),p=l.normalize,q=l.join,r=o.type,s=o.indexOf,t=o.hasOwn,u=n.warn,v="string",w="array",x="object",y="function",z="change",A=0;e.extend=function(a){return f(a||{})},e.config=function(a){a.warn===!1?n.disable():n.enable()},e.emitter=function(a){return new k(a)},e.keyPath=l,e.utils=o,a.exports=e},function(a,b,c){"use strict";function d(a){this._obs={},this._context=a}var e=c(19),f=e.patch,g=e.type,h="__default_scope__",i=d.prototype;i.on=function(a,b,c){c=c||h,f(this._obs,a,[]),this._obs[a].push({cb:b,scope:c})},i.off=function(){var a,b,c,d=arguments,e=d.length;if(e>=3)a=[d[0]],b=d[1],c=d[2];else if(2==e&&"function"==g(d[0]))a=Object.keys(this._obs),b=d[0],c=d[1];else if(2==e)a=[d[0]],c=d[1];else{if(1!=e)return this._obs=[],this;a=Object.keys(this._obs),c=d[0]}c=c||h;var f=this;return a.forEach(function(a){var d=f._obs[a];if(d){var e=[];b?d.forEach(function(a){(a.cb!==b||a.scope!==c)&&e.push(a)}):d.forEach(function(a){a.scope!==c&&e.push(a)}),f._obs[a]=e}}),this},i.emit=function(a){var b=this._obs[a];if(b){var c=[].slice.call(arguments);c.shift();var d=this;b.forEach(function(a){a.cb&&a.cb.apply(d._context||null,c)})}},a.exports=d},function(a,b,c){"use strict";function d(a){return new String(a).replace(/\[([^\[\]]+)\]/g,function(a,b){return"."+b.replace(/^["']|["']$/g,"")})}function e(a,b,c,e){var f=d(b).split("."),g=f.pop(),h=a;return f.forEach(function(a){h=h[a]}),e?e(h,g,c):h[g]=c,a}function f(){return void 0}function g(a){return a===f()||null===a}function h(a,b){var c=d(b).split("."),e=a;return c.forEach(function(a){return g(e)?!(e=f()):void(e=e[a])}),e}function i(a,b){var c=!!a;return c||(a=""),/^\[.*\]$/.exec(b)?a+b:"number"==typeof b?a+"["+b+"]":c?a+"."+b:b}function j(a){var b=/(\.[^\.]+|\[([^\[\]])+\])$/;return b.exec(a)?a.replace(b,""):""}a.exports={normalize:d,set:e,get:h,join:i,digest:j}},function(a,b,c){"use strict";var d=c(19),e=["splice","push","pop","shift","unshift","reverse","sort","$concat"],f=Array.prototype.push,g=Array.prototype.slice,h={$concat:function(){var a=g.call(arguments),b=this;return a.forEach(function(a){"array"==d.type(a)?a.forEach(function(a){f.call(b,a)}):f.call(b,a)}),b}},i="__hook__";a.exports=function(a,b){e.forEach(function(c){if(a[c]&&a[c][i])return void a[c][i](b);var e=a[c]||h[c];d.def(a,c,{enumerable:!1,value:function(){return b(a,c,e,arguments)}}),d.def(a[c],i,{enumerable:!1,value:function(a){b=a}})})}},function(a,b,c){"use strict";var d=!0;a.exports={enable:function(){d=!0},disable:function(){d=!1},warn:function(a){return d?console.warn?console.warn(a):void console.log(a):void 0}}},function(a,b,c){"use strict";function d(a,b){return a&&a.hasOwnProperty(b)}var e=void 0;a.exports={type:function(a){if(null===a)return"null";if(a===e)return"undefined";var b=/\[object (\w+)\]/.exec(Object.prototype.toString.call(a));return b?b[1].toLowerCase():""},objEach:function(a,b){if(a)for(var c in a)if(d(a,c)&&b(c,a[c])===!1)break},patch:function(a,b,c){!a[b]&&(a[b]=c)},diff:function(a,b,c){var d=this;if(c=void 0===c?4:c,0>=c)return a!==b;if("array"==this.type(a)&&"array"==this.type(b))return a.length!==b.length?!0:a.some(function(a,e){return d.diff(a,b[e],c-1)});if("object"==this.type(a)&&"object"==this.type(b)){var e=Object.keys(a),f=Object.keys(b);return e.length!=f.length?!0:e.some(function(e){return!~f.indexOf(e)||d.diff(a[e],b[e],c-1)})}return a!==b},copyArray:function(a){for(var b=a.length,c=new Array(b);b--;)c[b]=a[b];return c},copyObject:function(a){var b={};return this.objEach(a,function(a,c){b[a]=c}),b},copyValue:function(a){var b=this.type(a);switch(b){case"object":return this.copyObject(a);case"array":return this.copyArray(a);default:return a}},def:function(){return Object.defineProperty.apply(Object,arguments)},indexOf:function(a,b){return~a.indexOf(b)},merge:function(a,b){return b?(this.objEach(b,function(b,c){a[b]=c}),a):a},hasOwn:d}}])}); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | var webpack = require('gulp-webpack') 3 | var uglify = require('gulp-uglifyjs') 4 | var header = require('gulp-header') 5 | var meta = require('./package.json') 6 | var watch = require('gulp-watch') 7 | 8 | var banner = ['/**', 9 | '* Zect v${version}', 10 | '* (c) 2015 ${author}', 11 | '* Released under the ${license} License.', 12 | '*/', 13 | ''].join('\n') 14 | var bannerVars = { 15 | version : meta.version, 16 | author: 'guankaishe', 17 | license: 'MIT' 18 | } 19 | 20 | gulp.task('watch', function () { 21 | watch(['lib/*.js', './package.json'], function () { 22 | gulp.start('default') 23 | }) 24 | }); 25 | 26 | gulp.task('default', function() { 27 | return gulp.src('index.js') 28 | .pipe(webpack({ 29 | output: { 30 | library: 'Zect', 31 | libraryTarget: 'umd', 32 | filename: 'zect.js' 33 | } 34 | })) 35 | .pipe(header(banner, bannerVars)) 36 | .pipe(gulp.dest('dist/')) 37 | .pipe(uglify('zect.min.js', { 38 | mangle: true, 39 | compress: true 40 | })) 41 | .pipe(header(banner, bannerVars)) 42 | .pipe(gulp.dest('dist/')) 43 | }); 44 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/zect') -------------------------------------------------------------------------------- /lib/compile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (__$expr__) { 2 | if (/^[_$][\w$]*$/.test(__$expr__)) { 3 | // access property if begin with _ or $ 4 | return function ($scope) { 5 | return $scope[__$expr__] 6 | } 7 | } else { 8 | return new Function('$scope', 'with($scope){return (' + __$expr__ + ')}') 9 | } 10 | } -------------------------------------------------------------------------------- /lib/compiler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var $ = require('./dm') 4 | var util = require('./util') 5 | var Expression = require('./expression') 6 | var _execute = require('./execute') 7 | var _relative = util.relative 8 | /** 9 | * Whether a text is with express syntax 10 | */ 11 | var _isExpr = Expression.isExpr 12 | /** 13 | * Get varibales of expression 14 | */ 15 | var _extractVars = Expression.extract 16 | 17 | function noop () {} 18 | 19 | var keywords = ['$index', '$value', '$parent', '$vm', '$scope'] 20 | /** 21 | * watch changes of variable-name of keypath 22 | * @return unwatch 23 | */ 24 | function _watch(vm, vars, update) { 25 | var watchKeys = [] 26 | function _handler (kp) { 27 | if (watchKeys.some(function(key) { 28 | if (_relative(kp, key)) { 29 | return true 30 | } 31 | })) update.apply(null, arguments) 32 | } 33 | 34 | if (vars && vars.length) { 35 | vars.forEach(function (k) { 36 | if (~keywords.indexOf(k)) return 37 | while (k) { 38 | if (!~watchKeys.indexOf(k)) watchKeys.push(k) 39 | k = util.digest(k) 40 | } 41 | }) 42 | if (!watchKeys.length) return noop 43 | return vm.$watch(_handler) 44 | } 45 | return noop 46 | } 47 | 48 | 49 | var _strip = Expression.strip 50 | 51 | /** 52 | * Compoiler constructor for wrapping node with consistent API 53 | * @node 54 | */ 55 | function Compiler (node) { 56 | this.$el = node 57 | } 58 | 59 | var cproto = Compiler.prototype 60 | 61 | Compiler.inherit = function (Ctor) { 62 | Ctor.prototype = Object.create(Compiler.prototype) 63 | return Ctor 64 | } 65 | cproto.$bundle = function () { 66 | return this.$el 67 | } 68 | cproto.$floor = function () { 69 | return this.$el 70 | } 71 | cproto.$ceil = function () { 72 | return this.$el 73 | } 74 | cproto.$mount = function (pos) { 75 | $(pos).replace(this.$bundle()) 76 | return this 77 | } 78 | cproto.$remove = function () { 79 | var $el = this.$bundle() 80 | _parentNode($el) && $($el).remove() 81 | return this 82 | } 83 | cproto.$appendTo = function (pos) { 84 | _appendChild(pos, this.$bundle()) 85 | return this 86 | } 87 | cproto.$insertBefore = function (pos) { 88 | _insertBefore(_parentNode(pos), this.$bundle(), pos) 89 | return this 90 | } 91 | cproto.$insertAfter = function (pos) { 92 | _insertBefore(_parentNode(pos), this.$bundle(), _nextSibling(pos)) 93 | return this 94 | } 95 | cproto.$destroy = function () { 96 | this.$el = null 97 | return this 98 | } 99 | function _nextTo (src, tar) { 100 | var next = tar.nextSibling 101 | while(next) { 102 | if (next === src) return true 103 | else if (next.nodeType == 3 && /^\s*$/m.test(next.nodeValue)) { 104 | next = next.nextSibling 105 | continue 106 | } else { 107 | break 108 | } 109 | } 110 | return false 111 | } 112 | cproto.$nextTo = function (tar) { 113 | // Compiler of Node 114 | tar = tar instanceof Compiler ? tar.$ceil() : tar 115 | return _nextTo(tar, this.$floor()) 116 | } 117 | cproto.$preTo = function (tar) { 118 | tar = tar instanceof Compiler ? tar.$floor() : tar 119 | return _nextTo(this.$ceil(), tar) 120 | } 121 | /** 122 | * Can be overwrited 123 | * @type {[type]} 124 | */ 125 | cproto.$update = noop 126 | /** 127 | * Standard directive 128 | */ 129 | var _did = 0 130 | Compiler.Directive = Compiler.inherit(function Directive (vm, scope, tar, def, name, expr) { 131 | var d = this 132 | var bindParams = [] 133 | var isExpr = !!_isExpr(expr) 134 | 135 | d.$expr = expr 136 | 137 | isExpr && (expr = _strip(expr)) 138 | 139 | if (def.multi) { 140 | // extract key and expr from "key: expression" format 141 | var key 142 | expr = expr.replace(/^[^:]+:/, function (m) { 143 | key = m.replace(/:$/, '').replace(/(^\s*['"]?|['"]?\s*$)/g, '') 144 | return '' 145 | }).trim() 146 | bindParams.push(key) 147 | } 148 | 149 | d.$id = 'd' + _did++ 150 | d.$name = name 151 | d.$el = tar 152 | d.$vm = vm 153 | d.$scope = scope || null 154 | 155 | var bind = def.bind 156 | var unbind = def.unbind 157 | var upda = def.update 158 | var prev 159 | var unwatch 160 | 161 | 162 | // set properties 163 | util.objEach(def, function (k, v) { 164 | d[k] = v 165 | }) 166 | 167 | /** 168 | * execute wrap with directive name 169 | */ 170 | function _exec(expr) { 171 | return _execute(vm, scope, expr, name) 172 | } 173 | 174 | /** 175 | * update handler 176 | */ 177 | function _update(kp) { 178 | if (d.$destroyed) return 179 | var nexv = _exec(expr) 180 | 181 | if (util.diff(nexv, prev)) { 182 | var p = prev 183 | prev = nexv 184 | upda && upda.call(d, nexv, p, kp) 185 | } 186 | } 187 | 188 | /** 189 | * If expression is a string iteral, use it as value 190 | */ 191 | prev = isExpr ? _exec(expr):expr 192 | bindParams.push(prev) 193 | bindParams.push(expr) 194 | // watch variable changes of expression 195 | if (def.watch !== false && isExpr) { 196 | unwatch = _watch(vm, _extractVars(expr), _update) 197 | } 198 | 199 | d.$destroy = function () { 200 | unbind && unbind.call(d) 201 | unwatch && unwatch() 202 | d.$el = null 203 | d.$vm = null 204 | d.$scope = null 205 | d.$destroyed = true 206 | } 207 | d.$update = _update 208 | 209 | // ([property-name], expression-value, expression) 210 | bind && bind.apply(d, bindParams, expr) 211 | upda && upda.call(d, prev) 212 | 213 | }) 214 | 215 | 216 | var _eid = 0 217 | Compiler.Element = Compiler.inherit(function ZElement(vm, scope, tar, def, name, expr) { 218 | var d = this 219 | var bind = def.bind 220 | var unbind = def.unbind 221 | var upda = def.update 222 | var delta = def.delta 223 | var deltaUpdate = def.deltaUpdate 224 | var isMultiExpr = def.multiExpr && util.type(expr) == 'array' 225 | var isExclusion = def.multiExpr == 'exclusion' 226 | var multiExprMetas 227 | var prev 228 | var unwatch 229 | 230 | 231 | d.$expr = expr 232 | if (isMultiExpr) { 233 | multiExprMetas = expr.map(function (exp) { 234 | var isExpr = _isExpr(exp) 235 | return [!!isExpr, isExpr ? _strip(exp) : exp] 236 | }) 237 | } 238 | d.$id = 'e' + _eid ++ 239 | d.$name = name 240 | d.$vm = vm 241 | d.$el = tar 242 | d.$scope = scope // save the scope reference 243 | 244 | var tagHTML = util.tagHTML(tar) 245 | d.$before = _createComment(tagHTML[0]) 246 | d.$after = _createComment(tagHTML[1]) 247 | d.$container = document.createDocumentFragment() 248 | 249 | _appendChild(d.$container, d.$before) 250 | _appendChild(d.$container, d.$after) 251 | 252 | // set properties 253 | util.objEach(def, function (k, v) { 254 | d[k] = v 255 | }) 256 | 257 | d.$bundle = function () { 258 | var $ceil = this.$ceil() 259 | var $floor = this.$floor() 260 | var $con = this.$container 261 | var that = this 262 | 263 | if (!_contains($con, $ceil)) { 264 | util.domRange(_parentNode($ceil), $ceil, $floor) 265 | .forEach(function(n) { 266 | _appendChild(that.$container, n) 267 | }) 268 | _insertBefore($con, $ceil, $con.firstChild) 269 | _appendChild($con, $floor) 270 | } 271 | return $con 272 | } 273 | d.$floor = function () { 274 | return this.$after 275 | } 276 | d.$ceil = function () { 277 | return this.$before 278 | } 279 | 280 | d.$destroy = function () { 281 | unbind && unbind.call(d) 282 | unwatch && unwatch() 283 | d.$el = null 284 | d.$vm = null 285 | d.$scope = null 286 | d.$destroyed = true 287 | } 288 | /** 289 | * update handler 290 | */ 291 | function _update(kp, nv, pv, method, ind, len) { 292 | if (d.$destroyed) return 293 | 294 | var nexv 295 | if (isMultiExpr) { 296 | var lastV 297 | nexv = expr.map(function (exp, i) { 298 | if (multiExprMetas[i][0]) { 299 | if (lastV && isExclusion) return false 300 | return (lastV = _exec(multiExprMetas[i][1])) 301 | } else { 302 | return exp 303 | } 304 | }) 305 | } else { 306 | nexv = _exec(expr) 307 | } 308 | var deltaResult 309 | if ( delta && (deltaResult = delta.call(d, nexv, prev, kp)) ) { 310 | return deltaUpdate && deltaUpdate.call(d, nexv, prev, kp, deltaResult) 311 | } 312 | if (util.diff(nexv, prev)) { 313 | var p = prev 314 | prev = nexv 315 | upda && upda.call(d, nexv, p, kp, method, ind, len) 316 | } 317 | } 318 | 319 | d.$update = _update 320 | 321 | /** 322 | * execute wrap with directive name 323 | */ 324 | function _exec(expr) { 325 | return _execute(vm, scope, expr, name) 326 | } 327 | 328 | if (isMultiExpr) { 329 | var watchedKeys = [] 330 | // result exclusion 331 | var lastV 332 | prev = expr.map(function (exp, i) { 333 | if (multiExprMetas[i][0]) { 334 | exp = multiExprMetas[i][1] 335 | watchedKeys = watchedKeys.concat(_extractVars(exp)) 336 | if (lastV && isExclusion) { 337 | return false 338 | } else { 339 | return (lastV = _exec(exp)) 340 | } 341 | } else { 342 | return exp 343 | } 344 | }) 345 | if (watchedKeys.length) { 346 | unwatch = _watch(vm, watchedKeys, _update) 347 | } 348 | } else { 349 | var isExpr = !!_isExpr(expr) 350 | isExpr && (expr = _strip(expr)) 351 | prev = isExpr ? _exec(expr) : expr 352 | if (def.watch !== false && isExpr) { 353 | unwatch = _watch(vm, _extractVars(expr), _update) 354 | } 355 | } 356 | bind && bind.call(d, prev, expr) 357 | upda && upda.call(d, prev) 358 | }) 359 | 360 | var _tid = 0 361 | Compiler.Text = Compiler.inherit(function ZText(vm, scope, tar, originExpr, parts, exprs) { 362 | var d = this 363 | d.$expr = originExpr 364 | d.$id = 't' + _tid ++ 365 | 366 | function _exec (expr) { 367 | return _execute(vm, scope, expr, null) 368 | } 369 | var cache = new Array(exprs.length) 370 | var isUnescape = exprs.some(function (expr) { 371 | return Expression.isUnescape(expr) 372 | }) 373 | var unwatches = [] 374 | 375 | exprs.forEach(function(exp, index) { 376 | // watch change 377 | exp = _strip(exp) 378 | var vars = _extractVars(exp) 379 | 380 | function _update() { 381 | if (d.$destroyed) return 382 | 383 | var pv = cache[index] 384 | var nv = _exec(exp) 385 | 386 | if (util.diff(nv, pv)) { 387 | // re-render 388 | cache[index] = nv 389 | render() 390 | } 391 | } 392 | // initial value 393 | cache[index] = _exec(exp) 394 | 395 | unwatches.push(_watch(vm, vars, _update)) 396 | }) 397 | 398 | if (isUnescape) { 399 | var $tmp = document.createElement('div') 400 | var $con = document.createDocumentFragment() 401 | var $before = _createComment('{' + _strip(originExpr)) 402 | var $after = _createComment('}') 403 | 404 | var pn = _parentNode(tar) 405 | _insertBefore(pn, $before, tar) 406 | _insertBefore(pn, $after, _nextSibling(tar)) 407 | } 408 | 409 | function render() { 410 | var frags = [] 411 | parts.forEach(function(item, index) { 412 | frags.push(item) 413 | if (index < exprs.length) { 414 | frags.push(cache[index]) 415 | } 416 | }) 417 | 418 | var value = Expression.unveil(frags.join('')) 419 | 420 | if (isUnescape) { 421 | var cursor = _nextSibling($before) 422 | while(cursor && cursor !== $after) { 423 | var next = _nextSibling(cursor) 424 | _parentNode(cursor).removeChild(cursor) 425 | cursor = next 426 | } 427 | $tmp.innerHTML = value 428 | ;[].slice.call($tmp.childNodes).forEach(function (n) { 429 | _appendChild($con, n) 430 | }) 431 | _insertBefore(_parentNode($after), $con, $after) 432 | } else { 433 | tar.nodeValue = value 434 | } 435 | } 436 | 437 | this.$destroy = function () { 438 | d.$destroyed = true 439 | unwatches.forEach(function (f) { 440 | f() 441 | }) 442 | } 443 | 444 | this.$update = function () { 445 | if (d.$destroyed) return 446 | 447 | var hasDiff 448 | exprs.forEach(function(exp, index) { 449 | exp = _strip(exp) 450 | var pv = cache[index] 451 | var nv = _exec(exp) 452 | 453 | if (!hasDiff && util.diff(nv, pv)) { 454 | hasDiff = true 455 | } 456 | cache[index] = nv 457 | }) 458 | hasDiff && render() 459 | } 460 | 461 | /** 462 | * initial render 463 | */ 464 | render() 465 | }) 466 | 467 | var _aid = 0 468 | Compiler.Attribute = function ZAttribute (vm, scope, tar, name, value) { 469 | var d = this 470 | d.$name = name 471 | d.$expr = value 472 | d.$id = 'a' + _aid ++ 473 | 474 | var isNameExpr = _isExpr(name) 475 | var isValueExpr = _isExpr(value) 476 | 477 | var nexpr = isNameExpr ? _strip(name) : null 478 | var vexpr = isValueExpr ? _strip(value) : null 479 | 480 | var unwatches = [] 481 | 482 | function _exec(expr) { 483 | return _execute(vm, scope, expr, name + '=' + value) 484 | } 485 | // validate atrribute name, from: http://www.w3.org/TR/REC-xml/#NT-NameChar 486 | // /^(:|[a-zA-Z0-9]|_|-|[\uC0-\uD6]|[\uD8-\uF6]|[\uF8-\u2FF]|[\u370-\u37D]|[\u37F-\u1FFF]|[\u200C-\u200D]|[\u2070-\u218F]|[\u2C00-\u2FEF]|[\u3001-\uD7FF]|[\uF900-\uFDCF]|[\uFDF0-\uFFFD]|[\u10000-\uEFFFF])+$/ 487 | 488 | // cache last name/value 489 | var preName = isNameExpr ? _exec(nexpr) : name 490 | var preValue = isValueExpr ? _exec(vexpr) : value 491 | var $tar = $(tar) 492 | function _emptyUndef(v) { 493 | return util.isUndef(v) ? '' : v 494 | } 495 | $tar.attr(preName, _emptyUndef(preValue)) 496 | 497 | function _updateName() { 498 | if (d.$destroyed) return 499 | 500 | var next = _exec(nexpr) 501 | 502 | if (util.diff(next, preName)) { 503 | $tar.removeAttr(preName) 504 | .attr(next, _emptyUndef(preValue)) 505 | preValue = next 506 | } 507 | } 508 | function _updateValue() { 509 | if (d.$destroyed) return 510 | 511 | var next = _exec(vexpr) 512 | if (util.diff(next, preValue)) { 513 | $tar.attr(preName, _emptyUndef(next)) 514 | preValue = next 515 | } 516 | } 517 | 518 | 519 | this.$destroy = function () { 520 | unwatches.forEach(function (f) { 521 | f() 522 | }) 523 | d.$destroyed = true 524 | } 525 | 526 | this.$update = function () { 527 | if (d.$destroyed) return 528 | 529 | isNameExpr && _updateName() 530 | isValueExpr && _updateValue() 531 | } 532 | /** 533 | * watch attribute name expression variable changes 534 | */ 535 | if (isNameExpr) { 536 | unwatches.push(_watch(vm, _extractVars(name), _updateName)) 537 | } 538 | /** 539 | * watch attribute value expression variable changes 540 | */ 541 | if (isValueExpr) { 542 | unwatches.push(_watch(vm, _extractVars(value), _updateValue)) 543 | } 544 | 545 | } 546 | 547 | function _appendChild (con, child) { 548 | return con.appendChild(child) 549 | } 550 | function _createComment (ns) { 551 | return document.createComment(ns) 552 | } 553 | function _insertBefore (con, child, pos) { 554 | return con.insertBefore(child, pos) 555 | } 556 | function _parentNode (tar) { 557 | return tar && tar.parentNode 558 | } 559 | function _nextSibling (tar) { 560 | return tar && tar.nextSibling 561 | } 562 | function _contains (con, tar) { 563 | return tar && tar.parentNode === con 564 | } 565 | 566 | module.exports = Compiler 567 | -------------------------------------------------------------------------------- /lib/conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ns = 'z-' // default namespace is z that means "Zect" 4 | 5 | module.exports = { 6 | set namespace (n) { 7 | _ns = n + '-' 8 | }, 9 | get namespace () { 10 | return _ns 11 | } 12 | } -------------------------------------------------------------------------------- /lib/directives.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Build-in Global Directives 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var $ = require('./dm') 8 | var conf = require('./conf') 9 | var util = require('./util') 10 | 11 | 12 | module.exports = function() { 13 | return { 14 | 'attr': { 15 | multi: true, 16 | bind: function(attname) { 17 | this.attrs = attname ? attname.trim().split(/\s+/) : [] 18 | if (this.attrs.length) { 19 | this._$el = $(this.$el) 20 | } 21 | }, 22 | update: function(next) { 23 | var that = this 24 | this.attrs.forEach(function (attname) { 25 | if (util.isUndef(next)) { 26 | that._$el.removeAttr(attname) 27 | } else { 28 | that._$el.attr(attname, next) 29 | } 30 | }) 31 | }, 32 | unbind: function () { 33 | this._$el = this.attname = null 34 | } 35 | }, 36 | 'class': { 37 | multi: true, 38 | bind: function(className) { 39 | this.classes = className ? className.trim().split(/\s+/) : [] 40 | if (this.classes.length) { 41 | this._$el = $(this.$el) 42 | } 43 | }, 44 | update: function(isUseClass) { 45 | var that = this 46 | this.classes.forEach(function (className) { 47 | if (isUseClass) that._$el.addClass(className) 48 | else that._$el.removeClass(className) 49 | }) 50 | }, 51 | unbind: function () { 52 | this._$el = this.className = null 53 | } 54 | }, 55 | 'html': { 56 | update: function (nextHTML) { 57 | this.$el.innerHTML = nextHTML 58 | } 59 | }, 60 | 'model': { 61 | bind: function () { 62 | var tagName = this.$el.tagName 63 | var type = tagName.toLowerCase() 64 | var $el = this._$el = $(this.$el) 65 | 66 | // pick input element type spec 67 | type = type == 'input' ? $el.attr('type') || 'text' : type 68 | 69 | switch (type) { 70 | case 'tel': 71 | case 'url': 72 | case 'text': 73 | case 'search': 74 | case 'password': 75 | case 'textarea': 76 | this.evtType = 'input' 77 | break 78 | 79 | case 'date': 80 | case 'week': 81 | case 'time': 82 | case 'month': 83 | case 'datetime': 84 | case 'datetime-local': 85 | case 'color': 86 | case 'range': 87 | case 'number': 88 | case 'select': 89 | case 'checkbox': 90 | this.evtType = 'change' 91 | break 92 | default: 93 | console.warn('"' + conf.namespace + 'model" only support input,textarea,select') 94 | return 95 | } 96 | 97 | var vm = this.$vm 98 | var _update = this.$update 99 | var vType = type == 'checkbox' ? 'checked':'value' 100 | var that = this 101 | 102 | /** 103 | * DOM input 2 state 104 | */ 105 | this._requestChange = function () { 106 | vm.$set(that._prop, that.$el[vType]) 107 | } 108 | /** 109 | * State 2 DOM input 110 | */ 111 | this._update = function () { 112 | var nv = vm.$get(that._prop) 113 | nv = util.isUndef(nv) ? '' : nv 114 | if (that.$el[vType] !== nv) { 115 | that.$el[vType] = nv 116 | } 117 | } 118 | this.$update = function () { 119 | that._update() 120 | _update && _update.apply(this, arguments) 121 | } 122 | $el.on(this.evtType, this._requestChange) 123 | }, 124 | watch: function (prop) { 125 | if (this._watches) { 126 | this._watches.forEach(function (fn) { 127 | fn() 128 | }) 129 | this._watches = [] 130 | } 131 | if (!prop) return 132 | var watches = this._watches = [] 133 | var wKeypath = util.normalize(prop) 134 | while (wKeypath) { 135 | watches.push(this.$vm.$watch(wKeypath, this._update)) 136 | wKeypath = util.digest(wKeypath) 137 | } 138 | }, 139 | update: function (prop) { 140 | this._prop = prop 141 | this.watch(prop) 142 | this._update() 143 | }, 144 | unbind: function () { 145 | this._$el.off(this.evtType, this._requestChange) 146 | this._watches.forEach(function (f) { 147 | f() 148 | }) 149 | this._$el = null 150 | this._requestChange = this._update = noop 151 | } 152 | }, 153 | 'on': { 154 | multi: true, 155 | watch: false, 156 | bind: function(evtType, handler, expression ) { 157 | this._expr = expression 158 | this.type = evtType 159 | this._$el = $(this.$el) 160 | }, 161 | update: function (handler) { 162 | this.off() 163 | var fn = handler 164 | if (util.type(fn) !== 'function') 165 | return console.warn('"' + conf.namespace + 'on" only accept function. {' + this._expr + '}') 166 | 167 | this.fn = fn.bind(this.$vm) 168 | this._$el && this._$el.on(this.type, this.fn, false) 169 | 170 | }, 171 | off: function () { 172 | if (this.fn) { 173 | this._$el && this._$el.off(this.type, this.fn) 174 | this.fn = null 175 | } 176 | }, 177 | unbind: function() { 178 | this.off() 179 | this._$el = this.type = null 180 | } 181 | }, 182 | 'show': { 183 | update: function(next) { 184 | this.$el.style.display = next ? '' : 'none' 185 | } 186 | }, 187 | 'style': { 188 | multi: true, 189 | bind: function (sheet) { 190 | this.sheet = sheet 191 | }, 192 | update: function (next) { 193 | this.$el.style && (this.$el.style[this.sheet] = next) 194 | }, 195 | unbind: function () { 196 | this.sheet = null 197 | } 198 | }, 199 | 'src': { 200 | bind: function () { 201 | this._$el = $(this.$el) 202 | }, 203 | update: function (src) { 204 | if (util.isNon(src)) { 205 | this._$el.removeAttr('src') 206 | } else { 207 | this._$el.attr('src', src) 208 | } 209 | }, 210 | unbind: function () { 211 | this._$el = null 212 | } 213 | } 214 | } 215 | } 216 | function noop () {} -------------------------------------------------------------------------------- /lib/dm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * DOM manipulations 3 | */ 4 | 5 | 'use strict'; 6 | var util = require('./util') 7 | var is = require('./is') 8 | 9 | function Selector(sel) { 10 | if (util.type(sel) == 'string') { 11 | return Shell(util.copyArray(document.querySelectorAll(sel))) 12 | } 13 | else if (util.type(sel) == 'array') { 14 | return Shell(sel) 15 | } 16 | else if (sel instanceof Shell) return sel 17 | else if (is.DOM(sel)) { 18 | return Shell(new ElementArray(sel)) 19 | } 20 | else { 21 | throw new Error('Unexpect selector !') 22 | } 23 | } 24 | 25 | function Shell(nodes) { 26 | if (nodes instanceof Shell) return nodes 27 | var $items = new ElementArray() 28 | nodes.forEach(function (item) { 29 | $items.push(item) 30 | }) 31 | return $items 32 | } 33 | 34 | function ElementArray () { 35 | this.push = function () { 36 | Array.prototype.push.apply(this, arguments) 37 | } 38 | this.forEach = function () { 39 | Array.prototype.forEach.apply(this, arguments) 40 | } 41 | this.push.apply(this, arguments) 42 | } 43 | 44 | ElementArray.prototype = Object.create(Shell.prototype) 45 | 46 | var proto = Shell.prototype 47 | proto.find = function(sel) { 48 | var subs = [] 49 | this.forEach(function(n) { 50 | subs = subs.concat(util.copyArray(n.querySelectorAll(sel))) 51 | }) 52 | return Shell(subs) 53 | } 54 | proto.attr = function(attname, attvalue) { 55 | var len = arguments.length 56 | var el = this[0] 57 | 58 | if (len > 1) { 59 | el.setAttribute(attname, attvalue) 60 | } else if (len == 1) { 61 | return (el.getAttribute(attname) || '').toString() 62 | } 63 | return this 64 | } 65 | proto.removeAttr = function(attname) { 66 | this.forEach(function(el) { 67 | el.removeAttribute(attname) 68 | }) 69 | return this 70 | } 71 | proto.addClass = function(clazz) { 72 | this.forEach(function(el) { 73 | 74 | // IE9 below not support classList 75 | // el.classList.add(clazz) 76 | 77 | var classList = el.className.split(' ') 78 | if (!~classList.indexOf(clazz)) classList.push(clazz) 79 | el.className = classList.join(' ') 80 | }) 81 | return this 82 | } 83 | proto.removeClass = function(clazz) { 84 | this.forEach(function(el) { 85 | 86 | // IE9 below not support classList 87 | // el.classList.remove(clazz) 88 | 89 | var classList = el.className.split(' ') 90 | var index = classList.indexOf(clazz) 91 | if (~index) classList.splice(index, 1) 92 | el.className = classList.join(' ') 93 | }) 94 | return this 95 | } 96 | proto.each = function(fn) { 97 | this.forEach(fn) 98 | return this 99 | } 100 | proto.on = function(type, listener, capture) { 101 | this.forEach(function(el) { 102 | el.addEventListener(type, listener, capture) 103 | }) 104 | return this 105 | } 106 | proto.off = function(type, listener) { 107 | this.forEach(function(el) { 108 | el.removeEventListener(type, listener) 109 | }) 110 | return this 111 | } 112 | proto.html = function(html) { 113 | var len = arguments.length 114 | if (len >= 1) { 115 | this.forEach(function(el) { 116 | el.innerHTML = html 117 | }) 118 | } else if (this.length) { 119 | return this[0].innerHTML 120 | } 121 | return this 122 | } 123 | proto.parent = function() { 124 | if (!this.length) return null 125 | return Shell([_parentNode(this[0])]) 126 | } 127 | proto.remove = function() { 128 | this.forEach(function(el) { 129 | var parent = _parentNode(el) 130 | parent && parent.removeChild(el) 131 | }) 132 | return this 133 | } 134 | proto.insertBefore = function (pos) { 135 | var tar 136 | if (!this.length) return this 137 | else if (this.length == 1) { 138 | tar = this[0] 139 | } else { 140 | tar = _createDocumentFragment() 141 | this.forEach(function (el) { 142 | _appendChild(tar, el) 143 | }) 144 | } 145 | _parentNode(pos).insertBefore(tar, pos) 146 | return this 147 | } 148 | proto.insertAfter = function (pos) { 149 | var tar 150 | if (!this.length) return this 151 | else if (this.length == 1) { 152 | tar = this[0] 153 | } else { 154 | tar = _createDocumentFragment() 155 | this.forEach(function (el) { 156 | _appendChild(tar, el) 157 | }) 158 | } 159 | _parentNode(pos).insertBefore(tar, pos.nextSibling) 160 | return this 161 | } 162 | // return element by index 163 | proto.get = function(i) { 164 | return this[i] 165 | } 166 | proto.append = function(n) { 167 | if (this.length) _appendChild(this[0], n) 168 | return this 169 | } 170 | proto.appendTo = function (p) { 171 | if (this.length == 1) _appendChild(p, this[0]) 172 | else if (this.length > 1) { 173 | var f = _createDocumentFragment() 174 | this.forEach(function (n) { 175 | _appendChild(f, n) 176 | }) 177 | _appendChild(p, f) 178 | } 179 | } 180 | proto.replace = function(n) { 181 | var tar = this[0] 182 | var pn = _parentNode(tar) 183 | pn && pn.replaceChild(n, tar) 184 | return this 185 | } 186 | 187 | function _parentNode (e) { 188 | return e && e.parentNode 189 | } 190 | 191 | function _createDocumentFragment () { 192 | return document.createDocumentFragment() 193 | } 194 | 195 | function _appendChild (p, c) { 196 | return p.appendChild(c) 197 | } 198 | module.exports = Selector 199 | -------------------------------------------------------------------------------- /lib/elements.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Build-in Global Custom-Elements 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var $ = require('./dm') 8 | var conf = require('./conf') 9 | var util = require('./util') 10 | var is = require('./is') 11 | var Scope = require('./scope') 12 | var Expression = require('./expression') 13 | 14 | module.exports = function() { 15 | return { 16 | 'if': { 17 | multiExpr: 'exclusion', 18 | bind: function(cnd, exprs) { 19 | this._tmpCons = exprs.map(function () { 20 | return document.createDocumentFragment() 21 | }) 22 | 23 | /** 24 | * Initial unmount childNodes 25 | */ 26 | var cursor = 0 27 | ;[].slice 28 | .call(this.$el.childNodes) 29 | .forEach(function(e) { 30 | if (is.ElseElement(e)) { 31 | cursor ++ 32 | } else { 33 | this._tmpCons[cursor].appendChild(e) 34 | } 35 | }.bind(this)) 36 | 37 | /** 38 | * Instance method 39 | */ 40 | var mounteds = {} 41 | this._mount = function (index) { 42 | if (mounteds[index]) return 43 | mounteds[index] = true 44 | var $floor = this.$floor() 45 | $floor.parentNode.insertBefore(this._tmpCons[index], $floor) 46 | } 47 | this._unmount = function (index) { 48 | if (!mounteds) return 49 | mounteds[index] = false 50 | var $ceil = this.$ceil() 51 | var $floor = this.$floor() 52 | 53 | var that = this 54 | util.domRange($ceil.parentNode, $ceil, $floor) 55 | .forEach(function(n) { 56 | that._tmpCons[index].appendChild(n) 57 | }) 58 | } 59 | this.compileds = {} 60 | this._lIndex = -1 61 | }, 62 | update: function(next) { 63 | var lIndex = this._lIndex 64 | var rearIndex = next.length - 1 65 | var tIndex = -1 66 | var cnd = false 67 | next.some(function (v, i) { 68 | if (v) tIndex = i 69 | return !!v 70 | }) 71 | 72 | cnd = next[tIndex] 73 | // is last else without condition 74 | if (!~tIndex && next.length > 1 && !this.$expr[rearIndex]) { 75 | tIndex = rearIndex 76 | cnd = true 77 | } 78 | 79 | this._lIndex = tIndex 80 | if (lIndex != tIndex && ~lIndex) { 81 | this._unmount(lIndex) 82 | } 83 | 84 | // not else and all conditions is false 85 | if (!~tIndex) return 86 | 87 | if (!cnd) { 88 | this._unmount(tIndex) 89 | } else if (this.compileds[tIndex]) { 90 | this._mount(tIndex) 91 | } else { 92 | this.compileds[tIndex] = true 93 | this.$vm.$compile(this._tmpCons[tIndex], this.$scope) 94 | this._mount(tIndex) 95 | } 96 | }, 97 | unbind: function () { 98 | this.$update = this._mount = this._unmount = noop 99 | this.compileds = this._tmpCons = null 100 | } 101 | }, 102 | 'repeat': { 103 | bind: function(items, expr) { 104 | var name = this.$el.getAttribute('ref') 105 | var that = this 106 | if (name) { 107 | this.$vm.$refs[name] = this 108 | } 109 | this.$items = function() { 110 | return this.$vms 111 | }, 112 | this.$itemBindings = function(index) { 113 | if (!that.$vms || !that.$vms.length) return [] 114 | var target = that.$vms[index] 115 | if (!target) return [] 116 | return target.$scope.bindings 117 | } 118 | this.child = this.$el.firstElementChild 119 | this.expr = expr 120 | if (!this.child) { 121 | return console.warn('"' + conf.namespace + 'repeat"\'s childNode must has a HTMLElement node. {' + expr + '}') 122 | } 123 | // if use filter, Zect can't patch array by array-method 124 | this._noArrayFilter = Expression.notFunctionCall(expr) 125 | }, 126 | unbind: function () { 127 | this.$vms && this.$vms.forEach(function (vm) { 128 | destroyVM(vm) 129 | }) 130 | this.child = this.$vms = this._lastItems = null 131 | this.$items = this.$itemBindings = noop 132 | }, 133 | delta: function (nv, pv, kp) { 134 | if (!kp) return false 135 | var exprProp = util.normalize(Expression.strip(this.$expr).trim()) 136 | var path = kp.replace(exprProp, '') 137 | var index 138 | var matches 139 | /** 140 | * 1. mount.0 141 | * 2. mount.0.0.prop 142 | * 3. $value.prop 143 | */ 144 | if (exprProp == '$value' && (matches = path.match(/^\d+\.(\d+)(\.|$)/))) { 145 | path = path.replace(/\d+\.?/, '') 146 | } else if (matches = path.match(/^\.(\d+)(\.|$)/)) { 147 | path = path.replace(/^\./, '') 148 | } else { 149 | return false 150 | } 151 | index = Number(matches[1]) 152 | // can be delta update 153 | if (this.$vms && index < this.$vms.length) return { 154 | index: index, 155 | path: path, 156 | mount: exprProp 157 | } 158 | else return false 159 | }, 160 | deltaUpdate: function (nextItems, preItems, kp, payload) { 161 | var index = payload.index 162 | var nv = nextItems[index] 163 | // delta update 164 | this._lastItems[index] = nv 165 | 166 | var $vm = this.$vms[index] 167 | updateVMData($vm, nv, index, payload.path) 168 | }, 169 | update: function(items, preItems, kp, method, args) { 170 | if (!items || !items.forEach) { 171 | return console.warn('"' + conf.namespace + 'repeat" only accept Array data. {' + this.expr + '}') 172 | } 173 | var that = this 174 | var valueOnly = Expression.variableOnly(this.$expr) 175 | 176 | // it's not modify 177 | if (valueOnly && method == 'splice' && args.length == 2 && (!args[1] || args[1] < 0)) return 178 | 179 | var $floor = this.$floor() 180 | var $ceil = this.$ceil() 181 | var arrayPatcher = { 182 | splice: function () { 183 | var ind = Number(args[0] || 0) 184 | var len = Number(args[1] || 0) 185 | var max = this.$vms.length 186 | ind = ind > max ? max : ind 187 | if (args.length > 2) { 188 | /** 189 | * Insert 190 | */ 191 | // create vms for each inserted item 192 | var insertVms = [].slice.call(args, 2).map(function (item, index) { 193 | return createSubVM.call(that, item, ind + index) 194 | }) 195 | // insert items into current $vms 196 | this.$vms.splice.apply(this.$vms, [ind, len].concat(insertVms)) 197 | 198 | // element bound for inserted item vm element 199 | $(insertVms.map(function (vm) { 200 | return vm.$compiler.$bundle() 201 | })).insertAfter( 202 | ind === 0 203 | ? $ceil 204 | : this.$vms[ind - 1].$compiler.$bundle() 205 | ) 206 | // get last update index 207 | var start = ind + insertVms.length 208 | this.$vms.forEach(function (vm, i) { 209 | if (i >= start) { 210 | updateVMIndex(vm, i) 211 | } 212 | }) 213 | 214 | } else { 215 | /** 216 | * remove 217 | */ 218 | this.$vms.splice 219 | .apply(this.$vms, args) 220 | .forEach(function (vm) { 221 | destroyVM(vm) 222 | }) 223 | 224 | this.$vms.forEach(function (vm, i) { 225 | if (i >= ind) { 226 | updateVMIndex(vm, i) 227 | } 228 | }) 229 | } 230 | }, 231 | push: function () { 232 | var index = items.length - 1 233 | var vm = createSubVM.call(that, items[index], index) 234 | this.$vms.push(vm) 235 | vm.$compiler.$insertBefore($floor) 236 | }, 237 | pop: function () { 238 | var vm = this.$vms.pop() 239 | destroyVM(vm) 240 | }, 241 | shift: function () { 242 | var vm = this.$vms.shift() 243 | destroyVM(vm) 244 | this.$vms.forEach(function (v, i) { 245 | updateVMIndex(v, i) 246 | }) 247 | }, 248 | unshift: function () { 249 | var vm = createSubVM.call(that, items[0], 0) 250 | this.$vms.unshift(vm) 251 | vm.$compiler.$insertAfter($ceil) 252 | this.$vms.forEach(function (v, i) { 253 | if (i !== 0) { 254 | updateVMIndex(v, i) 255 | } 256 | }) 257 | }, 258 | $concat: function () { 259 | var len = this.$vms.length 260 | $(items.slice(len).map(function (item, i) { 261 | var vm = createSubVM.call(that, item, i + len) 262 | that.$vms.push(vm) 263 | return vm.$compiler.$bundle() 264 | })).insertBefore($floor) 265 | } 266 | } 267 | 268 | var patch = arrayPatcher[method] 269 | if (valueOnly && this._noArrayFilter && patch) { 270 | patch.call(this) 271 | this._lastItems = util.copyArray(items) 272 | return 273 | } 274 | /** 275 | * vms diff 276 | */ 277 | var source = this._lastItems 278 | ? this._lastItems.map(function (item) { 279 | return { 280 | data: item 281 | } 282 | }) 283 | : null 284 | var diffItems = items.map(function (item, index) { 285 | var data = { 286 | data: item 287 | } 288 | if (!source) { 289 | data.status = 'created' 290 | } else { 291 | var i = -1 292 | var dontUpdated 293 | source.some(function (s, k) { 294 | if (s.used) return 295 | 296 | var hasDiff = util.diff(s.data, item) 297 | if (!hasDiff) { 298 | i = k 299 | // and reuse the pos 300 | if (index === i) return (dontUpdated = true) 301 | else if (!~i) i = k 302 | } 303 | }) 304 | if (~i && dontUpdated) { 305 | source[i].used = true 306 | data.status = 'reused' 307 | } else if (~i) { 308 | source[i].used = true 309 | data.status = 'moved' 310 | data.from = i 311 | } else { 312 | source.some(function (s, k) { 313 | if (!s.used && index == k) { 314 | i = k 315 | return true 316 | } 317 | }) 318 | if (~i) { 319 | source[i].used = true 320 | data.status = 'updated' 321 | data.from = i 322 | } else { 323 | data.status = 'created' 324 | } 325 | } 326 | } 327 | return data 328 | }) 329 | 330 | 331 | /** 332 | * reuse those instance that data changed and index unmatch 333 | * state from "created" to "recycled" 334 | */ 335 | var reusables = (source || []).reduce(function (collects, item, index) { 336 | if (!item.used) { 337 | collects.push(index) 338 | } 339 | return collects 340 | }, []) 341 | 342 | diffItems.some(function (item) { 343 | if (!reusables.length) return true 344 | 345 | if (item.status == 'created') { 346 | item.from = reusables.pop() 347 | item.status = 'recycled' 348 | } 349 | }) 350 | /** 351 | * destroy 352 | */ 353 | reusables.forEach(function (i) { 354 | destroyVM(that.$vms[i]) 355 | }) 356 | /** 357 | * Patch 358 | */ 359 | var floor = $ceil 360 | this.$vms = diffItems.map(function (item, index) { 361 | var vm 362 | switch (item.status) { 363 | case 'created': 364 | vm = createSubVM.call(that, item.data, index) 365 | break 366 | case 'updated': 367 | vm = that.$vms[index] 368 | updateVMData(vm, item.data, index, kp) 369 | break 370 | case 'moved': 371 | vm = that.$vms[item.from] 372 | updateVMIndex(vm, index) 373 | break 374 | case 'reused': 375 | vm = that.$vms[index] 376 | break 377 | case 'recycled': 378 | vm = that.$vms[item.from] 379 | updateVMData(vm, item.data, item.from, kp) 380 | break 381 | } 382 | var $compiler = vm.$compiler 383 | if (!$compiler.$preTo(floor)) { 384 | vm.$compiler.$insertAfter(floor) 385 | } 386 | floor = $compiler.$floor() 387 | return vm 388 | }) 389 | // prevent data source changed 390 | this._lastItems = util.copyArray(items) 391 | } 392 | } 393 | } 394 | } 395 | function shallowClone (data) { 396 | return util.type(data) == 'object' ? util.copyObject(data) : {} 397 | } 398 | // function RepeatViewModel (el, data, index, parentVM, parentScope) { 399 | // data = shallowClone(data) 400 | // data.$index = index 401 | // data.$value = data 402 | // var $scope = new Scope(data, parentScope) 403 | // // on the top of current scope 404 | // parentScope && parentScope.children.push($scope) 405 | // this.$index = index 406 | // this.$value = data 407 | // this.$compiler = parentVM.$compile(el, $scope) 408 | // this.$scope = $scope 409 | // } 410 | /** 411 | * create a sub-vm for array item with specified index 412 | */ 413 | function createSubVM (item, index) { 414 | var subEl = this.child.cloneNode(true) 415 | var data = shallowClone(item) 416 | 417 | data.$index = index 418 | data.$value = item 419 | 420 | var $scope = new Scope(data, this.$scope) 421 | // this.$scope is a parent scope, 422 | // on the top of current scope 423 | if(this.$scope) { 424 | this.$scope.children.push($scope) 425 | // data.$parent = this.$scope.data 426 | } 427 | return { 428 | $index: index, 429 | $value: item, 430 | $compiler: this.$vm.$compile(subEl, $scope), 431 | $scope: $scope 432 | } 433 | } 434 | function updateVMData (vm, data, index, mounted) { 435 | // next time will add props to data object 436 | var $data = vm.$scope.data = shallowClone(data) 437 | $data.$index = index 438 | $data.$value = data 439 | // $data.$parent = vm.$scope.$parent ? vm.$scope.$parent.data : null 440 | 441 | vm.$value = data 442 | vm.$index = index 443 | vm.$scope.$update(mounted || '') 444 | } 445 | function updateVMIndex (vm, index) { 446 | vm.$index = index 447 | var $data = vm.$scope.data 448 | $data.$index = index 449 | vm.$scope.$update() 450 | } 451 | function destroyVM (vm) { 452 | var $parent = vm.$scope.$parent 453 | $parent && $parent.$removeChild(vm.$scope) 454 | // $compiler be inclued in $scope.bindings probably 455 | vm.$compiler.$remove().$destroy() 456 | vm.$scope.bindings.forEach(function (bd) { 457 | bd.$destroy() 458 | }) 459 | } 460 | function noop () {} 461 | -------------------------------------------------------------------------------- /lib/execute.js: -------------------------------------------------------------------------------- 1 | /** 2 | * execute expression from template with specified Scope and ViewModel 3 | */ 4 | 5 | var util = require('./util') 6 | var __$compile__ = require('./compile') 7 | var __$compiledExprs___ = {} 8 | /** 9 | * Calc expression value 10 | */ 11 | function _execute($vm, $scope/*, expression, [label], [target]*/) { 12 | /** 13 | * $scope is passed when call instance method $compile, 14 | * Each "scope" object maybe include "$parent, data, methods" properties 15 | */ 16 | // var $parent = $scope && $scope.$parent ? util.extend({}, $scope.$parent.methods, $scope.$parent.data) : {} 17 | if ($scope && $scope.$parent) { 18 | $scope.data.$parent = $scope.$parent.data 19 | } 20 | var __$expression__ = arguments[2] 21 | var __$fn__ = __$compiledExprs___[__$expression__] 22 | $scope = $scope || {} 23 | $scope = util.extend({}, $vm.$methods, $vm.$data, $scope.methods, $scope.data) 24 | try { 25 | if (!__$fn__) { 26 | __$fn__ = __$compiledExprs___[__$expression__] = __$compile__(__$expression__) 27 | } 28 | return util.immutable(__$fn__($scope)) 29 | } catch (e) { 30 | __$expression__ = /^\{/.test(__$expression__) 31 | ? '. ' + __$expression__ 32 | : '. {' + __$expression__ + '}' // expr 33 | // arguments[3] // label 34 | // arguments[4] // target 35 | switch (e.name) { 36 | case 'ReferenceError': 37 | console.warn(e.message + __$expression__) 38 | break 39 | default: 40 | console.error( 41 | (arguments[3] ? '\'' + arguments[3] + '\': ' : ''), 42 | e.message + __$expression__, 43 | arguments[4] || '' 44 | ) 45 | } 46 | return '' 47 | } 48 | } 49 | module.exports = _execute -------------------------------------------------------------------------------- /lib/expression.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var execute = require('./execute') 4 | var util = require('./util') 5 | var _sep = ';' 6 | var _sepRegexp = new RegExp(_sep, 'g') 7 | var _literalSep = ',' 8 | var _exprRegexp = /\{[\s\S]*?\}/g 9 | var _varsRegexp = /("|').+?[^\\]\1|\.\w*|\$\w*|\w*:|\b(?:this|true|false|null|undefined|new|typeof|Number|String|Object|Array|Math|Date|JSON)\b|([a-z_]\w*)\(|([a-z_]\w*)/gi 10 | /** 11 | * Whether a text is with express syntax 12 | */ 13 | function _isExpr(c) { 14 | return c ? !!c.trim().match(/^\{[\s\S]*?\}$/m) : false 15 | } 16 | module.exports = { 17 | sep: _sep, 18 | literalSep: _literalSep, 19 | 20 | sepRegexp: _sepRegexp, 21 | exprRegexp: _exprRegexp, 22 | 23 | isExpr: _isExpr, 24 | isUnescape: function(expr) { 25 | return !!expr.match(/^\{\- /) 26 | }, 27 | execLiteral: function (expr, vm, scope) { 28 | if (!_isExpr(expr)) return {} 29 | return execute(vm, scope, expr.replace(_sepRegexp, _literalSep)) 30 | }, 31 | veil: function (expr) { 32 | return expr.replace(/\\{/g, '\uFFF0') 33 | .replace(/\\}/g, '\uFFF1') 34 | }, 35 | unveil: function (expr) { 36 | return expr.replace(/\uFFF0/g, '\\{') 37 | .replace(/\uFFF1/g, '\\}') 38 | }, 39 | strip: function (expr) { 40 | // -\d*\.?\d* TBD 41 | var m = expr.trim().match(/^\{([\s\S]*)\}$/m) 42 | return m && m[1] ? m[1].replace(/^- /, '') : '' 43 | }, 44 | extract: function(expr) { 45 | if (!expr) return null 46 | var vars = expr.match(_varsRegexp) 47 | vars = !vars ? [] : vars.filter(function(i) { 48 | if (!i.match(/^[\."'\]\[]/) && !i.match(/\($/)) { 49 | return i 50 | } 51 | }) 52 | return vars 53 | }, 54 | /** 55 | * abc 56 | * abc.0 57 | * abc[0] 58 | * abc[0].abc 59 | * abc["xx-xx"] || abc['xx-xx'] 60 | */ 61 | variableOnly: function (expr) { 62 | return !util.normalize(expr || '').split('.').some(function (k) { 63 | return !k.match(/^([a-zA-Z_$][\w$]*|\d+|".*"|'.*')$/) 64 | }) 65 | }, 66 | notFunctionCall: function (expr) { 67 | return !/[()]/.test(expr) 68 | } 69 | } -------------------------------------------------------------------------------- /lib/is.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var conf = require('./conf') 3 | module.exports = { 4 | Element: function(el) { 5 | // 1: ELEMENT_NODE, 11: DOCUMENT_FRAGMENT_NODE 6 | return el.nodeType == 1 || el.nodeType == 11 7 | }, 8 | DOM: function (el) { 9 | // 8: COMMENT_NODE 10 | return this.Element(el) || el.nodeType == 8 11 | }, 12 | IfElement: function(tn) { 13 | return tn == (conf.namespace + 'if').toUpperCase() 14 | }, 15 | ElseElement: function(node) { 16 | return node.hasAttribute && node.hasAttribute(conf.namespace + 'else') 17 | }, 18 | RepeatElement: function(tn) { 19 | return tn == (conf.namespace + 'repeat').toUpperCase() 20 | } 21 | } -------------------------------------------------------------------------------- /lib/scope.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Scope abstraction is a colletor when compiler child template with scope 3 | */ 4 | 5 | 'use strict'; 6 | 7 | function Scope (data, parent) { 8 | this.data = data 9 | this.bindings = [] 10 | this.children = [] 11 | this.$parent = parent || null 12 | } 13 | 14 | Scope.prototype.$update = function () { 15 | var args = arguments 16 | this.bindings.forEach(function (bd) { 17 | bd.$update.apply(bd, args) 18 | }) 19 | this.children.forEach(function (child) { 20 | child.$update.apply(child, args) 21 | }) 22 | } 23 | Scope.prototype.$removeChild = function (scope) { 24 | var i = this.children.indexOf(scope) 25 | if (~i) { 26 | scope.$parent = null 27 | this.children.splice(i, 1) 28 | } 29 | return this 30 | } 31 | Scope.prototype.$addChild = function (scope) { 32 | if (!~this.children.indexOf(scope)) this.children.push(scope) 33 | return this 34 | } 35 | 36 | module.exports = Scope -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Mux = require('muxjs') 4 | var mUtils = Mux.utils 5 | var _normalize = Mux.keyPath.normalize 6 | var _digest = Mux.keyPath.digest 7 | 8 | function _keys(o) { 9 | return Object.keys(o) 10 | } 11 | function _forEach (items, fn) { 12 | var len = items.length || 0 13 | for (var i = 0; i < len; i ++) { 14 | if(fn(items[i], i)) break 15 | } 16 | } 17 | function _slice (obj) { 18 | if (!obj) return [] 19 | return [].slice.call(obj) 20 | } 21 | var escapeCharMap = { 22 | '&': '&', 23 | '<': '<', 24 | '>': '>', 25 | '\"': '"', 26 | '\'': ''', 27 | '/': '/' 28 | } 29 | var escapeRex = new RegExp(_keys(escapeCharMap).join('|'), 'g') 30 | module.exports = { 31 | type: mUtils.type, 32 | diff: mUtils.diff, 33 | merge: mUtils.merge, 34 | objEach: mUtils.objEach, 35 | copyArray: mUtils.copyArray, 36 | copyObject: mUtils.copyObject, 37 | 38 | extend: function(obj) { 39 | if (this.type(obj) != 'object') return obj; 40 | var source, prop; 41 | for (var i = 1, length = arguments.length; i < length; i++) { 42 | source = arguments[i]; 43 | for (prop in source) { 44 | obj[prop] = source[prop]; 45 | } 46 | } 47 | return obj; 48 | }, 49 | valueDiff: function(next, pre) { 50 | return next !== pre || next instanceof Object 51 | }, 52 | walk: function(node, fn) { 53 | var into = fn(node) !== false 54 | var that = this 55 | if (into) { 56 | _slice(node.childNodes).forEach(function (node) { 57 | that.walk(node, fn) 58 | }) 59 | } 60 | }, 61 | domRange: function (tar, before, after) { 62 | var children = [] 63 | var nodes = tar.childNodes 64 | var start = false 65 | for (var i = 0; i < nodes.length; i++) { 66 | var item = nodes[i] 67 | if (item === after) break 68 | else if (start) { 69 | children.push(item) 70 | } else if (item == before) { 71 | start = true 72 | } 73 | } 74 | return children 75 | }, 76 | immutable: function (obj) { 77 | var that = this 78 | var _t = this.type(obj) 79 | var n 80 | 81 | if (_t == 'array') { 82 | n = obj.map(function (item) { 83 | return that.immutable(item) 84 | }) 85 | } else if (_t == 'object') { 86 | n = {} 87 | this.objEach(obj, function (k, v) { 88 | n[k] = that.immutable(v) 89 | }) 90 | } else { 91 | n = obj 92 | } 93 | return n 94 | }, 95 | tagHTML: function (tag) { 96 | var h = tag.outerHTML 97 | var open = h.match(/^<[^>]+?>/) 98 | var close = h.match(/<\/[^<]+?>$/) 99 | 100 | return [open ? open[0]:'', close ? close[0]:''] 101 | }, 102 | relative: function (src, dest) { 103 | src = _normalize(src) 104 | dest = _normalize(dest) 105 | 106 | if (src == dest) return true 107 | else { 108 | var start = src.indexOf(dest) === 0 109 | var subkp = src.replace(dest, '').match(/^[\.\[]/) 110 | return start && subkp 111 | } 112 | }, 113 | escape: function (str) { 114 | if (!this.type(str) == 'string') return str 115 | return str.replace(escapeRex, function (m) { 116 | return escapeCharMap[m] 117 | }) 118 | }, 119 | isUndef: function (o) { 120 | return o === void(0) 121 | }, 122 | isNon: function (o) { 123 | var t = this.type(o) 124 | return t === 'undefined' || t === 'null' 125 | }, 126 | bind: function (fn, ctx) { 127 | var dummy = function () { 128 | return fn.apply(ctx, arguments) 129 | } 130 | dummy.toString = function () { 131 | return fn.toString.apply(fn, arguments) 132 | } 133 | return dummy 134 | }, 135 | forEach: _forEach, 136 | normalize: _normalize, 137 | digest: _digest 138 | } 139 | -------------------------------------------------------------------------------- /lib/zect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var $ = require('./dm') 4 | var is = require('./is') 5 | var Mux = require('muxjs') 6 | var util = require('./util') 7 | var conf = require('./conf') 8 | var execute = require('./execute') 9 | var Compiler = require('./compiler') 10 | var Expression = require('./expression') 11 | 12 | var Directive = Compiler.Directive 13 | var AttributeDirective = Compiler.Attribute 14 | var TextDirective = Compiler.Text 15 | var ElementDirective = Compiler.Element 16 | 17 | 18 | /** 19 | * private vars 20 | */ 21 | var buildInDirts = require('./directives')(Zect) // preset directives getter 22 | var elements = require('./elements')(Zect) // preset directives getter 23 | var allDirectives = [buildInDirts, {}] // [preset, global] 24 | var gdirs = allDirectives[1] 25 | var gcomps = {} // global define components 26 | 27 | var _isExpr = Expression.isExpr 28 | /** 29 | * Global API 30 | */ 31 | function Zect(options) { 32 | var insOpt = _mergeMethodMixins([options]) 33 | return ViewModel.call(this, insOpt) 34 | } 35 | Zect.create = Zect.extend = function(options) { 36 | function Class(opt) { 37 | var insOpt = _mergeMethodMixins([options, opt]) 38 | /** 39 | * Prototype inherit 40 | */ 41 | return ViewModel.call(this, insOpt) 42 | } 43 | _inherit(Class, Zect) 44 | return Class 45 | } 46 | Zect.component = function(id, definition) { 47 | var Comp = Zect.extend(definition) 48 | gcomps[id.toLowerCase()] = Comp 49 | return Comp 50 | } 51 | Zect.directive = function(id, definition) { 52 | gdirs[id] = definition 53 | } 54 | Zect.namespace = function(ns) { 55 | conf.namespace = ns 56 | } 57 | Zect.$ = $ 58 | _inherit(Zect, Compiler) 59 | 60 | /******************************* 61 | ViewModel Constructor 62 | *******************************/ 63 | function ViewModel(options) { 64 | // inherit Compiler 65 | 66 | var vm = this 67 | var el = options.el 68 | var components = [gcomps, options.components || {}] 69 | var directives = allDirectives.concat([options.directives || {}]) 70 | 71 | var _directives = [] // local refs for all directives instance of the vm 72 | var _components = [] // local refs for all components 73 | var NS = conf.namespace 74 | var componentProps = [NS + 'component', NS + 'data', NS + 'methods', NS + 'ref', NS + 'replace'] 75 | var $childrens = options.$childrens 76 | 77 | // set $parent ref 78 | vm.$parent = options.$parent || null 79 | 80 | /** 81 | * Mounted element detect 82 | */ 83 | if (util.type(el) == 'string') { 84 | el = document.querySelector(el) 85 | } 86 | if (el && options.template) { 87 | el.innerHTML = options.template 88 | } else if (options.template) { 89 | el = document.createElement('div') 90 | el.innerHTML = options.template 91 | } else if (!is.Element(el)) { 92 | throw new Error('Unmatch el option') 93 | } 94 | 95 | // replace "$NS-template" of actual instance's DOM 96 | if (el.children.length == 1 && el.firstElementChild.tagName.toLowerCase() == (NS + 'template')) { 97 | var $holder = el.firstElementChild 98 | var childNodes = _slice($holder.childNodes) 99 | var attributes = _slice($holder.attributes) 100 | 101 | el.removeChild($holder) 102 | /** 103 | * Migrate childNodes 104 | */ 105 | $(childNodes).appendTo(el) 106 | /** 107 | * Merge attributes 108 | */ 109 | attributes.forEach(function (att) { 110 | var nv 111 | if (att.name == 'class') { 112 | nv = att.value + (el.className 113 | ? ' ' + el.className 114 | : '') 115 | 116 | } else if (!el.hasAttribute(att.name)) { 117 | nv = att.value 118 | } else { 119 | return 120 | } 121 | el.setAttribute(att.name, nv) 122 | }) 123 | } 124 | // content insertion 125 | var points = _slice(el.querySelectorAll('content')) 126 | if (points) { 127 | var $con 128 | if ($childrens && $childrens.length) { 129 | $con = document.createDocumentFragment() 130 | _slice($childrens).forEach(function (n) { 131 | $con.appendChild(n) 132 | }) 133 | } 134 | points.forEach(function (p) { 135 | if (!$childrens || !$childrens.length) { 136 | return $(p).remove() 137 | } 138 | var $p = $(p) 139 | var select = $p.attr('select') 140 | var tar 141 | var ind 142 | 143 | if (select 144 | && (tar = $con.querySelector(select)) 145 | && ~(ind = $childrens.indexOf(tar)) ) { 146 | 147 | $p.replace(tar) 148 | $childrens.splice(ind, 1) 149 | } else if (!select) { 150 | $p.replace($con) 151 | $childrens = null 152 | } 153 | }) 154 | } 155 | 156 | /** 157 | * Replace external component element holder with internal child element 158 | */ 159 | if (options.replace) { 160 | if (el.children.length !== 1) { 161 | console.warn('Can\'t using \'' + NS + 'replace=true\' for a component that has no or multiple child-elements.') 162 | } else if (el.parentNode) { 163 | var replacedEl = el.firstElementChild 164 | _cloneArributes(replacedEl, el) 165 | el.parentNode.replaceChild(replacedEl, el) 166 | el = replacedEl 167 | } else { 168 | el = el.firstElementChild 169 | } 170 | } 171 | 172 | vm.$el = el 173 | 174 | /** 175 | * get component define by tagName 176 | */ 177 | vm.$component = getComponent 178 | 179 | /** 180 | * Component instance refs 181 | */ 182 | vm.$refs = {} 183 | 184 | /** 185 | * assign methods 186 | */ 187 | var methods = {} 188 | util.objEach(options.methods, function(k, v) { 189 | if (util.type(v) !== 'function') return console.warn(k + ' is not a function.') 190 | vm[k] = methods[k] = util.bind(v, vm) 191 | }) 192 | vm.$methods = methods 193 | 194 | var $data 195 | var dataOpt = {} 196 | 197 | Object.defineProperty(vm, '$data', { 198 | enumerable: true, 199 | get: function() { 200 | // $data will be undefined in created calling, so using dataOpt as temporary 201 | return $data || dataOpt 202 | }, 203 | set: function(v) { 204 | if (!$data) return util.merge(dataOpt, v) 205 | 206 | $data.$set(v) 207 | return $data 208 | } 209 | }) 210 | vm.$set = function () { 211 | $data.$set.apply($data, arguments) 212 | } 213 | vm.$get = function () { 214 | return $data.$get.apply($data, arguments) 215 | } 216 | vm.$watch = function (/*[ keypath ], fn*/) { 217 | return $data.$watch.apply($data, arguments) 218 | } 219 | vm.$unwatch = function (/*[ keypath ], fn*/) { 220 | return $data.$unwatch.apply($data, arguments) 221 | } 222 | 223 | var created = options.created 224 | if (options.$data) { 225 | $data = options.$data 226 | // if state model instance passsing, call after set 227 | created && created.call(vm) 228 | } else { 229 | util.merge(dataOpt, _funcOrObject(options, 'data')) 230 | // Call before vm-$data instance 231 | created && created.call(vm) 232 | // Instance observable state model 233 | var mopts = { 234 | props: dataOpt, 235 | computed: options.computed, 236 | computedContext: vm 237 | } 238 | $data = new Mux(mopts) 239 | } 240 | 241 | /** 242 | * DOM Compile 243 | * @TODO the unique interface for a compiled node($el, $remove) 244 | */ 245 | vm.$compile = function (el, scope) { 246 | var compiler 247 | 248 | util.walk(el, function (node) { 249 | var isRoot = node === el 250 | var result = compile(node, scope, isRoot) 251 | if (isRoot) compiler = result.inst 252 | return result.into 253 | }) 254 | return compiler 255 | } 256 | 257 | var beforeDestroy = options.destroy 258 | vm.$destroy = function () { 259 | if (vm.$destroyed) return 260 | beforeDestroy && beforeDestroy.call(vm) 261 | 262 | ;[_components, _directives].forEach(function (items) { 263 | items.forEach(function (inst) { 264 | inst.$destroy() 265 | }) 266 | }) 267 | 268 | $data.$destroy() 269 | 270 | // instance methods/properties 271 | vm.$el = null 272 | vm.$get = null 273 | vm.$set = null 274 | vm.$refs = null 275 | vm.$watch = null 276 | vm.$unwatch = null 277 | vm.$compile = null 278 | vm.$component = null 279 | 280 | // private vars 281 | directives = null 282 | components = null 283 | _directives = null 284 | _components = null 285 | 286 | // marked 287 | vm.$destroyed = true 288 | } 289 | vm.$compiler = vm.$compile(el) 290 | 291 | /** 292 | * Call ready after compile 293 | */ 294 | options.ready && options.ready.call(vm) 295 | 296 | function _getAllDirts () { 297 | var _dirts = {} 298 | directives.forEach(function(group) { 299 | util.objEach(group, function(id, def) { 300 | _dirts[NS + id] = def 301 | }) 302 | }) 303 | return _dirts 304 | } 305 | function _setBindings2Scope (scope, ref) { 306 | scope && scope.bindings && (scope.bindings.push(ref)) 307 | } 308 | function compile (node, scope, isRoot) { 309 | /** 310 | * 1. ELEMENT_NODE; 311 | * 2. ATTRIBUTE_NODE; 312 | * 3. TEXT_NODE; 313 | * 8. COMMENT_NODE; 314 | * 9. DOCUMENT_NODE; 315 | * 11. DOCUMENT_FRAGMENT; 316 | */ 317 | var into = true 318 | var inst 319 | switch (node.nodeType) { 320 | case 1: 321 | /** 322 | * static block no parsing 323 | */ 324 | if (node.hasAttribute(NS + 'static')) { 325 | into = false 326 | break 327 | } 328 | /** 329 | * convert those $ns-if, $ns-repeat attribute to block element 330 | */ 331 | node = compilePseudoDirectiveElement(node) 332 | /** 333 | * scope syntax 334 | */ 335 | if (inst = compileElement(node, scope, isRoot)) { 336 | into = false 337 | break 338 | } 339 | /** 340 | * Attribute directive 341 | */ 342 | compileDirective(node, scope) 343 | /** 344 | * Compile custom-element 345 | */ 346 | if (inst = compileComponent(node, vm, scope, isRoot)) { 347 | into = false 348 | break 349 | } 350 | break 351 | case 3: 352 | // ignore whitespace 353 | if (node.nodeValue.trim()) inst = compileText(node, vm, scope, isRoot) 354 | into = false 355 | break 356 | case 11: 357 | // document fragment 358 | break 359 | default: 360 | into = false 361 | } 362 | return { 363 | into: !!into, 364 | inst: !inst && isRoot ? new Compiler(node) : inst 365 | } 366 | } 367 | 368 | function compilePseudoDirectiveElement (node) { 369 | var repeatAttName = NS + 'repeat' 370 | var ifAttName = NS + 'if' 371 | var valueAttName 372 | var matchedAttName 373 | 374 | if (node.hasAttribute(ifAttName)) { 375 | matchedAttName = ifAttName 376 | valueAttName = 'is' 377 | } else if (node.hasAttribute(repeatAttName)) { 378 | matchedAttName = repeatAttName 379 | valueAttName = 'items' 380 | } else { 381 | return node 382 | } 383 | var attValue = node.getAttribute(matchedAttName) 384 | var blockNode = document.createElement(matchedAttName) 385 | node.removeAttribute(matchedAttName) 386 | node.parentNode && node.parentNode.replaceChild(blockNode, node) 387 | blockNode.appendChild(node) 388 | blockNode.setAttribute(valueAttName, attValue) 389 | 390 | while(node.hasAttribute(ifAttName) || node.hasAttribute(repeatAttName)) { 391 | compilePseudoDirectiveElement(node) 392 | } 393 | return blockNode 394 | } 395 | 396 | /** 397 | * Reverse component Constructor by tagName 398 | */ 399 | function getComponent(tn) { 400 | var cid = tn.toLowerCase() 401 | var compDef 402 | components.some(function (comp) { 403 | if (comp.hasOwnProperty(cid)) { 404 | compDef = comp[cid] 405 | return true 406 | } 407 | }) 408 | return compDef 409 | } 410 | /** 411 | * Compile element for block syntax handling 412 | */ 413 | function compileElement(node, scope, isRoot) { 414 | var tagName = node.tagName 415 | var inst 416 | switch(true) { 417 | /** 418 | * <*-if> 419 | */ 420 | case is.IfElement(tagName): 421 | var children = _slice(node.children) 422 | var exprs = [$(node).attr('is')] 423 | children.forEach(function(c) { 424 | if (is.ElseElement(c)) { 425 | exprs.push($(c).attr(conf.namespace + 'else') || '') 426 | } 427 | }) 428 | inst = new ElementDirective( 429 | vm, 430 | scope, 431 | node, 432 | elements['if'], 433 | NS + 'if', 434 | exprs 435 | ) 436 | if (!isRoot) { 437 | inst.$mount(node) 438 | } 439 | // save elements refs 440 | _directives.push(inst) 441 | // save bindins to scope 442 | _setBindings2Scope(scope, inst) 443 | return inst 444 | /** 445 | * <*-repeat> 446 | */ 447 | case is.RepeatElement(tagName): 448 | inst = new ElementDirective( 449 | vm, 450 | scope, 451 | node, 452 | elements.repeat, 453 | NS + 'repeat', 454 | $(node).attr('items') 455 | ) 456 | if (!isRoot) { 457 | inst.$mount(node) 458 | } 459 | _directives.push(inst) 460 | _setBindings2Scope(scope, inst) 461 | return inst 462 | } 463 | } 464 | 465 | /** 466 | * comment 467 | */ 468 | function compileComponent (node, parentVM, scope) { 469 | var cAttName = NS + 'component' 470 | var CompName = node.getAttribute(cAttName) 471 | var tagName = node.tagName 472 | // filtrate most no-compoment element 473 | if (!CompName && (tagName == 'DIV' || tagName == 'SPAN' || tagName == 'A' || tagName == 'IMG')) return 474 | CompName = CompName || tagName 475 | var Comp = getComponent(CompName) 476 | 477 | /** 478 | * Tag is not a custom component element 479 | */ 480 | if (!Comp) return 481 | var $node = $(node) 482 | $node.removeAttr(cAttName) 483 | 484 | // don't need deep into self 485 | if (node === parentVM.$el) return 486 | // suport expression, TBD 487 | var refName = NS + 'ref' 488 | var dAttName = NS + 'data' 489 | var mAttName = NS + 'methods' 490 | var rAttName = NS + 'replace' 491 | 492 | var ref = $node.attr(refName) 493 | var dataExpr = $node.attr(dAttName) 494 | var methods = $node.attr(mAttName) 495 | var replace = $node.attr(rAttName) 496 | 497 | $node.removeAttr(refName) 498 | .removeAttr(dAttName) 499 | .removeAttr(mAttName) 500 | .removeAttr(rAttName) 501 | 502 | var _isDataExpr = _isExpr(dataExpr) 503 | var bindingData 504 | var bindingMethods 505 | /** 506 | * Watch 507 | */ 508 | var execLiteral = Expression.execLiteral 509 | var ast = {} 510 | var revealAst = {} 511 | var compVM 512 | 513 | function _parseExpr (exp) { 514 | var name 515 | exp = exp.replace(/^[^:]+:/, function (m) { 516 | name = m.replace(/:$/, '').trim() 517 | return '' 518 | }).trim() 519 | return { 520 | name: name, 521 | expr: exp, 522 | vars: Expression.extract(exp) 523 | } 524 | } 525 | function _setBindingObj (expr) { 526 | var r = _parseExpr(expr) 527 | ast[r.name] = r 528 | ;(r.vars || []).forEach(function (v) { 529 | !revealAst[v] && (revealAst[v] = []); 530 | !~revealAst[v].indexOf(r.name) && revealAst[v].push(r.name) 531 | }) 532 | } 533 | 534 | bindingData = execLiteral(dataExpr, parentVM, scope) 535 | bindingMethods = execLiteral(methods, parentVM, scope) // --> bindingMethods 536 | 537 | compVM = new Comp({ 538 | el: node, 539 | data: bindingData, 540 | methods: bindingMethods, 541 | $parent: parentVM, 542 | $childrens: _slice(node.childNodes), 543 | replace: replace == 'true' 544 | }) 545 | 546 | var plainDataExpr = _isDataExpr ? Expression.strip(dataExpr) : '' 547 | var sep = Expression.sep 548 | 549 | if (plainDataExpr) { 550 | if (plainDataExpr.match(sep)) { 551 | plainDataExpr.replace(new RegExp(sep + '\\s*$'), '') // trim last seperator 552 | .split(sep) 553 | .forEach(_setBindingObj) 554 | } else { 555 | _setBindingObj(plainDataExpr) 556 | } 557 | } 558 | // watch and binding 559 | if (_isDataExpr) { 560 | parentVM.$data.$watch(function (keyPath) { 561 | var nextState 562 | util.objEach(revealAst, function (varName, bindingNames) { 563 | if (keyPath.indexOf(varName) === 0) { 564 | ;!nextState && (nextState = {}) 565 | bindingNames.forEach(function (n) { 566 | nextState[n] = execute(parentVM, scope, ast[n].expr) 567 | }) 568 | } 569 | }) 570 | nextState && compVM.$set(nextState) 571 | }) 572 | } 573 | // set ref to parentVM 574 | ref && (parentVM.$refs[ref] = compVM) 575 | 576 | _components.push(compVM) 577 | _setBindings2Scope(scope, compVM) 578 | // TBM -- to be modify, instance method should not be attached here 579 | compVM.$update = function () { 580 | _isDataExpr && compVM.$set(execLiteral(dataExpr, parentVM, scope)) 581 | } 582 | return compVM 583 | } 584 | /** 585 | * Compile attributes to directive 586 | */ 587 | function compileDirective (node, scope) { 588 | var ast = { 589 | attrs: {}, 590 | dires: {} 591 | } 592 | var dirtDefs = _getAllDirts() 593 | /** 594 | * attributes walk 595 | */ 596 | _slice(node.attributes).forEach(function(att) { 597 | var aname = att.name 598 | var v = att.value 599 | // parse att 600 | if (~componentProps.indexOf(aname)) { 601 | return 602 | } else if (_isExpr(aname)) { 603 | // variable attribute name 604 | ast.attrs[aname] = v 605 | } else if (aname.indexOf(NS) === 0) { 606 | var def = dirtDefs[aname] 607 | if (def) { 608 | // directive 609 | ast.dires[aname] = { 610 | def: def, 611 | expr: v 612 | } 613 | } else { 614 | return 615 | } 616 | } else if (_isExpr(v.trim())) { 617 | // named attribute with expression 618 | ast.attrs[aname] = v 619 | } else { 620 | return 621 | } 622 | node.removeAttribute(aname) 623 | }) 624 | 625 | /** 626 | * Attributes binding 627 | */ 628 | util.objEach(ast.attrs, function(name, value) { 629 | var attd = new AttributeDirective(vm, scope, node, name, value) 630 | _directives.push(attd) 631 | _setBindings2Scope(scope, attd) 632 | }) 633 | 634 | /** 635 | * Directives binding 636 | */ 637 | util.objEach(ast.dires, function(dname, spec) { 638 | var def = spec.def 639 | var expr = spec.expr 640 | var sep = ';' 641 | var d 642 | // multiple defines expression parse 643 | if (def.multi && expr.match(sep)) { 644 | Expression.strip(expr) 645 | .split(sep) 646 | .forEach(function(item) { 647 | // discard empty expression 648 | if (!item.trim()) return 649 | 650 | d = new Directive(vm, scope, node, def, dname, '{' + item + '}') 651 | _directives.push(d) 652 | _setBindings2Scope(scope, d) 653 | }) 654 | } else { 655 | d = new Directive(vm, scope, node, def, dname, expr) 656 | _directives.push(d) 657 | _setBindings2Scope(scope, d) 658 | } 659 | }) 660 | } 661 | 662 | function compileText (node, vm, scope) { 663 | var originExpr = node.nodeValue 664 | var v = Expression.veil(originExpr) 665 | var exprReg = Expression.exprRegexp 666 | 667 | var parts = v.split(exprReg) 668 | var exprs = v.match(exprReg) 669 | 670 | var inst 671 | // expression match or not 672 | if (exprs && exprs.length) { 673 | inst = new TextDirective(vm, scope, node, originExpr, parts, exprs) 674 | _directives.push(inst) 675 | _setBindings2Scope(scope, inst) 676 | } 677 | return inst 678 | } 679 | } 680 | 681 | /** 682 | * private functions 683 | */ 684 | function _slice (obj) { 685 | if (!obj) return [] 686 | return [].slice.call(obj) 687 | } 688 | function _funcOrObject(obj, prop) { 689 | var tar = obj[prop] 690 | return util.type(tar) == 'function' ? tar.call(obj):tar 691 | } 692 | function _extend (args) { 693 | return util.extend.apply(util, args) 694 | } 695 | function _inherit (Ctor, Parent) { 696 | var proto = Ctor.prototype 697 | Ctor.prototype = Object.create(Parent.prototype) 698 | return proto 699 | } 700 | function _mergeOptions (opts) { 701 | var dest = {} 702 | _extend([dest].concat(opts)) 703 | ;['data', 'methods', 'directives', 'components'].forEach(function (prop) { 704 | dest[prop] = _extend([{}].concat(opts.map(function (opt) { 705 | return _funcOrObject(opt, prop) 706 | }))) 707 | }) 708 | return dest 709 | } 710 | function _mergeMethodMixins (optMixins) { 711 | var mixins = [] 712 | /** 713 | * Merge option mixins 714 | */ 715 | optMixins.forEach(function (o) { 716 | if (o) { 717 | mixins.push(o) 718 | o.mixins && (mixins = mixins.concat(o.mixins)) 719 | } 720 | }) 721 | var insOpt = _mergeOptions(mixins) 722 | /** 723 | * Merge method mixins 724 | */ 725 | var m = insOpt.methods = insOpt.methods || {} 726 | optMixins.forEach(function (o) { 727 | var mxs = o && o.methods && o.methods.mixins 728 | if (mxs) { 729 | mxs.forEach(function (mx) { 730 | util.objEach(mx, function (k, v) { 731 | if (k !== 'mixins') m[k] = v 732 | }) 733 | }) 734 | } 735 | }) 736 | delete insOpt.methods.mixins 737 | return insOpt 738 | } 739 | function _cloneArributes(el, target) { 740 | var $tar = $(target) 741 | _slice(el.attributes).forEach(function (att) { 742 | if (att.name == 'class') $tar.addClass(att.value) 743 | else $tar.attr(att.name, att.value) 744 | }) 745 | return target 746 | } 747 | 748 | module.exports = Zect 749 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zect", 3 | "version": "1.2.26", 4 | "description": "A lightweight Web components and MVVM framework.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "./node_modules/.bin/gulp && npm start", 8 | "start": "./node_modules/.bin/mocha-phantomjs ./test/runner.html" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/switer/Zect.git" 13 | }, 14 | "keywords": [ 15 | "mvvm", 16 | "zect", 17 | "web components", 18 | "data-binding" 19 | ], 20 | "author": "switer", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/switer/Zect/issues" 24 | }, 25 | "devDependencies": { 26 | "gulp": "^3.8.10", 27 | "gulp-header": "^1.2.2", 28 | "gulp-uglifyjs": "^0.5.0", 29 | "gulp-watch": "^4.1.0", 30 | "gulp-webpack": "^1.1.2", 31 | "chai": "^3.2.0", 32 | "mocha": "^2.3.2", 33 | "mocha-phantomjs": "^3.6.0", 34 | "phantomjs": "1.9.7-15" 35 | }, 36 | "dependencies": { 37 | "muxjs": "^2.4.18" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/array.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |

20 | 21 | 22 | 28 | 29 | 30 | 31 |
32 | 33 | 159 | 160 | -------------------------------------------------------------------------------- /test/debug.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 |
12 | 13 |

1

14 |
15 |

2

16 |
17 |

3

18 |
19 |
20 | 21 | 42 | 43 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 24 | 25 | 26 |

switer

27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | 44 |
45 | 46 | 47 | 48 | 49 | 55 | is check: {inputs.check ? 'yes':'no'}
56 | {inputs.date}
57 | {inputs.range}
58 | {inputs.color}
59 | {inputs.tel}
60 | {inputs.url}
61 | {inputs.search}
62 | 63 |

{part(title)}

68 | 69 |

{- contents.text}

70 | 71 | 72 |
73 | 74 | 177 | 178 | -------------------------------------------------------------------------------- /test/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Testing 7 | 8 | 9 | 10 | 15 | 16 | 17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/spec.directive.if.js: -------------------------------------------------------------------------------- 1 | describe('#Directives/if', function () { 2 | it('without else', function () { 3 | var app = new Zect({ 4 | data: function () { 5 | return { 6 | status: 0 7 | } 8 | }, 9 | template: tools.template(function () {/* 10 |
11 |
12 | */}) 13 | }) 14 | assert(!!app.$el.querySelector('.cnd1')) 15 | assert(!app.$el.querySelector('.cnd2')) 16 | app.$data.status = 1 17 | assert(!app.$el.querySelector('.cnd1')) 18 | assert(!!app.$el.querySelector('.cnd2')) 19 | }) 20 | 21 | it('with else-if and without else', function () { 22 | var app = new Zect({ 23 | data: function () { 24 | return { 25 | status: 0 26 | } 27 | }, 28 | template: tools.template(function () {/* 29 | 30 |
31 |
32 |
33 |
34 | */}) 35 | }) 36 | assert(!!app.$el.querySelector('.cnd1')) 37 | assert(!app.$el.querySelector('.cnd2')) 38 | app.$data.status = 1 39 | assert(!app.$el.querySelector('.cnd1')) 40 | assert(!!app.$el.querySelector('.cnd2')) 41 | app.$data.status = 2 42 | assert(!app.$el.querySelector('.cnd1')) 43 | assert(!app.$el.querySelector('.cnd2')) 44 | app.$data.status = 0 45 | assert(!!app.$el.querySelector('.cnd1')) 46 | assert(!app.$el.querySelector('.cnd2')) 47 | app.$data.status = 1 48 | assert(!app.$el.querySelector('.cnd1')) 49 | assert(!!app.$el.querySelector('.cnd2')) 50 | }) 51 | 52 | it('with else and without else-if', function () { 53 | var app = new Zect({ 54 | data: function () { 55 | return { 56 | status: 0 57 | } 58 | }, 59 | template: tools.template(function () {/* 60 | 61 |
62 |
63 |
64 |
65 | */}) 66 | }) 67 | assert(!!app.$el.querySelector('.cnd1')) 68 | assert(!app.$el.querySelector('.cnd2')) 69 | app.$data.status = 10 70 | assert(!app.$el.querySelector('.cnd1')) 71 | assert(!!app.$el.querySelector('.cnd2')) 72 | }) 73 | it('with else and with else-if', function () { 74 | var app = new Zect({ 75 | data: function () { 76 | return { 77 | status: 0 78 | } 79 | }, 80 | template: tools.template(function () {/* 81 | 82 |
83 |
84 |
85 |
86 |
87 |
88 | */}) 89 | }) 90 | assert(!!app.$el.querySelector('.cnd1')) 91 | assert(!app.$el.querySelector('.cnd2')) 92 | assert(!app.$el.querySelector('.cnd3')) 93 | app.$data.status = 1 94 | assert(!app.$el.querySelector('.cnd1')) 95 | assert(!!app.$el.querySelector('.cnd2')) 96 | assert(!app.$el.querySelector('.cnd3')) 97 | app.$data.status = 2 98 | assert(!app.$el.querySelector('.cnd1')) 99 | assert(!app.$el.querySelector('.cnd2')) 100 | assert(!!app.$el.querySelector('.cnd3')) 101 | }) 102 | }) -------------------------------------------------------------------------------- /test/spec.directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('#Directive', function () { 4 | it('attr', function () { 5 | var app = new Zect({ 6 | template: '', 7 | data: function () { 8 | return { 9 | attValue: '' 10 | } 11 | } 12 | }) 13 | assert(app.$el.hasAttribute('attName')) 14 | assert.equal(app.$el.getAttribute('attName'), '') 15 | app.$data.attValue = 'v' 16 | assert.equal(app.$el.getAttribute('attName'), 'v') 17 | app.$data.attValue = null 18 | assert.equal(app.$el.getAttribute('attName'), 'null') 19 | }) 20 | it('attr:multiple', function () { 21 | var app = new Zect({ 22 | template: '', 23 | data: function () { 24 | return { 25 | attValue: '' 26 | } 27 | } 28 | }) 29 | assert(app.$el.hasAttribute('attName1')) 30 | assert(app.$el.hasAttribute('attName2')) 31 | assert.equal(app.$el.getAttribute('attName1'), '') 32 | assert.equal(app.$el.getAttribute('attName2'), '') 33 | app.$data.attValue = 'v' 34 | assert.equal(app.$el.getAttribute('attName1'), 'v') 35 | assert.equal(app.$el.getAttribute('attName2'), 'v') 36 | app.$data.attValue = null 37 | assert.equal(app.$el.getAttribute('attName1'), 'null') 38 | assert.equal(app.$el.getAttribute('attName2'), 'null') 39 | }) 40 | it('class:multiple', function () { 41 | var app = new Zect({ 42 | template: '', 43 | data: function () { 44 | return { 45 | addClass: false 46 | } 47 | } 48 | }) 49 | assert.equal(app.$el.className, 'att') 50 | app.$data.addClass = true 51 | assert.equal(app.$el.className, 'att class1 class2') 52 | }) 53 | }) -------------------------------------------------------------------------------- /test/spec.directive.repeat.js: -------------------------------------------------------------------------------- 1 | describe('#Directives/repeat', function () { 2 | it('repeat', function () { 3 | var app = new Zect({ 4 | data: function () { 5 | return { 6 | items: [{id: 1, name: '1'}, {id: 2, name: '2'}] 7 | } 8 | }, 9 | template: tools.template(function () {/* 10 | 11 |
{name}
12 |
13 | */}) 14 | }) 15 | var $items = app.$el.querySelectorAll('[data-id]') 16 | expect($items.length).to.equal(2) 17 | /** 18 | * delta update 19 | */ 20 | app.$data.items[0].id = 3 21 | app.$data.items[1].id = 4 22 | var $nextItems = app.$el.querySelectorAll('[data-id]') 23 | expect($items[0]).to.equal($nextItems[0]) 24 | expect($items[1]).to.equal($nextItems[1]) 25 | expect($items[0].dataset.id).to.equal('3') 26 | expect($items[1].dataset.id).to.equal('4') 27 | }) 28 | it('repeat:x2', function () { 29 | var app = new Zect({ 30 | data: function () { 31 | return { 32 | items: [[{id: 1, name: '1'}, {id: 2, name: '2'}]] 33 | } 34 | }, 35 | template: tools.template(function () {/* 36 | 37 |
    38 | 39 |
  • {name}
  • 40 |
    41 |
42 |
43 | */}) 44 | }) 45 | var $uls = [].slice.call(app.$el.querySelectorAll('ul')) 46 | var $lis = [].slice.call(app.$el.querySelectorAll('ul li')) 47 | expect($uls.length).to.equal(1) 48 | expect($lis.length).to.equal(2) 49 | /** 50 | * delta update 51 | */ 52 | // app.$data.items[0][0].id = 3 53 | app.$data.items[0][1].id = 4 54 | var $nextUls = app.$el.querySelectorAll('ul') 55 | var $nextLis = app.$el.querySelectorAll('ul li') 56 | expect($uls[0]).to.equal($nextUls[0]) 57 | expect($lis[0]).to.equal($nextLis[0]) 58 | expect($lis[1]).to.equal($nextLis[1]) 59 | expect($nextLis[1].dataset.id).to.equal('4') 60 | 61 | /** 62 | * delta update item 63 | */ 64 | app.$data.$set('items[0][1]', {id: 5, name: '5'}) 65 | $nextLis = app.$el.querySelectorAll('ul li') 66 | expect($lis[0]).to.equal($nextLis[0]) 67 | expect($lis[1]).to.equal($nextLis[1]) 68 | expect($nextLis[1].dataset.id).to.equal('5') 69 | 70 | /** 71 | * array.shift() 72 | */ 73 | app.$data.items[0].shift() 74 | $nextLis = app.$el.querySelectorAll('ul li') 75 | assert.notEqual($lis[0], $nextLis[0]) 76 | assert.equal($nextLis.length, 1) 77 | assert.equal($nextLis[0].dataset.id, '5') 78 | 79 | /** 80 | * array.splice() 81 | */ 82 | app.$data.items[0].splice(0, 0, {id: 2, name: '2'}) 83 | $nextLis = app.$el.querySelectorAll('ul li') 84 | assert.equal($nextLis.length, 2) 85 | assert.equal($nextLis[0].dataset.id, '2') 86 | }) 87 | it('repeat:diff', function () { 88 | var app = new Zect({ 89 | data: function () { 90 | return { 91 | items: [[{id: 0, name: '0'}, {id: 1, name: '1'}, {id: 2, name: '2'}]] 92 | } 93 | }, 94 | template: tools.template(function () {/* 95 | 96 |
    97 | 98 |
  • {name}
  • 99 |
    100 |
101 |
102 | */}) 103 | }) 104 | var $uls = [].slice.call(app.$el.querySelectorAll('ul')) 105 | var $lis = [].slice.call(app.$el.querySelectorAll('ul li')) 106 | assert.equal($uls.length, 1) 107 | assert.equal($lis.length, 3) 108 | // diff update 109 | app.$data.$set('items[0]', [{ 110 | id: 1, 111 | name: '1' 112 | }, { 113 | id: 2, 114 | name: '2' 115 | }]) 116 | var $nextLis = [].slice.call(app.$el.querySelectorAll('ul li')) 117 | assert.equal($nextLis.length, 2) 118 | assert.equal($nextLis[0], $lis[1]) 119 | assert.equal($nextLis[1], $lis[2]) 120 | }) 121 | it('repeat:reused', function () { 122 | /** 123 | * and test recycled 124 | */ 125 | var app = new Zect({ 126 | data: function () { 127 | return { 128 | items: [1,2,3,4] 129 | } 130 | }, 131 | template: tools.template(function () {/* 132 |
    133 | 134 |
  • {$value}
  • 135 |
    136 |
137 | */}) 138 | }) 139 | 140 | var $lis = [].slice.call(app.$el.querySelectorAll('ul li')) 141 | app.$data.items = [4,5,3,6] 142 | var $nextLis = [].slice.call(app.$el.querySelectorAll('ul li')) 143 | 144 | assert.equal($nextLis[0], $lis[3]) // moved 145 | assert.equal($nextLis[1], $lis[1]) // updated 146 | assert.equal($nextLis[2], $lis[2]) // reused 147 | assert.equal($nextLis[3], $lis[0]) // recycled 148 | 149 | assert.equal($nextLis[0].innerHTML, 4) 150 | assert.equal($nextLis[1].innerHTML, 5) 151 | assert.equal($nextLis[2].innerHTML, 3) 152 | assert.equal($nextLis[3].innerHTML, 6) 153 | }) 154 | it('repeat:destroy', function () { 155 | /** 156 | * and test recycled 157 | */ 158 | var app = new Zect({ 159 | data: function () { 160 | return { 161 | items: [1,2,3,4] 162 | } 163 | }, 164 | template: tools.template(function () {/* 165 | 166 |
    167 | 168 |
  • {$value}
  • 169 |
    170 |
171 |
172 | */}), 173 | methods: { 174 | to2d: function (list) { 175 | var next = [] 176 | list = list.slice(0) 177 | while(list.length) { 178 | next.push(list.splice(0, 2)) 179 | } 180 | return next 181 | } 182 | } 183 | }) 184 | app.$data.items = [] 185 | assert.equal(app.$el.querySelectorAll('ul li').length, 0) // moved 186 | }) 187 | it('repeat:ref', function () { 188 | var app = new Zect({ 189 | data: function () { 190 | return { 191 | items: [1,2,3,4] 192 | } 193 | }, 194 | template: tools.template(function () {/* 195 | 196 |
{$value}
197 |
198 | */}) 199 | }) 200 | assert.equal(app.$refs.repeat.$itemBindings(0)[0].$expr, '{$value}') // moved 201 | }) 202 | it('repeat:nested-variables', function () { 203 | var app = new Zect({ 204 | template: tools.template(function () {/* 205 | 206 | 207 | 208 |
{$parent.$parent.$index}.{$parent.$index}.{$value}
209 |
210 |
211 |
212 | */}), 213 | data: function () { 214 | return { 215 | list: [{_items: [{_values: [1]}]}] 216 | } 217 | } 218 | }) 219 | assert.equal(app.$el.children[0].innerHTML, '0.0.1') 220 | }) 221 | }) -------------------------------------------------------------------------------- /test/spec.js: -------------------------------------------------------------------------------- 1 | describe('#Global API', function () { 2 | it('Zect()', function () { 3 | var app = new Zect({ 4 | el: '#app' 5 | }) 6 | tools.checkInstance(app) 7 | }) 8 | it('Zect.extend()', function () { 9 | var Comp = Zect.extend({ 10 | template: '
template
' 11 | }) 12 | var app = new Comp({ 13 | el: document.createElement('div') 14 | }) 15 | tools.checkInstance(app) 16 | assert(app.$el.querySelector('.tpl')) 17 | }) 18 | it('Zect.component()', function () { 19 | var Comp = Zect.component('c-comp', { 20 | template: '
' 21 | }) 22 | var app = new Zect({ 23 | template: tools.template(function () {/* 24 |
25 | 26 |
27 |
28 | */}) 29 | }) 30 | tools.checkInstance(app) 31 | tools.checkInstance(new Comp()) 32 | assert(app.$el.querySelectorAll('.c-comp').length == 2) 33 | }) 34 | it('Zect.namespace()', function () { 35 | Zect.namespace('r') 36 | var app = new Zect({ 37 | el: document.createElement('div'), 38 | template: '
' 39 | }) 40 | Zect.namespace('z') 41 | tools.checkInstance(app) 42 | assert(app.$el.querySelector('.tpl')) 43 | }) 44 | it('Zect.directive()', function () { 45 | /** 46 | * Add data-set attribute 47 | * @multiple 48 | */ 49 | Zect.directive('dataset', { 50 | multi: true, 51 | bind: function (key, value, expr) { 52 | expect(key).to.be.a('string') 53 | expect(expr).to.be.a('string') 54 | this.key = key 55 | }, 56 | update: function (value) { 57 | this.$el.dataset[this.key] = value 58 | } 59 | }) 60 | /** 61 | * Check value is number 62 | * @multiple 63 | */ 64 | Zect.directive('number', { 65 | bind: function (value, expr) { 66 | expect(expr).to.be.a('string') 67 | expect(value).to.be.a('number') 68 | }, 69 | update: function (value) { 70 | expect(value).to.be.a('number') 71 | } 72 | }) 73 | var app = new Zect({ 74 | el: document.createElement('div'), 75 | data: function () { 76 | return { 77 | num: 123 78 | } 79 | }, 80 | template: '
' 81 | }) 82 | tools.checkInstance(app) 83 | expect(app.$el.querySelector('.tpl').dataset.tpl).to.equal('test') 84 | }) 85 | }) 86 | 87 | describe('#Instance Options', function () { 88 | it('el', function () { 89 | var app = new Zect({ 90 | el: '#app' 91 | }) 92 | tools.checkInstance(app) 93 | assert(app.$el === document.querySelector('#app')) 94 | 95 | var el = document.createElement('div') 96 | app = new Zect({ 97 | el: el 98 | }) 99 | tools.checkInstance(app) 100 | assert(app.$el === el) 101 | 102 | app = new Zect({ 103 | el: '#app', 104 | template: '
' 105 | }) 106 | tools.checkInstance(app) 107 | assert(app.$el.querySelector('.tpl')) 108 | }) 109 | }) 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /test/tools.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | window.tools = { 4 | checkInstance: function (inst) { 5 | // element 6 | assert(inst.$el.nodeType == 1 || inst.$el.nodeType == 11) 7 | // instance properties 8 | expect(inst.$refs).to.be.an('object') 9 | expect(inst.$methods).to.be.an('object') 10 | // instance methods 11 | expect(inst.$set).to.be.a('function') 12 | expect(inst.$get).to.be.a('function') 13 | expect(inst.$watch).to.be.a('function') 14 | expect(inst.$unwatch).to.be.a('function') 15 | expect(inst.$compile).to.be.a('function') 16 | expect(inst.$component).to.be.a('function') 17 | expect(inst.$destroy).to.be.a('function') 18 | }, 19 | template: function (fn) { 20 | return fn.toString().replace(/^function\s*\(\s*\)\s*\{\s*\/\*|\*\/\s*\}$/g, '') 21 | } 22 | } --------------------------------------------------------------------------------