JS实现二级列表(包含下拉刷新、上拉加载更多、侧滑操作等)

本篇文章讲述的是使用js实现可收缩二级列表,包含以下特性:

1,点击打开/收缩二级列表;

2,列表下拉刷新数据;

3,列表上拉加载更多数据;

4,列表item项可侧滑操作(类似于QQ和微信);

5,数据缓存;

先上效果图:


进入正题,下面是具体实践:

功能实现使用到了Framework7框架,Framework7官网地址为:http://framework7.taobao.org

后面我会上传完整demo(包含Framework7库,可直接使用)。


一,准备工作


1,引入Framework7库;

引入Framework7的详细教程我就不写了,可以直接看官网教程:http://framework7.taobao.org/get-started/#.WO8wgVK75bU。也可以直接下载这篇文章的demo,demo里已引入所有文件并做好了所有配置,可供参考。


二,具体实现


依旧简单粗暴贴代码吧。下面是完整代码文件结构:


以上图示文件中,主要代码有:monitor_main.js、accordion.js、monitor_main.html、monitor_main_tpl.html、monitor_main.css,其余均为Framework7库文件或配置文件。下面贴出主要代码:

1,页面布局monitor_main.html

<div class="pages navbar-through">
    <div data-page="monitor_main" class="page">
        <div class="page-header" style="position: absolute;z-index:9">
            <div class="page-header-inner">
                <div class="left">
                    <a href="#" class="link">
                        <img src="images/monitor/menu.png" class="nav_open_panel">
                    </a>
                </div>
                <div class="center">JS二级列表</div>
                <div class="right">
                    <a href="#"></a>
                </div>
            </div>
        </div>
        <div style="width:100%; margin-top:0px; height:100%;" id="monitor_areas_list"
             class="page-content pull-to-refresh-content infinite-scroll" data-ptr-distance="55" data-distance="1">

            <div class="pull-to-refresh-layer">
                <div class="preloader cus-preloader"></div>
                <div class="pull-to-refresh-arrow"></div>
            </div>

            <div class="list-block cusv1-accordion-list" style="margin: 48px 0">
                <ul id="areas_ul">
                </ul>
            </div>
        </div>
    </div>
</div>

2,列表使用模板 monitor_main_tpl.html
<li class="cusv1-accordion-item area_accordion_item" data-area_id={{item_area_id}} data-area_index={{item_area_index}}>
    <a href="#" class="item-link item-content accordion-item-header" style="display:block">
        <div class="area_list_item_unit1">
            <span class="area_list_source">{{item_area_name}}</span>
            <span class="area_equip_num">{{item_equip_num}}</span>
            <img src="images/monitor/icon_arrow_to_right.png" class="icon_to_expand_area">
        </div>
        <div class="area_list_item_unit2">
            <img src="images/monitor/monitor_location_icon.png" class="loc_icon"/>
            <span class="equip_list_location" style="width:80%">{{item_area_location}}</span>
        </div>
    </a>
    <div class="accordion-item-content">
        <div class="list-block">
            <ul style="padding-left: 0">
                {{#each subs}}
                <li class="swipeout equip_according_list_item"
                    data-area_id={{area_id}} data-device_id={{device_id}} data-device_name={{device_name}}
                    data-isConcerned={{isConcerned}}>
                    <div class="swipeout-content">
                        <div class="concerns_click_area">
                            <img class="expanded_equips_item_state" src="{{concerns_state_icon}}"/>
                        </div>
                        <div class="expanded_equips_click_area">
                            <span class="expanded_equip_item_source">{{device_name}}</span>
                            <img src="images/monitor/icon_arrow_to_right.png" class="icon_to_equip_details">
                        </div>
                    </div>
                    <div class="swipeout-actions-right">
                        <a href="#" class="swipeout-close cancle_concerns equips_item_swipeout_btn"
                           style="background-color:{{btn_item_left_bg}}">{{btn_item_left_text}}</a>
                    </div>
                </li>
                {{/each}}
            </ul>
        </div>
    </div>
</li>

3,收缩列表工具类 accordion.js

/*===============================================================================
 ************   Accordion   ************
 ===============================================================================*/
(function () {
    var $ = $$, app = myApp;

    app.accordionOpenv1 = function (item) {
        item = $(item);
        var list = item.parents('.cusv1-accordion-list').eq(0);
        var content = item.children('.accordion-item-content');
        if (content.length === 0) content = item.find('.accordion-item-content');
        var expandedItem = list.length > 0 && item.parent().children('.accordion-item-expanded');
        if (expandedItem.length > 0) {
            app.accordionClose(expandedItem);
        }
        content.css('height', content[0].scrollHeight + 'px').transitionEnd(function () {
            if (item.hasClass('accordion-item-expanded')) {
                content.transition(0);
                content.css('height', 'auto');
                var clientLeft = content[0].clientLeft;
                content.transition('');
                item.trigger('opened');
            }
            /* else {
             content.css('height', '');
             item.trigger('closed');
             } */
        });
        item.trigger('open');
        item.addClass('accordion-item-expanded');
    };
    app.accordionClosev1 = function (item) {
        item = $(item);
        var content = item.children('.accordion-item-content');
        if (content.length === 0) content = item.find('.accordion-item-content');
        item.removeClass('accordion-item-expanded');
        content.transition(0);
        content.css('height', content[0].scrollHeight + 'px');
        // Relayout
        var clientLeft = content[0].clientLeft;
        // Close
        content.transition('');
        content.css('height', '').transitionEnd(function () {
            /* if (item.hasClass('accordion-item-expanded')) {
             content.transition(0);
             content.css('height', 'auto');
             var clientLeft = content[0].clientLeft;
             content.transition('');
             item.trigger('opened');
             }
             else {*/
            content.css('height', '');
            item.trigger('closed');
            // }
        });
        item.trigger('close');
    };

})();

4,逻辑实现 monitor_main.js

/**
 * Created on 2016/12/18.
 */

var $$ = Dom7;

var myApp = new Framework7();

var mainView = myApp.addView('.view-main');

var utils = {
    post: function (data, sf, ef, p) {
        var url = utils.postUrl();
        $$.ajax({
            url: url,
            async: true,
            method: 'POST',
            contentType: 'text/plain',
            crossDomain: true,
            data: data,
            success: function (e) {
                if ("function" == typeof sf)
                    sf(e, p);
            },
            error: function (e) {
                console.log(e);
                if ("function" == typeof ef)
                    ef(e, p);
            }
        });
    },
    get: function (url, sf, ef, p) {
        $$.ajax({
            url: url,
            async: true,
            method: 'GET',
            contentType: 'application/x-www-form-urlencoded',
            crossDomain: true,
            success: function (e) {
                if ('function' == typeof sf)
                    sf(e, p);
            },
            error: function (e) {
                if ('function' == typeof ef)
                    ef(e, p);
            }
        });
    }
}

var monitorMain = function () {
    var page, refreshContent, infiniteScroll, isLoading = false, isAreasLoading = false, data
        , templateStr, template, distreeData, cacheDatas = {}, isOperated = false, swipeout_item_data = {};

    /*
     * 获取模板文本
     * */
    $$.get("pages/monitor/monitor_main_tpl.html", function (data) {
        templateStr = data;
    });

    /*
     * 加载监控页面
     * */
    mainView.router.load(
        {
            url: 'pages/monitor/monitor_main.html'
        }
    );

    /*
     * 页面pageBeforeAnimation回调
     * */
    $$(document).on('pageBeforeAnimation', function (e) {
        var page = e.detail.page;
        if (page.name == "monitor_main") {
            beforeAnimal(page);
        }
    });

    /*
     * 页面pageBeforeAnimation回调执行动作
     * */
    function beforeAnimal(page) {
        var container = $$(page.container)
        container.find('.navbar').removeClass('navbar-hidden');
        container.find('.pull-to-refresh-content').css({'margin-top': '-44px', 'padding-bottom': '44px'});
        container.find('.pull-to-refresh-layer').css({'margin-top': '0'});
    }

    /*
     * 页面pageInit回调
     * */
    $$(document).on('pageInit', function (e) {
        page = e.detail.page;
        if (page.name == "monitor_main") {
            init(page);
        }
    });

    /*
     * 页面pageInit回调执行动作
     * */
    function init(p) {
        page = $$(p.container);
        refreshContent = page.find('.pull-to-refresh-content');
        infiniteScroll = page.find('.infinite-scroll');
        template = Template7.compile(templateStr);
        //查询数据
        beginQuery();
        //绑定事件
        bindEvent("on");
        //构建文本
        buildAreasBox(distreeData);
        // 缓存数据-保存数据到sessionStorage,缓存模块相关操作逻辑代码较多,此demo中已去除
        // sessionStorage.setItem("distreeData", JSON.stringify(distreeData));
    }

    /*
     * 查询数据
     * 先从缓存获取数据,缓存数据为空时再网络请求/模拟数据
     * 缓存模块相关操作逻辑代码较多,因缓存相关非此demo的重点,此demo中已去除
     * */
    function beginQuery() {
        // distreeData = JSON.parse(sessionStorage.getItem("distreeData"));
        if (distreeData == null) {
            // getMonitorAreasInfo("refresh");
            //无网络环境请求数据,此处自行组装模拟数据
            assembleAreasData("refresh");
        } else {
            buildAreasBox(distreeData);
        }
    }

    /*
     * 绑定/卸载事件
     * */
    function bindEvent(t) {
        var method = t == "on" ? t : "off";
        if (refreshContent) {
            //列表下拉刷新事件
            refreshContent[method]('refresh', refleshHandler);
        }
        if (infiniteScroll) {
            //列表上拉加载更多事件
            infiniteScroll[method]('infinite', loadMoreHandler);
        }
        if (page) {
            //二级列表打开/收缩事件
            page[method]('click', ".accordion-item-header", accordionToggle);
        }
    }

    /*
     * 构建列表文本
     * */
    function buildAreasBox(items) {
        data = items;
        var i, item, htmlstr = '', areaUl = page.find("#areas_ul");
        for (i = 0; i < items.length; i++) {
            item = items[i];
            htmlstr += template(item).trim();
        }
        //给列表插入文本
        areaUl.html(htmlstr);
    }

    /*
     * 渲染
     * 非此demo的重点,此demo中已去除相关代码
     * */
    function render(items) {
        cacheDatas = items;
        for (var key in items) {
            var el = page.find(".data-" + key);
            el.text(items[key]);
        }
    }

    /*
     * 查询数据
     * */
    function queryData() {
        if (isLoading) {
            resetState();
            return;
        }
        isLoading = true;
        // getMonitorAreasInfo("refresh");
        //无网络环境请求数据,此处自行组装模拟数据
        assembleAreasData("refresh");
    }

    /*
     * 重置状态
     * */
    function resetState() {
        isLoading = false;
        myApp.pullToRefreshDone();
        myApp.hideIndicator();
    }

    /*
     * destroy页面
     * */
    function destroyPage(p) {
        console.log("destroy warning main page");
        cacheDatas = {};
        bindEvent("off");
    }

    /*
     * 模拟一级列表(区域)数据
     * */
    function assembleAreasData(type) {
        distreeData = [];
        for (var i = 0; i < 12; i++) {
            var areaId = '0_' + i;
            var subsData = simulateEquipDatas(areaId);
            distreeData[i] = {
                item_area_index: i,
                item_area_id: areaId,
                item_area_name: '数控机床区' + i,
                item_equip_num: subsData.length,
                item_area_location: '华南地区/深圳分公司/' + i + '号厂房/中区',
                subs: subsData
            }
        }
    }

    /*
     * 模拟二级列表(设备)数据
     * */
    function simulateEquipDatas(areaId) {
        var equipsArr = [];
        for (var i = 0; i < 6; i++) {
            equipsArr[i] = {
                concerns_state_icon: "images/monitor/icon_concerns_no.png",
                isConcerned: false,
                btn_item_left_bg: 'darkorange',
                btn_item_left_text: '点击关注',
                device_id: '0_1_' + i,
                device_name: '数控切割机' + i,
                area_id: areaId
            }
        }
        return equipsArr;
    }

    /*
     * 网络请求一级列表(区域)数据
     * */
    function getMonitorAreasInfo(type) {
        var data = JSON.stringify('post_data');
        var params = {
            url: 'http://***',
            data: data
        }
        utils.post(params, function (e) {
            resetState();
            distreeData = [];
            // distreeData =
        }, function (e) {
            console.log("连接服务器失败,请检查网络");
            resetState();
        });
    }

    /*
     * 网络请求二级列表(设备)数据
     * */
    function getEquipsInfoByAreaId(area_id, _callback) {

        var data = {}
        data = JSON.stringify(data);
        var params = {
            url: 'http://***',
            data: data
        }
        utils.post(params, function (e) {
            var equipsArr = [];
            _callback(equipsArr);
        }, function (e) {
            console.log("连接服务器失败,请检查网络");
            resetState();
        });
    }

    /*
     * 二级列表打开/收缩事件
     * */
    function accordionToggle(e) {
        var item = $$(e.target).parents(".cusv1-accordion-item");
        if (item.length === 0) return;
        if (item.hasClass('accordion-item-expanded')) {
            item.find(".icon_to_expand_area").attr("src", "images/monitor/icon_arrow_to_right.png");
            myApp.accordionClosev1(item);
        } else {
            myApp.accordionOpenv1(item);
            item.find(".icon_to_expand_area").attr("src", "images/monitor/icon_arrow_to_bottom.png");
        }
    }

    /*
     * 刷新事件-列表下拉刷新
     * */
    function refleshHandler(e) {
        console.log("区域列表下拉刷新");

        myApp.showIndicator();

        queryData();

        myApp.hideIndicator();

        // 加载完毕需要重置
        myApp.pullToRefreshDone();
    }

    /*
     * 加载事件-列表上拉加载更多
     * */
    function loadMoreHandler(e) {
        console.log("区域列表上拉加载更多");

        // 如果正在加载,则退出
        if (isAreasLoading) return;

        // 设置flag
        isAreasLoading = true;

        //TODO  上拉加载对应操作
        // getMonitorAreasInfo('loadMore');
        //无网络环境请求数据,此处自行组装模拟数据
        // assembleAreasData("loadMore");
    }

    /*****************************************item侧滑处理-start********************************************/

    /*
     * item侧滑打开事件触发
     * 在item侧滑打开事件回调中获取当前item项数据
     * */
    $$(document).on('open', '.equip_according_list_item', function () {
        swipeout_item_data = {};
        swipeout_item_data[0] = $$(this).attr("data-device_id");
        swipeout_item_data[1] = $$(this).attr("data-isConcerned");
        swipeout_item_data[2] = $$(this).attr("data-device_name");
        swipeout_item_data[3] = $$(this).attr("data-area_id");
    });

    /*
     * 侧滑按钮点击事件
     * 在item侧滑按钮点击事件回调中处理数据变更
     * */
    $$(document).on('click', '.equips_item_swipeout_btn', function () {
        //获取当前item项
        var item = page.find(".equip_according_list_item[data-device_id ='" + swipeout_item_data[0] + "']");
        if (swipeout_item_data[1] == 'true') {
            item.attr('data-isConcerned', false);
            // TODO 对应数据库操作和缓存操作
        } else {
            item.attr('data-isConcerned', true);
            // TODO 对应数据库操作和缓存操作
        }
        isOperated = true;
    });

    /*
     * item侧滑关闭事件触发
     * 在item侧滑关闭事件回调中更新UI
     * 根据isOperated判断是否有点击侧滑按钮,处理对应逻辑
     * */
    $$(document).on('closed', '.equip_according_list_item', function (e) {
        if (isOperated) {
            //有点击侧滑按钮
            if (swipeout_item_data[1] == 'true') {
                //当前为关注状态时,点击后应更新UI为未关注状态
                $$(this).find('.expanded_equips_item_state').attr('src', 'images/monitor/icon_concerns_no.png');
                $$(this).find('.equips_item_swipeout_btn').css('background-color', 'darkorange');
                $$(this).find('.equips_item_swipeout_btn').text('点击关注');
                swipeout_item_data[1] = false;
            } else {
                //当前为未关注状态时,点击后应更新UI为关注状态
                $$(this).find('.expanded_equips_item_state').attr('src', 'images/monitor/icon_concerns_yes.png');
                $$(this).find('.equips_item_swipeout_btn').css('background-color', 'lightgray');
                $$(this).find('.equips_item_swipeout_btn').text('取消关注');
                swipeout_item_data[1] = true;
            }
            isOperated = false;
        } else {
            //未点击侧滑按钮

        }
    });

    /*****************************************item侧滑处理-end********************************************/

    return {
        init: init,
        beforeAnimal: beforeAnimal,
        resetState: resetState,
        refleshHandler: refleshHandler,
        loadMoreHandler: loadMoreHandler,
        bindEvent: bindEvent,
        destroyPage: destroyPage
    }
}();

三,写在后面

1,本demo纯手撸,不足之处欢迎批评指正;

2,本demo是我从实际项目中剥离出来的,因本篇博客的主题是js二级列表的实现,所以已剔除网络请求、数据库操作、缓存操作、异常处理以及其他相关联部分实现代码;

3,在实际项目中,已使用此技术方案实现所有需求,但之后又因需求变更而被废弃;

4,后面产品经理要求一级列表和二级列表均能下拉刷新和上拉加载,还提出了一些其他的操作需求,一番蛋疼之后我使用全新技术方案都实现了,后面有空了再整理出来;

5,不明之处欢迎交流讨论;









  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值