javasript_面向组件化编程_基于datatables的封装_myTable.js

编写目的

博主课余时间在老师那边做web前端开发,也看了些开发视频,接触到了使用jQuery进行面向组件的开发。在做网页时发现:表格datatable在展示数据时使用频繁,故尝试着在此基础上进行封装,形成自己的插件,事实证明结果也是蛮好的,可以达到很大程度的复用,减少代码量和便于修改。


需要注意的是自己编写的js 是在 jQuery.datatables 和 bootstrap.datatables上进行扩展封装的。

一、首先可以想到的是定义一个Table类,声明它的构造函数:

;(function($) {
    'use strict';
    function Table() {
        
    }
})(jQuery);

其次定义了一个对象之后我们需要让它能在其他文件中使用,可是又不想污染全局环境,就使用jQuery进行扩展,注意这是组件化开发,故需要和Html的特性的dom元素结合一起使用,即使用是面向组件的:

比如页面中有如下dom结构,其实就是一个很普通的表格:

<table class="table table-bordered table-hover" id="bookTable" style="width: 100%;">
    <thead>
        <tr>
            <th>
                ...
            </th>
        </tr>
    </thead>
    <tbody>
        <!--ajax-->
    </tbody>
</table>

 那么实际使用就是:

$('#bookTable').myTable(options);

其中options是一个自定义的参数对象,以键值对的形式初始化组件。myTable就是根据jQuery扩展出来的方法。


二、清楚了这个js是要让jQuery对象进行组件化之后,就要将方法暴露给jQuery对象。可以知道jQuery提供了方法来扩展方法库

$.fn.extend();

现在就是怎么使用这个方法,能达到最大程度的复用。进一步改造,得到如下形式:

;(function($) {
    'use strict';
    function Table($elem, options) {
        this.$elem = $elem;
        this.options = options;
    }

    Table.DEFAULTS = {
        sequence: 0,
        destroy: false,
        ordering: false,
        serverSide: true,
        searching: false,
        language: {
            "sInfo": "显示第 _START_ 至 _END_ 项结果,共 _TOTAL_ 项",
            "sInfoEmpty": "显示第 0 至 0 项结果,共 0 项",
            "oPaginate": {
                "sFirst": "首页",
                "sPrevious": "上页",
                "sNext": "下页",
                "sLast": "末页"
            },
            "sEmptyTable": "无数据",
            "sLengthMenu": "显示 _MENU_ 项结果"
        }
    };

    // 单例模式 生成 表格
    $.fn.extend({
        myTable: function (option) {
            return this.each(function () {
                let $this = $(this),
                    // 合并对象
                    options = $.extend({}, Table.DEFAULTS, typeof option === "object" && option),
                    mode = $this.data("inited");

                if (!mode) {
                    $this.data("inited", mode = new Table($this, options));
                }       
            });
        }
    });
})(jQuery);

myTable就是我们扩展出来的内容,myTable是一个函数,里面的 return 需要特别注意。需要单独讲一下:

return this.each(function () {
    let $this = $(this),
    // 合并对象
    options = $.extend({}, Table.DEFAULTS, typeof option === "object" && option),
    mode = $this.data("inited");

    if (!mode) {
        $this.data("inited", mode = new Table($this, options));
    }
});

myTable中的this就是调用这个方法进行组件初始化的jQuery对象,而使用jQuery选择器选出来的对象不一定只有一个,jQuery帮我们封装好了,无论是一个还是多个jQuery对象使用each方法都是没问题的。在this.each 方法中就是每个单独的 dom对象了。这样就可以实现调用一次方法可以让被选择到的jQuery对象全部被初始化。

个人认为这个return里面分为三步:

1.先将初始化的dom对象转换为jQuery对象

2.将传入的自定义参数对象和Table.DEFAULTS默认参数对象合并,合并时使用的是:$.extend();

options = $.extend({}, Table.DEFAULTS, typeof option === "object" && option)

如果option参数类型是对象类型则进行合并,否则默认采用Tables.DEFAULTS默认参数对象

3.采用 单例模式 的思想,获取该jQuery对象上挂载data属性 ‘inited’,如果没有,则表示“我还没有被初始化。”,并且将其初始化

mode = $this.data("inited");

if (!mode) {
    $this.data("inited", mode = new Table($this, options));
}

初始化时将,new Table()对象传入data属性 'inited'中,此时inited属性有了对象值了,若存在重复初始化,mode在下一次初始化时就有值不为空,就不再为其初始化,达到单例模式的作用,避免重复初始化。

这里终于用到了我们自己写的构造函数入口,第一个参数将$this 即初始化的jQuery对象传入,第二个参数是最终得到的参数对象。

mode = new Table($this, options)

三、向外提供了入口和向内提供一个构造函数之后,可以开始搭建这个自己构建的类的骨架了。

我也不是一开始就知道这个自定义的Table类是怎么构建的,而是在做项目的过程中一步一步发现哪些是该类本身应该有的行为,哪些可以封装进来,哪些代码在编写的过程中重复了。

就是在这个过程中,我个人觉得,一个表格有这几个抽象的行为

1.表格的初始化

2.删除表格的行

3.添加表格的行

1.表格的初始化:

将简单的赋值放在Table 构造函数里面,将一些逻辑代码放在_init() 函数里面并且挂载到Table的原型上,实现行为共享,但是数据相互独立。

经过初始化的改造,现在代码长这样了:

;(function($) {
    'use strict';
    function Table($elem, options) {
        this.$elem = $elem;
        this.options = options;
        this.$table = null;

        this._init();
    }

    Table.prototype._init = function () {
        const self = this;
        this.$table = this.$elem.DataTable(this.options);
        if (this.options.serverSide) {
            this.$elem.on('draw.dt', function () {
                self.$table.column(self.options.sequence, {
                    search: 'applied',
                    order: 'applied'
                }).nodes().each(function (cell, i) {
                    //i 从0开始,所以这里先加1
                    i = i + 1;
                    //服务器模式下获取分页信息,使用 DT 提供的 API 直接获取分页信息
                    const page = self.$table.page.info();
                    //当前第几页,从0开始
                    const pageno = page.page;
                    //每页数据
                    const length = page.length;
                    //行号等于 页数*每页数据长度+行号
                    cell.innerHTML = (i + pageno * length);
                });
            });
        } else {
            this.$table.on('draw.dt', function() {
                self.$table.column(self.options.sequence, {
                    search: 'applied',
                    order: 'applied'
                }).nodes().each(function(cell, i) {
                    cell.innerHTML = i + 1;
                });
            });

        }
    };

    Table.DEFAULTS = {
        sequence: 0,
        destroy: false,
        ordering: false,
        serverSide: true,
        searching: false,
        language: {
            "sInfo": "显示第 _START_ 至 _END_ 项结果,共 _TOTAL_ 项",
            "sInfoEmpty": "显示第 0 至 0 项结果,共 0 项",
            "oPaginate": {
                "sFirst": "首页",
                "sPrevious": "上页",
                "sNext": "下页",
                "sLast": "末页"
            },
            "sEmptyTable": "无数据",
            "sLengthMenu": "显示 _MENU_ 项结果"
        }
    };

    // 单例模式 生成 表格
    $.fn.extend({
        myTable: function (option) {
            return this.each(function () {
                let $this = $(this),
                    // 合并对象
                    options = $.extend({}, Table.DEFAULTS, typeof option === "object" && option),
                    mode = $this.data("inited");

                if (!mode) {
                    $this.data("inited", mode = new Table($this, options));
                }       
            });
        }
    });
})(jQuery);

_init() 函数里面主要分两步:1.进行dataTable的初始化;2.判断dataTable是服务器端渲染还是浏览器端渲染(毕竟两者的排序号列的形成和特定的行为调用是有区别的)

完成这一步其实已经可以调用自己写的接口来调用初始化组件了:

$("#myTable").myTable({
    // 形成的序号列是第几列,从0开始
    sequence: 1,
    ajax: {
        url: CONFIG.AJAX_URL+"/book/bookPage",
        type: "POST",
        data: {"send": 0, "payStatus": 0}
    },
    rowId: "book.bookId",
    columns: [
            {
                data: "book.bookId",
                render: function (data, __, row, ___) {
                bookId = data;
                return row;
                }
            },
               ....
            ]
});

把变的部分以参数键值对的方式传入,把不变的部分封装到类中。可以看到变的部分就是:

序号列是第几列;

在服务器渲染的前提下:

ajax请求的url等;

给每行<tr>设置的id属性;

每行的内容,列columns等等。

而在非服务器渲染的前提下,初始化就更简单了,极大地减少了初始化的代码量:

memberInfoTable.myTable({
    serverSide: false,
    // 其他参数
    pageLength: 5,
    lengthChange: false
});

2.删除表格的行:

删除表格的行这个行为不需要关心表格的结构,表格的内容,不管它有多少列,删除指定行就可以了。如此就可以为自定义类添加 删除行 的行为。

 // 表格 删除某一行
Table.prototype.deleteRow = function (options) {
    const self = this;
    this.$elem.on("click", options.row, function () {
    const $this = $(this);
    mbBox.confirm({
        message: "确定要删除吗?",
        callback() {
            const id = $($this.parents().parents()[0]).attr("id");
            self._deleteRow(id, options);
            }
        });
    });
};

Table.prototype._deleteRow = function (id, options) {
    const self = this;

    for (let j in options.data) {
        if (options.data.hasOwnProperty(j)) {
            options.data[j] = id;
        }
    }

    $.ajax({
        url: options.url,
        type: "get",
        dataType: "json",
        data: options.data,
        success: function (response) {
            if (response.code === 1) {
                self.$table.row("#" + id).remove().draw(false);
            }
            mbBox.alert({
                message: response.msg
            });
        },
        error: function (error) {
            mbBox.alert({
                title: "错误",
                message: error
            });
        }
    });
};

可是现在如何才能调用这个能删除行的行为呢?我们只能在暴露接口的地方进行修改了,在观看学习视频之后,发现可以将那个return改写成这样:

// 单例模式 生成 表格
$.fn.extend({
    myTable: function (option, value) {
        return this.each(function () {
            let $this = $(this),
            // 合并对象
            options = $.extend({}, Table.DEFAULTS, typeof option === "object" && option),
            mode = $this.data("inited");

            if (!mode) {
                $this.data("inited", mode = new Table($this, options));
            }

            if (option && typeof mode[option] === "function") {
                mode[option](value);
            }
        });
    }
});

多了最后的一个if 判断,和接口入口传入的参数:

if (option && typeof mode[option] === "function") {
    mode[option](value);
}

mode 是 new Table() 对象,调用一个对象的方法除了 obj.method() 以外,如果传入的是字符串 “方法名” 的话,可以使用 obj[method]()的方式调用该方法。经此进一步改造,就可以实现删除行为的挂载了,在初始化后如果还想给表格添加删除行为:

// 封装后的删除操作
bookTable.myTable("deleteRow",{
    row: ".deleteBook",
    url: CONFIG.AJAX_URL+"/book/delBook",
    data:{
        bookId: ""
    }
});

此时传入的 option 不是一个参数对象了,而是一个方法名字符串,然后第二个参数 value就是这个方法的参数对象,value在deleteRow() 这个方法中就变成了相应的options。

 

 

;
(function ($) {
    'use strict';

    function Table($elem, options) {
        this.$elem = $elem;
        this.options = options;
        this.$table = null;

        this._init();
    }

    Table.DEFAULTS = {
        sequence: 0,
        destroy: false,
        ordering: false,
        serverSide: true,
        searching: false,
        language: {
            "sInfo": "显示第 _START_ 至 _END_ 项结果,共 _TOTAL_ 项",
            "sInfoEmpty": "显示第 0 至 0 项结果,共 0 项",
            "oPaginate": {
                "sFirst": "首页",
                "sPrevious": "上页",
                "sNext": "下页",
                "sLast": "末页"
            },
            "sEmptyTable": "无数据",
            "sLengthMenu": "显示 _MENU_ 项结果"
        }
    };

    Table.prototype._init = function () {
        const self = this;
        this.$table = this.$elem.DataTable(this.options);
        if (this.options.serverSide) {
            this.$elem.on('draw.dt', function () {
                self.$table.column(self.options.sequence, {
                    search: 'applied',
                    order: 'applied'
                }).nodes().each(function (cell, i) {
                    //i 从0开始,所以这里先加1
                    i = i + 1;

                    //服务器模式下获取分页信息,使用 DT 提供的 API 直接获取分页信息

                    const page = self.$table.page.info();
                    //当前第几页,从0开始

                    const pageno = page.page;
                    //每页数据

                    const length = page.length;
                    //行号等于 页数*每页数据长度+行号

                    cell.innerHTML = (i + pageno * length);
                });
            });
        } else {
            this.$table.on('draw.dt', function () {
                self.$table.column(self.options.sequence, {
                    search: 'applied',
                    order: 'applied'
                }).nodes().each(function (cell, i) {
                    cell.innerHTML = i + 1;
                });
            });

        }
    };

    // 刷新表格
    Table.prototype.reloadTable = function (options) {

        this.$table.settings()[0].ajax.data = options;
        this.$table.ajax.reload();
    };

    // 表格 添加一行
    Table.prototype.addRow = function (options) {
        options.data.forEach((ele) => {
            // 将 ele 传出,过滤成想要的数据形式 {id: , body: }
            let result = options.filterData(ele);
            const $rowCode = $(this.$table.row.add(result.body).node());
            $rowCode.attr("id", result.id);
            $($rowCode.find("td").get(options.idColumn)).attr("data-id", result.id);
        });
        this.$table.draw();
    };

    // 表格 删除某一行
    Table.prototype.deleteRow = function (options) {
        const self = this;
        this.$elem.on("click", options.row, function () {
            const $this = $(this);
            mbBox.confirm({
                message: "确定要删除吗?",
                callback() {
                    const id = $($this.parents().parents()[0]).attr("id");

                    self._deleteRow(id, options);
                }
            });
        });
    };

    Table.prototype._deleteRow = function (id, options) {
        const self = this;

        for (let j in options.data) {
            if (options.data.hasOwnProperty(j)) {
                options.data[j] = id;
            }
        }

        $.ajax({
            url: options.url,
            type: "get",
            dataType: "json",
            data: options.data,
            success: function (response) {
                if (response.code === 1) {
                    self.$table.row("#" + id).remove().draw(false);
                    mbBox.alert({
                        message: "删除成功!"
                    });
                } else {
                    mbBox.alert({
                        message: response.msg
                    });
                }
            },
            error: function (error) {
                mbBox.alert({
                    title: "错误",
                    message: error
                });
            }
        });
    };

    // 单例模式 生成 表格
    $.fn.extend({
        myTable: function (option, value) {
            return this.each(function () {
                let $this = $(this),
                    // 合并对象
                    options = $.extend({}, Table.DEFAULTS, typeof option === "object" && option),
                    mode = $this.data("inited");

                if (!mode) {
                    $this.data("inited", mode = new Table($this, options));
                }

                if (option && typeof mode[option] === "function") {
                    mode[option](value);
                }
            });
        }
    });
})(jQuery);

 

使用方法: 支持JAVA和PHP两种后台。 PHP:直接将WebRoot下的文件放到php服务器上直接运行即可,记得启用sqlite3。 JAVA:修改user-manage.js,将访问后台的url由"datasource.php"改为"datasource.jsp",然后将WebRoot下的文件放到tomcat下直接运行。也可使用Eclipse直接导入此项目文件。 简要说明: 使用DataTable默认的ajax交互功能,对传给后台和从后台获取的数据都有命名格式要求,这样一来耦合度较高,不利于后期扩展,不能直接适用于需要跟多种不同前端或其他业务交互的项目。本例子主要展示了自行封装请求参数和返回数据的用法,对后台的交互没有任何格式和命名限制。 基于Bootstrap 2.3.2,相关的其他插件包括图标控件FontAwesome、等待提示控件Spinjs(修改版)、弹窗控件lhgdialog(修改版) 主要展现: 封装请求参数(查询、排序、分页,不再需要data、dataFilter和dataSrc) 封装返回数据 自定义查询参数 服务器分页 自行控制和自定义遮罩效果 生成自定义效果的单元格(在线离线) 生成复选框单元格 响应复选框选择事件 生成操作按钮单元格 响应操作栏按钮点击事件 响应行点击事件 渲染回调事件(默认选中第一行) 分页栏增加跳页功能(直接修改了dataTables.bootstrap.jsdataTables.bootstrap.css) CSS实现单元格超长文字省略号显示 CSS实现单元格连续纯字母数字强制换行显示
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值