原生代码可编辑的表格 oak_DataTable.js

作者:root



/* oak_DataTable */
oak_DataTable=function(o={}){
    this.cfg = o;
    /** fetch 参数 */
    init= {method: "POST",credentials: "include", cache: "no-cache"};
    this.initializer();
}
oak_DataTable.prototype = {
    NAME:'oak_DataTable',
    cfg:{},
    data:[],
    table:null,
    datasource:[],
    tbody:null,
    _plugins:[],
    jsonData:{},
    /** 初始化函数,自动加载 css 文件,建立表格,表格头,表格体 */
    initializer:function(){
        this._getCss();
        this.table = document.createElement('table');
        this.table.classList.add('oak-editor-table');
        this.table.style.width = this.cfg.width || '100%';
        this.creatThead();
        this.tbody = this.table.appendChild(document.createElement('tbody'));
    },
    /**
     * 事件监听
     * @param {事件名称} ev 
     * @param {绑定的事件处理函数} fn 
     */
    on:function(ev,fn){this.table.addEventListener(ev,fn);},
    /**
     * 取消事件监听
     * @param {*事件名称} ev 
     * @param {*绑定的事件处理函数} fn 
     */
    detach:function(ev,fn){this.table.removeEventListener(ev,fn);},
    /**
     * 自定义时间发射
     * @param {string 发射事件名字} e 
     * @param {object 事件跟随的参数} o 
     * @param {*} canBubble 
     * @param {*} cancelable 
     */
    fire:function(e,o={},canBubble=false,cancelable=false){
        var evt = document.createEvent("HTMLEvents");
            evt.initEvent(e, canBubble, cancelable);
            Object.assign(evt,o);
            this.table.dispatchEvent(evt);
    },
   
    /**
     * 
     * @param {string 服务器url地址} url 
     * @param {Object fetch用配置参数 } init 
     * @param {function  回调函数} fn 
     * @param {选择返回的数据格式 json 或 text} format 
     */
    ajax:function(url,init,fn=null,format='json'){
        if(!url){console.log('权限不足,请重新登陆');return;}
        fetch(url,init).then((response) => {return response[format]();}).then((a) => {
            if(typeof fn =='function')fn.call(this,a);
            }).catch((e)=>{console.log('Fetch Error : ', e);});
    },
    /**
    * 
    * @param {数组 或 字符串 url} source 
    * @param {对返回json 格式化的函数} fn 
    * @param {字典} map 
    * @param {选择返回的数据格式 json 或 text} format 
    */
    upData:function(source,fn=null,map=new Map(),format='json'){
        source = source || this.cfg.datasource; 
        if(source instanceof Array)
            this.uptbody(source);
        else {
            if(source){
                if(typeof(FormData.prototype.set)=="undefined")FormData.prototype.set=FormData.prototype.append;
                init.body = new FormData();
                for(const [k, v] of map)init.body.set(k,v);
                init.body.set('postvar',JSON.stringify(this.cfg.postvar));
                this.ajax(source,init,function(a){if(fn)fn.call(this,a);else this.uptbody(a);},format);
            }else console.log('没有数据源或远程数据接口。'); 
        }
    },
    /**
     * 更新表格数据
     * @param {object,json 分析过的数据} o 
     */
    uptbody:function (o){
        var d = this.tbody;
        while(d.rows.length >0){d.deleteRow(d.rows.length-1)}
        for(i=0;i<o.length;i++){
            this.creatRow(i,o[i]);
        }
    },
    setVar:function(k,v){this.cfg.postvar[k] = v;return this;},
    delVar:function(k){delete this.cfg.postvar[k];return this;},
    render:function(n){
        n = document.querySelector(n);
        n.appendChild(this.table);
        //this.upData();
        return this;
    },
   /** */
    creatThead:function(){
        var i,th,head = this.table.createTHead(),tr = head.insertRow(),
        colgroup = document.createElement("colgroup"),cols=this.cfg.columns;
        for(i=0; i < cols.length;i++){
            let colN =  document.createElement("col");
            if(cols[i].width)colN.style.width = cols[i].width;
            colgroup.appendChild(colN);
            th = tr.appendChild(document.createElement('th'));
            th.setAttribute('data-key',cols[i].key);
            if(cols[i].onclick)
                cols[i].onclick.call(this,{node:th,col:cols[i]});
            th.innerHTML = cols[i].label;
        }
        this.table.appendChild(colgroup);
    },
    /** 建立表格行
     *  @param  { number 索引} i
     *  @param  { array  表格行用的数据} record
     */
    creatRow:function(i,record){
            var trNode = this.tbody.insertRow(),map = new Map(),cols =this.cfg.columns;
            for(j=0;j < cols.length;j++){
                map.set(cols[j].key,record[cols[j].key]);
                this.creatCell(cols[j],trNode,map);
            }
            this.data[i] = map;
    },
    /** 在表格行插入列
     *  @param  {Object 参数} column
     *  @param  {node  表格行节点} row
     *  @param  {map 字典} map
     */
    creatCell:function(col,tr,map){
        var td=tr.insertCell();
        td.setAttribute('data-key',col.key);
        td.innerHTML = map.get(col.key);
        if(col.format){col.format.call(this,{col,td,map});}
        if(col.edit){td.classList.add('cursor_pointer');td.onclick=this._onClick.bind(this,{col,td,map});}
    },
    /**
     * 表格onclick 事件处理函数
     * @param {附属参数} o 
     * @param {事件对象} e 
     */
    _onClick:function(o){
        var changeData,td=o.td,value = td.innerHTML, host = this,n = this.creatNode('<input type="text"/>'); 
        if(td.querySelector('input') || td.tagName != 'TD') return;
        td.innerHTML='';
        n.style.height = td.offsetHeight -5 +'px';
        n.value = value;
        n.blurfn = function(){
            td.innerHTML = n.value || value ;
            if(td.innerHTML == value) return;
            changeData = {'key':o.col.key,'value':n.value};
            o.map.set(o.col.key,n.value);
            if(td.parentNode.rowIndex){
                host.fire('savedata',{changeData:changeData,map:o.map});
            }
            n.remove(true);
        };
        n.onkeypress = function(e){e.which == 13 && n.blur();};
        td.appendChild(n);
        if(o.col.editformat)o.col.editformat.call(this,o);
        n.onblur=n.blurfn;
        n.focus();
    },
    /**
     * 添加插件函数
     * @param {string 插件类名字} Plugin 
     * @param {object 插件类实例化时的参数} config 
     */
    plug:function (Plugin, config) {
        Plugin.prototype.HOST =this;
        var o = new Plugin(config);
        this[o.NAME] = o;
        this._plugins[o.NAME] = Plugin;
        return this;
    },
    /* 加载同目录下的同名字CSS文件 至引用html页面 */
    _getCss:function(a=[]){
        var host=this,cssPaths=[], o = document.getElementsByTagName('head').item(0) || document.getElementsByTagName('body').item(0);
        a.forEach((function(v){cssPaths.push(this.jsPATH+v)}).bind(this));
        cssPaths.push(this.jsPATH+this.NAME+'.css');
        if (o){ 
            cssPaths.forEach(function(css){
                o.appendChild(host.creatNode('<link charset="utf-8" rel="stylesheet"  href="'+css+'" async>'));
        })};
    },
    /* 使用字符串建立节点,tr td th 除外 */
    creatNode:function (s) { 
        var n = document.createElement("div"); 
        n.innerHTML = s; 
        return n.childNodes[0]; 
    },
    /**
     * 绝对定位窗口
     * @param {顶部节点} topnode 
     * @param {加入窗口的节点及内容} node 
     * @param {*} v 
     * @param {点击事件调用函数} fn 
     */
    creatBox:function(topnode,node,fn=null){
        var host = this,box = this.creatNode('<div style="overflow:auto;" class="oeditor-toolbar-box"></div>'),
             o= document.querySelector('.oeditor-toolbar-box');
        box.appendChild(node);
        o&&o.remove(true);
        box.style.left=Math.round(topnode.getBoundingClientRect().left) +'px';
        box.style.top=Math.round(topnode.getBoundingClientRect().top)+topnode.offsetHeight+'px';
        box.style.width=Math.round(topnode.getBoundingClientRect().width-2)+'px';
        window.addEventListener('scroll',function(e){box.style.top=Math.round(topnode.getBoundingClientRect().top)+
            topnode.offsetHeight+'px';});
        box.onmouseleave=function(){box.remove();}
        box.onclick=function(e){ 
            fn.call(host,e.target);
            box.remove();
        };
        return box;
    }
}
/*
取得js路径,自动加载同目录下的同名字CSS文件使用。
*/
oak_DataTable.prototype.jsPATH = document.scripts[document.scripts.length - 1].src.substring(0, document.scripts[document.scripts.length - 1].src.lastIndexOf("/") + 1);