-
Notifications
You must be signed in to change notification settings - Fork 3
/
mobile-picker.js
339 lines (339 loc) · 14.5 KB
/
mobile-picker.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
;(function(global){
var PICKERCOUNT = 0;
var body = document.getElementsByTagName("body")[0];
var coordinate = {start: {y:0},end: {y:0,status:true}, move: {y:0}};
var Util = {
removeClass: function(el, className) {
var reg = new RegExp('(\\s|^)' + className + '(\\s|$)');
el.className = el.className.replace(reg, ' ').replace(/^\s\s*/, '').replace(/\s\s*$/, '');
},
addClass: function(el, className) {
!Util.hasClass(el,className) && (el.className += (el.className ? ' ' : '') + className);
},
hasClass: function(el, className) {
return !!el.className && new RegExp('(^|\\s)' + className + '(\\s|$)').test(el.className);
},
loop: function(start,end,handle){
for(var i = start; i < end; i++){
Util.isFunc(handle) && handle(i);
}
},
isFunc: function(name){
return typeof name === 'function';
},
isArray: function(o) {
return Object.prototype.toString.call(o) === '[object Array]';
},
isObject: function(o) {
return typeof o === 'object';
},
damping: function (value) {//阻尼运算
var steps = [20, 40, 60, 80, 100],rates = [0.5, 0.4, 0.3, 0.2, 0.1];
var result = value,len = steps.length;
while (len--) {
if (value > steps[len]) {
result = (value - steps[len]) * rates[len];
for (var i = len; i > 0; i--) {
result += (steps[i] - steps[i - 1]) * rates[i - 1];
}
result += steps[0] * 1;
break;
}
}
return result;
},
createEle: function(parent, tag, className, html) {
var ele = document.createElement(tag);
className && Util.addClass(ele,className);
html && (ele.innerHTML = html);
parent && parent.appendChild(ele);
return ele;
},
getEle: function(ctx, selector) {
return ctx.querySelector(selector);
},
setTransform: function(el,y) {
el.style.transform = 'translate3d(0,'+ y +'px,0)';
}
}
function Picker(config){
this.index = ++PICKERCOUNT;//当前选择器的索引
this.target = config.target instanceof HTMLElement ? config.target : typeof config.target === "string" ? Util.getEle(document,config.target) : null;//触发选择器的dom元素
this.data = config.data || [];//需要显示的数据
this.value = config.value ? (Util.isArray(config.value) ? config.value : config.value.split(',')) : [];//选择器默认值
this.childKey = config.childKey || 'child';//子数据索引名
this.valueKey = config.valueKey || 'value';//用于索引初始值的key
this.textKey = config.textKey || 'value';//用于显示的key
this.autoFill = !(config.autoFill === false);//选择确定后是否填充到目标元素
this.confirm = config.confirm;//确定选择的回调
this.cancel = config.cancel;//取消回调
this.initCallback = config.initCallback;//实例化完成的回调
this.select = config.select;//单个列表选择后的回调
this.lock = config.lock === true;//锁定确定按钮,用于异步加载时等待使用
this.className = config.className || '';//定制的类名
this.init();
}
Picker.prototype = {
constructor: Picker,
init: function(){
this.initResult();
var html = '<div class="mp-container"><div class="mp-header"><span class="mp-cancel">取消</span><span class="mp-confirm'+(this.lock ? ' disabled' : '')+'">确定</span></div><div class="mp-content"><div class="mp-shadowup"></div><div class="mp-shadowdown"></div><div class="mp-line"></div><div class="mp-box"></div></div>';
var container = Util.createEle(body,'div','mp-mask',html);
this.className && Util.addClass(container,this.className);
container.id = 'mobilePicker'+this.index;
this.container = container;
this.box = Util.getEle(container, '.mp-box')//用于包含滚动元素的容器
this.createScroll(this.data);//核心方法:创建滚动的元素
this.value = [];
this.bindEvent();//绑定事件
this.finisInit();
},
initResult: function(config){
this.scrollCount = 0;//已渲染的数据层级数
this.selectIndex = [];//每个层级选中的索引集合
this.result = [];//选择器最终的结果
this.offset = [];//每个层级滚动的偏移量集合
},
finisInit: function(){
var value = this.fillResult();
Util.isFunc(this.initCallback) && this.initCallback(value,this.result);
},
update: function(options){
for(var i in options) {
this[i] = options[i];
}
this.initResult()
this.box.innerHTML = '';
this.createScroll(this.data);
this.value = [];
},
getData: function(indexes){//获取数据集合
var arr = [];
for(var i = 0; i < indexes.length; i++){
arr = i == 0 ? this.data : arr[indexes[i -1]][this.childKey];
}
return arr;
},
setResult: function(data){
if(!Util.isObject(data)) return data;
var temp = {};
for(var key in data){
key != this.childKey && (temp[key] = data[key]);
}
return temp;
},
createScroll: function(data){
var scroll = Util.createEle(this.box,'div','mp-list-wrap','<ul></ul>');
scroll.scrollIndex = this.scrollCount++;
this.addList(Util.getEle(scroll, 'ul'), data);
},
getText: function(data){
return Util.isObject(data) ? data[this.textKey] : data;
},
addList: function(parent, data){
var html = '',that = this;
var index = 0,scrollIndex = parent.parentNode.scrollIndex,text = '';
Util.loop(0,data.length,function(i){
text = that.getText(data[i]);
html += '<li>'+text+'</li>';
//初始化时有默认值,应该选中当前值,否则index就会为0,即选中第一个
if(that.value.length && that.value[scrollIndex] && (Util.isObject(data[i]) && data[i][that.valueKey] == that.value[scrollIndex][that.valueKey] || data[i] == that.value[scrollIndex])){
index = i;
}
});
parent.innerHTML = html;
this.offset.push(0);
this.selectItem(data, index, scrollIndex);//选中并创建下一级选择器
},
updateList: function(index,data){
var dom = this.box.childNodes[index];
if(!dom){
this.createScroll(data);
return;
}
dom = Util.getEle(dom,'ul');
this.addList(dom, data);
},
setScroll: function(index,data,value,callback) {
value && (this.value[index] = value);
this.offset.length = this.selectIndex.length = this.result.length = this.selectIndex.length = index;
if(index == 0){
this.data = data;
} else {
var temp = this.data[this.selectIndex[0]];
for(var i = 1, len = index; i < len; i++){
temp = temp[this.childKey][this.selectIndex[i]];
}
temp && (temp[this.childKey] = data);
}
this.updateList(index,data);
this.value = [];
Util.isFunc(callback) && callback(index,this.result);
},
removeScroll: function(index){
var that = this;
var node = this.box.childNodes[index];
if(node){
this.box.removeChild(node);
this.scrollCount--;
this.calcWidth();
}
},
calcWidth: function() {
var wraps = this.box.querySelectorAll('.mp-list-wrap');
for(var m = 0; m < wraps.length; m++){
wraps[m].style.width = (100 / this.scrollCount) + '%';
}
},
selectItem:function(data, index, scrollIndex){//params: 数据,选中的索引,当前scroll的索引
var that = this;
var oldScrollCount = this.scrollCount;
this.selectIndex.length = this.result.length = scrollIndex + 1;
this.selectIndex[scrollIndex] = index;
this.result[scrollIndex] = this.setResult(data[index]);
this.setOffset(scrollIndex, index);
if(data[index] && data[index][that.childKey] && Util.isArray(data[index][that.childKey]) && data[index][that.childKey].length){//存在子元素
if(that.scrollCount < scrollIndex + 2){//如果上一次的ul个数少于当前需要的个数,则创建新的ul
that.createScroll(data[index][that.childKey]);
} else {
that.updateList(scrollIndex + 1, data[index][that.childKey]);
}
} else {//说明当前的滚动器数目多于需要的,移除多余的
for ( var j = oldScrollCount - 1, len = that.selectIndex.length; j >= len; j-- ) {//删除多余的ul
that.removeScroll(j);
}
}
// this.scrollIndex = this.offset.length = this.selectIndex.length;
this.offset.length = this.selectIndex.length;
this.calcWidth();//计算滚动对象的宽度
Util.isFunc(that.select) && that.select(scrollIndex,this.result,index,data[index] && data[index][that.childKey] && Util.isArray(data[index][that.childKey]) && data[index][that.childKey].length);
},
fillContent: function(content){
var tagName = this.target.tagName.toLowerCase();
if(['input','select','textarea'].indexOf(tagName) != -1) {
this.target.value = content;
} else {
this.target.innerText = content;
}
},
fillResult: function(){
var value = '';
for(var i = 0,len = this.result.length; i < len; i++){
if(Util.isObject(this.result[i])){
this.result[i][this.textKey] && (value += this.result[i][this.textKey]);
} else {
value += this.result[i];
}
}
this.autoFill && this.fillContent(value);
return value;
},
hide: function(){
var that = this;
Util.getEle(this.container,'.mp-container').style.transform = 'translate3d(0,100%,0)';
Util.removeClass(body, 'mp-body');
setTimeout(function(){
that.container.style.visibility = 'hidden';
},250)
},
show: function(){
var that = this;
that.container.style.visibility = 'visible';
Util.addClass(body, 'mp-body');
setTimeout(function(){
Util.getEle(that.container,'.mp-container').style.transform= 'translate3d(0,0,0)';
},0)
},
setOffset: function(scrollIndex, index){
var scroll = this.box.childNodes[scrollIndex].querySelector('ul');
var offset = scroll.childNodes[0] ? scroll.childNodes[0].offsetHeight * index : 0;
Util.setTransform(scroll, -offset)
this.offset[scrollIndex] = offset;
},
setLock: function(value){
var confirm = Util.getEle(this.container,'.mp-confirm'),old = this.lock;
this.lock = value !== false;
if(old !== this.lock) {
this.lock ? Util.addClass(confirm,'disabled') : Util.removeClass(confirm, 'disabled');
}
},
bindEvent: function(){
var that = this;
that.target.disabled = true;
['touchstart','touchend','touchmove'].forEach(function(action){
that.box.parentNode.addEventListener(action,function(event){
event = event || window.event;
event.preventDefault();
var target = event.target;
var index = target.parentNode.scrollIndex;
var child = target.childNodes;
var liHeight = child[child.length - 1].offsetHeight;
var scrollHeight = child[child.length - 2].offsetTop;
if(target.tagName.toLowerCase() != 'ul') return;
switch(action) {
case 'touchstart':
if(coordinate.end.status){
coordinate.end.status = !coordinate.end.status;
coordinate.start.y = event.touches[0].clientY;
coordinate.start.time = Date.now();
}
break;
case 'touchmove':
coordinate.move.y = event.touches[0].clientY;
var distance = coordinate.start.y - coordinate.move.y;
var os = distance + that.offset[index];
if(os < 0){//已经滑到最顶部
Util.setTransform(target, Util.damping(-os));
} else if(os <= scrollHeight){
Util.setTransform(target, -os);
} else {//超过了整体的高度
Util.setTransform(target, -(scrollHeight + Util.damping(os-scrollHeight)));
}
break;
case 'touchend':
coordinate.end.y = event.changedTouches[0].clientY;
var count = Math.floor((that.offset[index] + (coordinate.start.y - coordinate.end.y))/liHeight + 0.5)
count = count < 0 ? 0 : Math.min(count, target.childNodes.length - 1);
var temp = that.offset[index];
that.offset[index] = count < 0 ? 0 : Math.min(count * liHeight,target.offsetHeight - 5 * liHeight)
Util.setTransform(target, -that.offset[index]);
coordinate.end.status = true;
that.selectIndex.length = index + 1;
that.selectIndex[index] = count;
that.selectItem(that.getData(that.selectIndex),count,index)
break;
}
},false)
});
that.target.addEventListener('touchstart',function(event){
(event || window.event).preventDefault();
//记录旧结果,用于取消恢复
that.oldResult = that.result.slice(0);
that.show();
});
// 用click事件代替touchstart防止点透
Util.getEle(that.container,'.mp-cancel').addEventListener('click',function(){
that.hide();
//恢复旧的结果
that.update({
value: that.oldResult,
valueKey: that.textKey
});
Util.isFunc(that.cancel) && that.cancel();
},false);
Util.getEle(that.container,'.mp-confirm').addEventListener('click',function(){
if(that.lock) return;
var value = that.fillResult();
that.hide();
Util.isFunc(that.confirm) && that.confirm(value, that.result);
});
}
}
if (typeof module !== 'undefined' && typeof exports === 'object') {
module.exports = Picker;
} else if (typeof define === 'function' && (define.amd || define.cmd)) {
define(function() { return Picker; });
} else {
global.Picker = Picker;
}
})(this);