(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 += '
' + '
' + (hasFlex ? '' : ''); $.each(wg, function (j, w) { // Wheels wheels[l] = w; lbl = w.label !== undefined ? w.label : j; html += '<' + (hasFlex ? 'div' : 'td') + ' class="dwfl"' + ' style="' + (s.fixedWidth ? ('width:' + (s.fixedWidth[l] || s.fixedWidth) + 'px;') : (s.minWidth ? ('min-width:' + (s.minWidth[l] || s.minWidth) + 'px;') : 'min-width:' + s.width + 'px;') + (s.maxWidth ? ('max-width:' + (s.maxWidth[l] || s.maxWidth) + 'px;') : '')) + '">' + '
' + (s.mode != 'scroller' ? '
+
' + // + button '
' : '') + // - button '
' + lbl + '
' + // Wheel label '
' + '
' + '
'; // Create wheel values html += generateWheelItems(l) + '
' + (hasFlex ? '' : ''); l++; }); html += (hasFlex ? '' : '
') + '
'; }); 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);