/* * @Author: 陶秋峰 * @Date: 2016-01-03 13:58:01 * @Last Modified by: 陶秋峰 * @Last Modified time: 2016-01-03 14:08:16 * @CopyRight 蛮蛮工作室 */ import Base, {IPV} from './base'; import has from './has'; import {Hash, Handle} from './interfaces'; import * as lang from './lang'; import on, {emit, once, EventEmitter, EventCallback} from './on'; import * as parser from './parser'; import Promise from './promise'; import * as registry from './registry'; import * as fsm from 'state-machine'; import View, {IView_def} from './view'; interface box{ l: number; t: number; r: number; b: number; w: number; h: number; } const delta = 10; function in_sight(elem_pos: ClientRect, win_pos: box) { let t = elem_pos.top - delta; let b = elem_pos.bottom + delta; let t_in = t >= 0 && t <= win_pos.b; let b_in = b >= 0 && b <= win_pos.b; return t_in || b_in; }; function get_win_body(){ let doc: HTMLDocument = window.document; return doc.body || doc.getElementsByTagName('body')[0]; } function get_root(){ let doc: HTMLDocument = window.document; return (doc.compatMode == 'BackCompat') ? get_win_body() : doc.documentElement; } function doc_scroll(){ let doc: HTMLDocument = window.document; let scrollRoot = get_root(); return "pageXOffset" in window ? {x: window.pageXOffset, y: window.pageYOffset } : {x: scrollRoot.scrollLeft, y: scrollRoot.scrollTop || 0 }; } function get_win_box(): box{ let doc: HTMLDocument = window.document; if(!doc){ return; } let scrollRoot = get_root(); let scroll = doc_scroll(); let w: number; let h: number; if(has("touch")){ w = window.innerWidth || scrollRoot.clientWidth; h = window.innerHeight || scrollRoot.clientHeight; }else{ w = scrollRoot.clientWidth; h = scrollRoot.clientHeight; } let x = scroll.x; let y = scroll.y; return { l: x, t: y, r: x + w, b: y + h, w: w, h: h }; } function lazyload(node: HTMLElement) { return new Promise(function(resolve, reject){ if (!node) { reject('argument should be an HTMLElement'); return; } // 外部强制触发加载组件事件 once(node, 'init', function(){ resolve({}); }); once(window, ['scroll', 'resize'], function () { let winCoords = get_win_box(); let elem_pos = node.getBoundingClientRect(); if (in_sight(elem_pos, winCoords)) { resolve({}); } }); }); }; interface IComponent{ tpl: string; } import ai, {IFsmState} from './ai'; import * as dom_attr from './dom-attr'; import * as dom_construct from './dom-construct'; // import * as string from 'dojo/string'; import query = require('jquery'); /** * @module 组件基类:base */ class Component extends Base implements EventEmitter { public on(event: string, listener: EventCallback): EventEmitter { let handler = on(this.domNode, event, listener); this._events.push(handler); return this; } public removeListener(event: string, listener: EventCallback): EventEmitter{ // normally, you don't need to do this return this; } protected tpl: string; protected _attachPoints: string[] = []; protected _attachEvents: Handle[] = []; public id: string; private _onMap: Hash; protected domNode: HTMLElement; protected _startupWidgets: Component[]; private _events: Handle[] = []; private views: Hash = {}; private build_views(defs: IView_def[]){ let p_defs = defs || []; //let tpl: string = string.substitute(this.templateString, this); let tpl = this.tpl; if (!tpl) { return; } tpl = tpl.replace(/{{/g, ''); // 替换掉tpl模板中使用的组件参数${name} // tpl = string.substitute(tpl, this); let domNode = dom_construct.to_dom('
' + tpl + '
'); if (!domNode) { return; } let tpls:Hash = {}; let nl: HTMLElement[] = []; //只有定义presentation的才进行tpl提取 p_defs.forEach((p)=>{ let _nl = query('[data-mm-presentation="' + p.name + '"]', domNode)[0]; _nl && nl.push(_nl); }); if (!nl || nl.length === 0) { return; } //
//
p2 content
//
p3 content
//
let l = nl.reverse(); // 1.先清空结点里面的内容 l.forEach((node)=>{ let name: string = dom_attr.get(node, 'data-mm-presentation'); tpls[name] = node.innerHTML; dom_construct.empty(node); }); //let tmp:HTMLDivElement = domConstruct.create('div'); //// 2.取得所有结点的内容 //array.forEach(l, function (node) { // let name = dom_attr.get(node, 'data-mm-presentation'); // let nodes = tpls[name]; // domConstruct.place(nodes, tmp, 'only'); // tpls[name] = tmp.innerHTML; //}, this); p_defs.forEach((p)=>{ let name = p.name; let tpl = tpls[p.name] || '
'; let data = this.dts[p.data]; if (!data) { console.error('data:', p.data, 'doesn\'t exist!'); return; } this.views[name] = new View(p, tpl, data); }); // 3.构建Views的关系 p_defs.forEach((p) => { let pb = this.views[p.name]; let ppb = p.parent && this.views[p.parent]; if (pb && ppb) { pb.set_parent(ppb); ppb.add_child(pb); } }); // 4.替换tpl,将p1.field=>it.p1.field p_defs.forEach((p) => { let pb = this.views[p.name]; pb && pb.replace_field(); }); // 去掉tpl部分内容 this.tpl = domNode.innerHTML; } private state: IFsmState; private rule: string; public create(params: {[attr: string]: any;}, tpl: string, node: HTMLElement, state: any, rule: string, def: IPV){ this.pv = def; this.state = state; this.rule = rule; let ctor = this.constructor; this.tpl = tpl; let proto = ctor.prototype; let onMap = (this._onMap = {}); for(name in proto){ if(/^on.+/.test(name)){ onMap[name.substring(2).toLowerCase()] = name; } } this.domNode = node; params && lang.mixin(this, params); let id = this.id = node.getAttribute('id'); if(!id){ id = this.id = lang.uuid(5); node.setAttribute('id', id); } registry.add(this); return lazyload(node).then(() => { return this.buildRendering(); }); } private init_data(){ let dts = this.dts; let p = []; for(let dt_name in dts){ let data = dts[dt_name]; if(data.auto_load_data){ p.push(data.load_data()); } } return Promise.all(p); } private start_ai(){ return this.fsm = ai(this, this.rule, this.state, true); } protected bind_events(){ this.state.transitions.forEach((s) => { this.bind_fsm_event(s.name); }); } private fsm: fsm.StateMachine; protected bind_fsm_event(event: string | string[]){ if (lang.is_array(event)){ let evts: string[] = arguments[0]; evts.forEach((e)=>{ this.bind_fsm_event(e); }); } else if (lang.is_string(event)) { let evt = arguments[0]; this.on(evt, (e) => { (this.fsm[evt])(e); }); } } protected buildRendering(){ this.build_dts(this.pv && this.pv.dts_def); this.build_views(this.pv && this.pv.views_def); this.domNode.innerHTML = this.tpl; this._attachTemplateNodes(); this.start_ai(); this.bind_events(); let node = this.domNode; return parser.parse(node) .then((widgets: Component[]) => { this._startupWidgets = widgets; for(let i = 0; i < widgets.length; i++){ this._processTemplateNode(widgets[i], function(n,p){ return 'true'; // return n[p]; }, function(widget: Component, type: string, callback){ return widget.on(type, callback); }); } }) .then(()=>{ return this.init_data(); }) .then(()=>{ return new Promise((resolve, reject)=>{ emit(this.domNode, { type: 'beforeinit', resolve: resolve, reject: reject }); }); }) .then(()=>{ this.do_render(); return new Promise((resolve, reject)=>{ emit(this.domNode, { type: 'afterinit', resolve: resolve, reject: reject }); }); // let d2 = new Deferred(); // this.onAfterInit(d2); // return d2; }); /* let d1 = new Deferred(); this.onBeforeInit(d1); return d1.then(lang.hitch(this, '_init_data')).then(lang.hitch(this, function () { })).then( lang.hitch(this, function () { me.onReady(); this.setCacheTpl(p_tpl); })).always(function () { if (has('host-node')) { let views = me.get_views(); array.forEach(views, function (view) { view.rebuild(null); }); } }); })).otherwise(function (err) { console.error(err); }); */ } private do_render(){ let ps = this.views; for(let p_name in ps){ let p = ps[p_name]; if (p && p.parents.length === 0) { let dom = p.render(this.domNode, true, 'only'); if (dom) { //顶级presentation中绑定的事件,先移除标识 let event_str: string; if (dom_attr.has(dom, 'data-mm-attach-event')) { event_str = dom_attr.get(dom, 'data-mm-attach-event'); dom_attr.remove(dom, 'data-mm-attach-event'); } this._parse(dom); event_str && dom_attr.set(dom, 'data-mm-attach-event', event_str); event_str = ''; } } } for(let view_name in this.views) { // 设置为自动渲染,组件内部调用render方法时忽略autorender参数 let view = this.views[view_name]; view.autorender = true; } } _parse(p_node: HTMLElement) { parser.parse(p_node) .then((widgets) => { this._attachTemplateNodes(p_node); widgets.forEach((widget) => { this._processTemplateNode(widget, (function (n, p) { return n[p]; }), (function (widget: Component, type, callback) { return widget.on(type, callback); })); }); }); } private _attachTemplateNodes(rootNode = this.domNode){ let node: Element = rootNode; while(node){ if(node.nodeType == 1 && (this._processTemplateNode(node, function(n: HTMLElement, p: string){ return n.getAttribute(p); }, this._attach)) && node.firstElementChild){ node = node.firstElementChild; }else{ if(node == rootNode){ return; } while(!node.nextSibling){ node = node.parentElement; if(node == rootNode){ return; } } node = node.nextElementSibling; } } } private _processTemplateNode(base_node: HTMLElement | Component, getAttrFunc?: (base_node: HTMLElement | Component, attr_name: string) => string, attachFunc?: (base_node: HTMLElement | Component, event: string, fun: any) => any): boolean { if(getAttrFunc(base_node, "data-mm-type")){ return true; } let ret = true; let attachPoint = getAttrFunc(base_node, "data-mm-attach-point"); if(attachPoint){ let point; let points = attachPoint.split(/\s*,\s*/); while((point = points.shift())){ if(lang.is_array(this[point])){ this[point].push(base_node); }else{ this[point] = base_node; } ret = (point != "containerNode"); this._attachPoints.push(point); } } let attachEvent = getAttrFunc(base_node, "data-mm-attach-event"); if(attachEvent){ let event: string; let events = attachEvent.split(/\s*,\s*/); while((event = events.shift())){ if(event){ let funcNameArr = event.split(":"); event = funcNameArr[0].trim(); let onevent = funcNameArr[1].trim(); this._attachEvents.push(attachFunc(base_node, event, (...args: any[])=>{ emit(this, {type: onevent, args: args}); } ) ); } } } return ret; } protected _attach(node: HTMLElement, type: string, func){ type = type.replace(/^on/, "").toLowerCase(); return on(node, type, func); } protected destroyRendering(){ this._attachPoints.forEach((point): void => { delete this._attachPoints; }); this._attachPoints = []; this._attachEvents.forEach((handler): void => { handler.destroy(); }); this._attachEvents = []; } public destroy(){ registry.remove(this.id); this._events.forEach(function(handler){ handler.destroy(); }); } } export = Component