(function ($, window, document, undefined) {
var move,
ms = $.mobiscroll,
classes = ms.classes,
util = ms.util,
pr = util.jsPrefix,
has3d = util.has3d,
hasFlex = util.hasFlex,
getCoord = util.getCoord,
constrain = util.constrain,
testTouch = util.testTouch;
ms.presetShort('scroller', 'Scroller', false);
classes.Scroller = function (el, settings, inherit) {
var $markup,
btn,
isScrollable,
itemHeight,
multiple,
s,
scrollDebounce,
trigger,
click,
moved,
start,
startTime,
stop,
p,
min,
max,
target,
index,
lines,
timer,
that = this,
$elm = $(el),
iv = {},
pos = {},
pixels = {},
wheels = [];
// Event handlers
function onStart(ev) {
// Scroll start
if (testTouch(ev, this) && !move && !click && !btn && !isReadOnly(this)) {
// Prevent touch highlight
ev.preventDefault();
// Better performance if there are tap events on document
ev.stopPropagation();
move = true;
isScrollable = s.mode != 'clickpick';
target = $('.dw-ul', this);
setGlobals(target);
moved = iv[index] !== undefined; // Don't allow tap, if still moving
p = moved ? getCurrentPosition(target) : pos[index];
start = getCoord(ev, 'Y');
startTime = new Date();
stop = start;
scroll(target, index, p, 0.001);
if (isScrollable) {
target.closest('.dwwl').addClass('dwa');
}
if (ev.type === 'mousedown') {
$(document).on('mousemove', onMove).on('mouseup', onEnd);
}
}
}
function onMove(ev) {
if (move) {
if (isScrollable) {
// Prevent scroll
ev.preventDefault();
ev.stopPropagation();
stop = getCoord(ev, 'Y');
if (Math.abs(stop - start) > 3 || moved) {
scroll(target, index, constrain(p + (start - stop) / itemHeight, min - 1, max + 1));
moved = true;
}
}
}
}
function onEnd(ev) {
if (move) {
var time = new Date() - startTime,
curr = constrain(Math.round(p + (start - stop) / itemHeight), min - 1, max + 1),
val = curr,
speed,
dist,
ttop = target.offset().top;
// Better performance if there are tap events on document
ev.stopPropagation();
move = false;
if (ev.type === 'mouseup') {
$(document).off('mousemove', onMove).off('mouseup', onEnd);
}
if (has3d && time < 300) {
speed = (stop - start) / time;
dist = (speed * speed) / s.speedUnit;
if (stop - start < 0) {
dist = -dist;
}
} else {
dist = stop - start;
}
if (!moved) { // this is a "tap"
var idx = Math.floor((stop - ttop) / itemHeight),
li = $($('.dw-li', target)[idx]),
valid = li.hasClass('dw-v'),
hl = isScrollable;
time = 0.1;
if (trigger('onValueTap', [li]) !== false && valid) {
val = idx;
} else {
hl = true;
}
if (hl && valid) {
li.addClass('dw-hl'); // Highlight
setTimeout(function () {
li.removeClass('dw-hl');
}, 100);
}
if (!multiple && (s.confirmOnTap === true || s.confirmOnTap[index]) && li.hasClass('dw-sel')) {
that.select();
return;
}
} else {
val = constrain(Math.round(p - dist / itemHeight), min, max);
time = speed ? Math.max(0.1, Math.abs((val - curr) / speed) * s.timeUnit) : 0.1;
}
if (isScrollable) {
calc(target, index, val, 0, time, true);
}
}
}
function onBtnStart(ev) {
btn = $(this);
// +/- buttons
if (testTouch(ev, this)) {
step(ev, btn.closest('.dwwl'), btn.hasClass('dwwbp') ? plus : minus);
}
if (ev.type === 'mousedown') {
$(document).on('mouseup', onBtnEnd);
}
}
function onBtnEnd(ev) {
btn = null;
if (click) {
clearInterval(timer);
click = false;
}
if (ev.type === 'mouseup') {
$(document).off('mouseup', onBtnEnd);
}
}
function onKeyDown(ev) {
if (ev.keyCode == 38) { // up
step(ev, $(this), minus);
} else if (ev.keyCode == 40) { // down
step(ev, $(this), plus);
}
}
function onKeyUp() {
if (click) {
clearInterval(timer);
click = false;
}
}
function onScroll(ev) {
if (!isReadOnly(this)) {
ev.preventDefault();
ev = ev.originalEvent || ev;
var delta = ev.deltaY || ev.wheelDelta || ev.detail,
t = $('.dw-ul', this);
setGlobals(t);
scroll(t, index, constrain(((delta < 0 ? -20 : 20) - pixels[index]) / itemHeight, min - 1, max + 1));
clearTimeout(scrollDebounce);
scrollDebounce = setTimeout(function () {
calc(t, index, Math.round(pos[index]), delta > 0 ? 1 : 2, 0.1);
}, 200);
}
}
// Private functions
function step(ev, w, func) {
ev.stopPropagation();
ev.preventDefault();
if (!click && !isReadOnly(w) && !w.hasClass('dwa')) {
click = true;
// + Button
var t = w.find('.dw-ul');
setGlobals(t);
clearInterval(timer);
timer = setInterval(function () { func(t); }, s.delay);
func(t);
}
}
function isReadOnly(wh) {
if ($.isArray(s.readonly)) {
var i = $('.dwwl', $markup).index(wh);
return s.readonly[i];
}
return s.readonly;
}
function generateWheelItems(i) {
var html = '
',
w = wheels[i],
l = 1,
labels = w.labels || [],
values = w.values || [],
keys = w.keys || values;
$.each(values, function (j, v) {
if (l % 20 === 0) {
html += '
';
}
html += '
' +
'
1 ? ' style="line-height:' + Math.round(itemHeight / lines) + 'px;font-size:' + Math.round(itemHeight / lines * 0.8) + 'px;"' : '') + '>' + v + '
';
l++;
});
html += '
';
return html;
}
function setGlobals(t) {
multiple = t.closest('.dwwl').hasClass('dwwms');
min = $('.dw-li', t).index($(multiple ? '.dw-li' : '.dw-v', t).eq(0));
max = Math.max(min, $('.dw-li', t).index($(multiple ? '.dw-li' : '.dw-v', t).eq(-1)) - (multiple ? s.rows - (s.mode == 'scroller' ? 1 : 3) : 0));
index = $('.dw-ul', $markup).index(t);
}
function formatHeader(v) {
var t = s.headerText;
return t ? (typeof t === 'function' ? t.call(el, v) : t.replace(/\{value\}/i, v)) : '';
}
function getCurrentPosition(t) {
return Math.round(-util.getPosition(t, true) / itemHeight);
}
function ready(t, i) {
clearTimeout(iv[i]);
delete iv[i];
t.closest('.dwwl').removeClass('dwa');
}
function scroll(t, index, val, time, active) {
var px = -val * itemHeight,
style = t[0].style;
if (px == pixels[index] && iv[index]) {
return;
}
//if (time && px != pixels[index]) {
// Trigger animation start event
//trigger('onAnimStart', [$markup, index, time]);
//}
pixels[index] = px;
if (has3d) {
style[pr + 'Transition'] = util.prefix + 'transform ' + (time ? time.toFixed(3) : 0) + 's ease-out';
style[pr + 'Transform'] = 'translate3d(0,' + px + 'px,0)';
} else {
style.top = px + 'px';
}
if (iv[index]) {
ready(t, index);
}
if (time && active) {
t.closest('.dwwl').addClass('dwa');
iv[index] = setTimeout(function () {
ready(t, index);
}, time * 1000);
}
pos[index] = val;
}
function getValid(val, t, dir, multiple, select) {
var selected,
cell = $('.dw-li[data-val="' + val + '"]', t),
cells = $('.dw-li', t),
v = cells.index(cell),
l = cells.length;
if (multiple) {
setGlobals(t);
} else if (!cell.hasClass('dw-v')) { // Scroll to a valid cell
var cell1 = cell,
cell2 = cell,
dist1 = 0,
dist2 = 0;
while (v - dist1 >= 0 && !cell1.hasClass('dw-v')) {
dist1++;
cell1 = cells.eq(v - dist1);
}
while (v + dist2 < l && !cell2.hasClass('dw-v')) {
dist2++;
cell2 = cells.eq(v + dist2);
}
// If we have direction (+/- or mouse wheel), the distance does not count
if (((dist2 < dist1 && dist2 && dir !== 2) || !dist1 || (v - dist1 < 0) || dir == 1) && cell2.hasClass('dw-v')) {
cell = cell2;
v = v + dist2;
} else {
cell = cell1;
v = v - dist1;
}
}
selected = cell.hasClass('dw-sel');
if (select) {
if (!multiple) {
$('.dw-sel', t).removeAttr('aria-selected');
cell.attr('aria-selected', 'true');
}
// Add selected class to cell
$('.dw-sel', t).removeClass('dw-sel');
cell.addClass('dw-sel');
}
return {
selected: selected,
v: multiple ? constrain(v, min, max) : v,
val: cell.hasClass('dw-v') ? cell.attr('data-val') : null
};
}
function scrollToPos(time, index, manual, dir, active) {
// Call validation event
if (trigger('validate', [$markup, index, time, dir]) !== false) {
// Set scrollers to position
$('.dw-ul', $markup).each(function (i) {
var t = $(this),
multiple = t.closest('.dwwl').hasClass('dwwms'),
sc = i == index || index === undefined,
res = getValid(that._tempWheelArray[i], t, dir, multiple, true),
selected = res.selected;
if (!selected || sc) {
// Set valid value
that._tempWheelArray[i] = res.val;
// Scroll to position
scroll(t, i, res.v, sc ? time : 0.1, sc ? active : false);
}
});
trigger('onValidated', []);
// Reformat value if validation changed something
that._tempValue = s.formatValue(that._tempWheelArray, that);
if (that.live) {
that._hasValue = manual || that._hasValue;
setValue(manual, manual, 0, true);
}
that._header.html(formatHeader(that._tempValue));
if (manual) {
trigger('onChange', [that._tempValue]);
}
}
}
function calc(t, idx, val, dir, time, active) {
val = constrain(val, min, max);
// Set selected scroller value
that._tempWheelArray[idx] = $('.dw-li', t).eq(val).attr('data-val');
scroll(t, idx, val, time, active);
setTimeout(function () {
// Validate
scrollToPos(time, idx, true, dir, active);
}, 10);
}
function plus(t) {
var val = pos[index] + 1;
calc(t, index, val > max ? min : val, 1, 0.1);
}
function minus(t) {
var val = pos[index] - 1;
calc(t, index, val < min ? max : val, 2, 0.1);
}
function setValue(fill, change, time, noscroll, temp) {
if (that._isVisible && !noscroll) {
scrollToPos(time);
}
that._tempValue = s.formatValue(that._tempWheelArray, that);
if (!temp) {
that._wheelArray = that._tempWheelArray.slice(0);
that._value = that._hasValue ? that._tempValue : null;
}
if (fill) {
trigger('onValueFill', [that._hasValue ? that._tempValue : '', change]);
if (that._isInput) {
$elm.val(that._hasValue ? that._tempValue : '');
}
if (change) {
that._preventChange = true;
$elm.change();
}
}
}
// Call the parent constructor
classes.Frame.call(this, el, settings, true);
// Public functions
/**
* Gets the selected wheel values, formats it, and set the value of the scroller instance.
* If input parameter is true, populates the associated input element.
* @param {Array} values Wheel values.
* @param {Boolean} [fill=false] Also set the value of the associated input element.
* @param {Number} [time=0] Animation time
* @param {Boolean} [temp=false] If true, then only set the temporary value.(only scroll there but not set the value)
* @param {Boolean} [change=false] Trigger change on the input element
*/
that.setVal = that._setVal = function (val, fill, change, temp, time) {
that._hasValue = val !== null && val !== undefined;
that._tempWheelArray = $.isArray(val) ? val.slice(0) : s.parseValue.call(el, val, that) || [];
setValue(fill, change === undefined ? fill : change, time, false, temp);
};
/**
* Returns the selected value
*/
that.getVal = that._getVal = function (temp) {
var val = that._hasValue || temp ? that[temp ? '_tempValue' : '_value'] : null;
return util.isNumeric(val) ? +val : val;
};
/*
* Sets the wheel values (passed as an array)
*/
that.setArrayVal = that.setVal;
/*
* Returns the selected wheel values as an array
*/
that.getArrayVal = function (temp) {
return temp ? that._tempWheelArray : that._wheelArray;
};
// @deprecated since 2.14.0, backward compatibility code
// ---
that.setValue = function (val, fill, time, temp, change) {
that.setVal(val, fill, change, temp, time);
};
/**
* Return the selected wheel values.
*/
that.getValue = that.getArrayVal;
// ---
/**
* Changes the values of a wheel, and scrolls to the correct position
* @param {Array} idx Indexes of the wheels to change.
* @param {Number} [time=0] Animation time when scrolling to the selected value on the new wheel.
* @param {Boolean} [manual=false] Indicates that the change was triggered by the user or from code.
*/
that.changeWheel = function (idx, time, manual) {
if ($markup) {
var i = 0,
nr = idx.length;
$.each(s.wheels, function (j, wg) {
$.each(wg, function (k, w) {
if ($.inArray(i, idx) > -1) {
wheels[i] = w;
$('.dw-ul', $markup).eq(i).html(generateWheelItems(i));
nr--;
if (!nr) {
that.position();
scrollToPos(time, undefined, manual);
return false;
}
}
i++;
});
if (!nr) {
return false;
}
});
}
};
/**
* Returns the closest valid cell.
*/
that.getValidCell = getValid;
that.scroll = scroll;
// Protected overrides
that._generateContent = function () {
var lbl,
html = '',
l = 0;
$.each(s.wheels, function (i, wg) { // Wheel groups
html += '';
});
return html;
};
that._attachEvents = function ($markup) {
$markup
.on('keydown', '.dwwl', onKeyDown)
.on('keyup', '.dwwl', onKeyUp)
.on('touchstart mousedown', '.dwwl', onStart)
.on('touchmove', '.dwwl', onMove)
.on('touchend', '.dwwl', onEnd)
.on('touchstart mousedown', '.dwwb', onBtnStart)
.on('touchend', '.dwwb', onBtnEnd);
if (s.mousewheel) {
$markup.on('wheel mousewheel', '.dwwl', onScroll);
}
};
that._markupReady = function ($m) {
$markup = $m;
scrollToPos();
};
that._fillValue = function () {
that._hasValue = true;
setValue(true, true, 0, true);
};
that._readValue = function () {
var v = $elm.val() || '';
if (v !== '') {
that._hasValue = true;
}
that._tempWheelArray = that._hasValue && that._wheelArray ? that._wheelArray.slice(0) : s.parseValue.call(el, v, that) || [];
setValue();
};
that._processSettings = function () {
s = that.settings;
trigger = that.trigger;
itemHeight = s.height;
lines = s.multiline;
that._isLiquid = (s.layout || (/top|bottom/.test(s.display) && s.wheels.length == 1 ? 'liquid' : '')) === 'liquid';
// @deprecated since 2.15.0, backward compatibility code
// ---
if (s.formatResult) {
s.formatValue = s.formatResult;
}
// ---
if (lines > 1) {
s.cssClass = (s.cssClass || '') + ' dw-ml';
}
// Ensure a minimum number of 3 items if clickpick buttons present
if (s.mode != 'scroller') {
s.rows = Math.max(3, s.rows);
}
};
// Properties
that._selectedValues = {};
// Constructor
if (!inherit) {
that.init(settings);
}
};
// Extend defaults
classes.Scroller.prototype = {
_hasDef: true,
_hasTheme: true,
_hasLang: true,
_hasPreset: true,
_class: 'scroller',
_defaults: $.extend({}, classes.Frame.prototype._defaults, {
// Options
minWidth: 80,
height: 40,
rows: 3,
multiline: 1,
delay: 300,
readonly: false,
showLabel: true,
confirmOnTap: true,
wheels: [],
mode: 'scroller',
preset: '',
speedUnit: 0.0012,
timeUnit: 0.08,
formatValue: function (d) {
return d.join(' ');
},
parseValue: function (value, inst) {
var val = [],
ret = [],
i = 0,
found,
keys;
if (value !== null && value !== undefined) {
val = (value + '').split(' ');
}
$.each(inst.settings.wheels, function (j, wg) {
$.each(wg, function (k, w) {
keys = w.keys || w.values;
found = keys[0]; // Default to first wheel value if not found
$.each(keys, function (l, key) {
if (val[i] == key) { // Don't do strict comparison
found = key;
return false;
}
});
ret.push(found);
i++;
});
});
return ret;
}
})
};
ms.themes.scroller = ms.themes.frame;
})(jQuery, window, document);