web-es6
Version:
web es6 widget data-bind event-bind
464 lines (437 loc) • 13.9 kB
JavaScript
/**
* 兼容设置
*/
if(!window.Symbol){
window.Symbol=(v)=>v;
}
if(!Object.assign){
Object.assign=function(target, ...from){
from.forEach((o)=>{
if(typeof o=='object'){
for(let i in o){
target[i]=o[i];
}
}
});
return target;
}
}
const hasProxy=(!!window.Proxy);
const isString=(v)=>(v && v.constructor.name) ?v.constructor.name==='String' :(typeof v==='string');
const eventOptionsParameter=false; // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
const clear=true;
/**
* 常量属性设置
*/
const _id_=Symbol('id');
const _proxy_=Symbol('proxy');
const _colon_=Symbol('colon');
const _tags_=Symbol('tags');
const _event_=Symbol('event');
const _calc_=Symbol('calc');
/**
* 组件命名空间
*/
const nsid={};
const getNSID=()=>{
let id=(new Date().getTime())+':'+(Math.round(Math.random()*10000));
return (nsid[id]) ?getNSID() :id;
};
/**
* 组件属性读写代理
* @type {{get: proxy_widget.get, set: proxy_widget.set}}
*/
const proxy_widget={
get:function(target, property/*, receiver*/){
if(target[_calc_] && target[_calc_][property]){
return target[_calc_][property]();
}
return target[property];
},
set:function(target, property, value/*, receiver*/){
return target.set(property, value);
},
};
/**
* 组件工厂
* @param prototype
* @returns {create}
*/
const widget=function(prototype){
/**
* 生成组件
* @param setup
* @returns {{el:dom, set:function, fire:function, event:function}}
*/
let create=function(setup){
let pt=Object.assign({}, prototype, setup);
let _id=getNSID(), instance=clear ?{[_id_]:_id} :Object.assign({[_id_]:_id}, pt);
nsid[_id]=instance;
instance.eventName=(event)=>_id+':'+event;
instance[_proxy_]=hasProxy ?new Proxy(instance, proxy_widget) :instance;
if(typeof pt.el !=='undefined'){
if(isString(pt.el)){
instance.el=document.querySelector(pt.el);
if(!instance.el) throw new Error(`没有找到指定的el[${pt.el}],请确认`);
}
else if(pt.el)instance.el=pt.el;
else throw new Error('没有找到指定的el,请确认');
delete pt.el;//当指定el为未找到的对象,防止污染
}else instance.el=document.createElement('div');
instance.el.widget=hasProxy ?instance[_proxy_] :instance;
instance.set=(property, value)=>{
let ov=instance[property];
instance[property]=value;
instance.fire('data:'+property, value, ov);//触发属性修改事件
instance.fire('data', property, value, ov);//触发所有属性修改事件
return true;
};
instance[_event_]={};
instance.fire=(name, ...data) =>{
if(!isString(name) && name['name']){//事件已经发生,仅仅做转义处理
if(instance[_event_][name['name']]) instance[_event_][name['name']](name.e, ...data);
}else{//不存在此事件,需要产生一个新的事件用来冒泡
instance.el.dispatchEvent(new CustomEvent(instance.eventName(name), {
bubbles:true,
cancelable:true,
detail:data
}));
}
};
instance.event=(name, fun, options=false)=>{
let [, selector=false] =name.split('|');
instance[_event_][name]=fun.bind(hasProxy ?instance[_proxy_] :instance);
if(name=='init' && eventOptionsParameter) options={once:true};
let make=(dom, selector, fn)=>{
return function(e){
if(selector){
let filter=false;
dom.querySelectorAll(selector).forEach((d)=>{
if(d==e.target) filter=true;
});
if(!filter) return false;
}
fn(e, ...Array.from(e.detail));
};
};
instance.el.addEventListener(instance.eventName(name), make(instance.el, selector, instance[_event_][name]), options);
};
if(pt.event){//todo event, method, data 格式化写作
if(typeof pt.event==='function') pt.event=pt.event();
if(typeof pt.event!=='object') throw new Error('事件格式不正确,请确认event为对象或返回为对象');
for(let e in pt.event){
instance.event(e, pt.event[e]);
}
delete pt.event;
}
if(clear){
instance=Object.assign(instance, pt);
delete instance.template;
delete instance.style;
delete instance.tag;
}
if(pt.calc){
if(typeof pt.calc!=='object') throw new Error('计算属性设置格式不正确,请确认calc为对象');
instance[_calc_]={};
for(let c in pt.calc){
instance[_calc_][c]=pt.calc[c].bind(hasProxy ?instance[_proxy_] :instance);
instance[c]=instance[_calc_][c]();
}
}
if(widget.$){//兼容
instance.$el=widget.$(instance.el);
instance.$=(sel)=>widget.$(sel, instance.el);
}
//绑定x-tag事件和数据
let checkXTag=(el)=>{
if(el && el.tagName && !el.widget){
let name=el.tagName.toLowerCase();
widget[_tags_][name] && new widget[_tags_][name](Object.assign({el:el, isXTag:true}, getAttr(el)));
}
};
let update=(el)=>{
checkXTag(el);
if(el && el.attributes){
for(let de in widget[_colon_]){
if(el.attributes[':'+de] && !el[':'+de]){//如果存在 :bind :if 之类的属性名 并且没有设置过
el[':'+de]=true;//防止再次生成
widget[_colon_][de](hasProxy ?instance[_proxy_] :instance, el, el.attributes[':'+de], instance);
el.removeAttribute(':'+de);
}
}
}
if(!el.widget && el.children){
for(let i=0; i<el.children.length; i++){
update(el.children[i]);
}
}
};
instance.el.addEventListener('DOMNodeInserted', function(){
return (e)=>{
update(e.target);
e.stopPropagation();
};
}(), false);
if(pt.isXTag && instance.el.innerHTML.trim().length){
let tpl=instance.el.innerHTML;
instance.el.innerHTML='';
instance.el.innerHTML=tpl;
}else if(pt.template){
let html=pt.template;
if(typeof pt.template==='function') html=pt.template(instance);
instance.el.innerHTML=html;
}
if(pt.style){
let style=document.createElement('style');
style.innerHTML=pt.style;
style.setAttribute('scoped', true);
instance.el.appendChild(style);
}
instance.fire('init');
//E(instance.el).one(instance.eventName('init'));
// instance.fire('render');
return hasProxy ?instance[_proxy_] :instance;
};
if(prototype['tag']) widget[_tags_][prototype['tag']]=create;
return create;
};
//---------------------------------------------------------------------------------------------------------------- x-tag
widget[_tags_]={};
widget.tag=function(name, callback){
widget[_tags_][name]=callback;
};
/**
* 整理属性列表
* @param attrs
* @returns {{}}
*/
let value=(v)=>{
if(v){
switch(v.toLowerCase()){
case 'true':
return true;
case 'false':
return false;
}
if(/^[-]?([1-9]{1,}[0-9]+|[0-9])$/.test(v)) return parseInt(v);
}
return v;
};
let getAttr=(el)=>{
if(el.hasAttributes()){
let m={};
let attrs=el.attributes;
for(let i=0; i<attrs.length; i++){
let v=attrs[i].value;
if(v.substr(0, 1)!==':') m[attrs[i].name]=v ?value(v) :attrs[i].name;
}
return m;
}else return {};
};
document.addEventListener('DOMNodeInserted', function(e){
e.stopPropagation();
if(e.target && e.target.tagName && !e.target.widget){
let name=e.target.tagName.toLowerCase();
widget[_tags_][name] && new widget[_tags_][name](Object.assign({el:e.target}, getAttr(e.target)));
}
}, false);
/**
* 渲染标签,需主动调用
*/
widget.tagRender=function(){
for(let tag in widget[_tags_]){
let dom=document.querySelectorAll(tag);
if(!dom || dom.length==0) continue;
dom.forEach(function(){
new widget[_tags_][name](Object.assign({el:this}, getAttr(this)));
});
}
};
//------------------------------------------------------------------------------------------------------------ colon
widget[_colon_]={};
widget.colon=function(name, callback){
widget[_colon_][name]=callback;
};
/**
* 绑定事件 绑定 click 事件,c 捕获 o 一次 p 不调用pD,eventName 为转义事件名,后面的都是参数
* :on="click/cop:eventname:args:args2...|mousedown:eventName2"
*/
widget.colon('on', function(instance, target, attr){
attr.value.split('|').forEach((es)=>{
let [type,event=false,...data] =es.split(':');
if(event===false){
event=type;
type='click';
}
let options=eventOptionsParameter ?{} :false;
let sP=false, pD=true;
if(type.indexOf('/')> -1){
let [type_change, sets] =type.split('/');
type=type_change;
if(eventOptionsParameter){
if(sets.indexOf('c')> -1) options['capture']=true;
if(sets.indexOf('o')> -1) options['once']=true;
if(sets.indexOf('p')> -1) options['passive']=true;
}else{
if(sets.indexOf('c')> -1) options=true;
}
if(sets.indexOf('s')> -1) sP=true;
if(sets.indexOf('d')> -1) pD=false;
}
if(target.widget){
target.widget.event(type, (e, ...data2)=>{
if(pD) e.preventDefault();
if(sP) e.stopPropagation();
if(event) instance.fire({e, 'name':event}, ...data.concat(data2));
}, options);
}else{
target.addEventListener(type, (e, ...data2)=>{//todo 拦截 or 冒泡
if(pD) e.preventDefault();
if(sP) e.stopPropagation();
if(event) instance.fire({e, 'name':event}, ...data.concat(data2));
}, options);
}
});
});
widget.colon('bind', function(instance, target, attr){
attr.value.split('|').forEach((es)=>{
let d=false;
let [prop, key=false] =es.split(':');
if(target.widget){//绑定的是一个组件根
if(!key) key=prop;
instance.event('data:'+key, (e, value)=>{
if(!d){
d=true;//防止循环触发
target.widget.set(prop, value);
//target.widget[key] =value;
d=false;
}
});
target.widget.parentWidget=instance;
target.widget.event('data:'+prop, (e, value)=>{
if(!d){
d=true;//防止循环触发
instance.set(key, value);
//instance[prop] =value;
d=false;
}
});
target.widget.set(prop, instance[prop]);
}else{
if(!key){
key=prop;
prop='innerHTML';
}
switch(prop){//fix
case 'html':
prop='innerHTML';
break;
case 'val':
prop='value';
break;
}
target['_'+prop]=instance[key];
target[prop]=instance[key] ?instance[key] :'';
instance.event('data:'+key, (e, value)=>{
if(target['_'+prop]!=value){
target['_'+prop]=value;
target[prop]=value ?value :'';
}
});
switch(target.nodeName){
case 'INPUT':
case 'TEXTAREA':
let change=()=>{
if(target['_'+prop]!=target[prop]){
target['_'+prop]=target[prop];
instance.set(key, target[prop]);
//instance[key] =target[prop];
}
};
target.addEventListener("change", change);
target.addEventListener("input", change);
target.addEventListener("keyup", change);
target.addEventListener("mouseup", change);
break;
default:
//取消html编辑双向绑定,有变量类型问题
//target.addEventListener("DOMCharacterDataModified", (de)=>{//any => string, string !!!
// if(target[prop] !=de.prevValue){
// target['_'+prop] =de.prevValue;
// instance[key] =de.prevValue;
// }
//});
}
}
});
});
let _not_=(value)=>!value;
let _yes_=(value)=>!!value;
/**
* 切换是否显示 :if="any"
*/
widget.colon('if', function(instance, target, attr){
let _do=(attr.value[0]==='!') ?_not_ :_yes_;
let key=(attr.value[0]==='!') ?attr.value.substr(1) :attr.value;
let display=(target.style.display!=='' && target.style.display!=='none') ?target.style.display :'';
let _toggle=function(bool=null){
target.style.display=bool===null
?((target.style.display=='none') ?display :'none')
:(_do(bool) ?display :'none');
};
_toggle(instance[key]);
instance.event('data:'+key, (e, value)=>_toggle(value));
});
widget.caches={template:{}};
widget.colon('for', function(instance, target, attr){
let [key, tpl_id=false] =attr.value.split(':');
let html='';
if(tpl_id){
if(widget.caches.template[tpl_id]) html =widget.caches.template[tpl_id];
else{
let tpl = document.querySelector(tpl_id);
if(!tpl) throw "错误的模板文件,无法获取模板";
html = widget.caches.template[tpl_id]=tpl.innerHTML;
tpl.parentNode.removeChild(tpl);
}
} else html=target.innerHTML;
let tpl=widget.template(html);
let render=function(data){
target.innerHTML='';
if(['number', 'undefined', 'boolean', 'string'].indexOf(typeof data)== -1){
for(let idx in data){
target.innerHTML+=tpl(data[idx], idx);
}
}
};
render(instance[key]);
instance.event('data:'+key, (e, value)=>render(value));
});
/**
* 切换样式显示 :class="any:classname classname2|!any2:classname3"
*/
widget.colon('class', function(instance, target, attr){
attr.value.split('|').forEach((es)=>{
let [prop, style=false] =es.split(':');
let _do=(prop[0]==='!') ?_not_ :_yes_;
let key=(prop[0]==='!') ?prop.substr(1) :prop;
let toggleClass=(bool=null)=>{
let cls=(style===false) ?instance[key] :style;
cls.split(' ').forEach((c)=>{
if(c.length>0) target.classList.toggle(c, _do(bool));
});
};
toggleClass(instance[key]);
instance.event('data:'+key, (e, value)=>toggleClass(value));
});
});
widget.template=function(htmlString){
let t=htmlString.trim();
t=t.replace(/\{\{\^([\w]+)\}\}([^]*?)\{\{\/\1\}\}/g, '${item["$1"]?"":`$2`}');
t=t.replace(/\{\{#([\w]+)\}\}([^]*?)\{\{\/\1\}\}/g, '${item["$1"]?`$2`:""}');
t=t.replace(/\{\{-\}\}/g, '${index?index:""}');
t=t.replace(/\{\{\.\}\}/g, '${item}');
t=t.replace(/\{\{([\w]+)\}\}/g, '${item["$1"]?item["$1"]:""}');
return eval.call(null, '(item,index)=>`'+t+'`');
};
export default widget;