列表组件抽象(1)-概述

此次要介绍的是列表组件。为了写它,我花了有将近2周的晚上,才一点一点的把它写到如今这个程度。到目前为止,一共写了有17个文件,虽然没有覆盖到一些更复杂的场景,可是把我当时计划写这个组件的基本目的已经完成了。先给你们看看我最后写出来的文件状况:html

image

也许有人会好奇,一个列表的功能怎么会写出这么多东西出来?关于这个问题的答案,我稍后再来总结,先让我描述下我写这些东西以前产生的想法。前端

1. 背景介绍

我是去年5月份在上家公司开始作的前端开发,在那个公司,我花了2周的时间,尽力写了一套js的组件,用于咱们当时的一个管理系统的开发。你们都知道,在管理系统里面,最多见的两种页面类型无非就是列表页面和编辑页面。拿列表页面来讲,它里面用到的列表组件一般基于table进行开发的,当时考虑过jquery.datable这样的插件,后来放弃了。缘由是:jquery.datatable有不少多余的东西,我不须要;它的框架在使用时候的思路,跟我之前使用的其它公司开发平台里面列表组件彻底不一样,我以为我之前使用的组件思想更清晰简单。因此我就凭本身当时的能力开发出了一个能知足如下一些功能的table组件:jquery

1. 分页;2. 列宽拖拽;3. 序号列自动生成;4. 单选和多选;5. 列表头固定,以便在列表区域滚动的时候,列表头不会受滚动的影响;6. 树形列表;7. 列表各类事件等。git

后来我不单是作管理系统了,还作了更面向普通用于的网站类应用和移动端应用。这个时候又根据产品的需求开发出了三个另外的列表组件:第一个是带分页工具栏的列表组件,这个组件至关于就是前面那个table组件的缩小版,只不过html结构上再也不使用table,并且也不须要拖拽,树形控制那样的功能了;第二个在第一个的基础上去掉了分页工具栏,改为了利用window的滚动事件进行滚动翻页,固然为了留一个后路,这个组件提供了加载更多这样的手工进行下一页的按钮;第三个使用于移动端,因为移动端scroll事件必须得等到屏幕的滚动操做彻底中止下来之后才会触发,因此不能直接利用window的滚动事件,而改用了iscroll插件来模拟滚动,同时利用它提供的scroll事件来实现更加快速响应的滚动翻页功能。github

其实在我后来写第二个组件的时候,我当时就在考虑:无论是后台的列表也好,仍是前台的列表也好,除了表现出来的样子不同,它们在发送请求的方式,解析响应的方式,以及列表渲染的方式应该都是差很少的,并且在后台的列表中,相似分页排序这样的功能,在前台的列表里面也有较为常见的使用场景,既然如此,这些类似的东西,若是能抽象到一个父类里面,而后由先后台的列表组件去实现各自独有的部分,这样的代码的结构会不会更加简练一点。由于当时后台的那个列表组件,我已经写了1000多行了,本身有时候想改个东西,都要滚来滚去地阅读之前写的代码,十分不便。数据库

再拿后面的三个组件来讲,滚动分页与不滚动分页组件的区别,仅仅在于分页的控制不一样而已,若是把分页这一个部分彻底丢出去,那么剩下的部分,就不须要在两个组件里面都去实现了,只用在一个类里面实现一次。iscroll与window的scroll,也仅仅只是滚动事件的发布者不一样而已。若是我提供两个不一样的滚动翻页的组件,一个使用window scroll实现,一个使用iscroll实现,而后由列表组件去决定要使用哪一个分页组件,那么代码会更加简洁。编程

假如按以上的思路实现,我以前写的那些代码,能够获得如下这些方面的改进:json

1. 经过继承解决掉代码重复问题,并且加强了代码的可维护性;设计模式

2. 经过职责分离,好比把分页功能,排序功能从组件中分离出来,而后组件内部采用注入的方式进行使用,这样一个列表想要更换分页、排序等扩展功能就会变得很容易。api

因此这也就是我写前面截图那些文件的由来。至于为啥会有这么多个,这都是因为抽象出了一些公共的基类以及把分页,排序等功能从列表组件内分离出来以后的结果,并且这些文件实际上包含了前面提到4种列表的全部功能,因此总的文件数就比较大了。在实际使用中,要使用某一个类型的列表的时候,基本上只要使用simpleListView, tableView , scrollListView , iscrollListView便可。另外拆分红这么多文件以后,每一个文件就变得很小了,基本上都是200行左右,没有一个超过400行的,更容易阅读理解。

因为这一次的内容比较多,这篇文章没法详细的介绍全部内容,只能先把文件之间的结构以及应用场景介绍一下,后面几篇文章会针对一些我以为有必要进行解释说明的内容进行补充。欢迎感兴趣的朋友继续关注后面的文章,我会在近期陆续总结出来,好在最近的工做应该不会像以前那么忙。源代码已经上传到github,可经过下面几个地址来获取组件源码以及demo的源码:

组件源码:

https://github.com/liuyunzhuge/blog/tree/master/form/src/js/mod/listView

demo源码:

https://github.com/liuyunzhuge/blog/blob/master/form/src/js/app/listView_1.js

https://github.com/liuyunzhuge/blog/blob/master/form/src/js/app/listView_2.js

https://github.com/liuyunzhuge/blog/blob/master/form/src/js/app/listView_3.js

https://github.com/liuyunzhuge/blog/blob/master/form/src/js/app/listView_4.js

https://github.com/liuyunzhuge/blog/blob/master/form/src/js/app/tableView.js

接下来介绍下各个文件的具体做用。

2. 文件结构

1. base/listViewBase

它是全部列表组件的基类,基本上全部公共的事情,都是在它里面处理的,好比排序组件初始化,分页组件初始化,模板引擎管理组件初始化,以及请求发送和请求解析等。每个具体的列表管理组件都须要继承它。在这个类里面使用了模板方法的设计模式,以及在请求数据的先后,添加了多个事件回调,目的是为了子类可以进行灵活的个性化扩展。

2. base/pageViewBase以及simplePageView

pageViewBase是全部分页组件的基类。分页组件无论内部如何实现,其实对外须要提供的接口或者事件都是相同的。对于列表组件来讲,只须要知道分页组件何时分页参数发生改变了而已,而后在改变的时候,带上最新的分页参数从新请求数据。这个类从简单封装分页功能pageView.js这篇文章中提到的pageView.js里面抽象出来。那篇文章介绍的pageView.js的功能,如今只须要定义一个继承pageViewBase的类,而后把pageViewBase未实现的那些具体逻辑添加进去便可,也就是如今的更加简洁的simplePageView。

3. base/sortViewBase和sortFields

sortViewBase是全部排序组件的基类。排序组件的实现思路,其实跟分页组件的思路很像,对于列表组件来讲,只须要知道排序组件何时排序状态发生了变化便可。就像不少的table插件同样,通常列表都是单击某列的标题便可使得列表的数据按该列进行不一样方式的排序,排序组件负责实现的就是这些排序方式切换的操做逻辑。因为排序组件,最终要给列表组件提供排序参数,因此又把排序参数的管理分离出来,写成了sortFields类。这样作的目的主要是为了简化sortViewBase的实现。在编程中,把数据的逻辑与UI的逻辑分开,每每是写出更好代码的关键。

4. base/tplBase和mustacheTpl

base/tplBase是模板引擎管理组件的基类。模板引擎管理组件用于渲染列表的数据。它声明了两个接口,compile和render,用来进行模板的编译和模板的渲染。之因此要这么写,而是由于假若有人想采用这一套代码的话,也许我这里面用到的模板引擎并非他们团队人员所熟练使用的,因此为了可以更加灵活的适用其它模板引擎,我就把模板引擎的使用给抽象了。同时因为我在代码中都是使用mustache做为模板引擎,因此我也提供了继承tplBase的mustache版本的实现mustacheTpl。

5. simpleListView 和simpleSortView

看名字也能知道它们的做用了。simpleListView 继承自listViewBase,simpleSortView继承自sortViewBase。simpleListView 实现最简单的分页列表管理组件。simpleSortView给这个列表管理组件提供排序管理的功能。

6. scrollListView和scrollPageView

scrollListView继承自listViewBase,scrollPageView继承自pageViewBase。scrollListView实现相对于浏览器窗口或者某一个DOM元素进行滚动翻页的列表组件,这个列表组件与simpleListView的区别不只仅是翻页的不一样,同时在数据的渲染和UI交互上也有区别,毕竟翻到下一页的时候,须要一些更加友好的加载提示,以及不能删除以前已加载的数据内容。scrollPageView用来实现加载更多的翻页功能。

7. iscrollListView和iscrollPageView

跟前面不同的是iscrollListView继承自scrollListView,毕竟它跟scrollListView是有不少共性的,不过它比scrollListView多了一个iscroll实例的管理功能,由于它要用iscroll插件。iscrollPageView提供给iscrollListView针对iscroll插件的加载更多的翻页功能。

8. tableView

它继承自scrollListView。用来实现基于table的列表组件。支持单多选,支持获取选中行的数据,支持按索引获取行的数据,支持表头固定等。它是全部列表组件中相对复杂的一个。但实际上只是功能多,逻辑并不复杂。

9. tableDrag和tableOrder

我把它们做为插件提供给tableView,分别为它提供列宽拖动以及序号列生成的功能。这么作的目的也是考虑到尽量地简化tableView的功能。毕竟列宽拖拽这些功能都属于可选性质的。假如后面我想给tableView增长一个树形列表的管理功能,那么只要按照这两个插件的实现方式再写一个相似tableTree的插件便可。

10. tableDefault

这个纯粹是为了简化实例化tableView时,提供一些默认的配置,好比插件的配置等等。

以上就是这些文件的基本做用。下面会结合demo来演示四个列表组件的实际使用方式。

3. simpleListView

demo地址:http://liuyunzhuge.github.io/blog/form/dist/html/listView_1.html

demo文件:https://github.com/liuyunzhuge/blog/blob/master/form/src/js/app/listView_1.js

实际效果:

image

顶部打印了列表组件在请求数据时传递给后台的参数。排序组件因为涉及到可能有多个排序字段,因此用数组字符串进行传递,各个字段在数组中的顺序,表明它们在数据库中进行排序时的前后关系。

相关使用代码以下:

define(function (require) {

    var $ = require('jquery'),
        ListView = require('mod/listView/simpleListView'),
        api = {
            list: './api/pageView.json',
        };

    var list = new ListView('#blog_list', {
        url: api.list,
        tpl: ['{{#rows}}<li class="blog-entry">',
            '    <a href="#" class="diggit">',
            '        <span class="diggit-num">{{like}}</span>',
            '        <span class="diggit-text">推荐</span>',
            '    </a>',
            '    <div class="cell pl15">',
            '        <h3 class="f14 mb5 lh18"><a href="#" class="blog-title">{{title}}{{index}}</a></h3>',
            '        <p class="pt5 mb5 lh24 g3 fix">',
            '            <img src="{{avatar}}" alt="" class="bdc p1 w50 h50 l mr5">{{content}}</p>',
            '        <p class="mt10 lh20"><a href="#" class="blog-author">{{author}}</a><span class="dib ml15 mr15">发布于 {{publish_time}}</span><a',
            '                href="#" class="blog-stas">评论({{comment}})</a><a href="#" class="blog-stas">阅读({{read}})</a></p>',
            '    </div>',
            '</li>{{/rows}}'].join(''),
        parseData: function (data) {
            var start = list.pageView.data.start;

            data.forEach(function (d) {
                d.index = start;
                start = start + 1;
            });
        },
        pageView: {
            defaultSize: 3
        },
        sortView: {
            config: [
                {field: 'name', value: ''},
                {field: 'sales', value: 'desc', order: 2, type: 'int'},
                {field: 'time', value: 'asc', order: 1, type: 'datetime'}
            ]
        },
        beforeAjax: function () {
            var html = [],
                params = list.getParams(),
                hasOwn = Object.prototype.hasOwnProperty;

            for (var i in params) {
                if (hasOwn.call(params, i)) {
                    html.push('<p>' + i + ' : ' + JSON.stringify(params[i]) + '</p>');
                }
            }

            $('#log').html(html.join(''));
        }
    });

    list.query();
});

4. scrollListView

demo地址:

http://liuyunzhuge.github.io/blog/form/dist/html/listView_2.html

http://liuyunzhuge.github.io/blog/form/dist/html/listView_3.html#

demo文件:

https://github.com/liuyunzhuge/blog/blob/master/form/src/js/app/listView_2.js

https://github.com/liuyunzhuge/blog/blob/master/form/src/js/app/listView_3.js

这个之因此有两个demo,那是由于第一个demo是相对于window进行滚动翻页的,第二个demo是相对于某个DOM元素进行滚动翻页的。

实际效果这里就不展开了。毕竟这个demo要鼠标滚动操做才能看到更真实的效果。可经过前面的连接进行查看。

实际使用的代码也不展现了,由于跟前面的simpleListView差很少。

5. iscrollListView

demo地址:

http://liuyunzhuge.github.io/blog/form/dist/html/listView_4.html

demo文件:

https://github.com/liuyunzhuge/blog/blob/master/form/src/js/app/listView_4.js

预览的话能够按F12切换的手机模拟器进行demo预览,iscroll毕竟是移动端用的东西,pc端也能遇到,要用鼠标拖动才能进行滚动,我没有启用滚轮操做。其它的跟前面差很少。

6. tableView

demo地址:

http://liuyunzhuge.github.io/blog/form/dist/html/tableView.html

demo文件:

https://github.com/liuyunzhuge/blog/blob/master/form/src/js/app/tableView.js

实际效果:

image

tableView组件跟其它列表组件很大不一样的一点是,它对html结构要求更加严格:

image

好在这个结构实际上是很是清晰的。之所要弄成这个,跟表头固定的需求有关系,而后表头滚定又会带来其它相关的问题,这些我会在后面的文章再细说。

使用的方式是:

define(function (require) {

    var $ = require('jquery'),
        ListView = require('mod/listView/tableView'),
        TableDefault = require('mod/listView/tableDefault'),
        api = {
            list: './api/tableView.json',
        };

    var list = window.l = new ListView('#table_view', {
        multipleSelect: true,
        heightFixed: true,
        url: api.list,
        tableHd: ['<tr>',
            '    <th>序号</th>',
            '    <th><input type="checkbox" class="table_check_all"></th>',
            '    <th data-field="name" data-drag="false" class="sort_item">姓名 <i class="sort_icon"></i></th>',
            '    <th data-field="contact" data-drag-min="100" data-drag-max="200" class="sort_item">联系方式 <i class="sort_icon"></i></th>',
            '    <th data-field="email" class="sort_item">邮箱 <i class="sort_icon"></i></th>',
            '    <th>昵称</th>',
            '    <th>备注</th>',
            '</tr>'].join(""),
        colgroup: ['<colgroup>',
            '    <col width="70">',
            '    <col width="40">',
            '    <col width="120">',
            '    <col width="120">',
            '    <col width="180">',
            '    <col width="180">',
            '    <col  width="200">',
            '</colgroup>'].join(""),
        tpl: ['{{#rows}}<tr>',
            '<td><span class="table_view_order"></span></td>',
            '<td align="middle" class="tc"><input type="checkbox" class="table_check_row"></td>',
            '<td>{{name}}</td>',
            '<td>{{contact}}</td>',
            '<td>{{email}}</td>',
            '<td>{{nickname}}</td>',
            '<td><button class="btn-action" type="button">操做</button></td>',
            '</tr>{{/rows}}'].join(''),
        sortView: {
            config: [
                {field: 'name', value: ''},
                {field: 'contact', value: 'desc', order: 2},
                {field: 'email', value: 'asc', order: 1}
            ]
        },
        pageView: {
            defaultSize: 20
        },
        plugins: TableDefault.plugins
    });

    list.$element.on('click','.btn-action', function(e) {
        console.log(list.getRowData($(this).closest('tr').index()));
    });

    list.query();
});

在demo代码中还能够看到,tableView组件使用colgroup和tableHd两个option来定义列宽描述以及表头内容的html模板,之因此不直接写在html里面,也是为了html的简洁考虑。若是放在js里面的话,反而更统一一些,毕竟表体部分的html模板就是js中进行定义的。

经过以上这些demo能够看到,虽然前面定义了不少文件,可是在使用的时候,这些组件用起来并不复杂,要是结合我以前写的那些跟form相关的组件,那么去作一些带查询列表的列表管理功能也不是很难,基本只须要list.query(appForm.getData())便可。欢迎感兴趣的朋友前去使用,这些都是我已经在实际项目中使用了之后感受比较省时省力的方法,不比一些有名的jquery插件差。

7. 本文小结

总之,我是想到全部的列表的功能都具有类似性,不想在一个项目中,组件层面存在重复的代码,因此就写了这些东西,力求从此我在新项目中,可以将代码的重复度降到最低,方便维护跟扩展。本文也只是把我这段时间的成果起了个头,大致上介绍了下我写的这些列表组件的做用以及用法,可是里面还有不少的细节尚未彻底说明,为了让人看得更加明白,我这几天会加速把相关内容总结出来。最后,但愿这些东西无论是学习仍是工做方面,都能给一些朋友带来实际的帮助。若是以为里面有什么很差的、不对的,欢迎帮我指出。

相关文章
相关标签/搜索