From a3d4cddcb1ee368f70dfbac2ee29f4c402606226 Mon Sep 17 00:00:00 2001 From: woutervanwijk Date: Mon, 2 Dec 2013 12:32:17 +0100 Subject: [PATCH] bugfix for safari, searchresults --- flatclient/css/webclient.css | 12 +- flatclient/js/controls.js | 4 + flatclient/js/functionsvars.js | 2 +- flatclient/js/gui.js | 15 + flatclient/js/library.js | 5 +- webclient/css/jquery.mobile.iscrollview.css | 0 webclient/js/controls.js | 273 +++-- webclient/js/iscroll.bk | 1104 +++++++++++++++++++ webclient/js/iscroll.js | 73 +- webclient/js/jquery.mobile.iscrollview.js | 154 ++- 10 files changed, 1485 insertions(+), 157 deletions(-) mode change 100755 => 100644 webclient/css/jquery.mobile.iscrollview.css create mode 100755 webclient/js/iscroll.bk mode change 100755 => 100644 webclient/js/iscroll.js mode change 100755 => 100644 webclient/js/jquery.mobile.iscrollview.js diff --git a/flatclient/css/webclient.css b/flatclient/css/webclient.css index aa99c93..11036ae 100755 --- a/flatclient/css/webclient.css +++ b/flatclient/css/webclient.css @@ -316,13 +316,13 @@ .currenttrack2 { background-image: url('../images/icons/play_alt_12x12.png'); background-repeat: no-repeat; - background-position: 4px center; + background-position: 4px 51%; } .currenttrack { background-image: url('../images/icons/play_alt_16x16.png'); background-repeat: no-repeat; background-color: #eee; - background-position: 6px center; + background-position: 4px 50%; } .currenttrack2 a { @@ -584,10 +584,18 @@ a { } /* disable text selection for mouse swipe */ + body * { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -o-user-select: none; user-select: none; +} +/* but fix for text input (safari certainly needs it)*/ +input[type=text] { + -moz-user-select: text; + -webkit-user-select: text; + -o-user-select: text; + user-select: text; } \ No newline at end of file diff --git a/flatclient/js/controls.js b/flatclient/js/controls.js index 2295923..ebcdbd1 100755 --- a/flatclient/js/controls.js +++ b/flatclient/js/controls.js @@ -264,8 +264,10 @@ function setRandom(nwrandom) { function doRandom() { if (random == false) { + //mopidy.tracklist.setRandom(true).then(); mopidy.playback.setRandom(true).then(); } else { + //mopidy.tracklist.setRandom(false).then(); mopidy.playback.setRandom(false).then(); } setRandom(!random); @@ -273,8 +275,10 @@ function doRandom() { function doRepeat() { if (repeat == false) { + //mopidy.tracklist.setRepeat(true).then(); mopidy.playback.setRepeat(true).then(); } else { + //mopidy.tracklist.setRepeat(false).then(); mopidy.playback.setRepeat(false).then(); } setRepeat(!repeat); diff --git a/flatclient/js/functionsvars.js b/flatclient/js/functionsvars.js index 39ac52b..c8f0836 100755 --- a/flatclient/js/functionsvars.js +++ b/flatclient/js/functionsvars.js @@ -135,7 +135,7 @@ function albumTracksToTable(pl, target, uri) { for (var i = 0; i < pl.length; i++) { popupData[pl[i].uri] = pl[i]; liID = targetmin + '-' + pl[i].uri; - tmp += renderSongLi(pl[i], liID, uri, target); + tmp += renderSongLi(pl[i], liID, uri, 'playTrackByUri'); //child = '
  • '; // child += '

    ' + timeFromSeconds(pl[i].length / 1000) + '

    ' + pl[i].name + '

  • '; diff --git a/flatclient/js/gui.js b/flatclient/js/gui.js index bbcb3de..6d63b78 100755 --- a/flatclient/js/gui.js +++ b/flatclient/js/gui.js @@ -380,6 +380,9 @@ function updateStatusOfAll() { mopidy.playback.getTimePosition().then(processCurrentposition, console.error); mopidy.playback.getState().then(processPlaystate, console.error); +// mopidy.tracklist.getRepeat().then(processRepeat, console.error); +// mopidy.tracklist.getRandom().then(processRandom, console.error); + mopidy.playback.getRepeat().then(processRepeat, console.error); mopidy.playback.getRandom().then(processRandom, console.error); @@ -484,6 +487,7 @@ $(document).ready(function(event) { switchContent("playlists"); } + initgui = false; window.onhashchange = locationHashChanged; // Log all events @@ -571,4 +575,15 @@ $(document).ready(function(event) { $("#panel").panel("close"); event.stopImmediatePropagation(); } } ); + +//mopidy.on("state:online", debug); + }); + +function debug() { + if (mopidy.tracklist === undefined) { + alert('Broke at ' + new Date()); + } else { + setTimeout(debug, 100); + } +} diff --git a/flatclient/js/library.js b/flatclient/js/library.js index 29814d8..0673f26 100755 --- a/flatclient/js/library.js +++ b/flatclient/js/library.js @@ -46,7 +46,7 @@ function processSearchResults(resultArr) { var results = {'tracks': [], 'artists': [], 'albums': []}; var emptyResult = true; - console.log(resultArr, 'resultArr'); +// console.log(resultArr, 'resultArr'); for (var i = 0; i < resultArr.length; ++i) { for (var prop in results) { @@ -134,7 +134,8 @@ function processSearchResults(resultArr) { $('#expandsearch').show(); // Track results - playlisttotable(results.tracks, SEARCH_TRACK_TABLE, 'trackresultscache'); +// playlisttotable(results.tracks, SEARCH_TRACK_TABLE, 'trackresultscache'); + resultsToTables(results.tracks, SEARCH_TRACK_TABLE, 'trackresultscache'); setSongInfo(); showLoading(false); diff --git a/webclient/css/jquery.mobile.iscrollview.css b/webclient/css/jquery.mobile.iscrollview.css old mode 100755 new mode 100644 diff --git a/webclient/js/controls.js b/webclient/js/controls.js index df3cf99..c12ae03 100755 --- a/webclient/js/controls.js +++ b/webclient/js/controls.js @@ -1,17 +1,18 @@ /******************************************************** - * play an uri from a tracklist + * play an uri from a tracklist *********************************************************/ function playTrack(addtoqueue) { //stop directly, for user feedback if (!addtoqueue) { mopidy.playback.stop(true); - mopidy.tracklist.clear(); + mopidy.tracklist.clear(); } $('#popupTracks').popup('close'); $('#controlspopup').popup('close'); toast('Loading...'); playlisturi = $('#popupTracks').data("list"); + uri = $('#popupTracks').data("track"); var trackslist = new Array(); @@ -19,54 +20,44 @@ function playTrack(addtoqueue) { var tracks = getTracksFromUri(playlisturi); //find track that was selected - for (var selected = 0; selected < tracks.length; selected++) { + for (var selected = 0; selected < tracks.length; selected++) { if (tracks[selected].uri == uri) { - break; - } + break; + } } //find track that is playing for (var playing = 0; playing < currentplaylist.length; playing++) { if (currentplaylist[playing].uri == songdata.uri) { - break; - } + break; + } } //switch popup options switch (addtoqueue) { - case ADD_THIS_BOTTOM: + case ADD_THIS_BOTTOM: mopidy.tracklist.add(tracks.slice(selected, selected + 1)); - return false; - case PLAY_NEXT: - mopidy.tracklist.add(tracks.slice(selected, selected + 1), playing + 1); - return false; - case ADD_ALL_BOTTOM: + return false; + case PLAY_NEXT: + mopidy.tracklist.add(tracks.slice(selected, selected + 1), playing + 1); + return false; + case ADD_ALL_BOTTOM: mopidy.tracklist.add(tracks); - return false; + return false; } - - mopidy.tracklist.add(tracks); - - for (var i = 0; i <= selected; i++) { - mopidy.playback.next(); - } - - mopidy.playback.play(); - - // first add track to be played, then the other tracks -/* -// mopidy.tracklist.add(null, 0, playlisturi); - mopidy.tracklist.add( tracks.slice(selected, selected + 1) ); - //wait .5 second before adding the rest to give server the time to start playing - setTimeout(function() { - mopidy.tracklist.add(tracks.slice(0, selected), 0); - if (selected < tracks.length) { - mopidy.tracklist.add(tracks.slice(selected + 1) ); - } - }, 500); +// mopidy.tracklist.add(null, 0, playlisturi); + mopidy.tracklist.add(tracks); //.slice(selected, selected + 1) ); + //wait 1.5 second before adding the rest to give server the time to start playing + /* setTimeout(function() { + mopidy.tracklist.add(tracks.slice(0, selected), 0); + if (selected < tracks.length) { + mopidy.tracklist.add(tracks.slice(selected + 1) ); + } + }, 1500); + */ // mopidy.playback.changeTrack(tracks[selected]); for (var i = 0; i <= selected; i++) { @@ -74,23 +65,75 @@ function playTrack(addtoqueue) { } mopidy.playback.play(); //tracks[selected]); -*/ - console.log(selected); + //console.log(selected); + return false; +} + +/*** + * Plays a Track given by an URI + * @param uri + * @returns {boolean} + */ +function playTrackByUri(uri, playlisturi){ + //console.log('playuri'); + //stop directly, for user feedback + mopidy.playback.stop(true); + mopidy.tracklist.clear(); + + //this is deprecated, remove when popuptracks is removed completly + $('#popupTracks').popup('close'); + $('#controlspopup').popup('close'); + //end of deprecated + + toast('Loading...'); + + var trackslist = new Array(); + var track, tracksbefore, tracksafter; + var tracks = getTracksFromUri(playlisturi); + +//find track that was selected + for (var selected = 0; selected < tracks.length; selected++) { + if (tracks[selected].uri == uri) { + break; + } + } + +//find track that is playing + for (var playing = 0; playing < currentplaylist.length; playing++) { + if (currentplaylist[playing].uri == songdata.uri) { + break; + } + } + + mopidy.tracklist.add(tracks); + + for (var i = 0; i <= selected; i++) { + mopidy.playback.next(); + } + + mopidy.playback.play(); //tracks[selected]); +// console.log(selected); return false; } /******************************************************** * play an uri from the queue *********************************************************/ -function playTrackQueue() { + +/*** + * Plays a Track from a Playlist. + * @param uri + * @param playlisturi + * @returns {boolean} + */ +function playTrackQueueByUri(uri, playlisturi){ + //console.log('playqu'); //stop directly, for user feedback +//console.log('qu'); mopidy.playback.stop(true); $('#popupQueue').popup('close'); toast('Loading...'); - playlisturi = $('#popupQueue').data("list"); - uri = $('#popupQueue').data("track"); - var track; for (var i = 0; i < currentplaylist.length; i++) { if (currentplaylist[i].uri == uri) { @@ -103,10 +146,20 @@ function playTrackQueue() { } mopidy.playback.play(); //currentplaylist[track]); - console.log(track, currentplaylist[track]); + //console.log(track, currentplaylist[track]); return false; } +/*** + * @deprecated + * @returns {boolean} + */ +function playTrackQueue() { + playlisturi = $('#popupQueue').data("list"); + uri = $('#popupQueue').data("track"); + return playTrackByUri(uri, playlisturi); +} + /******************************************************** * remove a track from the queue *********************************************************/ @@ -128,7 +181,7 @@ function removeTrack() { console.log(currentplaylist[i].uri); } -function clearQueue () { +function clearQueue() { mopidy.tracklist.clear(); return false; } @@ -147,8 +200,10 @@ function doShuffle() { function setPlayState(nwplay) { if (nwplay) { $("#playimg").attr('src', 'images/icons/pause_32x32.png'); + $("#btplayNowPlaying >i").removeClass('fa-play').addClass('fa-pause'); } else { $("#playimg").attr('src', 'images/icons/play_alt_32x32.png'); + $("#btplayNowPlaying >i").removeClass('fa-pause').addClass('fa-play'); } play = nwplay; } @@ -209,22 +264,70 @@ function setRandom(nwrandom) { function doRandom() { if (random == false) { - mopidy.playback.setRandom(true); + mopidy.tracklist.setRandom(true).then(); +// mopidy.playback.setRandom(true).then(); } else { - mopidy.playback.setRandom(false); + mopidy.tracklist.setRandom(false).then(); +// mopidy.playback.setRandom(false).then(); } setRandom(!random); } function doRepeat() { if (repeat == false) { - mopidy.playback.setRepeat(true).then(); + mopidy.tracklist.setRepeat(true).then(); +// mopidy.playback.setRepeat(true).then(); } else { - mopidy.playback.setRepeat(false).then(); + mopidy.tracklist.setRepeat(false).then(); +// mopidy.playback.setRepeat(false).then(); } setRepeat(!repeat); } + +/* +function setRepeat(nwrepeat) { + if (repeat == nwrepeat) { + return + } + if (!nwrepeat) { + $("#repeatbt").attr('src', 'images/icons/reload_alt_18x21.png'); + } else { + $("#repeatbt").attr('src', 'images/icons/reload_18x21.png'); + } + repeat = nwrepeat; +} + +function setRandom(nwrandom) { + if(nwrandom){ + $("#flip-random").val('On'); + }else{ + $("#flip-random").val('Off'); + } + console.log('function setRandom called: '+nwrandom); +} + +function doRandom() { + console.log('obsolete function doRandom called'); +} +*/ +//$("#flip-random").change(function () { +// if ($(this).val() == "on") { +// mopidy.tracklist.setRandom(true).then(); +// } else if ($(this).val() == "off") { +// mopidy.tracklist.setRandom(false).then(); +// } +//}); +// +//$("#flip-repeat").change(function () { +// if ($(this).val() == "on") { +// mopidy.tracklist.setRepeat(true).then(); +// } else if ($(this).val() == "off") { +// mopidy.tracklist.setRepeat(false).then(); +// } +//}); + + /********************* * Track Slider * Use a timer to prevent looping of commands @@ -237,7 +340,7 @@ function doSeekPos(value) { pauseTimer(); //set timer to not trigger it too much clearTimeout(seekTimer); - $("#songelapsed").html(timeFromSeconds(val / 1000)); + $("#songelapsed").html(timeFromSeconds(val / 1000)); seekTimer = setTimeout(triggerPos, 500); } } @@ -249,7 +352,7 @@ function triggerPos() { // console.log(newposition); mopidy.playback.seek(newposition); // mopidy.playback.resume(); - resumeTimer(); + resumeTimer(); posChanging = false; } } @@ -322,8 +425,8 @@ function updateTimer() { function resumeTimer() { pauseTimer(); - if(songlength > 0) { - posTimer = setInterval(updateTimer, TRACK_TIMER); + if (songlength > 0) { + posTimer = setInterval(updateTimer, TRACK_TIMER); } } @@ -352,43 +455,44 @@ function addRadioUri(name, uri) { //value of name is based on the passing of an uri as a parameter or not var name = ''; if (!uri) { - name = $('#radionameinput').val(); + name = $('#radionameinput').val(); } else { - $('#radionameinput').val(''); + $('#radionameinput').val(''); } uri = uri || $('#radiouriinput').val(); if (validUri(uri)) { toast('Selecting radiostation...'); - //stop directly, for user feedback - mopidy.playback.stop(true); + //stop directly, for user feedback + mopidy.playback.stop(true); //hide ios/android keyboard document.activeElement.blur(); $("input").blur(); - clearQueue(); - mopidy.tracklist.add(null,0, uri ); + clearQueue(); + mopidy.tracklist.add(null, 0, uri); mopidy.playback.play(); - var tmpname = name || ''; - var i = 0; - //add station to list and check for doubles and add no more than 25 + var tmpname = name || ''; + var i = 0; + //add station to list and check for doubles and add no more than 25 for (var key in radioStations) { - rs = radioStations[key]; - if (i > 25) { - delete radioStations[key]; - continue; - } - i++; - if (rs && rs[1] == uri) { - tmpname = name || radioStations[key][0]; - delete radioStations[key]; - } - }; - $('#radionameinput').val(tmpname); - $('#radiouriinput').val(uri); - radioStations.unshift([tmpname, uri]); - saveRadioStations(); - updateRadioStations(); + rs = radioStations[key]; + if (i > 25) { + delete radioStations[key]; + continue; + } + i++; + if (rs && rs[1] == uri) { + tmpname = name || radioStations[key][0]; + delete radioStations[key]; + } + } + ; + $('#radionameinput').val(tmpname); + $('#radiouriinput').val(uri); + radioStations.unshift([tmpname, uri]); + saveRadioStations(); + updateRadioStations(); } else { - toast ('No valid url!'); + toast('No valid url!'); } return false; } @@ -398,14 +502,15 @@ function updateRadioStations() { $('#radiostationstable').empty(); var child = ''; for (var key in radioStations) { - var rs = radioStations[key]; - if(rs) { - name = rs[0] || rs[1]; - child = '
  • '; - child += '

    ' + name + '

  • '; - tmp += child; - } - }; + var rs = radioStations[key]; + if (rs) { + name = rs[0] || rs[1]; + child = '
  • '; + child += '

    ' + name + '

  • '; + tmp += child; + } + } + ; $('#radiostationstable').html(tmp); } diff --git a/webclient/js/iscroll.bk b/webclient/js/iscroll.bk new file mode 100755 index 0000000..f8576f0 --- /dev/null +++ b/webclient/js/iscroll.bk @@ -0,0 +1,1104 @@ +/*! + * iScroll v4.2.5 ~ Copyright (c) 2012 Matteo Spinelli, http://cubiq.org + * Released under MIT license, http://cubiq.org/license + */ +(function(window, doc){ +var m = Math, + dummyStyle = doc.createElement('div').style, + vendor = (function () { + var vendors = 't,webkitT,MozT,msT,OT'.split(','), + t, + i = 0, + l = vendors.length; + + for ( ; i < l; i++ ) { + t = vendors[i] + 'ransform'; + if ( t in dummyStyle ) { + return vendors[i].substr(0, vendors[i].length - 1); + } + } + + return false; + })(), + cssVendor = vendor ? '-' + vendor.toLowerCase() + '-' : '', + + // Style properties + transform = prefixStyle('transform'), + transitionProperty = prefixStyle('transitionProperty'), + transitionDuration = prefixStyle('transitionDuration'), + transformOrigin = prefixStyle('transformOrigin'), + transitionTimingFunction = prefixStyle('transitionTimingFunction'), + transitionDelay = prefixStyle('transitionDelay'), + + // Browser capabilities + isAndroid = (/android/gi).test(navigator.appVersion), + isIDevice = (/iphone|ipad/gi).test(navigator.appVersion), + isTouchPad = (/hp-tablet/gi).test(navigator.appVersion), + + has3d = prefixStyle('perspective') in dummyStyle, + hasTouch = 'ontouchstart' in window && !isTouchPad, + hasTransform = vendor !== false, + hasTransitionEnd = prefixStyle('transition') in dummyStyle, + + RESIZE_EV = 'onorientationchange' in window ? 'orientationchange' : 'resize', + START_EV = hasTouch ? 'touchstart' : 'mousedown', + MOVE_EV = hasTouch ? 'touchmove' : 'mousemove', + END_EV = hasTouch ? 'touchend' : 'mouseup', + CANCEL_EV = hasTouch ? 'touchcancel' : 'mouseup', + TRNEND_EV = (function () { + if ( vendor === false ) return false; + + var transitionEnd = { + '' : 'transitionend', + 'webkit' : 'webkitTransitionEnd', + 'Moz' : 'transitionend', + 'O' : 'otransitionend', + 'ms' : 'MSTransitionEnd' + }; + + return transitionEnd[vendor]; + })(), + + nextFrame = (function() { + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { return setTimeout(callback, 1); }; + })(), + cancelFrame = (function () { + return window.cancelRequestAnimationFrame || + window.webkitCancelAnimationFrame || + window.webkitCancelRequestAnimationFrame || + window.mozCancelRequestAnimationFrame || + window.oCancelRequestAnimationFrame || + window.msCancelRequestAnimationFrame || + clearTimeout; + })(), + + // Helpers + translateZ = has3d ? ' translateZ(0)' : '', + + // Constructor + iScroll = function (el, options) { + var that = this, + i; + + that.wrapper = typeof el == 'object' ? el : doc.getElementById(el); + that.wrapper.style.overflow = 'hidden'; + that.scroller = that.wrapper.children[0]; + + // Default options + that.options = { + hScroll: true, + vScroll: true, + x: 0, + y: 0, + bounce: true, + bounceLock: false, + momentum: true, + lockDirection: true, + useTransform: true, + useTransition: false, + topOffset: 0, + checkDOMChanges: false, // Experimental + handleClick: true, + + // Scrollbar + hScrollbar: true, + vScrollbar: true, + fixedScrollbar: isAndroid, + hideScrollbar: isIDevice, + fadeScrollbar: isIDevice && has3d, + scrollbarClass: '', + + // Zoom + zoom: false, + zoomMin: 1, + zoomMax: 4, + doubleTapZoom: 2, + wheelAction: 'scroll', + + // Snap + snap: false, + snapThreshold: 1, + + // Events + onRefresh: null, + onBeforeScrollStart: function (e) { e.preventDefault(); }, + onScrollStart: null, + onBeforeScrollMove: null, + onScrollMove: null, + onBeforeScrollEnd: null, + onScrollEnd: null, + onTouchEnd: null, + onDestroy: null, + onZoomStart: null, + onZoom: null, + onZoomEnd: null + }; + + // User defined options + for (i in options) that.options[i] = options[i]; + + // Set starting position + that.x = that.options.x; + that.y = that.options.y; + + // Normalize options + that.options.useTransform = hasTransform && that.options.useTransform; + that.options.hScrollbar = that.options.hScroll && that.options.hScrollbar; + that.options.vScrollbar = that.options.vScroll && that.options.vScrollbar; + that.options.zoom = that.options.useTransform && that.options.zoom; + that.options.useTransition = hasTransitionEnd && that.options.useTransition; + + // Helpers FIX ANDROID BUG! + // translate3d and scale doesn't work together! + // Ignoring 3d ONLY WHEN YOU SET that.options.zoom + if ( that.options.zoom && isAndroid ){ + translateZ = ''; + } + + // Set some default styles + that.scroller.style[transitionProperty] = that.options.useTransform ? cssVendor + 'transform' : 'top left'; + that.scroller.style[transitionDuration] = '0'; + that.scroller.style[transformOrigin] = '0 0'; + if (that.options.useTransition) that.scroller.style[transitionTimingFunction] = 'cubic-bezier(0.33,0.66,0.66,1)'; + + if (that.options.useTransform) that.scroller.style[transform] = 'translate(' + that.x + 'px,' + that.y + 'px)' + translateZ; + else that.scroller.style.cssText += ';position:absolute;top:' + that.y + 'px;left:' + that.x + 'px'; + + if (that.options.useTransition) that.options.fixedScrollbar = true; + + that.refresh(); + + that._bind(RESIZE_EV, window); + that._bind(START_EV); + if (!hasTouch) { + if (that.options.wheelAction != 'none') { + that._bind('DOMMouseScroll'); + that._bind('mousewheel'); + } + } + + if (that.options.checkDOMChanges) that.checkDOMTime = setInterval(function () { + that._checkDOMChanges(); + }, 500); + }; + +// Prototype +iScroll.prototype = { + enabled: true, + x: 0, + y: 0, + steps: [], + scale: 1, + currPageX: 0, currPageY: 0, + pagesX: [], pagesY: [], + aniTime: null, + wheelZoomCount: 0, + + handleEvent: function (e) { + var that = this; + switch(e.type) { + case START_EV: + if (!hasTouch && e.button !== 0) return; + that._start(e); + break; + case MOVE_EV: that._move(e); break; + case END_EV: + case CANCEL_EV: that._end(e); break; + case RESIZE_EV: that._resize(); break; + case 'DOMMouseScroll': case 'mousewheel': that._wheel(e); break; + case TRNEND_EV: that._transitionEnd(e); break; + } + }, + + _checkDOMChanges: function () { + if (this.moved || this.zoomed || this.animating || + (this.scrollerW == this.scroller.offsetWidth * this.scale && this.scrollerH == this.scroller.offsetHeight * this.scale)) return; + + this.refresh(); + }, + + _scrollbar: function (dir) { + var that = this, + bar; + + if (!that[dir + 'Scrollbar']) { + if (that[dir + 'ScrollbarWrapper']) { + if (hasTransform) that[dir + 'ScrollbarIndicator'].style[transform] = ''; + that[dir + 'ScrollbarWrapper'].parentNode.removeChild(that[dir + 'ScrollbarWrapper']); + that[dir + 'ScrollbarWrapper'] = null; + that[dir + 'ScrollbarIndicator'] = null; + } + + return; + } + + if (!that[dir + 'ScrollbarWrapper']) { + // Create the scrollbar wrapper + bar = doc.createElement('div'); + + if (that.options.scrollbarClass) bar.className = that.options.scrollbarClass + dir.toUpperCase(); + else bar.style.cssText = 'position:absolute;z-index:100;' + (dir == 'h' ? 'height:7px;bottom:1px;left:2px;right:' + (that.vScrollbar ? '7' : '2') + 'px' : 'width:7px;bottom:' + (that.hScrollbar ? '7' : '2') + 'px;top:2px;right:1px'); + + bar.style.cssText += ';pointer-events:none;' + cssVendor + 'transition-property:opacity;' + cssVendor + 'transition-duration:' + (that.options.fadeScrollbar ? '350ms' : '0') + ';overflow:hidden;opacity:' + (that.options.hideScrollbar ? '0' : '1'); + + that.wrapper.appendChild(bar); + that[dir + 'ScrollbarWrapper'] = bar; + + // Create the scrollbar indicator + bar = doc.createElement('div'); + if (!that.options.scrollbarClass) { + bar.style.cssText = 'position:absolute;z-index:100;background:rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.9);' + cssVendor + 'background-clip:padding-box;' + cssVendor + 'box-sizing:border-box;' + (dir == 'h' ? 'height:100%' : 'width:100%') + ';' + cssVendor + 'border-radius:3px;border-radius:3px'; + } + bar.style.cssText += ';pointer-events:none;' + cssVendor + 'transition-property:' + cssVendor + 'transform;' + cssVendor + 'transition-timing-function:cubic-bezier(0.33,0.66,0.66,1);' + cssVendor + 'transition-duration:0;' + cssVendor + 'transform: translate(0,0)' + translateZ; + if (that.options.useTransition) bar.style.cssText += ';' + cssVendor + 'transition-timing-function:cubic-bezier(0.33,0.66,0.66,1)'; + + that[dir + 'ScrollbarWrapper'].appendChild(bar); + that[dir + 'ScrollbarIndicator'] = bar; + } + + if (dir == 'h') { + that.hScrollbarSize = that.hScrollbarWrapper.clientWidth; + that.hScrollbarIndicatorSize = m.max(m.round(that.hScrollbarSize * that.hScrollbarSize / that.scrollerW), 8); + that.hScrollbarIndicator.style.width = that.hScrollbarIndicatorSize + 'px'; + that.hScrollbarMaxScroll = that.hScrollbarSize - that.hScrollbarIndicatorSize; + that.hScrollbarProp = that.hScrollbarMaxScroll / that.maxScrollX; + } else { + that.vScrollbarSize = that.vScrollbarWrapper.clientHeight; + that.vScrollbarIndicatorSize = m.max(m.round(that.vScrollbarSize * that.vScrollbarSize / that.scrollerH), 8); + that.vScrollbarIndicator.style.height = that.vScrollbarIndicatorSize + 'px'; + that.vScrollbarMaxScroll = that.vScrollbarSize - that.vScrollbarIndicatorSize; + that.vScrollbarProp = that.vScrollbarMaxScroll / that.maxScrollY; + } + + // Reset position + that._scrollbarPos(dir, true); + }, + + _resize: function () { + var that = this; + setTimeout(function () { that.refresh(); }, isAndroid ? 200 : 0); + }, + + _pos: function (x, y) { + if (this.zoomed) return; + + x = this.hScroll ? x : 0; + y = this.vScroll ? y : 0; + + if (this.options.useTransform) { + this.scroller.style[transform] = 'translate(' + x + 'px,' + y + 'px) scale(' + this.scale + ')' + translateZ; + } else { + x = m.round(x); + y = m.round(y); + this.scroller.style.left = x + 'px'; + this.scroller.style.top = y + 'px'; + } + + this.x = x; + this.y = y; + + this._scrollbarPos('h'); + this._scrollbarPos('v'); + }, + + _scrollbarPos: function (dir, hidden) { + var that = this, + pos = dir == 'h' ? that.x : that.y, + size; + + if (!that[dir + 'Scrollbar']) return; + + pos = that[dir + 'ScrollbarProp'] * pos; + + if (pos < 0) { + if (!that.options.fixedScrollbar) { + size = that[dir + 'ScrollbarIndicatorSize'] + m.round(pos * 3); + if (size < 8) size = 8; + that[dir + 'ScrollbarIndicator'].style[dir == 'h' ? 'width' : 'height'] = size + 'px'; + } + pos = 0; + } else if (pos > that[dir + 'ScrollbarMaxScroll']) { + if (!that.options.fixedScrollbar) { + size = that[dir + 'ScrollbarIndicatorSize'] - m.round((pos - that[dir + 'ScrollbarMaxScroll']) * 3); + if (size < 8) size = 8; + that[dir + 'ScrollbarIndicator'].style[dir == 'h' ? 'width' : 'height'] = size + 'px'; + pos = that[dir + 'ScrollbarMaxScroll'] + (that[dir + 'ScrollbarIndicatorSize'] - size); + } else { + pos = that[dir + 'ScrollbarMaxScroll']; + } + } + + that[dir + 'ScrollbarWrapper'].style[transitionDelay] = '0'; + that[dir + 'ScrollbarWrapper'].style.opacity = hidden && that.options.hideScrollbar ? '0' : '1'; + that[dir + 'ScrollbarIndicator'].style[transform] = 'translate(' + (dir == 'h' ? pos + 'px,0)' : '0,' + pos + 'px)') + translateZ; + }, + + _start: function (e) { + var that = this, + point = hasTouch ? e.touches[0] : e, + matrix, x, y, + c1, c2; + + if (!that.enabled) return; + + if (that.options.onBeforeScrollStart) that.options.onBeforeScrollStart.call(that, e); + + if (that.options.useTransition || that.options.zoom) that._transitionTime(0); + + that.moved = false; + that.animating = false; + that.zoomed = false; + that.distX = 0; + that.distY = 0; + that.absDistX = 0; + that.absDistY = 0; + that.dirX = 0; + that.dirY = 0; + + // Gesture start + if (that.options.zoom && hasTouch && e.touches.length > 1) { + c1 = m.abs(e.touches[0].pageX-e.touches[1].pageX); + c2 = m.abs(e.touches[0].pageY-e.touches[1].pageY); + that.touchesDistStart = m.sqrt(c1 * c1 + c2 * c2); + + that.originX = m.abs(e.touches[0].pageX + e.touches[1].pageX - that.wrapperOffsetLeft * 2) / 2 - that.x; + that.originY = m.abs(e.touches[0].pageY + e.touches[1].pageY - that.wrapperOffsetTop * 2) / 2 - that.y; + + if (that.options.onZoomStart) that.options.onZoomStart.call(that, e); + } + + if (that.options.momentum) { + if (that.options.useTransform) { + // Very lame general purpose alternative to CSSMatrix + matrix = getComputedStyle(that.scroller, null)[transform].replace(/[^0-9\-.,]/g, '').split(','); + x = +(matrix[12] || matrix[4]); + y = +(matrix[13] || matrix[5]); + } else { + x = +getComputedStyle(that.scroller, null).left.replace(/[^0-9-]/g, ''); + y = +getComputedStyle(that.scroller, null).top.replace(/[^0-9-]/g, ''); + } + + if (x != that.x || y != that.y) { + if (that.options.useTransition) that._unbind(TRNEND_EV); + else cancelFrame(that.aniTime); + that.steps = []; + that._pos(x, y); + if (that.options.onScrollEnd) that.options.onScrollEnd.call(that); + } + } + + that.absStartX = that.x; // Needed by snap threshold + that.absStartY = that.y; + + that.startX = that.x; + that.startY = that.y; + that.pointX = point.pageX; + that.pointY = point.pageY; + + that.startTime = e.timeStamp || Date.now(); + + if (that.options.onScrollStart) that.options.onScrollStart.call(that, e); + + that._bind(MOVE_EV, window); + that._bind(END_EV, window); + that._bind(CANCEL_EV, window); + }, + + _move: function (e) { + var that = this, + point = hasTouch ? e.touches[0] : e, + deltaX = point.pageX - that.pointX, + deltaY = point.pageY - that.pointY, + newX = that.x + deltaX, + newY = that.y + deltaY, + c1, c2, scale, + timestamp = e.timeStamp || Date.now(); + + if (that.options.onBeforeScrollMove) that.options.onBeforeScrollMove.call(that, e); + + // Zoom + if (that.options.zoom && hasTouch && e.touches.length > 1) { + c1 = m.abs(e.touches[0].pageX - e.touches[1].pageX); + c2 = m.abs(e.touches[0].pageY - e.touches[1].pageY); + that.touchesDist = m.sqrt(c1*c1+c2*c2); + + that.zoomed = true; + + scale = 1 / that.touchesDistStart * that.touchesDist * this.scale; + + if (scale < that.options.zoomMin) scale = 0.5 * that.options.zoomMin * Math.pow(2.0, scale / that.options.zoomMin); + else if (scale > that.options.zoomMax) scale = 2.0 * that.options.zoomMax * Math.pow(0.5, that.options.zoomMax / scale); + + that.lastScale = scale / this.scale; + + newX = this.originX - this.originX * that.lastScale + this.x, + newY = this.originY - this.originY * that.lastScale + this.y; + + this.scroller.style[transform] = 'translate(' + newX + 'px,' + newY + 'px) scale(' + scale + ')' + translateZ; + + if (that.options.onZoom) that.options.onZoom.call(that, e); + return; + } + + that.pointX = point.pageX; + that.pointY = point.pageY; + + // Slow down if outside of the boundaries + if (newX > 0 || newX < that.maxScrollX) { + newX = that.options.bounce ? that.x + (deltaX / 2) : newX >= 0 || that.maxScrollX >= 0 ? 0 : that.maxScrollX; + } + if (newY > that.minScrollY || newY < that.maxScrollY) { + newY = that.options.bounce ? that.y + (deltaY / 2) : newY >= that.minScrollY || that.maxScrollY >= 0 ? that.minScrollY : that.maxScrollY; + } + + that.distX += deltaX; + that.distY += deltaY; + that.absDistX = m.abs(that.distX); + that.absDistY = m.abs(that.distY); + + if (that.absDistX < 6 && that.absDistY < 6) { + return; + } + + // Lock direction + if (that.options.lockDirection) { + if (that.absDistX > that.absDistY + 5) { + newY = that.y; + deltaY = 0; + } else if (that.absDistY > that.absDistX + 5) { + newX = that.x; + deltaX = 0; + } + } + + that.moved = true; + that._pos(newX, newY); + that.dirX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0; + that.dirY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0; + + if (timestamp - that.startTime > 300) { + that.startTime = timestamp; + that.startX = that.x; + that.startY = that.y; + } + + if (that.options.onScrollMove) that.options.onScrollMove.call(that, e); + }, + + _end: function (e) { + if (hasTouch && e.touches.length !== 0) return; + + var that = this, + point = hasTouch ? e.changedTouches[0] : e, + target, ev, + momentumX = { dist:0, time:0 }, + momentumY = { dist:0, time:0 }, + duration = (e.timeStamp || Date.now()) - that.startTime, + newPosX = that.x, + newPosY = that.y, + distX, distY, + newDuration, + snap, + scale; + + that._unbind(MOVE_EV, window); + that._unbind(END_EV, window); + that._unbind(CANCEL_EV, window); + + if (that.options.onBeforeScrollEnd) that.options.onBeforeScrollEnd.call(that, e); + + if (that.zoomed) { + scale = that.scale * that.lastScale; + scale = Math.max(that.options.zoomMin, scale); + scale = Math.min(that.options.zoomMax, scale); + that.lastScale = scale / that.scale; + that.scale = scale; + + that.x = that.originX - that.originX * that.lastScale + that.x; + that.y = that.originY - that.originY * that.lastScale + that.y; + + that.scroller.style[transitionDuration] = '200ms'; + that.scroller.style[transform] = 'translate(' + that.x + 'px,' + that.y + 'px) scale(' + that.scale + ')' + translateZ; + + that.zoomed = false; + that.refresh(); + + if (that.options.onZoomEnd) that.options.onZoomEnd.call(that, e); + return; + } + + if (!that.moved) { + if (hasTouch) { + if (that.doubleTapTimer && that.options.zoom) { + // Double tapped + clearTimeout(that.doubleTapTimer); + that.doubleTapTimer = null; + if (that.options.onZoomStart) that.options.onZoomStart.call(that, e); + that.zoom(that.pointX, that.pointY, that.scale == 1 ? that.options.doubleTapZoom : 1); + if (that.options.onZoomEnd) { + setTimeout(function() { + that.options.onZoomEnd.call(that, e); + }, 200); // 200 is default zoom duration + } + } else if (this.options.handleClick) { + that.doubleTapTimer = setTimeout(function () { + that.doubleTapTimer = null; + + // Find the last touched element + target = point.target; + while (target.nodeType != 1) target = target.parentNode; + + if (target.tagName != 'SELECT' && target.tagName != 'INPUT' && target.tagName != 'TEXTAREA') { + ev = doc.createEvent('MouseEvents'); + ev.initMouseEvent('click', true, true, e.view, 1, + point.screenX, point.screenY, point.clientX, point.clientY, + e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, + 0, null); + ev._fake = true; + target.dispatchEvent(ev); + } + }, that.options.zoom ? 250 : 0); + } + } + + that._resetPos(400); + + if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e); + return; + } + + if (duration < 300 && that.options.momentum) { + momentumX = newPosX ? that._momentum(newPosX - that.startX, duration, -that.x, that.scrollerW - that.wrapperW + that.x, that.options.bounce ? that.wrapperW : 0) : momentumX; + momentumY = newPosY ? that._momentum(newPosY - that.startY, duration, -that.y, (that.maxScrollY < 0 ? that.scrollerH - that.wrapperH + that.y - that.minScrollY : 0), that.options.bounce ? that.wrapperH : 0) : momentumY; + + newPosX = that.x + momentumX.dist; + newPosY = that.y + momentumY.dist; + + if ((that.x > 0 && newPosX > 0) || (that.x < that.maxScrollX && newPosX < that.maxScrollX)) momentumX = { dist:0, time:0 }; + if ((that.y > that.minScrollY && newPosY > that.minScrollY) || (that.y < that.maxScrollY && newPosY < that.maxScrollY)) momentumY = { dist:0, time:0 }; + } + + if (momentumX.dist || momentumY.dist) { + newDuration = m.max(m.max(momentumX.time, momentumY.time), 10); + + // Do we need to snap? + if (that.options.snap) { + distX = newPosX - that.absStartX; + distY = newPosY - that.absStartY; + if (m.abs(distX) < that.options.snapThreshold && m.abs(distY) < that.options.snapThreshold) { that.scrollTo(that.absStartX, that.absStartY, 200); } + else { + snap = that._snap(newPosX, newPosY); + newPosX = snap.x; + newPosY = snap.y; + newDuration = m.max(snap.time, newDuration); + } + } + + that.scrollTo(m.round(newPosX), m.round(newPosY), newDuration); + + if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e); + return; + } + + // Do we need to snap? + if (that.options.snap) { + distX = newPosX - that.absStartX; + distY = newPosY - that.absStartY; + if (m.abs(distX) < that.options.snapThreshold && m.abs(distY) < that.options.snapThreshold) that.scrollTo(that.absStartX, that.absStartY, 200); + else { + snap = that._snap(that.x, that.y); + if (snap.x != that.x || snap.y != that.y) that.scrollTo(snap.x, snap.y, snap.time); + } + + if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e); + return; + } + + that._resetPos(200); + if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e); + }, + + _resetPos: function (time) { + var that = this, + resetX = that.x >= 0 ? 0 : that.x < that.maxScrollX ? that.maxScrollX : that.x, + resetY = that.y >= that.minScrollY || that.maxScrollY > 0 ? that.minScrollY : that.y < that.maxScrollY ? that.maxScrollY : that.y; + + if (resetX == that.x && resetY == that.y) { + if (that.moved) { + that.moved = false; + if (that.options.onScrollEnd) that.options.onScrollEnd.call(that); // Execute custom code on scroll end + } + + if (that.hScrollbar && that.options.hideScrollbar) { + if (vendor == 'webkit') that.hScrollbarWrapper.style[transitionDelay] = '300ms'; + that.hScrollbarWrapper.style.opacity = '0'; + } + if (that.vScrollbar && that.options.hideScrollbar) { + if (vendor == 'webkit') that.vScrollbarWrapper.style[transitionDelay] = '300ms'; + that.vScrollbarWrapper.style.opacity = '0'; + } + + return; + } + + that.scrollTo(resetX, resetY, time || 0); + }, + + _wheel: function (e) { + var that = this, + wheelDeltaX, wheelDeltaY, + deltaX, deltaY, + deltaScale; + + if ('wheelDeltaX' in e) { + wheelDeltaX = e.wheelDeltaX / 12; + wheelDeltaY = e.wheelDeltaY / 12; + } else if('wheelDelta' in e) { + wheelDeltaX = wheelDeltaY = e.wheelDelta / 12; + } else if ('detail' in e) { + wheelDeltaX = wheelDeltaY = -e.detail * 3; + } else { + return; + } + + if (that.options.wheelAction == 'zoom') { + deltaScale = that.scale * Math.pow(2, 1/3 * (wheelDeltaY ? wheelDeltaY / Math.abs(wheelDeltaY) : 0)); + if (deltaScale < that.options.zoomMin) deltaScale = that.options.zoomMin; + if (deltaScale > that.options.zoomMax) deltaScale = that.options.zoomMax; + + if (deltaScale != that.scale) { + if (!that.wheelZoomCount && that.options.onZoomStart) that.options.onZoomStart.call(that, e); + that.wheelZoomCount++; + + that.zoom(e.pageX, e.pageY, deltaScale, 400); + + setTimeout(function() { + that.wheelZoomCount--; + if (!that.wheelZoomCount && that.options.onZoomEnd) that.options.onZoomEnd.call(that, e); + }, 400); + } + + return; + } + + deltaX = that.x + wheelDeltaX; + deltaY = that.y + wheelDeltaY; + + if (deltaX > 0) deltaX = 0; + else if (deltaX < that.maxScrollX) deltaX = that.maxScrollX; + + if (deltaY > that.minScrollY) deltaY = that.minScrollY; + else if (deltaY < that.maxScrollY) deltaY = that.maxScrollY; + + if (that.maxScrollY < 0) { + that.scrollTo(deltaX, deltaY, 0); + } + }, + + _transitionEnd: function (e) { + var that = this; + + if (e.target != that.scroller) return; + + that._unbind(TRNEND_EV); + + that._startAni(); + }, + + + /** + * + * Utilities + * + */ + _startAni: function () { + var that = this, + startX = that.x, startY = that.y, + startTime = Date.now(), + step, easeOut, + animate; + + if (that.animating) return; + + if (!that.steps.length) { + that._resetPos(400); + return; + } + + step = that.steps.shift(); + + if (step.x == startX && step.y == startY) step.time = 0; + + that.animating = true; + that.moved = true; + + if (that.options.useTransition) { + that._transitionTime(step.time); + that._pos(step.x, step.y); + that.animating = false; + if (step.time) that._bind(TRNEND_EV); + else that._resetPos(0); + return; + } + + animate = function () { + var now = Date.now(), + newX, newY; + + if (now >= startTime + step.time) { + that._pos(step.x, step.y); + that.animating = false; + if (that.options.onAnimationEnd) that.options.onAnimationEnd.call(that); // Execute custom code on animation end + that._startAni(); + return; + } + + now = (now - startTime) / step.time - 1; + easeOut = m.sqrt(1 - now * now); + newX = (step.x - startX) * easeOut + startX; + newY = (step.y - startY) * easeOut + startY; + that._pos(newX, newY); + if (that.animating) that.aniTime = nextFrame(animate); + }; + + animate(); + }, + + _transitionTime: function (time) { + time += 'ms'; + this.scroller.style[transitionDuration] = time; + if (this.hScrollbar) this.hScrollbarIndicator.style[transitionDuration] = time; + if (this.vScrollbar) this.vScrollbarIndicator.style[transitionDuration] = time; + }, + + _momentum: function (dist, time, maxDistUpper, maxDistLower, size) { + var deceleration = 0.0006, + speed = m.abs(dist) / time, + newDist = (speed * speed) / (2 * deceleration), + newTime = 0, outsideDist = 0; + + // Proportinally reduce speed if we are outside of the boundaries + if (dist > 0 && newDist > maxDistUpper) { + outsideDist = size / (6 / (newDist / speed * deceleration)); + maxDistUpper = maxDistUpper + outsideDist; + speed = speed * maxDistUpper / newDist; + newDist = maxDistUpper; + } else if (dist < 0 && newDist > maxDistLower) { + outsideDist = size / (6 / (newDist / speed * deceleration)); + maxDistLower = maxDistLower + outsideDist; + speed = speed * maxDistLower / newDist; + newDist = maxDistLower; + } + + newDist = newDist * (dist < 0 ? -1 : 1); + newTime = speed / deceleration; + + return { dist: newDist, time: m.round(newTime) }; + }, + + _offset: function (el) { + var left = -el.offsetLeft, + top = -el.offsetTop; + + while (el = el.offsetParent) { + left -= el.offsetLeft; + top -= el.offsetTop; + } + + if (el != this.wrapper) { + left *= this.scale; + top *= this.scale; + } + + return { left: left, top: top }; + }, + + _snap: function (x, y) { + var that = this, + i, l, + page, time, + sizeX, sizeY; + + // Check page X + page = that.pagesX.length - 1; + for (i=0, l=that.pagesX.length; i= that.pagesX[i]) { + page = i; + break; + } + } + if (page == that.currPageX && page > 0 && that.dirX < 0) page--; + x = that.pagesX[page]; + sizeX = m.abs(x - that.pagesX[that.currPageX]); + sizeX = sizeX ? m.abs(that.x - x) / sizeX * 500 : 0; + that.currPageX = page; + + // Check page Y + page = that.pagesY.length-1; + for (i=0; i= that.pagesY[i]) { + page = i; + break; + } + } + if (page == that.currPageY && page > 0 && that.dirY < 0) page--; + y = that.pagesY[page]; + sizeY = m.abs(y - that.pagesY[that.currPageY]); + sizeY = sizeY ? m.abs(that.y - y) / sizeY * 500 : 0; + that.currPageY = page; + + // Snap with constant speed (proportional duration) + time = m.round(m.max(sizeX, sizeY)) || 200; + + return { x: x, y: y, time: time }; + }, + + _bind: function (type, el, bubble) { + (el || this.scroller).addEventListener(type, this, !!bubble); + }, + + _unbind: function (type, el, bubble) { + (el || this.scroller).removeEventListener(type, this, !!bubble); + }, + + + /** + * + * Public methods + * + */ + destroy: function () { + var that = this; + + that.scroller.style[transform] = ''; + + // Remove the scrollbars + that.hScrollbar = false; + that.vScrollbar = false; + that._scrollbar('h'); + that._scrollbar('v'); + + // Remove the event listeners + that._unbind(RESIZE_EV, window); + that._unbind(START_EV); + that._unbind(MOVE_EV, window); + that._unbind(END_EV, window); + that._unbind(CANCEL_EV, window); + + if (!that.options.hasTouch) { + that._unbind('DOMMouseScroll'); + that._unbind('mousewheel'); + } + + if (that.options.useTransition) that._unbind(TRNEND_EV); + + if (that.options.checkDOMChanges) clearInterval(that.checkDOMTime); + + if (that.options.onDestroy) that.options.onDestroy.call(that); + }, + + refresh: function () { + var that = this, + offset, + i, l, + els, + pos = 0, + page = 0; + + if (that.scale < that.options.zoomMin) that.scale = that.options.zoomMin; + that.wrapperW = that.wrapper.clientWidth || 1; + that.wrapperH = that.wrapper.clientHeight || 1; + + that.minScrollY = -that.options.topOffset || 0; + that.scrollerW = m.round(that.scroller.offsetWidth * that.scale); + that.scrollerH = m.round((that.scroller.offsetHeight + that.minScrollY) * that.scale); + that.maxScrollX = that.wrapperW - that.scrollerW; + that.maxScrollY = that.wrapperH - that.scrollerH + that.minScrollY; + that.dirX = 0; + that.dirY = 0; + + if (that.options.onRefresh) that.options.onRefresh.call(that); + + that.hScroll = that.options.hScroll && that.maxScrollX < 0; + that.vScroll = that.options.vScroll && (!that.options.bounceLock && !that.hScroll || that.scrollerH > that.wrapperH); + + that.hScrollbar = that.hScroll && that.options.hScrollbar; + that.vScrollbar = that.vScroll && that.options.vScrollbar && that.scrollerH > that.wrapperH; + + offset = that._offset(that.wrapper); + that.wrapperOffsetLeft = -offset.left; + that.wrapperOffsetTop = -offset.top; + + // Prepare snap + if (typeof that.options.snap == 'string') { + that.pagesX = []; + that.pagesY = []; + els = that.scroller.querySelectorAll(that.options.snap); + for (i=0, l=els.length; i= that.maxScrollX) { + that.pagesX[page] = pos; + pos = pos - that.wrapperW; + page++; + } + if (that.maxScrollX%that.wrapperW) that.pagesX[that.pagesX.length] = that.maxScrollX - that.pagesX[that.pagesX.length-1] + that.pagesX[that.pagesX.length-1]; + + pos = 0; + page = 0; + that.pagesY = []; + while (pos >= that.maxScrollY) { + that.pagesY[page] = pos; + pos = pos - that.wrapperH; + page++; + } + if (that.maxScrollY%that.wrapperH) that.pagesY[that.pagesY.length] = that.maxScrollY - that.pagesY[that.pagesY.length-1] + that.pagesY[that.pagesY.length-1]; + } + + // Prepare the scrollbars + that._scrollbar('h'); + that._scrollbar('v'); + + if (!that.zoomed) { + that.scroller.style[transitionDuration] = '0'; + that._resetPos(400); + } + }, + + scrollTo: function (x, y, time, relative) { + var that = this, + step = x, + i, l; + + that.stop(); + + if (!step.length) step = [{ x: x, y: y, time: time, relative: relative }]; + + for (i=0, l=step.length; i 0 ? 0 : pos.left < that.maxScrollX ? that.maxScrollX : pos.left; + pos.top = pos.top > that.minScrollY ? that.minScrollY : pos.top < that.maxScrollY ? that.maxScrollY : pos.top; + time = time === undefined ? m.max(m.abs(pos.left)*2, m.abs(pos.top)*2) : time; + + that.scrollTo(pos.left, pos.top, time); + }, + + scrollToPage: function (pageX, pageY, time) { + var that = this, x, y; + + time = time === undefined ? 400 : time; + + if (that.options.onScrollStart) that.options.onScrollStart.call(that); + + if (that.options.snap) { + pageX = pageX == 'next' ? that.currPageX+1 : pageX == 'prev' ? that.currPageX-1 : pageX; + pageY = pageY == 'next' ? that.currPageY+1 : pageY == 'prev' ? that.currPageY-1 : pageY; + + pageX = pageX < 0 ? 0 : pageX > that.pagesX.length-1 ? that.pagesX.length-1 : pageX; + pageY = pageY < 0 ? 0 : pageY > that.pagesY.length-1 ? that.pagesY.length-1 : pageY; + + that.currPageX = pageX; + that.currPageY = pageY; + x = that.pagesX[pageX]; + y = that.pagesY[pageY]; + } else { + x = -that.wrapperW * pageX; + y = -that.wrapperH * pageY; + if (x < that.maxScrollX) x = that.maxScrollX; + if (y < that.maxScrollY) y = that.maxScrollY; + } + + that.scrollTo(x, y, time); + }, + + disable: function () { + this.stop(); + this._resetPos(0); + this.enabled = false; + + // If disabled after touchstart we make sure that there are no left over events + this._unbind(MOVE_EV, window); + this._unbind(END_EV, window); + this._unbind(CANCEL_EV, window); + }, + + enable: function () { + this.enabled = true; + }, + + stop: function () { + if (this.options.useTransition) this._unbind(TRNEND_EV); + else cancelFrame(this.aniTime); + this.steps = []; + this.moved = false; + this.animating = false; + }, + + zoom: function (x, y, scale, time) { + var that = this, + relScale = scale / that.scale; + + if (!that.options.useTransform) return; + + that.zoomed = true; + time = time === undefined ? 200 : time; + x = x - that.wrapperOffsetLeft - that.x; + y = y - that.wrapperOffsetTop - that.y; + that.x = x - x * relScale + that.x; + that.y = y - y * relScale + that.y; + + that.scale = scale; + that.refresh(); + + that.x = that.x > 0 ? 0 : that.x < that.maxScrollX ? that.maxScrollX : that.x; + that.y = that.y > that.minScrollY ? that.minScrollY : that.y < that.maxScrollY ? that.maxScrollY : that.y; + + that.scroller.style[transitionDuration] = time + 'ms'; + that.scroller.style[transform] = 'translate(' + that.x + 'px,' + that.y + 'px) scale(' + scale + ')' + translateZ; + that.zoomed = false; + }, + + isReady: function () { + return !this.moved && !this.zoomed && !this.animating; + } +}; + +function prefixStyle (style) { + if ( vendor === '' ) return style; + + style = style.charAt(0).toUpperCase() + style.substr(1); + return vendor + style; +} + +dummyStyle = null; // for the sake of it + +if (typeof exports !== 'undefined') exports.iScroll = iScroll; +else window.iScroll = iScroll; + +})(window, document); diff --git a/webclient/js/iscroll.js b/webclient/js/iscroll.js old mode 100755 new mode 100644 index f8576f0..ed01a61 --- a/webclient/js/iscroll.js +++ b/webclient/js/iscroll.js @@ -1,5 +1,5 @@ /*! - * iScroll v4.2.5 ~ Copyright (c) 2012 Matteo Spinelli, http://cubiq.org + * iScroll v4.2 ~ Copyright (c) 2012 Matteo Spinelli, http://cubiq.org * Released under MIT license, http://cubiq.org/license */ (function(window, doc){ @@ -37,7 +37,7 @@ var m = Math, has3d = prefixStyle('perspective') in dummyStyle, hasTouch = 'ontouchstart' in window && !isTouchPad, - hasTransform = vendor !== false, + hasTransform = !!vendor, hasTransitionEnd = prefixStyle('transition') in dummyStyle, RESIZE_EV = 'onorientationchange' in window ? 'orientationchange' : 'resize', @@ -45,6 +45,7 @@ var m = Math, MOVE_EV = hasTouch ? 'touchmove' : 'mousemove', END_EV = hasTouch ? 'touchend' : 'mouseup', CANCEL_EV = hasTouch ? 'touchcancel' : 'mouseup', + WHEEL_EV = vendor == 'Moz' ? 'DOMMouseScroll' : 'mousewheel', TRNEND_EV = (function () { if ( vendor === false ) return false; @@ -52,7 +53,7 @@ var m = Math, '' : 'transitionend', 'webkit' : 'webkitTransitionEnd', 'Moz' : 'transitionend', - 'O' : 'otransitionend', + 'O' : 'oTransitionEnd', 'ms' : 'MSTransitionEnd' }; @@ -176,10 +177,9 @@ var m = Math, that._bind(RESIZE_EV, window); that._bind(START_EV); if (!hasTouch) { - if (that.options.wheelAction != 'none') { - that._bind('DOMMouseScroll'); - that._bind('mousewheel'); - } + that._bind('mouseout', that.wrapper); + if (that.options.wheelAction != 'none') + that._bind(WHEEL_EV); } if (that.options.checkDOMChanges) that.checkDOMTime = setInterval(function () { @@ -210,7 +210,8 @@ iScroll.prototype = { case END_EV: case CANCEL_EV: that._end(e); break; case RESIZE_EV: that._resize(); break; - case 'DOMMouseScroll': case 'mousewheel': that._wheel(e); break; + case WHEEL_EV: that._wheel(e); break; + case 'mouseout': that._mouseout(e); break; case TRNEND_EV: that._transitionEnd(e); break; } }, @@ -376,11 +377,11 @@ iScroll.prototype = { if (that.options.useTransform) { // Very lame general purpose alternative to CSSMatrix matrix = getComputedStyle(that.scroller, null)[transform].replace(/[^0-9\-.,]/g, '').split(','); - x = +(matrix[12] || matrix[4]); - y = +(matrix[13] || matrix[5]); + x = matrix[4] * 1; + y = matrix[5] * 1; } else { - x = +getComputedStyle(that.scroller, null).left.replace(/[^0-9-]/g, ''); - y = +getComputedStyle(that.scroller, null).top.replace(/[^0-9-]/g, ''); + x = getComputedStyle(that.scroller, null).left.replace(/[^0-9-]/g, '') * 1; + y = getComputedStyle(that.scroller, null).top.replace(/[^0-9-]/g, '') * 1; } if (x != that.x || y != that.y) { @@ -388,7 +389,6 @@ iScroll.prototype = { else cancelFrame(that.aniTime); that.steps = []; that._pos(x, y); - if (that.options.onScrollEnd) that.options.onScrollEnd.call(that); } } @@ -404,9 +404,9 @@ iScroll.prototype = { if (that.options.onScrollStart) that.options.onScrollStart.call(that, e); - that._bind(MOVE_EV, window); - that._bind(END_EV, window); - that._bind(CANCEL_EV, window); + that._bind(MOVE_EV); + that._bind(END_EV); + that._bind(CANCEL_EV); }, _move: function (e) { @@ -506,9 +506,9 @@ iScroll.prototype = { snap, scale; - that._unbind(MOVE_EV, window); - that._unbind(END_EV, window); - that._unbind(CANCEL_EV, window); + that._unbind(MOVE_EV); + that._unbind(END_EV); + that._unbind(CANCEL_EV); if (that.options.onBeforeScrollEnd) that.options.onBeforeScrollEnd.call(that, e); @@ -566,7 +566,7 @@ iScroll.prototype = { } } - that._resetPos(400); + that._resetPos(200); if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e); return; @@ -700,6 +700,19 @@ iScroll.prototype = { } }, + _mouseout: function (e) { + var t = e.relatedTarget; + + if (!t) { + this._end(e); + return; + } + + while (t = t.parentNode) if (t == this.wrapper) return; + + this._end(e); + }, + _transitionEnd: function (e) { var that = this; @@ -886,13 +899,13 @@ iScroll.prototype = { // Remove the event listeners that._unbind(RESIZE_EV, window); that._unbind(START_EV); - that._unbind(MOVE_EV, window); - that._unbind(END_EV, window); - that._unbind(CANCEL_EV, window); + that._unbind(MOVE_EV); + that._unbind(END_EV); + that._unbind(CANCEL_EV); if (!that.options.hasTouch) { - that._unbind('DOMMouseScroll'); - that._unbind('mousewheel'); + that._unbind('mouseout', that.wrapper); + that._unbind(WHEEL_EV); } if (that.options.useTransition) that._unbind(TRNEND_EV); @@ -972,7 +985,7 @@ iScroll.prototype = { if (!that.zoomed) { that.scroller.style[transitionDuration] = '0'; - that._resetPos(400); + that._resetPos(200); } }, @@ -1043,9 +1056,9 @@ iScroll.prototype = { this.enabled = false; // If disabled after touchstart we make sure that there are no left over events - this._unbind(MOVE_EV, window); - this._unbind(END_EV, window); - this._unbind(CANCEL_EV, window); + this._unbind(MOVE_EV); + this._unbind(END_EV); + this._unbind(CANCEL_EV); }, enable: function () { @@ -1101,4 +1114,4 @@ dummyStyle = null; // for the sake of it if (typeof exports !== 'undefined') exports.iScroll = iScroll; else window.iScroll = iScroll; -})(window, document); +})(window, document); \ No newline at end of file diff --git a/webclient/js/jquery.mobile.iscrollview.js b/webclient/js/jquery.mobile.iscrollview.js old mode 100755 new mode 100644 index b2961d7..a9129fb --- a/webclient/js/jquery.mobile.iscrollview.js +++ b/webclient/js/jquery.mobile.iscrollview.js @@ -31,7 +31,7 @@ regexp:false, todo:true */ /* jquery.mobile.iscrollview.js -Version: 1.2.6 +Version: 1.3.7 jQuery Mobile iScroll4 view widget Copyright (c), 2012, 2013 Watusiware Corporation Distributed under the MIT License @@ -64,14 +64,29 @@ Further changes: @addyosmani Licensed under the MIT license dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provided in demo) - jQuery 1.6.4 (JQM 1.0.1) or 1.7.1 (JQM 1.1) or 1.7.2 (JQM 1.2) - JQuery Mobile = 1.0.1 or 1.1 or 1.2-alpha1 + jQuery - see jQuery Mobile documentation, depends on JQM version + JQuery Mobile = 1.0.1 through 1.3.1 */ ; // Ignore jslint/jshint warning - for safety - terminate previous file if unterminated -(function ($, window, document, undefined) { /* Ignore islint warning on "undefined" */ - "use strict"; +// Prevent annoying "layerX/Y is deprecated" console messages when running in some Webkit browsers +// See also _doCallback() function. +// +// This needs to be a global function, (well, outside of the widget self-invoking function, anyway), +// because this code will generate warnings ("cannot delete") if executed within a strict function. +// The warnings are not generated, though, if executed within a non-strict function CALLED from a +// strict function. +// +// (Other than this little bit, the entirety of this widget is strict.) +function jqmIscrollviewRemoveLayerXYProps(e) { + delete e.layerX; + delete e.layerY; +} + +(function ($, window, document, undefined) { /* Ignore jslint warning on "undefined" */ + "use strict"; // Comment this out whilst debugging with Safari Web Inspector + // Otherwise, you will not be able to see variables when stopped at breakpoints //---------------------------------- // "class constants" @@ -159,6 +174,10 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid // Perform an iScroll callback. this._doCallback = function(callbackName, e, f) { + if (typeof e === "object") { // Prevent annoying "layerX/layerY is deprecated" console messages + jqmIscrollviewRemoveLayerXYProps(e); + } + var v = this.iscrollview, then = v._logCallback(callbackName, e); if (f) { f.call(this, e); } // Perform passed function if present @@ -312,15 +331,17 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid bottomOffset: 0, emulateBottomOffset: true, - pageClass: "iscroll-page", // Class to be applied to pages containing this widget - wrapperClass: "iscroll-wrapper", // Class to be applied to wrapper containing this widget - scrollerClass: "iscroll-scroller", // Class to be applied to scroller within wrapper - pullDownClass: "iscroll-pulldown", // Class for pulldown element (if any) - pullUpClass: "iscroll-pullup", // Class for pullup element (if any) - pullLabelClass: "iscroll-pull-label", // Class for pull element label span - pullUpSpacerClass: "iscroll-pullup-spacer", // Class added to generated pullup spacer - scrollerContentClass: "iscroll-content", // Real content of scroller, not including pull-up, pull-down - fixedHeightClass: "iscroll-fixed", // Class applied to elements that match fixedHeightSelector + pageClass: "iscroll-page", // Class to be applied to pages containing this widget + wrapperClass: "iscroll-wrapper", // Class to be applied to wrapper containing this widget + scrollerClass: "iscroll-scroller", // Class to be applied to scroller within wrapper + pullDownClass: "iscroll-pulldown", // Class for pulldown element (if any) + pullUpClass: "iscroll-pullup", // Class for pullup element (if any) + pullLabelClass: "iscroll-pull-label", // Class for pull element label span + pullUpSpacerClass: "iscroll-pullup-spacer", // Class added to generated pullup spacer + topSpacerClass: "iscroll-top-spacer", + bottomSpacerClass: "iscroll-bottom-spacer", + scrollerContentClass: "iscroll-content", // Real content of scroller, not including pull-up, pull-down + fixedHeightClass: "iscroll-fixed", // Class applied to elements that match fixedHeightSelector // The widget adds the fixedHeightClass to all elements that match fixedHeightSelector. // Don't add the fixedHeightClass to elements manually. Use data-iscroll-fixed instead. @@ -379,13 +400,19 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid // inside any pull-down/pull-up to replace the padding removed from the wrapper. addScrollerPadding: true, + // Add convenient spacer divs at top and bottom of content. + // These initially have no height. They are useful in situations + // where padding collapses into the document. For example, can be + // used to work with fullscreen header/footer + addSpacers: true, + // On some platforms (iOS, for example) we need to scroll to top after orientation change, // because the address bar pushed the window down. jQuery Mobile handles this for page links, // but doesn't for orientationchange. // If you have multiple scrollers, only enable this for one of them scrollTopOnResize: true, - scrollTopOnOrientatationChange: true, + scrollTopOnOrientationChange: true, // iScroll scrolls the first child of the wrapper. I don't see a use case for having more // than one child. What kind of mess is going to be shown in that case? So, by default, we @@ -503,6 +530,9 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid "scrollerContentClass", "pullLabelClass", "pullUpSpacerClass", + "topSpacerClass", + "bottomSpacerClass", + "addSpacer", "fixedHeightSelector", "resizeWrapper", "resizeEvents", @@ -536,7 +566,8 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid "onpullup", "onbeforerefresh", "onafterrefresh", - "fastDestroy" + "fastDestroy", + "preventPageScroll" ], //----------------------------------------------------------------------- @@ -819,7 +850,7 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid }, _unbindPage: function(types_in) { - var types = this._addEventsNamespace(types_in, this._instanceEventNamespace()); + var types = this._addEventsNamespace(types_in, this._pageEventNamespace()); this._logWidgetEvent("unbind $page", types); this.$page.unbind(types); }, @@ -983,13 +1014,23 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid if (this._instanceCount() === 1) { this.$page.addClass(this.options.pageClass); this.$page.find(this.options.fixedHeightSelector).each(function() { // Iterate over headers/footers/etc. - $(this).addClass(_this.options.fixedHeightClass); - }); + var + $fixedHeightElement = $(this), + // We need to exclude headers/footers in popups and panels. + // We cannot simply use a selector that requires the fixed-height element + // to be a child of .ui-page, because of the complication that JQM + // moves persistent headers/footers out of the page during transitions. + isPopup = $fixedHeightElement.closest(".ui-popup").length !== 0, + isPanel = $fixedHeightElement.closest(".ui-panel").length !== 0; + if (!isPopup && !isPanel) { + $fixedHeightElement.addClass(_this.options.fixedHeightClass); + } + }); if (HasTouch && this.options.preventPageScroll) { this._bindPage("touchmove", _pageTouchmoveFunc); - } } - }, + } + }, _undoAdaptPage: function() { var _this = this; @@ -1008,17 +1049,17 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid var barsHeight = 0, fixedHeightSelector = "." + this.options.fixedHeightClass, // Persistent footers are sometimes inside the page, sometimes outside of all pages! (as - // direct descendant of ). And sometimes both. During transitions, the page that + // direct descendant of /.ui-mobile-viewport). And sometimes both. During transitions, the page that // is transitioning in will have had it's persistent footer moved outside of the page, // while all other pages will have their persistent footer internal to the page. // // To deal with this, we find iscroll-fixed elements in the page, as well as outside - // of the page (as direct descendants of ). We avoid double-counting persistent + // of the page (as direct descendants of /.ui-mobile-viewport). We avoid double-counting persistent // footers that have the same data-id. (Experimentally, then, we also permit the user // to place fixed-height elements outside of the page, but unsure if this is of any // practical use.) $barsInPage = this.$page.find(fixedHeightSelector), - $barsOutsidePage = $("body").children(fixedHeightSelector); + $barsOutsidePage = $(".ui-mobile-viewport").children(fixedHeightSelector); $barsInPage.each(function() { // Iterate over headers/footers/etc. barsHeight += $(this).outerHeight(true); @@ -1080,8 +1121,15 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid case "content-box": // AKA W3C Ignore jshint warning default: // Ignore jslint warning // We will subtract padding, border, margin - adjust = $elem.outerHeight(true) - $elem.height(); - break; + // However... + // wrapper will never have padding, at least once we are done + // modifying it. This function is called before any removal of + // padding, though. So, if $wrapper, use same calculation as for padding-box, + // ignoring padding. + // (We actually don't call this for anything but $wrapper, but preseve + // functionality in case we ever use it on another element) + adjust = $elem.outerHeight($elem !== this.$wrapper ) - $elem.height(); + break; } return adjust; }, @@ -1185,7 +1233,7 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid // will not be visible until the user pulls up. //-------------------------------------------------------- _expandScrollerToFillWrapper: function() { - if (this.options.scrollShortContent || this.$pullDown.length || this.pullUp.length) { + if (this.options.scrollShortContent || this.$pullDown.length || this.$pullUp.length) { if (this._firstScrollerExpand) { this._origScrollerStyle = this.$scroller.attr("style") || null; this._firstScrollerExpand = false; @@ -1402,7 +1450,9 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid hidden = _this._setPageVisible(); if (_callbackBefore) { _callbackBefore(); } _this._triggerWidget("onbeforerefresh"); - _this.iscroll.refresh(); + // The if below is reportedly needed when using BackboneJS views when switching + // from one view to another. See pull request #80 + if (_this.iscroll) { _this.iscroll.refresh(); } _this._triggerWidget("onafterrefresh"); if (_callbackAfter) { _callbackAfter(); } _this._restorePageVisibility(hidden); @@ -1453,6 +1503,22 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid } }, + //----------------------------------------- + // Create spacers + //----------------------------------------- + _addSpacers: function() { + if(this.options.addSpacers) { + this.$scrollerContent.before( $( '
    ' ) ); + this.$scrollerContent.after( $( '
    ' ) ); + } + }, + + _undoAddSpacers: function() { + this.$wrapper.find(this.options.topSpacerClass).remove(); + this.$wrapper.find(this.options.bottomSpacerClass).remove(); + + }, + // Temporarily change page CSS to make it "visible" so that dimensions can be read. // This can be used in any event callback, and so can be used in _create(), since it's called // from pageinit event. Because event processing is synchronous, the browser won't render the @@ -1486,6 +1552,9 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid this.$wrapper = this.element; // JQuery object containing the element we are creating this widget for this.$page = this.$wrapper.parents(":jqmData(role='page')"); // The page containing the wrapper + // Merge options from data-iscroll, if present + $.extend(true, this.options, this.$wrapper.jqmData("iscroll")); + if (this.options.debug && this.options.traceCreateDestroy) { this._log("_create() start", then); } @@ -1493,7 +1562,7 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid this.createdAt = then; this._instanceCount(this._instanceCount() + 1); // The count of extant instances of this widget on the page this.instanceID = this._nextInstanceID(); // The serial ID of this instance of this widget on the page - this._nextInstanceID(this._instanceID + 1); + this._nextInstanceID(this.instanceID + 1); if (this.instanceID === 1) { this._pageID(nextPageID); nextPageID += 1; @@ -1510,14 +1579,10 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid // Find pull elements, if present this.$pullDown = $("." + this.options.pullDownClass, this.$scroller); this._modifyPullDown(); - this.$pullUp = $("." + this.options.pullUpClass, this.$scroller); this._modifyPullUp(); - // Merge options from data-iscroll, if present - $.extend(true, this.options, this.$wrapper.jqmData("iscroll")); - - this._modifyWrapper(); // Various changes to the wrapper + this._modifyWrapper(); // Various changes to the wrapper // Need this for deferred refresh processing this._bindPage("pagebeforeshow", this._pageBeforeShowFunc); @@ -1525,7 +1590,9 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid this._setTopOffsetForPullDown(); // If there's a pull-down, set the top offset this._setBottomOffsetForPullUp(); // If there's a pull-up, set the bottom offset this._resizeWrapper(); // Resize the wrapper to fill available space - this._addScrollerPadding(); // Put back padding removed from wrapper + this._addScrollerPadding(); // Put back padding removed from wrapper + this.$scrollerContent = this.$scroller.find("." + this.options.scrollerContentClass); + this._addSpacers(); // Add top/bottom spacers this._create_iscroll_object(); this._merge_from_iscroll_options(); // Merge iscroll options into widget options this._restorePageVisibility(hidden); @@ -1533,14 +1600,15 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid // Setup bindings for window resize and orientationchange if (this.options.resizeWrapper) { - this._isvBind(this.$window, this.options.resizeEvents, this._windowResizeFunc, "$window"); + if (this.options.resizeEvents.length) { + this._isvBind(this.$window, this.options.resizeEvents, this._windowResizeFunc, "$window"); + } if (this.options.scrollTopOnOrientationChange) { this._isvBind(this.$window, "orientationchange", this._orientationChangeFunc, "$window"); } } // Refresh on trigger of updatelayout of content - this.$scrollerContent = this.$scroller.find("." + this.options.scrollerContentClass); this._isvBind(this.$scrollerContent, "updatelayout", this._updateLayoutFunc, "$scrollerContent"); if (this.options.debug && this.options.traceCreateDestroy) { @@ -1560,7 +1628,9 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid // Unbind events this._isvUnbind(this.$scrollerContent, "updatelayout", "$scrollerContent"); - this._isvUnbind(this.$window, this.options.resizeEvents, "$window"); + if (this.options.resizeEvents.length) { + this._isvUnbind(this.$window, this.options.resizeEvents, "$window"); + } this._isvUnbind(this.$window, "orientationchange", "$window"); if (this._instanceCount() === 1) { this._unbindPage("pagebeforeshow"); @@ -1578,6 +1648,7 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid this._undoExpandScrollerToFillWrapper(); this._undoModifyPullDown(); this._undoModifyPullUp(); + this._undoAddSpacers(); this._undoAddScrollerPadding(); this._undoModifyWrapper(); this.$wrapper.removeClass(this.options.wrapperClass); @@ -1721,8 +1792,13 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid // Reset a pull block to the initial state _pullSetStateReset: function ($pull, text) { if ($pull.is("." + this.options.pullLoadingClass + ", ." + this.options.pullPulledClass)) { + var + $iconSpan = $pull.find(".iscroll-pull-icon"), + $iconSpanClone = $iconSpan.clone(); $pull.removeClass(this.options.pullPulledClass + " " + this.options.pullLoadingClass); this._replacePullText($pull, text); + //force animations to stop on iOS, which doesn't seem to want to give up. Stubborn bugger. + $iconSpan.replaceWith($iconSpanClone); } }, @@ -1841,6 +1917,8 @@ dependency: iScroll 4.1.9 https://github.com/cubiq/iscroll or later (4.2 provid }( jQuery, window, document )); +jQuery(document).trigger("iscroll_init"); + // Self-init jQuery(document).bind("pagecreate", function (e) { "use strict";