(function ($, window, document, undefined) { var $activeElm, preventShow, ms = $.mobiscroll, instances = ms.instances, util = ms.util, pr = util.jsPrefix, has3d = util.has3d, getCoord = util.getCoord, constrain = util.constrain, isString = util.isString, isOldAndroid = /android [1-3]/i.test(navigator.userAgent), isIOS8 = /(iphone|ipod|ipad).* os 8_/i.test(navigator.userAgent), animEnd = 'webkitAnimationEnd animationend', empty = function () { }, prevdef = function (ev) { ev.preventDefault(); }; ms.classes.Frame = function (el, settings, inherit) { var $ariaDiv, $ctx, $header, $markup, $overlay, $persp, $popup, $wnd, $wrapper, buttons, btn, doAnim, event, hasButtons, isModal, modalWidth, modalHeight, posEvents, preventPos, s, scrollLock, setReadOnly, wndWidth, wndHeight, that = this, $elm = $(el), elmList = [], posDebounce = {}; function onBtnStart(ev) { // Can't call preventDefault here, it kills page scroll if (btn) { btn.removeClass('dwb-a'); } btn = $(this); // Active button if (!btn.hasClass('dwb-d') && !btn.hasClass('dwb-nhl')) { btn.addClass('dwb-a'); } if (ev.type === 'mousedown') { $(document).on('mouseup', onBtnEnd); } } function onBtnEnd(ev) { if (btn) { btn.removeClass('dwb-a'); btn = null; } if (ev.type === 'mouseup') { $(document).off('mouseup', onBtnEnd); } } function onWndKeyDown(ev) { if (ev.keyCode == 13) { that.select(); } else if (ev.keyCode == 27) { that.cancel(); } } function onShow(prevFocus) { if (!prevFocus) { $popup.focus(); } that.ariaMessage(s.ariaMessage); } function onHide(prevAnim) { var activeEl, value, type, focus = s.focusOnClose; $markup.remove(); if ($activeElm && !prevAnim) { setTimeout(function () { if (focus === undefined || focus === true) { preventShow = true; activeEl = $activeElm[0]; type = activeEl.type; value = activeEl.value; try { activeEl.type = 'button'; } catch (ex) { } $activeElm.focus(); activeEl.type = type; activeEl.value = value; } else if (focus) { // If a mobiscroll field is focused, allow show if (instances[$(focus).attr('id')]) { ms.tapped = false; } $(focus).focus(); } }, 200); } that._isVisible = false; event('onHide', []); } function onPosition(ev) { clearTimeout(posDebounce[ev.type]); posDebounce[ev.type] = setTimeout(function () { var isScroll = ev.type == 'scroll'; if (isScroll && !scrollLock) { return; } that.position(!isScroll); }, 200); } function onFocus(ev) { if (!$popup[0].contains(ev.target)) { $popup.focus(); } } function show(beforeShow, $elm) { if (!ms.tapped) { if (beforeShow) { beforeShow(); } // Hide virtual keyboard if ($(document.activeElement).is('input,textarea')) { $(document.activeElement).blur(); } $activeElm = $elm; that.show(); } setTimeout(function () { preventShow = false; }, 300); // With jQuery < 1.9 focus is fired twice in IE } // Call the parent constructor ms.classes.Base.call(this, el, settings, true); /** * Positions the scroller on the screen. */ that.position = function (check) { var w, l, t, anchor, 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, sl, // scroll left st, // scroll top totalw = 0, minw = 0, css = {}, nw = Math.min($wnd[0].innerWidth || $wnd.innerWidth(), $persp.width()), //$persp.width(), // To get the width without scrollbar nh = $wnd[0].innerHeight || $wnd.innerHeight(); if ((wndWidth === nw && wndHeight === nh && check) || preventPos) { return; } if (that._isFullScreen || /top|bottom/.test(s.display)) { // Set width, if document is larger than viewport, needs to be set before onPosition (for calendar) $popup.width(nw); } if (event('onPosition', [$markup, nw, nh]) === false || !isModal) { return; } sl = $wnd.scrollLeft(); st = $wnd.scrollTop(); anchor = s.anchor === undefined ? $elm : $(s.anchor); // Set / unset liquid layout based on screen width, but only if not set explicitly by the user if (that._isLiquid && s.layout !== 'liquid') { if (nw < 400) { $markup.addClass('dw-liq'); } else { $markup.removeClass('dw-liq'); } } if (!that._isFullScreen && /modal|bubble/.test(s.display)) { $wrapper.width(''); $('.mbsc-w-p', $markup).each(function () { w = $(this).outerWidth(true); totalw += w; minw = (w > minw) ? w : minw; }); w = totalw > nw ? minw : totalw; $wrapper.width(w).css('white-space', totalw > nw ? '' : 'nowrap'); } modalWidth = that._isFullScreen ? nw : $popup.outerWidth(); modalHeight = that._isFullScreen ? nh : $popup.outerHeight(true); scrollLock = modalHeight <= nh && modalWidth <= nw; that.scrollLock = scrollLock; if (s.display == 'modal') { l = Math.max(0, sl + (nw - modalWidth) / 2); t = st + (nh - modalHeight) / 2; } else if (s.display == 'bubble') { scroll = true; arr = $('.dw-arrw-i', $markup); ap = anchor.offset(); at = Math.abs($ctx.offset().top - ap.top); al = Math.abs($ctx.offset().left - ap.left); // horizontal positioning aw = anchor.outerWidth(); ah = anchor.outerHeight(); l = constrain(al - ($popup.outerWidth(true) - aw) / 2, sl + 3, sl + nw - modalWidth - 3); // vertical positioning t = at - modalHeight; // above the input if ((t < st) || (at > st + nh)) { // if doesn't fit above or the input is out of the screen $popup.removeClass('dw-bubble-top').addClass('dw-bubble-bottom'); t = at + ah; // below the input } else { $popup.removeClass('dw-bubble-bottom').addClass('dw-bubble-top'); } // Calculate Arrow position arrw = arr.outerWidth(); arrl = constrain(al + aw / 2 - (l + (modalWidth - arrw) / 2), 0, arrw); // Limit Arrow position $('.dw-arr', $markup).css({ left: arrl }); } else { l = sl; if (s.display == 'top') { t = st; } else if (s.display == 'bottom') { t = st + nh - modalHeight; } } t = t < 0 ? 0 : t; css.top = t; css.left = l; $popup.css(css); // If top + modal height > doc height, increase doc height $persp.height(0); dh = Math.max(t + modalHeight, s.context == 'body' ? $(document).height() : $ctx[0].scrollHeight); $persp.css({ height: dh }); // Scroll needed if (scroll && ((t + modalHeight > st + nh) || (at > st + nh))) { preventPos = true; setTimeout(function () { preventPos = false; }, 300); $wnd.scrollTop(Math.min(t + modalHeight - nh, dh - nh)); } wndWidth = nw; wndHeight = nh; }; /** * Show mobiscroll on focus and click event of the parameter. * @param {jQuery} $elm - Events will be attached to this element. * @param {Function} [beforeShow=undefined] - Optional function to execute before showing mobiscroll. */ that.attachShow = function ($elm, beforeShow) { elmList.push({ readOnly: $elm.prop('readonly'), el: $elm }); if (s.display !== 'inline') { if (setReadOnly && $elm.is('input')) { $elm.prop('readonly', true).on('mousedown.dw', function (ev) { // Prevent input to get focus on tap (virtual keyboard pops up on some devices) ev.preventDefault(); }); } if (s.showOnFocus) { $elm.on('focus.dw', function () { if (!preventShow) { show(beforeShow, $elm); } }); } if (s.showOnTap) { $elm.on('keydown.dw', function (ev) { if (ev.keyCode == 32 || ev.keyCode == 13) { // Space or Enter ev.preventDefault(); ev.stopPropagation(); show(beforeShow, $elm); } }); that.tap($elm, function () { show(beforeShow, $elm); }); } } }; /** * Set button handler. */ that.select = function () { if (!isModal || that.hide(false, 'set') !== false) { that._fillValue(); event('onSelect', [that._value]); } }; /** * Cancel and hide the scroller instance. */ that.cancel = function () { if (!isModal || that.hide(false, 'cancel') !== false) { event('onCancel', [that._value]); } }; /** * Clear button handler. */ that.clear = function () { event('onClear', [$markup]); if (isModal && !that.live) { that.hide(false, 'clear'); } that.setVal(null, true); }; /** * Enables the scroller and the associated input. */ that.enable = function () { s.disabled = false; if (that._isInput) { $elm.prop('disabled', false); } }; /** * Disables the scroller and the associated input. */ that.disable = function () { s.disabled = true; if (that._isInput) { $elm.prop('disabled', true); } }; /** * Shows the scroller instance. * @param {Boolean} prevAnim - Prevent animation if true * @param {Boolean} prevFocus - Prevent focusing if true */ that.show = function (prevAnim, prevFocus) { // Create wheels var html; if (s.disabled || that._isVisible) { return; } if (doAnim !== false) { if (s.display == 'top') { doAnim = 'slidedown'; } if (s.display == 'bottom') { doAnim = 'slideup'; } } // Parse value from input that._readValue(); event('onBeforeShow', []); // Create wheels containers html = '
' + '
' + (isModal ? '
' : '') + // Overlay '' + // Popup (s.display === 'bubble' ? '
' : '') + // Bubble arrow '
' + // Popup content '
' + (s.headerText ? '
' + (isString(s.headerText) ? s.headerText : '') + '
' : '') + // Header '
'; // Wheel group container html += that._generateContent(); html += '
'; if (hasButtons) { html += '
'; $.each(buttons, function (i, b) { b = isString(b) ? that.buttons[b] : b; if (b.handler === 'set') { b.parentClass = 'dwb-s'; } if (b.handler === 'cancel') { b.parentClass = 'dwb-c'; } b.handler = isString(b.handler) ? that.handlers[b.handler] : b.handler; html += '
' + (b.text || '') + '
'; }); html += '
'; } html += '
'; $markup = $(html); $persp = $('.dw-persp', $markup); $overlay = $('.dwo', $markup); $wrapper = $('.dwwr', $markup); $header = $('.dwv', $markup); $popup = $('.dw', $markup); $ariaDiv = $('.dw-aria', $markup); that._markup = $markup; that._header = $header; that._isVisible = true; posEvents = 'orientationchange resize'; that._markupReady($markup); event('onMarkupReady', [$markup]); // Show if (isModal) { // Enter / ESC $(window).on('keydown', onWndKeyDown); // Prevent scroll if not specified otherwise if (s.scrollLock) { $markup.on('touchmove mousewheel wheel', function (ev) { if (scrollLock) { ev.preventDefault(); } }); } // Disable inputs to prevent bleed through (Android bug) if (pr !== 'Moz') { $('input,select,button', $ctx).each(function () { if (!this.disabled) { $(this).addClass('dwtd').prop('disabled', true); } }); } posEvents += ' scroll'; ms.activeInstance = that; $markup.appendTo($ctx); if (has3d && doAnim && !prevAnim) { $markup.addClass('dw-in dw-trans').on(animEnd, function () { $markup.off(animEnd).removeClass('dw-in dw-trans').find('.dw').removeClass('dw-' + doAnim); onShow(prevFocus); }).find('.dw').addClass('dw-' + doAnim); } } else if ($elm.is('div') && !that._hasContent) { $elm.html($markup); } else { $markup.insertAfter($elm); } event('onMarkupInserted', [$markup]); // Set position that.position(); $wnd .on(posEvents, onPosition) .on('focusin', onFocus); // Events $markup .on('selectstart mousedown', prevdef) // Prevents blue highlight on Android and text selection in IE .on('click', '.dwb-e', prevdef) .on('keydown', '.dwb-e', function (ev) { if (ev.keyCode == 32) { // Space ev.preventDefault(); ev.stopPropagation(); $(this).click(); } }) .on('keydown', function (ev) { // Trap focus inside modal if (ev.keyCode == 32) { // Space ev.preventDefault(); } else if (ev.keyCode == 9) { // Tab var $focusable = $markup.find('[tabindex="0"]').filter(function () { return this.offsetWidth > 0 || this.offsetHeight > 0; }), index = $focusable.index($(':focus', $markup)), i = $focusable.length - 1, target = 0; if (ev.shiftKey) { i = 0; target = -1; } if (index === i) { $focusable.eq(target).focus(); ev.preventDefault(); } } }); $('input', $markup).on('selectstart mousedown', function (ev) { ev.stopPropagation(); }); setTimeout(function () { // Init buttons $.each(buttons, function (i, b) { that.tap($('.dwb' + i, $markup), function (ev) { b = isString(b) ? that.buttons[b] : b; b.handler.call(this, ev, that); }, true); }); if (s.closeOnOverlay) { that.tap($overlay, function () { that.cancel(); }); } if (isModal && !doAnim) { onShow(prevFocus); } $markup .on('touchstart mousedown', '.dwb-e', onBtnStart) .on('touchend', '.dwb-e', onBtnEnd); that._attachEvents($markup); }, 300); event('onShow', [$markup, that._tempValue]); }; /** * Hides the scroller instance. */ that.hide = function (prevAnim, btn, force) { // If onClose handler returns false, prevent hide if (!that._isVisible || (!force && !that._isValid && btn == 'set') || (!force && event('onClose', [that._tempValue, btn]) === false)) { return false; } // Hide wheels and overlay if ($markup) { // Re-enable temporary disabled fields if (pr !== 'Moz') { $('.dwtd', $ctx).each(function () { $(this).prop('disabled', false).removeClass('dwtd'); }); } if (has3d && isModal && doAnim && !prevAnim && !$markup.hasClass('dw-trans')) { // If dw-trans class was not removed, means that there was no animation $markup.addClass('dw-out dw-trans').find('.dw').addClass('dw-' + doAnim).on(animEnd, function () { onHide(prevAnim); }); } else { onHide(prevAnim); } // Stop positioning on window resize $wnd .off(posEvents, onPosition) .off('focusin', onFocus); } if (isModal) { $(window).off('keydown', onWndKeyDown); delete ms.activeInstance; } }; that.ariaMessage = function (txt) { $ariaDiv.html(''); setTimeout(function () { $ariaDiv.html(txt); }, 100); }; /** * Return true if the scroller is currently visible. */ that.isVisible = function () { return that._isVisible; }; // Protected functions to override that.setVal = empty; that._generateContent = empty; that._attachEvents = empty; that._readValue = empty; that._fillValue = empty; that._markupReady = empty; that._processSettings = empty; that._presetLoad = function (s) { // Add default buttons s.buttons = s.buttons || (s.display !== 'inline' ? ['set', 'cancel'] : []); // Hide header text in inline mode by default s.headerText = s.headerText === undefined ? (s.display !== 'inline' ? '{value}' : false) : s.headerText; }; // Generic frame functions /** * Attach tap event to the given element. */ that.tap = function (el, handler, prevent) { var startX, startY, moved; if (s.tap) { el.on('touchstart.dw', function (ev) { // Can't always call preventDefault here, it kills page scroll if (prevent) { ev.preventDefault(); } startX = getCoord(ev, 'X'); startY = getCoord(ev, 'Y'); moved = false; }).on('touchmove.dw', function (ev) { // If movement is more than 20px, don't fire the click event handler if (Math.abs(getCoord(ev, 'X') - startX) > 20 || Math.abs(getCoord(ev, 'Y') - startY) > 20) { moved = true; } }).on('touchend.dw', function (ev) { var that = this; if (!moved) { // preventDefault and setTimeout are needed by iOS ev.preventDefault(); //setTimeout(function () { handler.call(that, ev); //}, isOldAndroid ? 400 : 10); } // Prevent click events to happen ms.tapped = true; setTimeout(function () { ms.tapped = false; }, 500); }); } el.on('click.dw', function (ev) { if (!ms.tapped) { // If handler was not called on touchend, call it on click; handler.call(this, ev); } ev.preventDefault(); }); }; /** * Destroys the mobiscroll instance. */ that.destroy = function () { // Force hide without animation that.hide(true, false, true); // Remove all events from elements $.each(elmList, function (i, v) { v.el.off('.dw').prop('readonly', v.readOnly); }); that._destroy(); }; /** * Scroller initialization. */ that.init = function (ss) { that._init(ss); that._isLiquid = (s.layout || (/top|bottom/.test(s.display) ? 'liquid' : '')) === 'liquid'; that._processSettings(); // Unbind all events (if re-init) $elm.off('.dw'); doAnim = isOldAndroid ? false : s.animate; buttons = s.buttons || []; isModal = s.display !== 'inline'; setReadOnly = s.showOnFocus || s.showOnTap; $wnd = $(s.context == 'body' ? window : s.context); $ctx = $(s.context); that.context = $wnd; that.live = true; // If no set button is found, live mode is activated $.each(buttons, function (i, b) { if (b == 'ok' || b == 'set' || b.handler == 'set') { that.live = false; return false; } }); that.buttons.set = { text: s.setText, handler: 'set' }; that.buttons.cancel = { text: (that.live) ? s.closeText : s.cancelText, handler: 'cancel' }; that.buttons.clear = { text: s.clearText, handler: 'clear' }; that._isInput = $elm.is('input'); hasButtons = buttons.length > 0; if (that._isVisible) { that.hide(true, false, true); } event('onInit', []); if (isModal) { that._readValue(); if (!that._hasContent) { that.attachShow($elm); } } else { that.show(); } $elm.on('change.dw', function () { if (!that._preventChange) { that.setVal($elm.val(), true, false); } that._preventChange = false; }); }; that.buttons = {}; that.handlers = { set: that.select, cancel: that.cancel, clear: that.clear }; that._value = null; that._isValid = true; that._isVisible = false; // Constructor s = that.settings; event = that.trigger; if (!inherit) { that.init(settings); } }; ms.classes.Frame.prototype._defaults = { // Localization lang: 'en', setText: 'Set', selectedText: 'Selected', closeText: 'Close', cancelText: 'Cancel', clearText: 'Clear', // Options disabled: false, closeOnOverlay: true, showOnFocus: false, showOnTap: true, display: 'modal', scrollLock: true, tap: true, btnClass: 'dwb', btnWidth: true, focusOnClose: !isIOS8 // Temporary for iOS8 }; ms.themes.frame.mobiscroll = { rows: 5, showLabel: false, headerText: false, btnWidth: false, selectedLineHeight: true, selectedLineBorder: 1, dateOrder: 'MMddyy', weekDays: 'min', checkIcon: 'ion-ios7-checkmark-empty', btnPlusClass: 'mbsc-ic mbsc-ic-arrow-down5', btnMinusClass: 'mbsc-ic mbsc-ic-arrow-up5', btnCalPrevClass: 'mbsc-ic mbsc-ic-arrow-left5', btnCalNextClass: 'mbsc-ic mbsc-ic-arrow-right5' }; // Prevent re-show on window focus $(window).on('focus', function () { if ($activeElm) { preventShow = true; } }); // Prevent standard behaviour on body click $(document).on('mouseover mouseup mousedown click', function (ev) { if (ms.tapped) { ev.stopPropagation(); ev.preventDefault(); return false; } }); })(jQuery, window, document);