├── README.md ├── foio.js └── test.html /README.md: -------------------------------------------------------------------------------- 1 | mmvm-dom 2 | 3 | -------------------------------------------------------------------------------- /foio.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | /********************全局数据结构begin****************************/ 3 | var foio = {}; 4 | //用于 5 | var rmsAttr = /foio-(\w+)-?(.*)/; 6 | var openTag = '{{'; 7 | var closeTag = '}}'; 8 | foio.vmodels = []; 9 | //每个指令相关的操作 10 | var directives = { 11 | //model指令相关操作 12 | model: function(binding) { 13 | var elem = binding.element; 14 | var closetVmodel = getClosetVmodel(binding); 15 | if (!binding.xtype) { 16 | binding.xtype = elem.tagName === "SELECT" ? "select" : 17 | elem.type === "checkbox" ? "checkbox" : 18 | elem.type === "radio" ? "radio" : 19 | /^change/.test(elem.getAttribute("data-duplex-event")) ? "change" : 20 | "input"; 21 | } 22 | 23 | binding.bound = function(type, callback) { 24 | if (elem.addEventListener) { 25 | elem.addEventListener(type, callback, false); 26 | } else { 27 | elem.attachEvent("on" + type, callback); 28 | } 29 | }; 30 | 31 | var updateVModel = function(e) { 32 | var val = elem.value; 33 | //搜索vmodels 34 | closetVmodel[binding.expr] = val; 35 | }; 36 | 37 | switch (binding.xtype) { 38 | case "input": 39 | binding.bound('input', updateVModel); 40 | binding.bound('DOMAutoComplete', updateVModel); 41 | elem.value = closetVmodel.binder[binding.expr].apply(binding); 42 | binding.updateView = function(newVal) { 43 | elem.value = newVal; 44 | }; 45 | break; 46 | } 47 | }, 48 | 49 | //text指令相关操作 50 | text: function(binding) { 51 | binding.updateView = function(newVal) { 52 | binding.element.nodeValue = newVal; 53 | }; 54 | //初始化绑定 55 | var closetVmodel = getClosetVmodel(binding); 56 | binding.element.nodeValue = closetVmodel.binder[binding.expr].apply(binding); 57 | }, 58 | }; 59 | /********************全局数据结构end****************************/ 60 | 61 | 62 | function getClosetVmodel(binding) { 63 | var vmodels = binding.vmodels; 64 | //搜索vmodels 65 | for (var i = binding.vmodels.length - 1; i >= 0; i--) { 66 | var vmodel = binding.vmodels[i]; 67 | if (vmodel.hasOwnProperty(binding.expr)) { 68 | return vmodel; 69 | } 70 | } 71 | } 72 | 73 | /********************节点扫描相关函数begin****************************/ 74 | foio.scan = function(elem, vmodel) { 75 | var elem = (elem || window.document.body); 76 | var vmodels = vmodel ? [].concat(vmodel) : []; 77 | scanTag(elem, vmodels); 78 | }; 79 | 80 | function scanTag(elem, vmodels) { 81 | //首先扫描节点上的foio-controller属性 82 | var cnode = elem.getAttributeNode('foio-controller'); 83 | //当节点有controller标记时 84 | if (cnode) { 85 | //防止后续属性扫描时被重复处理 86 | var newVmodel = foio.vmodels[cnode.value]; 87 | //controller未定义 88 | if (!newVmodel) { 89 | return; 90 | } 91 | //形成controller的作用域链接,方便像父scope搜索变量 92 | vmodels = [newVmodel].concat(vmodels); 93 | elem.removeAttribute(cnode.name); 94 | } 95 | //扫描其他属性 96 | scanAttr(elem, vmodels); 97 | } 98 | 99 | function scanText(textNode, vmodels) { 100 | var bindings = []; 101 | var tokens = scanExpr(textNode.data); 102 | var docFragment = document.createDocumentFragment(); 103 | if (tokens.length) { 104 | for (var i = 0, token; (token = tokens[i++]);) { 105 | var node = document.createTextNode(token.value); 106 | if (token.expr) { 107 | token.expr = token.value; 108 | token.type = 'text'; 109 | token.element = node; 110 | bindings.push(token); 111 | } 112 | docFragment.appendChild(node); 113 | } 114 | } 115 | textNode.parentNode.replaceChild(docFragment, textNode); 116 | 117 | if (bindings.length) { 118 | executeBindings(bindings, vmodels); 119 | } 120 | } 121 | 122 | 123 | function scanExpr(str) { 124 | var tokens = [], 125 | value, start = 0, 126 | stop; 127 | do { 128 | stop = str.indexOf(openTag, start); 129 | if (stop === -1) { 130 | break; 131 | } 132 | value = str.slice(start, stop); 133 | if (value) { 134 | tokens.push({ 135 | value: value, 136 | expr: false 137 | }); 138 | } 139 | start = stop + openTag.length; 140 | stop = str.indexOf(closeTag, start); 141 | if (stop === -1) { 142 | break; 143 | } 144 | value = str.slice(start, stop); 145 | if (value) { 146 | tokens.push({ 147 | value: value, 148 | expr: true 149 | }); 150 | } 151 | start = stop + closeTag.length; 152 | } while (1); 153 | 154 | value = str.slice(start); 155 | if (value) { 156 | tokens.push({ 157 | value: value, 158 | expr: false 159 | }); 160 | } 161 | return tokens; 162 | } 163 | 164 | 165 | function scanAttr(elem, vmodels) { 166 | var bindings = []; 167 | var match = false; 168 | /* 169 | 当vmodels.length为0时,表示还没有遍历到foio-controller节点, 170 | 因此不需要扫描本节点的其他属性 171 | */ 172 | if (vmodels.length) { 173 | var attributes = elem.getAttributes ? elem.getAttributes(elem) : elem.attributes; 174 | //遍历节点的属性 175 | for (var i = 0, attr; (attr = attributes[i++]);) { 176 | //如果已指定属性specified为true 177 | if (attr.specified) { 178 | //获取指令,其中rmsAttr = /foio-(\w+)-?(.*)/ 179 | if ((match = attr.name.match(rmsAttr))) { 180 | var type = match[1]; 181 | var param = match[2] || ""; 182 | var value = attr.value; 183 | var name = attr.name; 184 | //存在相关指令 185 | if (directives[type]) { 186 | var binding = { 187 | type: type, 188 | param: param, 189 | element: elem, 190 | name: name, 191 | expr: value, 192 | }; 193 | bindings.push(binding); 194 | } 195 | } 196 | } 197 | } 198 | 199 | //处理绑定 200 | if (bindings.length) { 201 | executeBindings(bindings, vmodels); 202 | } 203 | } 204 | scanChildNodes(elem, vmodels); 205 | } 206 | 207 | 208 | function scanChildNodes(parent, vmodels) { 209 | var nodes = Array.prototype.slice.call(parent.childNodes); 210 | for (var i = 0, node; (node = nodes[i++]);) { 211 | switch (node.nodeType) { 212 | case 1: 213 | scanTag(node, vmodels); 214 | break; 215 | case 3: 216 | scanText(node, vmodels); 217 | break; 218 | } 219 | } 220 | } 221 | 222 | 223 | function executeBindings(bindings, vmodels) { 224 | for (var i = 0, binding; (binding = bindings[i++]);) { 225 | binding.vmodels = vmodels; 226 | directives[binding.type](binding); 227 | }; 228 | } 229 | 230 | /********************节点扫描相关函数end****************************/ 231 | 232 | foio.controller = function(options) { 233 | var $id = options.$id; 234 | //id不能为空 235 | if (!$id) { 236 | throw Error('controller必须指定id'); 237 | } 238 | //$id不能重复 239 | if (foio.vmodels[$id]) { 240 | throw Error('重复定义controller:' + $id); 241 | } 242 | var vmodel = modelFactory(options); 243 | return foio.vmodels[$id] = vmodel; 244 | }; 245 | 246 | function modelFactory(options) { 247 | //要返回的对象 248 | var $vmodel = {}; 249 | for (var name in options) { 250 | var value = options[name]; 251 | //通过vmodle的binder,处理依赖注册 252 | if (!$vmodel['binder']) { 253 | $vmodel['binder'] = {}; 254 | } 255 | //忽略特殊model 256 | if (name.charAt(0) !== "$") { 257 | var accessor = makeAccessor(name, value); 258 | $vmodel = Object.defineProperty($vmodel, name, accessor); 259 | $vmodel['binder'][name] = accessor.get; 260 | } 261 | } 262 | return $vmodel; 263 | } 264 | 265 | function makeAccessor(name, value) { 266 | //依赖数组 267 | var dependencyList = []; 268 | var oldVal = value; 269 | return { 270 | get: function() { 271 | if (this.element && !this.$active) { 272 | dependencyList.push(this); 273 | this.$active = true; 274 | } 275 | return value; 276 | }, 277 | set: function(newVal) { 278 | if (oldVal === newVal) { 279 | return; 280 | } 281 | oldVal = newVal; 282 | for (var dependIdx in dependencyList) { 283 | if (dependencyList[dependIdx].$active) { 284 | dependencyList[dependIdx].updateView(newVal); 285 | } 286 | } 287 | }, 288 | enumerable: true, 289 | configurable: true 290 | }; 291 | } 292 | 293 | window.foio = foio; 294 | })(); 295 | 296 | //DOM加载完成后开始扫描DOM 297 | document.addEventListener("DOMContentLoaded", function(event) { 298 | foio.scan(window.document.body); 299 | }); 300 | -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 |你的名字:{{nickname}}
35 |你的年龄:{{age}}
36 |你的自我介绍:{{profile}}
37 |