/*jslint eqeq: true, plusplus: true, undef: true, sloppy: true, vars: true, forin: true, nomen: true */ (function ($) { $.mobiscroll.classes.Scroller = function (elem, settings) { var m, hi, v, dw, persp, overlay, ww, // Window width wh, // Window height mw, // Modal width mh, // Modal height lock, anim, theme, lang, click, hasButtons, scrollable, moved, start, startTime, stop, p, min, max, modal, target, index, timer, readOnly, preventChange, preventPos, wndw, doc, buttons, btn, that = this, e = elem, elm = $(e), s = extend({}, defaults), pres = {}, iv = {}, pos = {}, pixels = {}, wheels = [], elmList = [], input = elm.is('input'), visible = false, onStart = function (e) { // Scroll start if (testTouch(e) && !move && !click && !btn && !isReadOnly(this)) { // Prevent touch highlight e.preventDefault(); move = true; scrollable = 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(e, 'Y'); startTime = new Date(); stop = start; scroll(target, index, p, 0.001); if (scrollable) { target.closest('.dwwl').addClass('dwa'); } $(document).on(MOVE_EVENT, onMove).on(END_EVENT, onEnd); } }, onMove = function (e) { if (scrollable) { // Prevent scroll e.preventDefault(); e.stopPropagation(); stop = getCoord(e, 'Y'); scroll(target, index, constrain(p + (start - stop) / hi, min - 1, max + 1)); } if (start !== stop) { moved = true; } }, onEnd = function (e) { var time = new Date() - startTime, val = constrain(p + (start - stop) / hi, min - 1, max + 1), speed, dist, tindex, ttop = target.offset().top; if (time < 300) { speed = (stop - start) / time; dist = (speed * speed) / s.speedUnit; if (stop - start < 0) { dist = -dist; } } else { dist = stop - start; } tindex = Math.round(p - dist / hi); if (!dist && !moved) { // this is a "tap" var idx = Math.floor((stop - ttop) / hi), li = $($('.dw-li', target)[idx]), hl = scrollable; if (event('onValueTap', [li]) !== false) { tindex = idx; } else { hl = true; } if (hl) { li.addClass('dw-hl'); // Highlight setTimeout(function () { li.removeClass('dw-hl'); }, 200); } } if (scrollable) { calc(target, tindex, 0, true, Math.round(val)); } move = false; target = null; $(document).off(MOVE_EVENT, onMove).off(END_EVENT, onEnd); }, onBtnStart = function (e) { if (btn) { btn.removeClass('dwb-a'); } btn = $(this); $(document).on(END_EVENT, onBtnEnd); // Active button if (!btn.hasClass('dwb-d') && !btn.hasClass('dwb-nhl')) { btn.addClass('dwb-a'); } // +/- buttons if (btn.hasClass('dwwb')) { if (testTouch(e)) { step(e, btn.closest('.dwwl'), btn.hasClass('dwwbp') ? plus : minus); } } }, onBtnEnd = function (e) { if (click) { clearInterval(timer); click = false; } if (btn) { btn.removeClass('dwb-a'); btn = null; } $(document).off(END_EVENT, onBtnEnd); }, onKeyDown = function (e) { if (e.keyCode == 38) { // up step(e, $(this), minus); } else if (e.keyCode == 40) { // down step(e, $(this), plus); } }, onKeyUp = function (e) { if (click) { clearInterval(timer); click = false; } }, onScroll = function (e) { if (!isReadOnly(this)) { e.preventDefault(); e = e.originalEvent || e; var delta = e.wheelDelta ? (e.wheelDelta / 120) : (e.detail ? (-e.detail / 3) : 0), t = $('.dw-ul', this); setGlobals(t); calc(t, Math.round(pos[index] - delta), delta < 0 ? 1 : 2); } }; // Private functions function step(e, w, func) { e.stopPropagation(); e.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', dw).index(wh); return s.readonly[i]; } return s.readonly; } function generateWheelItems(i) { var html = '
', ww = wheels[i], w = ww.values ? ww : convert(ww), l = 1, labels = w.labels || [], values = w.values, keys = w.keys || values; $.each(values, function (j, v) { if (l % 20 == 0) { html += '
'; } html += '
' + v + '
'; l++; }); html += '
'; return html; } function setGlobals(t) { min = $('.dw-li', t).index($('.dw-v', t).eq(0)); max = $('.dw-li', t).index($('.dw-v', t).eq(-1)); index = $('.dw-ul', dw).index(t); } function formatHeader(v) { var t = s.headerText; return t ? (typeof t === 'function' ? t.call(e, v) : t.replace(/\{value\}/i, v)) : ''; } function read() { that.temp = that.values ? that.values.slice(0) : s.parseValue(elm.val() || '', that); setVal(); } function getCurrentPosition(t) { var style = window.getComputedStyle ? getComputedStyle(t[0]) : t[0].style, matrix, px; if (has3d) { $.each(['t', 'webkitT', 'MozT', 'OT', 'msT'], function (i, v) { if (style[v + 'ransform'] !== undefined) { matrix = style[v + 'ransform']; return false; } }); matrix = matrix.split(')')[0].split(', '); px = matrix[13] || matrix[5]; } else { px = style.top.replace('px', ''); } return Math.round(m - (px / hi)); } function ready(t, i) { clearTimeout(iv[i]); delete iv[i]; t.closest('.dwwl').removeClass('dwa'); } function scroll(t, index, val, time, active) { var px = (m - val) * hi, style = t[0].style, i; if (px == pixels[index] && iv[index]) { return; } if (time && px != pixels[index]) { // Trigger animation start event event('onAnimStart', [dw, index, time]); } pixels[index] = px; style[pr + 'Transition'] = 'all ' + (time ? time.toFixed(3) : 0) + 's ease-out'; if (has3d) { 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) { var cell = $('.dw-li[data-val="' + val + '"]', t), cells = $('.dw-li', t), v = cells.index(cell), l = cells.length; // Scroll to a valid cell if (!cell.hasClass('dw-v')) { 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; } } return { cell: cell, v: v, val: cell.hasClass('dw-v') ? cell.attr('data-val') : null }; } function scrollToPos(time, index, manual, dir, active) { // Call validation event if (event('validate', [dw, index, time, dir]) !== false) { // Set scrollers to position $('.dw-ul', dw).each(function (i) { var t = $(this), sc = i == index || index === undefined, res = getValid(that.temp[i], t, dir), cell = res.cell; if (!(cell.hasClass('dw-sel')) || sc) { // Set valid value that.temp[i] = res.val; if (!s.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'); // Scroll to position scroll(t, i, res.v, sc ? time : 0.1, sc ? active : false); } }); // Reformat value if validation changed something v = s.formatResult(that.temp); if (that.live) { setVal(manual, manual, 0, true); } $('.dwv', dw).html(formatHeader(v)); if (manual) { event('onChange', [v]); } } } function event(name, args) { var ret; args.push(that); $.each([userdef, theme, pres, settings], function (i, v) { if (v && v[name]) { // Call preset event ret = v[name].apply(e, args); } }); return ret; } function calc(t, val, dir, anim, orig) { val = constrain(val, min, max); var cell = $('.dw-li', t).eq(val), o = orig === undefined ? val : orig, active = orig !== undefined, idx = index, time = anim ? (val == o ? 0.1 : Math.abs((val - o) * s.timeUnit)) : 0; // Set selected scroller value that.temp[idx] = cell.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, val > max ? min : val, 1, true); } function minus(t) { var val = pos[index] - 1; calc(t, val < min ? max : val, 2, true); } function setVal(fill, change, time, noscroll, temp) { if (visible && !noscroll) { scrollToPos(time); } v = s.formatResult(that.temp); if (!temp) { that.values = that.temp.slice(0); that.val = v; } if (fill) { if (input) { elm.val(v); if (change) { preventChange = true; elm.change(); } } event('onValueFill', [v, change]); } } function attachPosition(ev, checkLock) { var debounce; wndw.on(ev, function (e) { clearTimeout(debounce); debounce = setTimeout(function () { if ((lock && checkLock) || !checkLock) { that.position(!checkLock); } }, 200); }); } // Public functions /** * Positions the scroller on the screen. */ that.position = function (check) { var nw = persp.width(), // To get the width without scrollbar nh = wndw[0].innerHeight || wndw.innerHeight(); if (!(ww === nw && wh === nh && check) && !preventPos && (event('onPosition', [dw, nw, nh]) !== false) && modal) { var w, l, t, aw, // anchor width ah, // anchor height ap, // anchor position at, // anchor top al, // anchor left arr, // arrow arrw, // arrow width arrl, // arrow left dh, scroll, totalw = 0, minw = 0, sl = wndw.scrollLeft(), st = wndw.scrollTop(), wr = $('.dwwr', dw), d = $('.dw', dw), css = {}, anchor = s.anchor === undefined ? elm : s.anchor; if (/modal|bubble/.test(s.display)) { $('.dwc', dw).each(function () { w = $(this).outerWidth(true); totalw += w; minw = (w > minw) ? w : minw; }); w = totalw > nw ? minw : totalw; wr.width(w).css('white-space', totalw > nw ? '' : 'nowrap'); } mw = d.outerWidth(); mh = d.outerHeight(true); lock = mh <= nh && mw <= nw; that.scrollLock = lock; if (s.display == 'modal') { l = (nw - mw) / 2; t = st + (nh - mh) / 2; } else if (s.display == 'bubble') { scroll = true; arr = $('.dw-arrw-i', dw); ap = anchor.offset(); at = Math.abs($(s.context).offset().top - ap.top); al = Math.abs($(s.context).offset().left - ap.left); // horizontal positioning aw = anchor.outerWidth(); ah = anchor.outerHeight(); l = constrain(al - (d.outerWidth(true) - aw) / 2 - sl, 3, nw - mw - 3); // vertical positioning t = at - mh; // above the input if ((t < st) || (at > st + nh)) { // if doesn't fit above or the input is out of the screen d.removeClass('dw-bubble-top').addClass('dw-bubble-bottom'); t = at + ah; // below the input } else { d.removeClass('dw-bubble-bottom').addClass('dw-bubble-top'); } // Calculate Arrow position arrw = arr.outerWidth(); arrl = constrain(al + aw / 2 - (l + (mw - arrw) / 2) - sl, 0, arrw); // Limit Arrow position $('.dw-arr', dw).css({ left: arrl }); } else { if (s.display == 'top') { t = st; } else if (s.display == 'bottom') { t = st + nh - mh; } } css.top = t < 0 ? 0 : t; css.left = l; d.css(css); // If top + modal height > doc height, increase doc height persp.height(0); dh = Math.max(t + mh, s.context == 'body' ? $(document).height() : doc.scrollHeight); persp.css({ height: dh, left: sl }); // Scroll needed if (scroll && ((t + mh > st + nh) || (at > st + nh))) { preventPos = true; setTimeout(function () { preventPos = false; }, 300); wndw.scrollTop(Math.min(t + mh - nh, dh - nh)); } } ww = nw; wh = nh; }; /** * Enables the scroller and the associated input. */ that.enable = function () { s.disabled = false; if (input) { elm.prop('disabled', false); } }; /** * Disables the scroller and the associated input. */ that.disable = function () { s.disabled = true; if (input) { elm.prop('disabled', true); } }; /** * 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) */ that.setValue = function (values, fill, time, temp, change) { that.temp = $.isArray(values) ? values.slice(0) : s.parseValue.call(e, values + '', that); setVal(fill, change === undefined ? fill : change, time, false, temp); }; /** * Return the selected wheel values. */ that.getValue = function () { return that.values; }; /** * Return selected values, if in multiselect mode. */ that.getValues = function () { var ret = [], i; for (i in that._selectedValues) { ret.push(that._selectedValues[i]); } return ret; }; /** * 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 (dw) { 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', dw).eq(i).html(generateWheelItems(i)); nr--; if (!nr) { that.position(); scrollToPos(time, undefined, manual); return false; } } i++; }); if (!nr) { return false; } }); } }; /** * Return true if the scroller is currently visible. */ that.isVisible = function () { return visible; }; /** * Attach tap event to the given element. */ that.tap = function (el, handler) { var startX, startY; if (s.tap) { el.on('touchstart.dw mousedown.dw', function (e) { e.preventDefault(); startX = getCoord(e, 'X'); startY = getCoord(e, 'Y'); }).on('touchend.dw', function (e) { // If movement is less than 20px, fire the click event handler if (Math.abs(getCoord(e, 'X') - startX) < 20 && Math.abs(getCoord(e, 'Y') - startY) < 20) { handler.call(this, e); } setTap(); }); } el.on('click.dw', function (e) { if (!tap) { // If handler was not called on touchend, call it on click; handler.call(this, e); } e.preventDefault(); }); }; /** * Shows the scroller instance. * @param {Boolean} prevAnim - Prevent animation if true */ that.show = function (prevAnim) { // Create wheels var lbl, l = 0, mAnim = ''; if (s.disabled || visible) { return; } if (s.display == 'top') { anim = 'slidedown'; } if (s.display == 'bottom') { anim = 'slideup'; } // Parse value from input read(); event('onBeforeShow', []); if (anim && !prevAnim) { mAnim = 'dw-' + anim + ' dw-in'; } // Create wheels containers var html = '