Last active
January 3, 2016 18:19
-
-
Save 6174/8501512 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function defineProperty(me, children, data, p){ | |
Object.defineProperty(me, p,{ | |
get:function(){ | |
if(typeof data[p] == "object") | |
return children[p]; | |
else return data[p]; | |
}, | |
set:function(v){ | |
data[p] = v ; | |
me.dispatchEvent({ | |
type:"propertyChange", | |
propertyName:p, | |
path:p | |
}); | |
} | |
}); | |
if(typeof data[p] == "object") {//犹豫要不要识别null | |
children[p] = new ViewModel(data[p]); | |
children[p].addEventListener("propertyChange",function(e){ | |
me.dispatchEvent({ | |
type:"propertyChange", | |
propertyName:p, | |
path:p+"."+e.path | |
}); | |
}) | |
} | |
} | |
//这个new不new都没所谓! | |
function ArrayViewModel(data,parent) { | |
var me = new Array(data.length); | |
for(var i = 0; i < data.length; i++) { | |
defineProperty(me, {}, data, i); | |
} | |
EventSource.call(me); | |
return me; | |
} | |
function ViewModel(data,parent) { | |
if( Array.isArray(data)) | |
return new ArrayViewModel(data,parent); | |
for(var p in data) { | |
if(data.hasOwnProperty(p)) { | |
defineProperty(this, {}, data, p); | |
} | |
} | |
EventSource.call(this); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function EventSource() { | |
var eventHandlers = {}; | |
this.addEventListener = function (type, handler) { | |
if (!eventHandlers[type]) { | |
eventHandlers[type] = []; | |
} | |
eventHandlers[type].push(handler); | |
}; | |
this.removeEventListener = function (type, handler) { | |
if (!eventHandlers[type]) { | |
return; | |
} | |
eventHandlers[type] = eventHandlers[type].filter(function (f) { | |
return f != handler; | |
}) | |
}; | |
this.dispatchEvent = function (e) { | |
if (eventHandlers.hasOwnProperty(e.type)) { | |
eventHandlers[e.type].forEach(function (f) { | |
f.call(this, e); | |
}) | |
} | |
if (this["on" + e.type]) { | |
this["on" + e.type](e); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function HTMLTemplate(str){ | |
var input = null; | |
var EOF = {}; | |
var element = null; | |
var attr = ""; | |
var attributeNode = null; | |
var state = data; | |
var text = null; | |
var tag = ""; | |
var errors = []; | |
var isEndTag = false; | |
var stack = []; | |
var i; | |
var attrSetter = null; | |
var parameterName = null; | |
var parameters = null; | |
function AttributeSetter(attributeNode) { | |
this.parts = []; | |
this.appendPart = function(part){ | |
this.parts.push(part); | |
} | |
this.apply = function(){ | |
attributeNode.value = this.parts.join(""); | |
} | |
} | |
function consumeCharacterReference(additionalAllowedCharacter){ | |
var c = input[i]; | |
if(c=="\t"||c=="\n"||c=="\f"||c==" "||c=="<"||c=="&"||c==EOF||c==additionalAllowedCharacter) { | |
return; | |
} | |
if(c=="#") { | |
c = consume(1); | |
var range = /[0-9]/; | |
var num = []; | |
var n = 10; | |
if(c=="x"||c=="X") { | |
c = consume(1); | |
range = /[a-fA-F0-9]/; | |
n = 16; | |
} | |
while(c.match(range)) { | |
num.push(c); | |
c=consume(1) | |
} | |
if(c==";") | |
consume(1); | |
return String.fromCharCode(parseInt(num.join(""),n)); | |
} | |
} | |
function unconsume(n) { | |
i-=n; | |
return input[i]; | |
} | |
function consume(n) { | |
i+=n; | |
return input[i]; | |
} | |
function next(n) { | |
if(i+n>input.length) | |
return input.slice(i,input.length-i); | |
input.slice(i,n); | |
} | |
function error(){ | |
} | |
function _assert(flag) { | |
if(!flag) | |
debugger; | |
} | |
var data = function(c){ | |
if(c=="&") { | |
c = consumeCharacterReference(); | |
if(c) { | |
if(text) | |
text.appendData(c); | |
else { | |
text = document.createTextNode(c); | |
element.appendChild(text); | |
} | |
} | |
else { | |
if(text) | |
text.appendData("&"); | |
else { | |
text = document.createTextNode("&"); | |
element.appendChild(text); | |
} | |
} | |
return data; | |
} | |
else if(c=="<") { | |
text = null; | |
return tagOpen; | |
} | |
else if(c=="\0") { | |
error(); | |
} | |
else if(c=="$") { | |
return afterDollarInText; | |
} | |
else if(c==EOF) { | |
text = null; | |
} | |
else { | |
if(text) | |
text.appendData(c); | |
else { | |
text = document.createTextNode(c); | |
element.appendChild(text); | |
} | |
return data; | |
} | |
}; | |
var tagOpen = function(c){ | |
isEndTag = false; | |
if(c=="!") { | |
error(); | |
} | |
else if(c=="/") { | |
isEndTag = true; | |
return endTagOpen; | |
} | |
else if(c.match(/[a-zA-Z]/)) { | |
tag = c.toLowerCase(); | |
return tagName; | |
} | |
else if(c=="?") { | |
error(); | |
} | |
else { | |
error(); | |
return data("<"); | |
} | |
}; | |
var endTagOpen = function(c){ | |
if(c.match(/[a-zA-Z]/)) { | |
tag = c.toLowerCase(); | |
return tagName; | |
} | |
else if(c==">") { | |
error(); | |
return data; | |
} | |
else if(c==EOF) { | |
error(); | |
data("<"); | |
data("/"); | |
return data(EOF); | |
} | |
else { | |
return bogusCommentState; | |
} | |
}; | |
var tagName = function(c){ | |
if(c=="\n"||c=="\f"||c=="\t"||c==" ") { | |
stack.push(element); | |
element = document.createElement(tag); | |
stack[stack.length-1].appendChild(element); | |
return beforeAttributeName; | |
} | |
else if(c=="/") { | |
stack.push(element); | |
element = document.createElement(tag); | |
stack[stack.length-1].appendChild(element); | |
return selfclosingStartTag; | |
} | |
else if(c==">") { | |
if(isEndTag) { | |
_assert(tag == element.tagName.toLowerCase()); | |
element = stack.pop(); | |
} | |
else { | |
stack.push(element); | |
element = document.createElement(tag); | |
stack[stack.length-1].appendChild(element); | |
} | |
return data; | |
} | |
else if(c.match(/[a-zA-Z]/)) { | |
tag += c.toLowerCase(); | |
return tagName; | |
} | |
else if(c=="\0") { | |
error(); | |
tag += "\uFFFD"; | |
return tagName; | |
} | |
else if(c==EOF) { | |
error(); | |
return data(EOF); | |
} | |
else { | |
return rcdata; | |
} | |
}; | |
var beforeAttributeName = function(c){ | |
if(c=="\n"||c=="\f"||c=="\t"||c==" ") { | |
return beforeAttributeName; | |
} | |
else if(c=="/") { | |
return selfclosingStartTag; | |
} | |
else if(c==">") { | |
return data; | |
} | |
else if(c.match(/[a-zA-Z]/)) { | |
attr += c.toLowerCase(); | |
return attributeName; | |
} | |
else if(c=="\0") { | |
return data; | |
} | |
else if(c=="\""||c=="\'"||c=="<"||c=="=") { | |
return data; | |
} | |
else if(c==EOF) { | |
return data(EOF); | |
} | |
else { | |
attr = ""; | |
return attributeName; | |
} | |
}; | |
var attributeName = function(c){ | |
if(c=="\n"||c=="\f"||c=="\t"||c==" ") { | |
attributeNode = document.createAttribute(attr); | |
element.setAttributeNode(attributeNode); | |
attr = ""; | |
return afterAttributeName; | |
} | |
else if(c=="/") { | |
attributeNode = document.createAttribute(attr); | |
element.setAttributeNode(attributeNode); | |
attr = ""; | |
return selfclosingStartTag; | |
} | |
else if(c=="=") { | |
attributeNode = document.createAttribute(attr); | |
element.setAttributeNode(attributeNode); | |
attr = ""; | |
return beforeAttributeValue; | |
} | |
else if(c==">") { | |
attributeNode = document.createAttribute(attr); | |
element.setAttributeNode(attributeNode); | |
attr = ""; | |
return data; | |
} | |
else if(c.match(/[a-zA-Z]/)) { | |
attr += c.toLowerCase(); | |
return attributeName; | |
} | |
else if(c=="\0") { | |
error(); | |
attr += "\uFFFD"; | |
return attributeName; | |
} | |
else if(c=="\""||c=="\'"||c=="<") { | |
error(); | |
attr += c; | |
return attributeName; | |
} | |
else if(c==EOF) { | |
error(); | |
return data(EOF); | |
} | |
else { | |
attr += c; | |
return attributeName; | |
} | |
}; | |
var afterAttributeName = function(c){ | |
if(c=="\n"||c=="\f"||c=="\t"||c==" ") { | |
return afterAttributeName; | |
} | |
else if(c=="/") { | |
return selfclosingStartTag; | |
} | |
else if(c=="=") { | |
return beforeAttributeName; | |
} | |
else if(c==">") { | |
return data; | |
} | |
else if(c.match(/[a-zA-Z]/)) { | |
attr = c.toLowerCase(); | |
return attributeName; | |
} | |
else if(c=="\0") { | |
error(); | |
attr = "\uFFFD"; | |
return attributeName; | |
} | |
else if(c=="\""||c=="\'"||c=="<") { | |
error(); | |
attr = c; | |
return attributeName; | |
} | |
else if(c==EOF) { | |
error(); | |
return data(EOF); | |
} | |
else { | |
attr = c; | |
return attributeName; | |
} | |
}; | |
var beforeAttributeValue = function(c){ | |
if(c=="\n"||c=="\f"||c=="\t"||c==" ") { | |
return afterAttributeName; | |
} | |
else if(c=="\"") { | |
return attributeValueDQ; | |
} | |
else if(c=="&") { | |
return attributeValueUQ(c); | |
} | |
else if(c=="'") { | |
return attributeValueSQ; | |
} | |
else if(c=="\0") { | |
error(); | |
attributeNode.value += "\uFFFD"; | |
return attributeValueUQ; | |
} | |
else if(c==">") { | |
error(); | |
return data; | |
} | |
else if(c=="`"||c=="<"||c=="=") { | |
error(); | |
return attributeName; | |
} | |
else if(c==EOF) { | |
error(); | |
return data(EOF); | |
} | |
else { | |
attributeNode.value += c; | |
return attributeValueUQ; | |
} | |
}; | |
var attributeValueDQ = function(c){ | |
if(c=="\"") { | |
if(attrSetter) { | |
attrSetter.appendPart(attributeNode.value); | |
} | |
attrSetter = null; | |
return afterAttributeValueQ; | |
} | |
else if(c=="$") { | |
return afterDollarInAttributeValueDQ; | |
} | |
else if(c=="&") { | |
var c = consumeCharacterReference("\""); | |
if(!c) | |
attributeNode.value += "&"; | |
else | |
attributeNode.value += c; | |
return attributeValueDQ; | |
} | |
else if(c=="\0") { | |
error(); | |
attributeNode.value += "\uFFFD"; | |
return attributeValueUQ; | |
} | |
else if(c==EOF) { | |
error(); | |
return data(EOF); | |
} | |
else { | |
attributeNode.value += c; | |
return attributeValueDQ; | |
} | |
}; | |
var attributeValueSQ = function(c){ | |
if(c=="\'") { | |
if(attrSetter) { | |
attrSetter.appendPart(attributeNode.value); | |
} | |
attrSetter = null; | |
return afterAttributeValue; | |
} | |
else if(c=="$") { | |
return afterDollarInAttributeValueSQ; | |
} | |
else if(c=="&") { | |
var c = consumeCharacterReference("\'"); | |
if(!c) | |
attributeNode.value += "&"; | |
else | |
attributeNode.value += c; | |
return attributeValueUQ; | |
} | |
else if(c=="\0") { | |
error(); | |
attributeNode.value += "\uFFFD"; | |
return attributeValueUQ; | |
} | |
else if(c==EOF) { | |
error(); | |
return data(EOF); | |
} | |
else { | |
attributeNode.value += c; | |
return attributeValueSQ; | |
} | |
}; | |
var attributeValueUQ = function(c){ | |
if(c=="\n"||c=="\f"||c=="\t"||c==" ") { | |
if(attrSetter) { | |
attrSetter.appendPart(attributeNode.value); | |
} | |
attrSetter = null; | |
return beforeAttributeName; | |
} | |
else if(c=="$") { | |
return afterDollarInAttributeValueUQ; | |
} | |
else if(c=="&") { | |
consumeCharacterReference(">"); | |
return attributeValueUQ; | |
} | |
else if(c=="\0") { | |
error(); | |
attributeNode.value += "\uFFFD"; | |
return attributeValueUQ; | |
} | |
else if(c=="\""||c=="'"||c=="<"||c=="="||c=="`") { | |
error(); | |
attributeNode.value += c; | |
return attributeValueUQ; | |
} | |
else if(c==EOF) { | |
return data(EOF); | |
} | |
else { | |
attributeNode.value += c; | |
return attributeValueUQ; | |
} | |
}; | |
var afterAttributeValueQ = function(c){ | |
if(c=="\n"||c=="\f"||c=="\t"||c==" ") { | |
return beforeAttributeName; | |
} | |
else if(c=="/") { | |
return selfclosingStartTag; | |
} | |
else if(c==">") { | |
return data; | |
} | |
else if(c==EOF) { | |
return data(EOF); | |
} | |
else { | |
error(); | |
return beforeAttributeName(c); | |
} | |
}; | |
var selfclosingStartTag = function(c){ | |
if(c==">") { | |
element = stack.pop(); | |
return data; | |
} | |
else if(c==EOF) { | |
error(); | |
return data(EOF); | |
} | |
else { | |
error(); | |
return beforeAttributeName(c); | |
} | |
}; | |
var afterDollarInText = function(c) { | |
if(c=="{") { | |
return parameterInText; | |
} | |
else { | |
//TODO: | |
} | |
}; | |
var parameterInText = function(c) { | |
if(c=="}") { | |
text = document.createTextNode(""); | |
var name = parameterName.join("") | |
if(parameters[name]) | |
parameters[name].push(text); | |
else parameters[name] = [text]; | |
element.appendChild(text); | |
parameterName = []; | |
text = null; | |
return data; | |
} | |
else { | |
if(parameterName===null) | |
parameterName = []; | |
parameterName.push(c); | |
return parameterInText; | |
} | |
} | |
var afterDollarInAttributeValueDQ = function(c) { | |
if(c=="{") { | |
return parameterInAttributeValueDQ | |
} | |
else { | |
//TODO: | |
} | |
} | |
var afterDollarInAttributeValueSQ = function(c) { | |
if(c=="{") { | |
return parameterInAttributeValueSQ | |
} | |
else { | |
//TODO: | |
} | |
} | |
var afterDollarInAttributeValueUQ = function(c) { | |
if(c=="{") { | |
return parameterInAttributeValueUQ | |
} | |
else { | |
//TODO: | |
} | |
} | |
var parameterInAttributeValueDQ = function(c) { | |
if(c=="}") { | |
if(!attrSetter) { | |
attrSetter = new AttributeSetter(attributeNode); | |
} | |
attrSetter.appendPart(attributeNode.value); | |
attributeNode.value = ""; | |
var text = { | |
setter:attrSetter, | |
value:"", | |
set textContent(v){ | |
this.value = v; | |
this.setter.apply(); | |
}, | |
toString:function(){ return this.value;} | |
}; | |
var parameterAttr = parameterName.join("").split("|") | |
var name = parameterAttr[0] | |
if(parameters[name]) | |
parameters[name].push(text); | |
else parameters[name] = [text]; | |
parameterName = []; | |
attrSetter.appendPart(text); | |
text = null; | |
if(parameterAttr[1]) { | |
void function(element,attributeName){ | |
element.addEventListener(parameterAttr[1],function(){ | |
setBack(name,element[attributeName]) | |
},false); | |
}(element,attributeNode.name); | |
} | |
return attributeValueDQ; | |
} | |
else { | |
if(parameterName===null) | |
parameterName = []; | |
parameterName.push(c); | |
return parameterInAttributeValueDQ; | |
} | |
} | |
var parameterInAttributeValueSQ = function(c) { | |
if(c=="}") { | |
if(!attrSetter) { | |
attrSetter = new AttributeSetter(attributeNode); | |
} | |
attrSetter.appendPart(attributeNode.value); | |
attributeNode.value = ""; | |
var text = { | |
setter:attrSetter, | |
value:"", | |
set textContent(v){ | |
this.value = v; | |
this.setter.apply(); | |
}, | |
toString:function(){ return this.value;} | |
}; | |
var name = parameterName.join("") | |
if(parameters[name]) | |
parameters[name].push(text); | |
else parameters[name] = [text]; | |
parameterName = []; | |
attrSetter.appendPart(text); | |
text = null; | |
return attributeValueSQ; | |
} | |
else { | |
if(parameterName===null) | |
parameterName = []; | |
parameterName.push(c); | |
return parameterInAttributeValueSQ; | |
} | |
} | |
var parameterInAttributeValueUQ = function(c) { | |
if(c=="}") { | |
if(!attrSetter) { | |
attrSetter = new AttributeSetter(attributeNode); | |
} | |
attrSetter.appendPart(attributeNode.value); | |
attributeNode.value = ""; | |
var text = { | |
setter:attrSetter, | |
value:"", | |
set textContent(v){ | |
this.value = v; | |
this.setter.apply(); | |
}, | |
toString:function(){ return this.value;} | |
}; | |
var name = parameterName.join("") | |
if(parameters[name]) | |
parameters[name].push(text); | |
else parameters[name] = [text]; | |
parameterName = []; | |
attrSetter.appendPart(text); | |
text = null; | |
return attributeValueUQ; | |
} | |
else { | |
if(parameterName===null) | |
parameterName = []; | |
parameterName.push(c); | |
return parameterInAttributeValueUQ; | |
} | |
} | |
function parse(){ | |
input = str.split(""); | |
input.push(EOF); | |
var root = document.createDocumentFragment(); | |
state = data; | |
element = root; | |
stack = []; | |
i = 0; | |
while(i<input.length) { | |
state = state(input[i++]); | |
} | |
return root; | |
} | |
var fragment = null; | |
var setBack = function(){}; | |
this.apply = function(obj) { | |
input = null; | |
element = null; | |
attr = ""; | |
attributeNode = null; | |
state = data; | |
text = null; | |
tag = ""; | |
errors = []; | |
isEndTag = false; | |
stack = []; | |
i; | |
parameters = Object.create(null); | |
fragment = parse(str); | |
this.bind(obj); | |
setBack = function(name,value) { | |
obj[name] = value; | |
} | |
if(obj.addEventListener) { | |
obj.addEventListener("propertyChange",function(e){ | |
parameters[e.propertyName].forEach(function(textNode){ | |
textNode.textContent = obj[e.propertyName]; | |
}); | |
},false); | |
} | |
return fragment; | |
}; | |
Object.defineProperty(this,"fragment",{ | |
getter:function(){ | |
return fragment; | |
} | |
}); | |
this.bind = function(obj) { | |
if(fragment==null) | |
return; | |
Object.keys(parameters).forEach(function(prop){ | |
parameters[prop].forEach(function(textNode){ | |
textNode.textContent = obj[prop]; | |
}); | |
}); | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="EventSource.js"></script> | |
<script src="ViewModel.js"></script> | |
<script src="HTMLTemplate.js"></script> | |
<script type="text/xhtml-template" id="t"> | |
<div style="background:rgb(${r},${g},${b});width:100px;height:100px;"></div> | |
<input value="${r|input}" type="text"/> | |
<input value="${g|input}" /> | |
<input value="${b|input}" /> | |
</script> | |
<div id="container"> | |
</div> | |
<script type="text/javascript"> | |
var obj = {r:255,g:200,b:255}; | |
var vm = new ViewModel(obj); | |
var tmpl = new HTMLTemplate(document.querySelector("#t").innerHTML); | |
var fragment = tmpl.apply(vm); | |
document.querySelector("#container").appendChild(fragment); | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function ArrayViewModel(data,parent) { | |
var me = new Array(data.length); | |
var children = {}; | |
for(var i = 0; i < data.length; i++) { | |
void function(p){ | |
Object.defineProperty(this,p,{ | |
get:function(){ | |
if(typeof data[p] == "object") | |
return children[p]; | |
else return data[p]; | |
}, | |
set:function(v){ | |
data[p] = v ; | |
if(typeof data[p] == "object") { | |
children[p] = new ViewModel(data[p]); | |
children[p].addEventListener("propertyChange",function(e){ | |
me.dispatchEvent({type:"propertyChange",propertyName:p,path:p+"."+e.path}); | |
}) | |
} | |
this.dispatchEvent({type:"propertyChange",propertyName:p,path:p}); | |
} | |
}); | |
if(typeof data[p] == "object") { | |
children[p] = new ViewModel(data[p]); | |
children[p].addEventListener("propertyChange",function(e){ | |
me.dispatchEvent({type:"propertyChange",propertyName:p,path:p+"."+e.path}); | |
}) | |
} | |
}.call(me,i); | |
} | |
EventSource.call(me); | |
return me; | |
} | |
function ViewModel(data,parent) { | |
if(data instanceof Array) | |
return new ArrayViewModel(data,parent); | |
var children = {}; | |
var me = this; | |
for(var p in data) { | |
if(data.hasOwnProperty(p)) { | |
void function(p){ | |
Object.defineProperty(this,p,{ | |
get:function(){ | |
if(typeof data[p] == "object") | |
return children[p]; | |
else return data[p]; | |
}, | |
set:function(v){ | |
data[p] = v ; | |
if(typeof data[p] == "object") { | |
children[p] = new ViewModel(data[p]); | |
children[p].addEventListener("propertyChange",function(e){ | |
me.dispatchEvent({type:"propertyChange",propertyName:p,path:p+"."+e.path}); | |
}) | |
} | |
this.dispatchEvent({type:"propertyChange",propertyName:p,path:p}); | |
} | |
}); | |
if(typeof data[p] == "object") { | |
children[p] = new ViewModel(data[p]); | |
children[p].addEventListener("propertyChange",function(e){ | |
me.dispatchEvent({type:"propertyChange",propertyName:p,path:p+"."+e.path}); | |
}) | |
} | |
}.call(this,p); | |
} | |
} | |
EventSource.call(this); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment