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

时间:2022-12-22 22:19:15

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

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

2,列表下拉刷新数据;

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

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

5,数据缓存;

先上效果图:

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


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

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

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


一,准备工作


1,引入Framework7库;

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


二,具体实现


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

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

以上图示文件中,主要代码有: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,不明之处欢迎交流讨论;