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