自定义样式的日期选择器 | APICloud开发者进阶之路
duangduang~每周最期待的周五又来啦!提前祝小伙伴们周末愉快呀!按照惯例,继续分享我们《30天,App开发从0到1》内容。本周为大家精选的章节是第二部分第八章第一小节——自定义样式的日期选择器。敲黑板!!!感兴趣的小伙伴带上小板凳上课啦!
学习目标:
实现自定义样式的日期选择器
以下示例讲解仅选择部分核心代码进行详细说明,读者可在 GitHub 本书的资源范例中获取示例的完整代码。
8.1
自定义样式的日期选择器
APICloud 模块因其易用性、高效性,在 APICloud 应用开发中会被频繁地使用。UI 类的APICloud 模块,可以修改颜色、字体、背景色等样式,形成不同的风格与外观样式,但模块的整体布局结构是无法改变的。本示例提供一种思路,将 HTML 页面与模块混合搭配,利用HTML 快速布局形成不同的页面格局,形成另类的视觉体验。
下面采用 HTML 页面与 APICloud 模块混合嵌套的方式,实现一个不一样的日期选择器,如图 8-1 所示。
图 8-1
8.1.1
使用模块UICustomPicker
UICustomPicker 模块是一个自定义内容选择器,可自定义模块位置、内容取值范围、内容标签、设置选中内容,还可用于实现固定取值范围的内容选择器;多项内容之间没有级联关系。
8.1.2
开发流程及要点概述
本示例的实现思路是先用 HTML 代码创建一个背景页面,然后将模块打开在这个背景层上面,从而从视觉上实现既定的目标样式。
(1)实现 HTML 静态页面开发
为相关页面添加如下 HTML 代码,使用了弹性盒子布局。篇幅所限,CSS 样式部分就不在这里列出,具体可从示例源码中获取查看。注意页面中的 onclick 点击事件使用了 tapmode 属性去消除 300 ms 的点击延迟。
<body class="fl ex-box fl ex-column">
<div class="fl ex-1"></div>
<div class="sheet">
<div class="fl ex-box sheet-header">
<div class="Btn"></div>
<div id="title" class="fl ex-1">请选择日期</div>
<div class="Btn" tapmode="touched" onclick="fnCompleteBtnTouched();">完成</div>
</div>
<div class="sheet-body">
<div class="title fl ex-box">
<span class="fl ex-1">年</span>
<span class="fl ex-1">月</span>
<span class="fl ex-1">日</span>
</div>
<div id="picker-container" class="fl ex-box fl ex-column">
<div class="fl ex-1"></div>
<div class="cell"></div>
<div class="fl ex-1"></div>
</div>
</div>
<div class="cancel" tapmode="touched" onclick="fnCancelBtnTouched();">取消</div>
</div>
</body>
(2)创建日期选择器
创建模块实例,在 open 方法中定义了模块的位置、大小尺寸和颜色样式、可选的时间范围等参数。在 open 方法的回调中记录了模块的 ID 值,用于后续操作模块的逻辑方法时使用。同时加入了设置模块初始化显示的默认值方法和防止选择错误日期(如 2 月 30 日)的方法。
为相关页面添加如下代码:
// JavaScript 部分代码
var UICustomPicker; //模块对象
var vPickerId; // 记录当前模块ID的变量
function fnOpenPicker() { // 创建联动选择器
UICustomPicker = api.require('UICustomPicker'); // 引入模块
// 定义模块初始化需要的参数
// 根据页面HTML布局,定义模块所在位置参数
var tY = api.winHeight - 184 - 10; // 定义模块rect中的Y,起始高度数值
var tW = api.frameWidth - 40; // 定义模块rect中的w,宽度数值
// 定义模块可选择的时间范围参数
// 获取当前年份
var tNow = new Date();
var tYear = tNow.getFullYear(); // 获取当前年份
var tMonth = tNow.getMonth(); // 获取当前月份
var tDate = tNow.getDate(); // 获取当前日期
var tMinYear = tYear - 100; // 可选最小时间,100年前
var tMaxYear = tYear + 100; // 可选最大时间,100年后
UICustomPicker.open({
rect: {
x: 20,
y: tY,
w: tW,
h: 135
},
styles: {
bg: 'rgba(61,61,61,0.0)',
normalColor: 'rgba(61,61,61,0.5)',
selectedColor: '#3d3d3d',
selectedSize: 28,
tagColor: '#3685dd',
tagSize: 16
},
data: [{
scope: tMinYear + '-' + tMaxYear
}, {
scope: '1-12'
}, {
scope: '1-31'
}],
autoHide: false,
loop: true,
rows: 3,
fi xedOn: api.frameName,
fi xed: true
}, function(ret, err) {
if (ret) {
if('number' == typeof ret.id) {
vPickerId = ret.id; // 记录当前模块的ID
}
if('show' === ret.eventType) {
// 设置当前时间为默认值
var tDefault = [tYear,tMonth+1,tDate];
fnSetSelectedValue(tDefault);
}
if('selected' === ret.eventType) {
//判断选择值的合法性
fnCheckSelectedValue(ret.data);
}
}
});
}
(3)加入时间校验逻辑
因为现实时间存在闰年,并且每个月的天数不同,所以需要完善日期选择器,加上补充逻辑,以避免出现选择了 ×××× 年 2 月 31 日的错误发生。
/**
* 闰年判断
* @param {Number} pYear 4位数字组成的年份值
* @constructor
*/
Date.prototype.isLeapYear = function(pYear) {
var self = this;
var tYear = 'number' === typeof pYear ? pYear:self.getFullYear();
return (tYear % 4 == 0) && (tYear % 100 != 0 || tYear % 400 == 0);
}
var oSelectedData; // 选择的时间数组
/**
* 判断选择值的合法性
* @param {Array} pData 日期选择器选择后的回调数据
* @return {void}
*/
function fnCheckSelectedValue(pData) {
if('[object Array]' !== Object.prototype.toString.call(pData)) {
return;
}
//判断特殊日期
//获取月份进行判断
var tData = pData;
switch (tData[1]) {
case '2':
//判断是否为闰年
var tNum = '28';
if(new Date().isLeapYear(tData[0])){
tNum = '29';
}
if( parseInt(tData[2]) > parseInt(tNum) ){
tData[2] = tNum;
fnSetSelectedValue(tData);
}
else {
oSelectedData = tData;
}
break;
case '4':
case '6':
case '9':
case '11':
if( tData[2] == '31') {
tData[2] = '30';
fnSetSelectedValue(tData);
}
else {
oSelectedData = tData;
}
break;
default:
oSelectedData = tData;
}
}
/**
* 主动设置选择器的选择值
* @param {Array} pData 日期选择器选择后的回调数据
* @return {void}
*/
function fnSetSelectedValue(pData) {
if('[object Array]' !== Object.prototype.toString.call(pData)) {
return;
}
UICustomPicker.setValue({
id: vPickerId,
data: pData
});
oSelectedData = pData;
}
(4)加入 HTML 页面按钮点击事件
点击事件是实现模块和其他页面的交互逻辑的。
fnCancelBtnTouched() 函数方法中使用了 api.pageParam 这个 api 的属性,其中 cb_win( 表示回调的 win 窗口名称 ) 和 cb_frm(表示回调的 frame 窗口名称),具体对应的值是上一级打开本页面的窗口传送过来的,这样的好处是方便本页面封装成一个通用的公共页面,更加灵活。
为相关页面添加如下 JS 代码:
//取消按钮点击事件
function fnCancelBtnTouched() {
api.execScript({ // 调用上级页面方法来关闭选择器
name: api.pageParam.cb_win,
frameName: api.pageParam.cb_frm,
script: 'fnCloseSheetFrame();'
});
}
fnCompleteBtnTouched() 完成按钮的点击事件,将关闭页面的方法放在了上级页面,使用 api.execScript 方法去调用执行。这样处理是为了避免页面关闭的执行过快,后续的逻辑代码还没来得及执行或没有执行完,从而产生错误异常。
//完成按钮点击
function fnCompleteBtnTouched() {
if(!oSelectedData || oSelectedData.length == 0) {
api.toast({
msg: '请选择日期!',
duration: 2000,
location: 'bottom'
});
return;
}
else {
/* 执行完成后续业务逻辑 */
// console.log('选择数据:'+JSON.stringify(oSelectedData));
api.execScript({ //执行选择后的回调方法
name: api.pageParam.cb_win,
frameName: api.pageParam.cb_frm,
script: api.pageParam.cb_fun+'('+JSON.stringify(oSelectedData)+');'
});
}
}
本示例的点击事件中使用的 api.pageParam 对象是由上级页面传递的,目的是将页面选择的数据回传给调用的上级页面。