607 lines
23 KiB
JavaScript
607 lines
23 KiB
JavaScript
/*======================================================
|
|
************ Picker ************
|
|
======================================================*/
|
|
var Picker = function (params) {
|
|
var p = this;
|
|
var defaults = {
|
|
updateValuesOnMomentum: false,
|
|
updateValuesOnTouchmove: true,
|
|
rotateEffect: false,
|
|
momentumRatio: 7,
|
|
freeMode: false,
|
|
// Common settings
|
|
closeByOutsideClick: true,
|
|
scrollToInput: true,
|
|
inputReadOnly: true,
|
|
convertToPopover: true,
|
|
onlyInPopover: false,
|
|
toolbar: true,
|
|
toolbarCloseText: 'Done',
|
|
toolbarTemplate:
|
|
'<div class="toolbar">' +
|
|
'<div class="toolbar-inner">' +
|
|
'<div class="left"></div>' +
|
|
'<div class="right">' +
|
|
'<a href="#" class="link close-picker">{{closeText}}</a>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'</div>'
|
|
};
|
|
params = params || {};
|
|
for (var def in defaults) {
|
|
if (typeof params[def] === 'undefined') {
|
|
params[def] = defaults[def];
|
|
}
|
|
}
|
|
p.params = params;
|
|
p.cols = [];
|
|
p.initialized = false;
|
|
|
|
// Inline flag
|
|
p.inline = p.params.container ? true : false;
|
|
|
|
// 3D Transforms origin bug, only on safari
|
|
var originBug = app.device.ios || (navigator.userAgent.toLowerCase().indexOf('safari') >= 0 && navigator.userAgent.toLowerCase().indexOf('chrome') < 0) && !app.device.android;
|
|
|
|
// Should be converted to popover
|
|
function isPopover() {
|
|
var toPopover = false;
|
|
if (!p.params.convertToPopover && !p.params.onlyInPopover) return toPopover;
|
|
if (!p.inline && p.params.input) {
|
|
if (p.params.onlyInPopover) toPopover = true;
|
|
else {
|
|
if (app.device.ios) {
|
|
toPopover = app.device.ipad ? true : false;
|
|
}
|
|
else {
|
|
if ($(window).width() >= 768) toPopover = true;
|
|
}
|
|
}
|
|
}
|
|
return toPopover;
|
|
}
|
|
function inPopover() {
|
|
if (p.opened && p.container && p.container.length > 0 && p.container.parents('.popover').length > 0) return true;
|
|
else return false;
|
|
}
|
|
|
|
// Value
|
|
p.setValue = function (arrValues, transition) {
|
|
var valueIndex = 0;
|
|
if (p.cols.length === 0) {
|
|
p.value = arrValues;
|
|
p.updateValue(arrValues);
|
|
return;
|
|
}
|
|
for (var i = 0; i < p.cols.length; i++) {
|
|
if (p.cols[i] && !p.cols[i].divider) {
|
|
p.cols[i].setValue(arrValues[valueIndex], transition);
|
|
valueIndex++;
|
|
}
|
|
}
|
|
};
|
|
p.updateValue = function (forceValues) {
|
|
var newValue = forceValues || [];
|
|
var newDisplayValue = [];
|
|
for (var i = 0; i < p.cols.length; i++) {
|
|
if (!p.cols[i].divider) {
|
|
newValue.push(p.cols[i].value);
|
|
newDisplayValue.push(p.cols[i].displayValue);
|
|
}
|
|
}
|
|
if (newValue.indexOf(undefined) >= 0) {
|
|
return;
|
|
}
|
|
p.value = newValue;
|
|
p.displayValue = newDisplayValue;
|
|
if (p.params.onChange) {
|
|
p.params.onChange(p, p.value, p.displayValue);
|
|
}
|
|
if (p.input && p.input.length > 0) {
|
|
$(p.input).val(p.params.formatValue ? p.params.formatValue(p, p.value, p.displayValue) : p.value.join(' '));
|
|
$(p.input).trigger('change');
|
|
}
|
|
};
|
|
|
|
// Columns Handlers
|
|
p.initPickerCol = function (colElement, updateItems) {
|
|
var colContainer = $(colElement);
|
|
var colIndex = colContainer.index();
|
|
var col = p.cols[colIndex];
|
|
if (col.divider) return;
|
|
col.container = colContainer;
|
|
col.wrapper = col.container.find('.picker-items-col-wrapper');
|
|
col.items = col.wrapper.find('.picker-item');
|
|
|
|
var i, j;
|
|
var wrapperHeight, itemHeight, itemsHeight, minTranslate, maxTranslate;
|
|
col.replaceValues = function (values, displayValues) {
|
|
col.destroyEvents();
|
|
col.values = values;
|
|
col.displayValues = displayValues;
|
|
var newItemsHTML = p.columnHTML(col, true);
|
|
col.wrapper.html(newItemsHTML);
|
|
col.items = col.wrapper.find('.picker-item');
|
|
col.calcSize();
|
|
col.setValue(col.values[0], 0, true);
|
|
col.initEvents();
|
|
};
|
|
col.calcSize = function () {
|
|
if (p.params.rotateEffect) {
|
|
col.container.removeClass('picker-items-col-absolute');
|
|
if (!col.width) col.container.css({width:''});
|
|
}
|
|
var colWidth, colHeight;
|
|
colWidth = 0;
|
|
colHeight = col.container[0].offsetHeight;
|
|
wrapperHeight = col.wrapper[0].offsetHeight;
|
|
itemHeight = col.items[0].offsetHeight;
|
|
itemsHeight = itemHeight * col.items.length;
|
|
minTranslate = colHeight / 2 - itemsHeight + itemHeight / 2;
|
|
maxTranslate = colHeight / 2 - itemHeight / 2;
|
|
if (col.width) {
|
|
colWidth = col.width;
|
|
if (parseInt(colWidth, 10) === colWidth) colWidth = colWidth + 'px';
|
|
col.container.css({width: colWidth});
|
|
}
|
|
if (p.params.rotateEffect) {
|
|
if (!col.width) {
|
|
col.items.each(function () {
|
|
var item = $(this);
|
|
item.css({width:'auto'});
|
|
colWidth = Math.max(colWidth, item[0].offsetWidth);
|
|
item.css({width:''});
|
|
});
|
|
col.container.css({width: (colWidth + 2) + 'px'});
|
|
}
|
|
col.container.addClass('picker-items-col-absolute');
|
|
}
|
|
};
|
|
col.calcSize();
|
|
|
|
col.wrapper.transform('translate3d(0,' + maxTranslate + 'px,0)').transition(0);
|
|
|
|
|
|
var activeIndex = 0;
|
|
var animationFrameId;
|
|
|
|
// Set Value Function
|
|
col.setValue = function (newValue, transition, valueCallbacks) {
|
|
if (typeof transition === 'undefined') transition = '';
|
|
var newActiveIndex = col.wrapper.find('.picker-item[data-picker-value="' + newValue + '"]').index();
|
|
if(typeof newActiveIndex === 'undefined' || newActiveIndex === -1) {
|
|
return;
|
|
}
|
|
var newTranslate = -newActiveIndex * itemHeight + maxTranslate;
|
|
// Update wrapper
|
|
col.wrapper.transition(transition);
|
|
col.wrapper.transform('translate3d(0,' + (newTranslate) + 'px,0)');
|
|
|
|
// Watch items
|
|
if (p.params.updateValuesOnMomentum && col.activeIndex && col.activeIndex !== newActiveIndex ) {
|
|
$.cancelAnimationFrame(animationFrameId);
|
|
col.wrapper.transitionEnd(function(){
|
|
$.cancelAnimationFrame(animationFrameId);
|
|
});
|
|
updateDuringScroll();
|
|
}
|
|
|
|
// Update items
|
|
col.updateItems(newActiveIndex, newTranslate, transition, valueCallbacks);
|
|
};
|
|
|
|
col.updateItems = function (activeIndex, translate, transition, valueCallbacks) {
|
|
if (typeof translate === 'undefined') {
|
|
translate = $.getTranslate(col.wrapper[0], 'y');
|
|
}
|
|
if(typeof activeIndex === 'undefined') activeIndex = -Math.round((translate - maxTranslate)/itemHeight);
|
|
if (activeIndex < 0) activeIndex = 0;
|
|
if (activeIndex >= col.items.length) activeIndex = col.items.length - 1;
|
|
var previousActiveIndex = col.activeIndex;
|
|
col.activeIndex = activeIndex;
|
|
col.wrapper.find('.picker-selected').removeClass('picker-selected');
|
|
|
|
col.items.transition(transition);
|
|
|
|
var selectedItem = col.items.eq(activeIndex).addClass('picker-selected').transform('');
|
|
|
|
// Set 3D rotate effect
|
|
if (p.params.rotateEffect) {
|
|
var percentage = (translate - (Math.floor((translate - maxTranslate)/itemHeight) * itemHeight + maxTranslate)) / itemHeight;
|
|
|
|
col.items.each(function () {
|
|
var item = $(this);
|
|
var itemOffsetTop = item.index() * itemHeight;
|
|
var translateOffset = maxTranslate - translate;
|
|
var itemOffset = itemOffsetTop - translateOffset;
|
|
var percentage = itemOffset / itemHeight;
|
|
|
|
var itemsFit = Math.ceil(col.height / itemHeight / 2) + 1;
|
|
|
|
var angle = (-18*percentage);
|
|
if (angle > 180) angle = 180;
|
|
if (angle < -180) angle = -180;
|
|
// Far class
|
|
if (Math.abs(percentage) > itemsFit) item.addClass('picker-item-far');
|
|
else item.removeClass('picker-item-far');
|
|
// Set transform
|
|
item.transform('translate3d(0, ' + (-translate + maxTranslate) + 'px, ' + (originBug ? -110 : 0) + 'px) rotateX(' + angle + 'deg)');
|
|
});
|
|
}
|
|
|
|
if (valueCallbacks || typeof valueCallbacks === 'undefined') {
|
|
// Update values
|
|
col.value = selectedItem.attr('data-picker-value');
|
|
col.displayValue = col.displayValues ? col.displayValues[activeIndex] : col.value;
|
|
// On change callback
|
|
if (previousActiveIndex !== activeIndex) {
|
|
if (col.onChange) {
|
|
col.onChange(p, col.value, col.displayValue);
|
|
}
|
|
p.updateValue();
|
|
}
|
|
}
|
|
};
|
|
|
|
function updateDuringScroll() {
|
|
animationFrameId = $.requestAnimationFrame(function () {
|
|
col.updateItems(undefined, undefined, 0);
|
|
updateDuringScroll();
|
|
});
|
|
}
|
|
|
|
// Update items on init
|
|
if (updateItems) col.updateItems(0, maxTranslate, 0);
|
|
|
|
var allowItemClick = true;
|
|
var isTouched, isMoved, touchStartY, touchCurrentY, touchStartTime, touchEndTime, startTranslate, returnTo, currentTranslate, prevTranslate, velocityTranslate, velocityTime;
|
|
function handleTouchStart (e) {
|
|
if (isMoved || isTouched) return;
|
|
e.preventDefault();
|
|
isTouched = true;
|
|
touchStartY = touchCurrentY = e.type === 'touchstart' ? e.targetTouches[0].pageY : e.pageY;
|
|
touchStartTime = (new Date()).getTime();
|
|
|
|
allowItemClick = true;
|
|
startTranslate = currentTranslate = $.getTranslate(col.wrapper[0], 'y');
|
|
}
|
|
function handleTouchMove (e) {
|
|
if (!isTouched) return;
|
|
e.preventDefault();
|
|
allowItemClick = false;
|
|
touchCurrentY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
|
|
if (!isMoved) {
|
|
// First move
|
|
$.cancelAnimationFrame(animationFrameId);
|
|
isMoved = true;
|
|
startTranslate = currentTranslate = $.getTranslate(col.wrapper[0], 'y');
|
|
col.wrapper.transition(0);
|
|
}
|
|
|
|
var diff = touchCurrentY - touchStartY;
|
|
currentTranslate = startTranslate + diff;
|
|
returnTo = undefined;
|
|
|
|
// Normalize translate
|
|
if (currentTranslate < minTranslate) {
|
|
currentTranslate = minTranslate - Math.pow(minTranslate - currentTranslate, 0.8);
|
|
returnTo = 'min';
|
|
}
|
|
if (currentTranslate > maxTranslate) {
|
|
currentTranslate = maxTranslate + Math.pow(currentTranslate - maxTranslate, 0.8);
|
|
returnTo = 'max';
|
|
}
|
|
// Transform wrapper
|
|
col.wrapper.transform('translate3d(0,' + currentTranslate + 'px,0)');
|
|
|
|
// Update items
|
|
col.updateItems(undefined, currentTranslate, 0, p.params.updateValuesOnTouchmove);
|
|
|
|
// Calc velocity
|
|
velocityTranslate = currentTranslate - prevTranslate || currentTranslate;
|
|
velocityTime = (new Date()).getTime();
|
|
prevTranslate = currentTranslate;
|
|
}
|
|
function handleTouchEnd (e) {
|
|
if (!isTouched || !isMoved) {
|
|
isTouched = isMoved = false;
|
|
return;
|
|
}
|
|
isTouched = isMoved = false;
|
|
col.wrapper.transition('');
|
|
if (returnTo) {
|
|
if (returnTo === 'min') {
|
|
col.wrapper.transform('translate3d(0,' + minTranslate + 'px,0)');
|
|
}
|
|
else col.wrapper.transform('translate3d(0,' + maxTranslate + 'px,0)');
|
|
}
|
|
touchEndTime = new Date().getTime();
|
|
var velocity, newTranslate;
|
|
if (touchEndTime - touchStartTime > 300) {
|
|
newTranslate = currentTranslate;
|
|
}
|
|
else {
|
|
velocity = Math.abs(velocityTranslate / (touchEndTime - velocityTime));
|
|
newTranslate = currentTranslate + velocityTranslate * p.params.momentumRatio;
|
|
}
|
|
|
|
newTranslate = Math.max(Math.min(newTranslate, maxTranslate), minTranslate);
|
|
|
|
// Active Index
|
|
var activeIndex = -Math.floor((newTranslate - maxTranslate)/itemHeight);
|
|
|
|
// Normalize translate
|
|
if (!p.params.freeMode) newTranslate = -activeIndex * itemHeight + maxTranslate;
|
|
|
|
// Transform wrapper
|
|
col.wrapper.transform('translate3d(0,' + (parseInt(newTranslate,10)) + 'px,0)');
|
|
|
|
// Update items
|
|
col.updateItems(activeIndex, newTranslate, '', true);
|
|
|
|
// Watch items
|
|
if (p.params.updateValuesOnMomentum) {
|
|
updateDuringScroll();
|
|
col.wrapper.transitionEnd(function(){
|
|
$.cancelAnimationFrame(animationFrameId);
|
|
});
|
|
}
|
|
|
|
// Allow click
|
|
setTimeout(function () {
|
|
allowItemClick = true;
|
|
}, 100);
|
|
}
|
|
|
|
function handleClick(e) {
|
|
if (!allowItemClick) return;
|
|
$.cancelAnimationFrame(animationFrameId);
|
|
/*jshint validthis:true */
|
|
var value = $(this).attr('data-picker-value');
|
|
col.setValue(value);
|
|
}
|
|
|
|
col.initEvents = function (detach) {
|
|
var method = detach ? 'off' : 'on';
|
|
col.container[method](app.touchEvents.start, handleTouchStart);
|
|
col.container[method](app.touchEvents.move, handleTouchMove);
|
|
col.container[method](app.touchEvents.end, handleTouchEnd);
|
|
col.items[method]('click', handleClick);
|
|
};
|
|
col.destroyEvents = function () {
|
|
col.initEvents(true);
|
|
};
|
|
|
|
col.container[0].f7DestroyPickerCol = function () {
|
|
col.destroyEvents();
|
|
};
|
|
|
|
col.initEvents();
|
|
|
|
};
|
|
p.destroyPickerCol = function (colContainer) {
|
|
colContainer = $(colContainer);
|
|
if ('f7DestroyPickerCol' in colContainer[0]) colContainer[0].f7DestroyPickerCol();
|
|
};
|
|
// Resize cols
|
|
function resizeCols() {
|
|
if (!p.opened) return;
|
|
for (var i = 0; i < p.cols.length; i++) {
|
|
if (!p.cols[i].divider) {
|
|
p.cols[i].calcSize();
|
|
p.cols[i].setValue(p.cols[i].value, 0, false);
|
|
}
|
|
}
|
|
}
|
|
$(window).on('resize', resizeCols);
|
|
|
|
// HTML Layout
|
|
p.columnHTML = function (col, onlyItems) {
|
|
var columnItemsHTML = '';
|
|
var columnHTML = '';
|
|
if (col.divider) {
|
|
columnHTML += '<div class="picker-items-col picker-items-col-divider ' + (col.textAlign ? 'picker-items-col-' + col.textAlign : '') + ' ' + (col.cssClass || '') + '">' + col.content + '</div>';
|
|
}
|
|
else {
|
|
for (var j = 0; j < col.values.length; j++) {
|
|
columnItemsHTML += '<div class="picker-item" data-picker-value="' + col.values[j] + '">' + (col.displayValues ? col.displayValues[j] : col.values[j]) + '</div>';
|
|
}
|
|
columnHTML += '<div class="picker-items-col ' + (col.textAlign ? 'picker-items-col-' + col.textAlign : '') + ' ' + (col.cssClass || '') + '"><div class="picker-items-col-wrapper">' + columnItemsHTML + '</div></div>';
|
|
}
|
|
return onlyItems ? columnItemsHTML : columnHTML;
|
|
};
|
|
p.layout = function () {
|
|
var pickerHTML = '';
|
|
var pickerClass = '';
|
|
var i;
|
|
p.cols = [];
|
|
var colsHTML = '';
|
|
for (i = 0; i < p.params.cols.length; i++) {
|
|
var col = p.params.cols[i];
|
|
colsHTML += p.columnHTML(p.params.cols[i]);
|
|
p.cols.push(col);
|
|
}
|
|
pickerClass = 'picker-modal picker-columns ' + (p.params.cssClass || '') + (p.params.rotateEffect ? ' picker-3d' : '');
|
|
pickerHTML =
|
|
'<div class="' + (pickerClass) + '">' +
|
|
(p.params.toolbar ? p.params.toolbarTemplate.replace(/{{closeText}}/g, p.params.toolbarCloseText) : '') +
|
|
'<div class="picker-modal-inner picker-items">' +
|
|
colsHTML +
|
|
'<div class="picker-center-highlight"></div>' +
|
|
'</div>' +
|
|
'</div>';
|
|
|
|
p.pickerHTML = pickerHTML;
|
|
};
|
|
|
|
// Input Events
|
|
function openOnInput(e) {
|
|
e.preventDefault();
|
|
if (p.opened) return;
|
|
p.open();
|
|
if (p.params.scrollToInput && !isPopover()) {
|
|
var pageContent = p.input.parents('.page-content');
|
|
if (pageContent.length === 0) return;
|
|
|
|
var paddingTop = parseInt(pageContent.css('padding-top'), 10),
|
|
paddingBottom = parseInt(pageContent.css('padding-bottom'), 10),
|
|
pageHeight = pageContent[0].offsetHeight - paddingTop - p.container.height(),
|
|
pageScrollHeight = pageContent[0].scrollHeight - paddingTop - p.container.height(),
|
|
newPaddingBottom;
|
|
var inputTop = p.input.offset().top - paddingTop + p.input[0].offsetHeight;
|
|
if (inputTop > pageHeight) {
|
|
var scrollTop = pageContent.scrollTop() + inputTop - pageHeight;
|
|
if (scrollTop + pageHeight > pageScrollHeight) {
|
|
newPaddingBottom = scrollTop + pageHeight - pageScrollHeight + paddingBottom;
|
|
if (pageHeight === pageScrollHeight) {
|
|
newPaddingBottom = p.container.height();
|
|
}
|
|
pageContent.css({'padding-bottom': (newPaddingBottom) + 'px'});
|
|
}
|
|
pageContent.scrollTop(scrollTop, 300);
|
|
}
|
|
}
|
|
}
|
|
function closeOnHTMLClick(e) {
|
|
if (inPopover()) return;
|
|
if (p.input && p.input.length > 0) {
|
|
if (e.target !== p.input[0] && $(e.target).parents('.picker-modal').length === 0) p.close();
|
|
}
|
|
else {
|
|
if ($(e.target).parents('.picker-modal').length === 0) p.close();
|
|
}
|
|
}
|
|
|
|
if (p.params.input) {
|
|
p.input = $(p.params.input);
|
|
if (p.input.length > 0) {
|
|
if (p.params.inputReadOnly) p.input.prop('readOnly', true);
|
|
if (!p.inline) {
|
|
p.input.on('click', openOnInput);
|
|
}
|
|
if (p.params.inputReadOnly) {
|
|
p.input.on('focus mousedown', function (e) {
|
|
e.preventDefault();
|
|
});
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (!p.inline && p.params.closeByOutsideClick) $('html').on('click', closeOnHTMLClick);
|
|
|
|
// Open
|
|
function onPickerClose() {
|
|
p.opened = false;
|
|
if (p.input && p.input.length > 0) {
|
|
p.input.parents('.page-content').css({'padding-bottom': ''});
|
|
if (app.params.material) p.input.trigger('blur');
|
|
}
|
|
if (p.params.onClose) p.params.onClose(p);
|
|
|
|
// Destroy events
|
|
p.container.find('.picker-items-col').each(function () {
|
|
p.destroyPickerCol(this);
|
|
});
|
|
}
|
|
|
|
p.opened = false;
|
|
p.open = function () {
|
|
var toPopover = isPopover();
|
|
|
|
if (!p.opened) {
|
|
|
|
// Layout
|
|
p.layout();
|
|
|
|
// Append
|
|
if (toPopover) {
|
|
p.pickerHTML = '<div class="popover popover-picker-columns"><div class="popover-inner">' + p.pickerHTML + '</div></div>';
|
|
p.popover = app.popover(p.pickerHTML, p.params.input, true);
|
|
p.container = $(p.popover).find('.picker-modal');
|
|
$(p.popover).on('close', function () {
|
|
onPickerClose();
|
|
});
|
|
}
|
|
else if (p.inline) {
|
|
p.container = $(p.pickerHTML);
|
|
p.container.addClass('picker-modal-inline');
|
|
$(p.params.container).append(p.container);
|
|
}
|
|
else {
|
|
p.container = $(app.pickerModal(p.pickerHTML));
|
|
$(p.container)
|
|
.on('close', function () {
|
|
onPickerClose();
|
|
});
|
|
}
|
|
|
|
// Store picker instance
|
|
p.container[0].f7Picker = p;
|
|
|
|
// Init Events
|
|
p.container.find('.picker-items-col').each(function () {
|
|
var updateItems = true;
|
|
if ((!p.initialized && p.params.value) || (p.initialized && p.value)) updateItems = false;
|
|
p.initPickerCol(this, updateItems);
|
|
});
|
|
|
|
// Set value
|
|
if (!p.initialized) {
|
|
if (p.value) p.setValue(p.value, 0);
|
|
else if (p.params.value) {
|
|
p.setValue(p.params.value, 0);
|
|
}
|
|
}
|
|
else {
|
|
if (p.value) p.setValue(p.value, 0);
|
|
}
|
|
|
|
// Material Focus
|
|
if (p.input && p.input.length > 0 && app.params.material) {
|
|
p.input.trigger('focus');
|
|
}
|
|
}
|
|
|
|
// Set flag
|
|
p.opened = true;
|
|
p.initialized = true;
|
|
|
|
if (p.params.onOpen) p.params.onOpen(p);
|
|
};
|
|
|
|
// Close
|
|
p.close = function () {
|
|
if (!p.opened || p.inline) return;
|
|
if (inPopover()) {
|
|
app.closeModal(p.popover);
|
|
return;
|
|
}
|
|
else {
|
|
app.closeModal(p.container);
|
|
return;
|
|
}
|
|
};
|
|
|
|
// Destroy
|
|
p.destroy = function () {
|
|
p.close();
|
|
if (p.params.input && p.input.length > 0) {
|
|
p.input.off('click focus', openOnInput);
|
|
}
|
|
$('html').off('click', closeOnHTMLClick);
|
|
$(window).off('resize', resizeCols);
|
|
};
|
|
|
|
if (p.inline) {
|
|
p.open();
|
|
}
|
|
else {
|
|
if (!p.initialized && p.params.value) p.setValue(p.params.value);
|
|
}
|
|
|
|
return p;
|
|
};
|
|
app.picker = function (params) {
|
|
return new Picker(params);
|
|
}; |