that will be the scroller.
// This way you don't need to worry about wrapping all the elements to be scrolled if there
// is more than one. If there is only one child, we create this
unnecessarily, but -
// big deal. If, for some reason, you want to create the markup for the scroller yourself,
// set this to false.
createScroller: true,
// True to defer refresh() on non-active pages until pagebeforeshow. This avoids
// unnecessary refresh in case of resize/orientation change when pages are cached,
// as well as unnecessary refresh when pages are updated when they are not the active
// page.
deferNonActiveRefresh: true,
// Same deal, for re-sizing the wrapper
deferNonActiveResize: true,
// True to prevent hover in scroller touch devices. If this is false, you will get
// "piano keyboard" effect in JQM <1.1 when scrolling due to hover, which is both
// time-consuming and distracting. A negative is that with the current implementation, you will
// never get a "hover" visual effect within a scroller on touch devices, even when not scrolling.
// But you still will on desktop browser with mouse, and you will still get "down" effect
// when a link is selected. This really is a jQuery Mobile problem with listview, and is
// fixed in JQM 1.1.
preventTouchHover: JQMIsV1_0 && HasTouch, // Enable if touch device and JQM version is < 1.1
// This is an experimental feature under development and DOES NOT WORK completely!
// For one, it breaks mousewheel with jQuery Mobile 1.1 (because jQuery Mobile 1.1 breaks
// mousewheel...)
bindIscrollUsingJqueryEvents: false,
// If fastDestroy is true, don't tear down the widget on destroy. The assumption is destroy
// will only be called when the page is removed, so there is no need. Is anyone really
// going to un-enhance a scroller? If so, set this to false, but then you will have to
// fix the unbind issue...
fastDestroy: false,
// Prevent scrolling the page by grabbing areas outside of the scroller.
// Normally, this should be true. Set this false if you are NOT using a fixed-height page,
// but instead are using iScroll to scroll an area within a scollable page. If you have
// multiple scrollers on a scrollable page, then set this false for all of them.
// Note that we ALWAYS prevent scrolling the page by dragging inside the scroller.
preventPageScroll: true,
pullDownResetText : "Pull down to refresh...",
pullDownPulledText : "Release to refresh...",
pullDownLoadingText : "Loading...",
pullUpResetText : "Pull up to refresh...",
pullUpPulledText : "Release to refresh...",
pullUpLoadingText : "Loading...",
pullPulledClass : "iscroll-pull-pulled",
pullLoadingClass : "iscroll-pull-loading",
//-------------------------------------------------------------
// For better or worse, widgets have two mechanisms for dealing
// with events. The needs to be a set of options that correspond
// to each event. If present, the option is a function. As
// well, the widget prepends the widget event prefix ("iscroll_")
// to each event name and triggers a jQuery event by that name.
// BOTH mechanisms can be used simultaneously, though not sure
// why you'd want to. If you need to handle an event during
// iScroll4 instantiation, (only one I know about that might be
// called is refresh) then you have to use a function option.
//-------------------------------------------------------------
onrefresh: null,
onbeforescrollstart: null,
onscrollstart: null,
onbeforescrollmove: null,
onscrollmove: null,
onbeforescrollend: null,
onscrollend: null,
ontouchend: null,
ondestroy: null,
onzoomstart: null,
onzoom: null,
onzoomend: null,
onpulldownreset: null,
onpulldownpulled: null,
onpulldown: null,
onpullupreset: null,
onpulluppulled: null,
onpullup: null,
onbeforerefresh: null,
onafterrefresh: null
},
//---------------------------------------------------------------------------------------
// Array of keys of options that are widget-only options (not options in iscroll4 object)
//---------------------------------------------------------------------------------------
_widgetOnlyOptions: [
"debug",
"traceIscrollEvents",
"tracedIscrollEvents",
"traceIscrollCallbacks",
"tracedIscrollCallbacks",
"traceWidgetEvents",
"tracedWidgetEvents",
"traceWidgetCallbacks",
"tracedWidgetCallbacks",
"traceResizeWrapper",
"traceRefresh",
"traceCreateDestroy",
"bottomOffset",
"emulateBottomOffset",
"pageClass",
"wrapperClass",
"scrollerClass",
"pullDownClass",
"pullUpClass",
"scrollerContentClass",
"pullLabelClass",
"pullUpSpacerClass",
"fixedHeightSelector",
"resizeWrapper",
"resizeEvents",
"refreshOnPageBeforeShow",
"fixInput",
"wrapperAdd",
"refreshDelay",
"scrollShortContent",
"removeWrapperPadding",
"addScrollerPadding",
"createScroller",
"deferNonActiveRefresh",
"preventTouchHover",
"deferNonActiveResize",
"bindIscrollUsingJqueryEvents",
"scrollTopOnResize",
"scrollTopOnOrientationChange",
"pullDownResetText",
"pullDownPulledText",
"pullDownLoadingText",
"pullUpResetText",
"pullUpPulledText",
"pullUpLoadingText",
"pullPulledClass",
"pullLoadingClass",
"onpulldownreset",
"onpulldownpulled",
"onpulldown",
"onpullupreset",
"onpulluppulled",
"onpullup",
"onbeforerefresh",
"onafterrefresh",
"fastDestroy"
],
//-----------------------------------------------------------------------
// Map of widget event names to corresponding iscroll4 object event names
//-----------------------------------------------------------------------
_event_map: {
onrefresh: "onRefresh",
onbeforescrollstart: "onBeforeScrollStart",
onscrollstart: "onScrollStart",
onbeforescrollmove: "onBeforeScrollMove",
onscrollmove: 'onScrollMove',
onbeforescrollend: 'onBeforeScrollEnd',
onscrollend: "onScrollEnd",
ontouchend: "onTouchEnd",
ondestroy: "onDetroy",
onzoomstart: "onZoomStart",
onzoom: "onZoom",
onzoomend: "onZoomEnd"
},
//------------------------------------------------------------------------------
// Functions that adapt iscroll callbacks to Widget Factory conventions.
// These are copied to the iscroll object's options object on instantiation.
// They call the private _trigger method provided by the widget factory
// base object. Normally, iscroll4 callbacks can be null or omitted. But since
// we can't know in advance whether the corresponding widget events might be bound
// or delegated in the future, we have set a callback for each that calls _trigger.
// This will call the corresponding widget callback as well as trigger the
// corresponding widget event if bound.
//
// Event callbacks are passed two values:
//
// e The underlying DOM event (if any) associated with this event
// d Data map
// iscrollview : The iscrollview object
//------------------------------------------------------------------------------
_proxy_event_funcs: {
onRefresh: function(e) {
this._doCallback("onRefresh", e, function(e) {
this._emulateBottomOffset();
this.iscrollview._pullOnRefresh.call(this.iscrollview,e);
});
},
onBeforeScrollStart: function(e) {
this._doCallback("onBeforeScrollStart", e, function(e) {
this._fixInput(e);
});
},
onScrollStart: function(e) { this._doCallback("onScrollStart", e); },
onBeforeScrollMove: function(e) {
this._doCallback("onBeforeScrollMove", e);
e.preventDefault(); // Don't scroll the page for touchmove inside scroller
},
onScrollMove: function(e) {
this._doCallback("onScrollMove", e, function(e) {
this.iscrollview._pullOnScrollMove.call(this.iscrollview, e);
});
},
onBeforeScrollEnd: function(e) { this._doCallback("onBeforeScrollEnd", e); },
onScrollEnd: function(e) {
this._doCallback("onScrollEnd", e, function(e){
this.iscrollview._pullOnScrollEnd.call(this.iscrollview, e);
});
},
onTouchEnd: function(e) { this._doCallback("onTouchEnd", e); },
onDestroy: function(e) { this._doCallback("onDestroy", e); },
onZoomStart: function(e) { this._doCallback("onZoomStart", e); },
onZoom: function(e) { this._doCallback("onZoom", e); },
onZoomEnd: function(e) { this._doCallback("onZoomEnd", e); }
},
// Merge options from the iscroll object into the widget options
// So, this will backfill iscroll4 defaults that we don't set in
// the widget, giving a full set of iscroll options, and leaving
// widget-only options untouched.
_merge_from_iscroll_options: function() {
var options = $.extend(true, {}, this.iscroll.options);
// Delete event options from the temp options
$.each(this._proxy_event_funcs, function(k,v) {delete options[k];});
if (this.options.emulateBottomOffset) { delete options.bottomOffset; }
$.extend(this.options, options); // Merge result into widget options
},
// Create a set of iscroll4 object options from the widget options.
// We have to omit any widget-specific options that are
// not also iscroll4 options. Also, copy the proxy event functions to the
// iscroll4 options.
_create_iscroll_options: function() {
var options = $.extend(true, {}, this.options); // temporary copy of widget options
// Remove options that are widget-only options
$.each(this._widgetOnlyOptions, function(i,v) {delete options[v];});
// Remove widget event options
$.each(this._event_map, function(k,v) {delete options[k];});
if (this.options.emulateBottomOffset) { delete options.bottomOffset; }
// Add proxy event functions
return $.extend(options, this._proxy_event_funcs);
},
// Formats number with fixed digits
_pad: function(num, digits, padChar) {
var str = num.toString(),
_padChar = padChar || "0";
while (str.length < digits) { str = _padChar + str; }
return str;
},
// Format time for logging
_toTime: function(date) {
return this._pad(date.getHours(), 2) + ":" +
this._pad(date.getMinutes(), 2) + ":" +
this._pad(date.getSeconds(), 2) + "." +
this._pad(date.getMilliseconds(), 3);
},
// Log a message to console
// text - message to log
// now - optional timestamp, if missing generates new timestamp
// Returns timestamp
_log: function(text, now) {
var _now, id, idStr;
if (!this.options.debug) { return null; }
_now = now || new Date();
id = this.$wrapper.attr("id");
idStr = id ? "#" + id : "";
console.log(this._toTime(_now) + " " +
$.mobile.path.parseUrl(this.$page.jqmData("url")).filename + idStr + " " +
text );
return _now;
},
// Log elapsed time from then to now
_logInterval: function(text, then) {
var now;
if (!this.options.debug) { return null; }
now = new Date();
return this._log(text + " " + (now - then) + "mS from " + this._toTime(then), now );
},
// Log an event
// Like _logInterval, but additional optional parameter e
// If e is present, additionally show interval from original event to now
_logEvent: function(text, e, then) {
var now,
eventTime,
haveEvent = e && e instanceof Object,
type = haveEvent ? e.type : e,
_text = type + " " + text;
if (!this.options.debug) { return null; }
now = new Date();
if (then) {
_text += " end " + (+(now-then)) + "mS from " + this._toTime(then);
}
else if (haveEvent) {
_text += " begin";
}
if (haveEvent) {
eventTime = new Date(e.timeStamp);
_text += " (" + (now - eventTime) + "mS from " +e.type + " @ " + this._toTime(eventTime) + ")";
}
return this._log(_text, now);
},
// Log a callback issued by iScroll
_logCallback: function(callbackName, e, then) {
if (!this.options.debug ||
!this.options.traceIscrollCallbacks ||
(this.options.tracedIscrollCallbacks.length !== 0 &&
$.inArray(callbackName, this.options.tracedIscrollCallbacks) === -1) ) {
return null;
}
if (e) { return this._logEvent(callbackName, e, then); }
if (then) { return this._logInterval(callbackName + " end", then); }
return this._log(callbackName + " begin");
},
// Log an event handled by Iscroll
// e can be Event or event name
_logIscrollEvent: function(text, e, then) {
var haveEvent = e instanceof Event,
type = haveEvent ? e.type : e;
if (!this.options.debug ||
!this.options.traceIscrollEvents ||
(this.options.tracedIscrollEvents.length !== 0 &&
$.inArray(type, this.options.tracedIscrollEvents) === -1)) {
return null;
}
return this._logEvent(text, e, then);
},
// Log an event handled by the widget
_logWidgetEvent: function(text, e, then) {
var haveEvent = e instanceof Object,
type = haveEvent ? e.type : e;
if (!this.options.debug ||
!this.options.traceWidgetEvents ||
(this.options.tracedWidgetEvents.length !== 0 &&
$.inArray(type, this.options.tracedWidgetEvents) === -1)) {
return null;
}
return this._logEvent(text, e, then);
},
// Log a callback issued by the widtet
_logWidgetCallback: function(callbackName, e, then) {
if (!this.options.debug ||
!this.options.traceWidgetCallbacks ||
(this.options.tracedWidgetCallbacks.length !== 0 &&
$.inArray(callbackName, this.options.tracedWidgetCallbacks) === -1) ) {
return null;
}
if (e) { return this._logEvent(callbackName, e, then); }
if (then) { return this._logInterval(callbackName + " end", then); }
return this._log(callbackName + " begin");
},
// Log elapsed time from then to now and later to now
_logInterval2: function(text, then, later) {
var now;
if (!this.options.debug) { return; }
now = new Date();
this._log(text + " " +
(now - later) + "mS from " + this._toTime(later) +
" (" + (now - then) + "mS from " + this._toTime(then) + ")" );
},
_startTiming: function() {
if (!this.options.debug) { return null; }
return new Date();
},
// Returns the event namespace for the page containing this widget
_pageEventNamespace: function() {
return ".iscroll_" + this.pageID;
},
// Returns the event namespace for this widget
_instanceEventNamespace: function() {
return this._pageEventNamespace() + "_" + this.instanceID;
},
// Takes a space-separated list of event types, and appends the given namespace to each
_addEventsNamespace: function(types_in, namespace) {
var types = types_in.split(" ");
$.each(types, function(k,v) {types[k] += namespace;});
return types.join(" ");
},
// All bind/unbind done by the widget goes through here, to permit logging
// Note: this used to be called _bind, but starting with JQM 1.2, this is a naming conflict with
// the Widget Factory code in JQM
_isvBind: function(obj, types_in, func, objName) {
var types = this._addEventsNamespace(types_in, this._instanceEventNamespace());
this._logWidgetEvent("bind " + objName, types);
obj.bind(types, $.proxy(func, this));
},
_bindPage: function(types_in, func) {
var types = this._addEventsNamespace(types_in, this._pageEventNamespace());
this._logWidgetEvent("bind $page", types);
this.$page.bind(types, $.proxy(func, this));
},
_isvUnbind: function(obj, types_in, objName) {
var types = this._addEventsNamespace(types_in, this._instanceEventNamespace());
this._logWidgetEvent("unbind " + objName, types);
obj.unbind(types);
},
_unbindPage: function(types_in) {
var types = this._addEventsNamespace(types_in, this._instanceEventNamespace());
this._logWidgetEvent("unbind $page", types);
this.$page.unbind(types);
},
// Currently unused - just in case we need it
_delegate: function(obj, selector, type, func, objName) {
this._logWidgetEvent("delegate " + objName + " " + selector, type);
obj.delegate(selector, type, $.proxy(func, this));
},
_triggerWidget: function(type, e) {
var then = this._logWidgetCallback(type);
this._trigger(type, e, {"iscrollview":this});
this._logWidgetCallback(type, e, then);
},
//-------------------------------------------------------------------
// Returns status of dirty flag, indicating that refresh() was called
// while the page was not active, and refresh will be deferred until
// pagebeforeshow.
//-------------------------------------------------------------------
isDirty: function() {
return this._dirty;
},
//-----------------------------------------------------------------------------
// Restore an element's styles to original
// If the style was never modified by the widget, the value passed in
// originalStyle will be undefined.
//
//If there originally was no style attribute, but styles were added by the
// widget, the value passed in originalStyle will be null.
//
// If there originally was a style attribute, but the widget modified it
// (actually, set some CSS, which changes the style, the value is a string in
// originalStyle.
//-----------------------------------------------------------------------------
_restoreStyle: function($ele, originalStyle) {
if (originalStyle === undefined) { return; }
if (originalStyle === null) { $ele.removeAttr("style"); }
else { $ele.attr("style", originalStyle); }
},
//------------------------------------------------------------------------------
// Functions that we bind to. They are declared as named members rather than as
// inline closures so we can properly unbind them.
//------------------------------------------------------------------------------
_pageBeforeShowFunc: function(e) {
var then = this._logWidgetEvent("_pageBeforeShowFunc", e);
if (this._dirty) {
this.resizeWrapper(true);
this.refresh(null, this._dirtyCallbackBefore, this._dirtyCallbackAfter, true);
this._dirty = false;
this._dirtyCallbackBefore = null;
this._dirtyCallbackAfter = null;
}
else if (this.options.refreshOnPageBeforeShow || this._sizeDirty) {
this.refresh(null,$.proxy(this._resizeWrapper, this),null,true);
}
this._sizeDirty = false;
this._logWidgetEvent("_pageBeforeShowFunc", e, then);
},
// Called on resize events
// TODO: Detect if size is unchanged, and if so just ignore?
_windowResizeFunc: function(e) {
var then = this._logWidgetEvent("_windowResizeFunc", e);
// Defer if not active page
if (this.options.deferNonActiveResize && !(this.$page.is($.mobile.activePage))) {
this._sizeDirty = true;
if (this.options.traceResizeWrapper) { this._log("resizeWrapper() (deferred)"); }
}
else {
this.resizeWrapper(true);
this.refresh(null,null,null,true);
}
this._logWidgetEvent("_windowResizeFunc", e, then);
},
// On some platforms (iOS, for example) you need to scroll back 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
_orientationChangeFunc: function(e) {
var then = this._logWidgetEvent("_orientationChangeFunc", e);
if (this.options.scrollTopOnOrientationChange) {
$.mobile.silentScroll(0);
}
this._logWidgetEvent("_orientationChangeFunc", e, then);
},
// Called when jQuery Mobile updates content such that a reflow is needed. This happens
// on collapsible content, etc.
_updateLayoutFunc: function(e) {
this.refresh();
},
// Get or set the count of instances on the page containing the widget
// This increases or decreases depending on the number of iscrollview widgets currently
// instantiated on the page.
_instanceCount: function(count_in) {
var key = "iscroll-private",
count = 0,
data = this.$page.jqmData(key) || {};
if (count_in !== undefined) {
count = count_in;
data.instanceCount = count;
this.$page.jqmData(key, data);
}
else {
if (data.instanceCount !== undefined) {
count = data.instanceCount;
}
}
return count;
},
_nextInstanceID: function(id_in) {
var key = "iscroll-private",
id = 1,
data = this.$page.jqmData(key) || {};
if (id_in !== undefined) {
id = id_in;
data.nextInstanceID = id;
this.$page.jqmData(key, data);
}
else {
if (data.nextInstanceID !== undefined) {
id = data.nextInstanceID;
}
}
return id;
},
_pageID: function(id_in) {
var key = "iscroll-private",
id = 1,
data = this.$page.jqmData(key) || {};
if (id_in !== undefined) {
id = id_in;
data.pageID = id;
this.$page.jqmData(key, data);
}
else {
if (data.pageID !== undefined) {
id = data.pageID;
}
}
return id;
},
//--------------------------------------------------------------------------
// Adapt the page for this widget
//--------------------------------------------------------------------------
_adaptPage: function() {
var _this = this;
// Only adapt the page if this is the only iscrollview widget instantiated on the page
// If the count >1, then the page has already been adapted. When the count goes back
// to 0, the changes will be un-done
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);
});
if (HasTouch && this.options.preventPageScroll) {
this._bindPage("touchmove", _pageTouchmoveFunc);
}
}
},
_undoAdaptPage: function() {
var _this = this;
if (this._instanceCount() === 1) {
this.$page.find(this.options.fixedHeightSelector).each(function() { // Iterate over headers/footers/etc.
$(this).removeClass(_this.options.fixedHeightClass);
});
this.$page.removeClass(this.options.pageClass);
}
},
//--------------------------------------------------------
// Calculate total bar heights.
//--------------------------------------------------------
_calculateBarsHeight: function() {
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
// 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
// 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);
$barsInPage.each(function() { // Iterate over headers/footers/etc.
barsHeight += $(this).outerHeight(true);
});
$barsOutsidePage.each(function() {
var id = $(this).jqmData("id"); // Find data-id if present
// Count bars outside of the page if they don't have data-id (so not a persistent
// footer, but something the developer put there and tagged with data-iscroll-fixed class),
// or if a matching data-id is NOT found among the bars that are inside the page.
if (id === "" || !$barsInPage.is( ":jqmData(id='" + id + "')" )) {
barsHeight += $(this).outerHeight(true);
}
});
return barsHeight;
},
//-----------------------------------------------------------------------
// Determine the box-sizing model of an element
// While jQuery normalizes box-sizing models when retriving geometry,
// it doesn't consider it when SETTING geometry. So, this is useful when
// setting geometry. (e.g. the height of the wrapper)
//-----------------------------------------------------------------------
_getBoxSizing: function($elem) {
var boxSizing,
prefix = "";
if (IsFirefox) { prefix = "-moz-"; }
else if (IsWebkit) { prefix = "-webkit-"; } // note: can drop prefix for Chrome >=10, Safari >= 5.1 (534.12)
boxSizing = $elem.css(prefix + "box-sizing");
if (!boxSizing && prefix) { boxSizing = $elem.css("box-sizing"); } // Not found, try again with standard CSS
if (!boxSizing) { // Still not found - no CSS property available to guide us.
// See what JQuery thinks the global box model is
if ($.boxModel) { boxSizing = "content-box"; }
else { boxSizing = "border-box"; }
}
return boxSizing;
},
//-----------------------------------------------------------------
// Get the height adjustment for setting the height of an element,
// based on the content-box model
//-----------------------------------------------------------------
_getHeightAdjustForBoxModel: function($elem) {
// Take into account the box model. This defaults to either W3C or traditional
// model for a given browser, but can be overridden with CSS
var adjust;
switch (this._getBoxSizing($elem)) {
case "border-box": // AKA traditional, or IE5 (old browsers and IE quirks mode)
// only subtract margin
adjust = $elem.outerHeight(true) - $elem.outerHeight();
break;
case "padding-box": // Firefox-only
// subtract margin and border
adjust = $elem.outerHeight() - $elem.height();
break;
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;
}
return adjust;
},
//--------------------------------------------------------
// If there's a pull-down element, we need to set the
// topOffset to the height of that element. If user
// specified a topOffset option, use that instead, though.
//--------------------------------------------------------
_setTopOffsetForPullDown: function() {
if (this.$pullDown.length && !this.options.topOffset) {
this.options.topOffset = this.$pullDown.outerHeight(true);
}
},
//--------------------------------------------------------
// If there's a pull-up element, we need to set the
// bottomOffset to the height of that element. If user
// specified a bottomOffset option, use that instead, though.
//--------------------------------------------------------
_setBottomOffsetForPullUp: function() {
if (this.$pullUp.length && !this.options.bottomOffset) {
this.options.bottomOffset = this.$pullUp.outerHeight(true);
}
},
_removeWrapperPadding: function() {
var $wrapper = this.$wrapper;
if (this.options.removeWrapperPadding) {
// Save padding so we can re-apply it to the iscroll-content div that we create
this._origWrapperPaddingLeft = $wrapper.css("padding-left");
this._origWrapperPaddingRight = $wrapper.css("padding-right");
this._origWrapperPaddingTop = $wrapper.css("padding-top");
this._origWrapperPaddingBottom = $wrapper.css("padding-bottom");
this.$wrapper.css("padding", 0);
}
},
//---------------------------------------------------------
// Modify some wrapper CSS
//---------------------------------------------------------
_modifyWrapperCSS: function() {
this._origWrapperStyle = this.$wrapper.attr("style") || null;
this._removeWrapperPadding();
},
_undoModifyWrapperCSS: function() {
this._restoreStyle(this.$wrapper, this._origWrapperStyle);
},
//---------------------------------------------------------
// Adds padding around scrolled content (not including
// any pull-down or pull-up) using a div with padding
// removed from wrapper.
//---------------------------------------------------------
_addScrollerPadding: function () {
if (this.options.removeWrapperPadding && this.options.addScrollerPadding) {
// We do not store $scrollerContent in the object, because elements might be added/deleted
// after instantiation. When we undo, we need the CURRENT children in order to unwrap
var $scrollerContentWrapper,
$scrollerChildren = this.$scroller.children(),
$scrollerContent = $scrollerChildren.not(this.$pullDown).not(this.$pullUp).not(this.$pullUpSpacer);
$scrollerContent.wrapAll("
");
$scrollerContentWrapper = $scrollerContent.parent().addClass(this.options.scrollerContentClass);
$scrollerContentWrapper.css({
"padding-left" : this._origWrapperPaddingLeft,
"padding-right" : this._origWrapperPaddingRight,
"padding-top" : this._origWrapperPaddingTop,
"padding-bottom" : this._origWrapperPaddingBottom
});
}
},
_undoAddScrollerPadding: function () {
if (this.options.removeWrapperPadding && this.options.addScrollerPadding) {
$("." + this.options.scrollerContentClass, this.$scroller).children().unwrap();
}
},
//---------------------------------------------------------
// Add some convenient classes in case user wants to style
// wrappers/scrollers that use iscroll.
//---------------------------------------------------------
_addWrapperClasses: function() {
this.$wrapper.addClass(this.options.wrapperClass);
this.$scroller.addClass(this.options.scrollerClass);
},
_undoAddWrapperClasses: function() {
this.$scroller.removeClass(this.options.scrollerClass);
this.$wrapper.removeClass(this.options.wrapperClass);
},
//--------------------------------------------------------
// Expands the scroller to fill the wrapper. This permits
// dragging an empty scroller, or one that is shorter than
// the wrapper. Otherwise, you could never do pull to
// refresh if some content wasn't initially present. As
// well, this pushes any pull-up element down so that it
// will not be visible until the user pulls up.
//--------------------------------------------------------
_expandScrollerToFillWrapper: function() {
if (this.options.scrollShortContent || this.$pullDown.length || this.pullUp.length) {
if (this._firstScrollerExpand) {
this._origScrollerStyle = this.$scroller.attr("style") || null;
this._firstScrollerExpand = false;
}
this.$scroller.css("min-height",
this.$wrapper.height() +
(this.$pullDown.length ? this.$pullDown.outerHeight(true) : 0) +
(this.$pullUp.length ? this.$pullUp.outerHeight(true) : 0)
);
}
},
_undoExpandScrollerToFillWrapper: function() {
this._restoreStyle(this.$scroller, this._origScrollerStyle);
},
//--------------------------------------------------------
//Resize the wrapper for the scrolled region to fill the
// viewport remaining after all fixed-height elements
// force makes it ignore resizeWrapper option
//--------------------------------------------------------
_resizeWrapper: function(force) {
var then = null,
viewportHeight,
barsHeight,
newWrapperHeight;
if ( !force && !this.options.resizeWrapper ) {
return;
}
if (this.options.traceResizeWrapper) {
then = this._log("resizeWrapper() start");
}
this.$wrapper.trigger("updatelayout"); // Let jQuery mobile update fixed header/footer, collapsables, etc.
// Get technically-correct viewport height. jQuery documentation is in error on this.
// The viewport height is NOT in all cases the same as the window height, because the height
// of window might have been manually set. And, guess what? jQuery Mobile sets it to 99.99%.
// The viewport is considered the parent of window, and can be retrieved as shown below.
// At 99.99% and common browser sizes, this is probably not an issue. But let's do it right.
//viewportHeight = this.$window.height(); // Wrong
viewportHeight = document.documentElement.clientHeight;
barsHeight = this._calculateBarsHeight();
newWrapperHeight =
viewportHeight -
barsHeight - // Height of fixed bars or "other stuff" outside of the wrapper
this._wrapperHeightAdjustForBoxModel + // Make adjustment based on content-box model
// Note: the following will fail for Safari desktop with Develop/User Agent/iPhone
// Fake fullscreen or webview by using custom user agent and removing "Safari" from string
(IsMobileSafari && !IsIPad ? 60 : 0) + // Add 60px for space recovered from Mobile Safari address bar
this.options.wrapperAdd; // User-supplied fudge-factor if needed
this.$wrapper.css("height", newWrapperHeight);
this._expandScrollerToFillWrapper();
if (this.options.traceResizeWrapper) {
this._logInterval("resizeWrapper() end" + (this._sizeDirty ? " (dirty)" : ""), then);
}
},
// Internal flag is meant for internal use from jquery.mobile.iscrollview only
// If this flag is omitted, then the force flag is set when calling _resizeWrapper
// This causes it to ignore the resizeWrapper option setting. i.e. if user code
// calls resizeWrapper, always resize the wrapper, regardless of setting.
resizeWrapper: function (internal) {
var hidden = this._setPageVisible();
this._resizeWrapper(internal !== undefined);
this._restorePageVisibility(hidden);
},
_undoResizeWrapper: function() {
},
//---------------------------------------------------------
// Make various modifications to the wrapper
//---------------------------------------------------------
_modifyWrapper: function() {
this._addWrapperClasses();
this._modifyWrapperCSS();
this._wrapperHeightAdjustForBoxModel = this._getHeightAdjustForBoxModel(this.$wrapper);
},
_undoModifyWrapper: function() {
this._undoResizeWrapper();
this._undoModifyWrapperCSS();
this._undoAddWrapperClasses();
},
//--------------------------------------------------------
// Modify the pull-down (if any) with reset text
// Also, read data-iscroll-release and data-iscroll-loading
// values (if present ) into the corresponding options.
//--------------------------------------------------------
_modifyPullDown: function () {
var $pullDownLabel, pulledText, loadingText;
if (this.$pullDown.length === 0) { return; }
$pullDownLabel = $("." + this.options.pullLabelClass, this.$pullDown);
if ($pullDownLabel.length) {
this._origPullDownLabelText = $pullDownLabel.text();
if (this._origPullDownLabelText) { this.options.pullDownResetText = this._origPullDownLabelText; }
else { $pullDownLabel.text(this.options.pullDownResetText); }
pulledText = $pullDownLabel.jqmData("iscroll-pulled-text");
if (pulledText) { this.options.pullDownPulledText = pulledText; }
loadingText = $pullDownLabel.jqmData("iscroll-loading-text");
if (loadingText) { this.options.pullDownLoadingText = loadingText; }
}
},
_undoModifyPullDown: function () {
if (this.$pullDown.length === 0) { return; }
var $pullDownLabel = $("." + this.options.pullLabelClass, this.$pullDown);
if ($pullDownLabel.length === 0) { return; }
$pullDownLabel.text(this._origPullDownLabelText);
},
//--------------------------------------------------------
// Modify the pullup element (if any) to prevent visual
// glitching. Position at the bottom of the scroller.
//
// Modify the pull-up (if any) with reset text
// Also, read data-iscroll-release and data-iscroll-loading
// values (if present ) into the corresponding options.
//--------------------------------------------------------
_modifyPullUp: function () {
var $pullUpLabel, pulledText, loadingText;
if (this.$pullUp.length === 0) { return; }
// Since we are positioning the pullUp element absolutely, it is pulled out of the
// document flow. We need to add a dummy
with the same height as the pullUp.
$("
").insertBefore(this.$pullUp).css(
"height", this.$pullUp.outerHeight(true) );
this.$pullUpSpacer = this.$pullUp.prev();
this.$pullUpSpacer.addClass(this.options.pullUpSpacerClass);
$pullUpLabel = $("." + this.options.pullLabelClass, this.$pullUp);
if ($pullUpLabel.length) {
this._origPullUpLabelText = $pullUpLabel.text();
if (this._origPullUpLabelText) { this.options.pullUpResetText = this._origPullUpLabelText; }
else { $pullUpLabel.text(this.options.pullUpResetText); }
pulledText = $pullUpLabel.jqmData("iscroll-pulled-text");
if (pulledText) { this.options.pullUpPulledText = pulledText; }
loadingText = $pullUpLabel.jqmData("iscroll-loading-text");
if (loadingText) { this.options.pullUpLoadingText = loadingText; }
}
},
_undoModifyPullUp: function () {
if (this.$pullUp.length === 0) { return; }
this.$pullUp.prev().remove(); // Remove the dummy div
if (this._origPullUpLabelText) {
$("." + this.options.pullLabelClass, this.$pullUp).text(this._origPullUpLabelText);
}
},
_correctPushedDownPage: function() {
// Scroll to top in case address bar pushed the page down
if (this.options.resizeWrapper && this.options.scrollTopOnResize) {
$.mobile.silentScroll(0);
}
},
//----------------------------------------------------------------------
// Refresh the iscroll object. Insure that refresh is called with proper
// timing. Call optional before and after refresh callbacks and trigger
// before and after refresh events.
//-----------------------------------------------------------------------
refresh: function(delay, callbackBefore, callbackAfter, noDefer) {
var _this, _delay, _callbackBefore, _callbackAfter, _noDefer, then;
// If non-active-page refresh is deferred, make a note of it.
// Note that each call to refresh() overwrites the callback and context variables.
// Our expectation is that callback and context will be identical for all such refresh
// calls. In any case, only the last callback and context will be used. This allows
// refresh of jQuery Mobile widgets within the scroller to be deferred, as well.
if (!noDefer && this.options.deferNonActiveRefresh && !(this.$page.is($.mobile.activePage))) {
this._dirty = true;
this._dirtyCallbackBefore = callbackBefore;
this._dirtyCallbackAfter = callbackAfter;
if (this.options.traceRefresh) {
this._log("refresh() (deferred)");
}
return;
}
// Let the browser complete rendering, then refresh the scroller
//
// Optional delay parameter for timeout before actually calling iscroll.refresh().
// If missing (undefined) or null, use options.refreshDelay.
//
// Optional callback parameters are called if present before and after iScroll internal
// refresh() is called. While the caller might bind to the before or after refresh events,
// this can be more convenient and avoids any ambiguity over WHICH call to refresh is involved.
_this = this;
_delay = delay;
_callbackBefore = callbackBefore;
_callbackAfter = callbackAfter;
_noDefer = noDefer;
then = this._startTiming();
if ((_delay === undefined) || (_delay === null) ) { _delay = this.options.refreshDelay; }
setTimeout(function() {
var later = null,
hidden;
if (_this.options.traceRefresh) {
later = _this._logInterval("refresh() start", then);
}
hidden = _this._setPageVisible();
if (_callbackBefore) { _callbackBefore(); }
_this._triggerWidget("onbeforerefresh");
_this.iscroll.refresh();
_this._triggerWidget("onafterrefresh");
if (_callbackAfter) { _callbackAfter(); }
_this._restorePageVisibility(hidden);
// Scroll to top in case address bar pushed the page down
if (!hidden) { _this._correctPushedDownPage(); }
if (_this.options.traceRefresh) {
_this._logInterval2("refresh() end" + (_noDefer ? " (dirty)" : ""), then, later);
}
}, _delay);
if (this.options.traceRefresh) {
this._log("refresh() will occur after >= " + _delay + "mS");
}
},
//---------------------------
// Create the iScroll object
//---------------------------
_create_iscroll_object: function() {
/*jslint newcap:true */
this.iscroll = new IScroll(this, this.$wrapper.get(0), this._create_iscroll_options());
/* jslint newcap:false */
},
//-----------------------------------------
// Create scroller
//-----------------------------------------
_createScroller: function() {
if (this.options.createScroller) {
if (this.$wrapper.children().length) {
// Wrap the content with a div
this.$wrapper.children().wrapAll("
");
}
else {
// Create an empty div for content and wrap with a div
this.$wrapper.append("
");
}
}
},
_undoCreateScroller: function() {
if (this.options.createScroller) {
this.$scroller.children().unwrap();
}
},
// 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
// change, as long as the page style is set back before the callback returns. So, a hidden
// page will remain hidden as long as _restorePageVisibility() is called before return.
// This way, we can just use normal dimension functions and avoid using jquery.actual, which
// slows things down significantly.
//
// jquery.actual goes up the tree from the element being measured and sets every element to
// visible, which is unnecessary. (We only have to be concerned about the page element, which
// jQuery Mobile sets to display:none for all but the currently-visible page.) It also does this
// for every dimension read. This essentially does the same thing, but then allows us to "batch"
// reading dimensions
_setPageVisible: function() {
var hidden = this.$page.is(":hidden");
if (hidden) { this.$page.css("display", "block"); }
return hidden;
},
_restorePageVisibility: function(hidden) {
if (hidden) { this.$page.css("display", ""); }
},
//-----------------------------------------
// Automatically called on page creation
//-----------------------------------------
_create: function() {
var then = new Date(),
hidden;
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
if (this.options.debug && this.options.traceCreateDestroy) {
this._log("_create() start", then);
}
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);
if (this.instanceID === 1) {
this._pageID(nextPageID);
nextPageID += 1;
}
this.pageID = this._pageID();
hidden = this._setPageVisible(); // Fake page visibility, so dimension functions work
this._adaptPage();
this._createScroller();
this.$scroller = this.$wrapper.children(":first"); // Get the first child of the wrapper, which is the
// element that we will scroll
if (this.$scroller.length === 0) { return; }
// 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
// Need this for deferred refresh processing
this._bindPage("pagebeforeshow", this._pageBeforeShowFunc);
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._create_iscroll_object();
this._merge_from_iscroll_options(); // Merge iscroll options into widget options
this._restorePageVisibility(hidden);
// Setup bindings for window resize and orientationchange
if (this.options.resizeWrapper) {
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) {
this._logInterval("_create() end", then);
}
},
//----------------------------------------------------------
// Destroy an instantiated plugin and clean up modifications
// the widget has made to the DOM
//----------------------------------------------------------
destroy: function () {
var then = null;
if (this.options.debug && this.options.traceCreateDestroy) {
then = this._log("destroy() start");
}
// Unbind events
this._isvUnbind(this.$scrollerContent, "updatelayout", "$scrollerContent");
this._isvUnbind(this.$window, this.options.resizeEvents, "$window");
this._isvUnbind(this.$window, "orientationchange", "$window");
if (this._instanceCount() === 1) {
this._unbindPage("pagebeforeshow");
if (HasTouch) {
this._unbindPage("touchmove");
}
}
// fastDestroy option skips tearing down the modifications to the page, because we assume
// that the page itself is being removed, and nobody is going to be silly enough to
// un-ehance a scroller and keep the page.
if (!this.options.fastDestroy) {
this.iscroll.destroy();
this.iscroll = null;
this._undoExpandScrollerToFillWrapper();
this._undoModifyPullDown();
this._undoModifyPullUp();
this._undoAddScrollerPadding();
this._undoModifyWrapper();
this.$wrapper.removeClass(this.options.wrapperClass);
this.$scroller.removeClass(this.options.scrollerClass);
this._undoCreateScroller();
}
this._instanceCount(this._instanceCount() - 1); // The count of extant instances of this widget on the page
if (this._instanceCount() === 0) {
this._undoAdaptPage();
}
// For UI 1.8, destroy must be invoked from the
// base widget
$.Widget.prototype.destroy.call(this);
if (this.options.debug && this.options.traceCreateDestroy) {
this._logInterval("destroy() end", then);
}
// For UI 1.9, define _destroy instead and don't
// worry about calling the base widget
},
// Enable the widget
enable: function() {
this.iscroll.enable();
$.Widget.prototype.enable.call(this);
},
// Disable the widget
disable: function() {
this.iscroll.disable();
$.Widget.prototype.disable.call(this);
},
//----------------------------------------------------------
//Respond to any changes the user makes to the option method
//----------------------------------------------------------
_setOption: function( key, value ) {
var hidden;
// iScroll4 doesn't officially support changing options after an iscroll object has been
// instantiated. However, some changes will work if you do a refresh() after changing the
// option. This is undocumented other than from user comments on the iscroll4 Google
// Groups support group. If an option change doesn't work with refresh(), then it
// is necessary to destroy and re-create the iscroll object. This is a functionality
// that the author of iscroll4 intends to support in the future.
//
// TODO: Research which options can be successfully changed without destroying and
// re-creating the iscroll object. For now, I'm taking a safe approach and
// always destroying and re-creating the iscroll object.
//switch (key) {
//case "hScroll":
//case "vScroll":
//case "hScrollbar":
//case "vScrollbar":
//this.options[ key ] = value; // Change our options object
//this.iscroll.options[ key ] = value; // ... and iscroll's options object
//this.iscroll.refresh(); // Don't think we need the timing hack here
//break;
//default:
this.options[ key ] = value;
this.iscroll.destroy();
hidden = this._setPageVisible();
this._create_iscroll_object();
this._restorePageVisibility(hidden);
//break;
//}
// For UI 1.8, _setOption must be manually invoked from
// the base widget
$.Widget.prototype._setOption.apply(this, arguments);
// For UI 1.9 the _super method can be used instead
// this._super( "_setOption", key, value );
},
//----------------------------------------------------
// Convenience wrappers around iscroll4 public methods
// So, you can use:
//
// $(".some-class").iscrollview("scrollTo", x, y, time, relative);
//
// instead of:
//
// $(".some-class").jqmData("iscrollview").iscroll.scrollTo(x, y, time, relative);
//
//----------------------------------------------------
scrollTo: function(x,y,time,relative) { this.iscroll.scrollTo(x,y,time,relative); },
scrollToElement: function(el,time) { this.iscroll.scrollToElement(el,time); },
scrollToPage: function(pageX,pageY,time) { this.iscroll.scrollToPage(pageX,pageY,time); },
stop: function() { this.iscroll.stop(); },
zoom: function(x,y,scale,time) { this.iscroll.zoom(x,y,scale,time); },
isReady: function() { return this.iscroll.isReady(); },
// See disable() enable() elsewhere above - they are standard widget methods
//----------------------------------------------------------------------------------
// Accessors for iscroll4 internal variables. These are sometimes useful externally.
// For example, let's say you are adding elements to the end of a scrolled list.
// You'd like to scroll up (using scrollToElement) if the new element would be
// below the visible area. But if the list is intially empty, you'd want to avoid
// this until the scrolling area is initially full. So you need to compare the
// scroller height (scrollerH) to the wrapper height (wrapperH).
//
// These are also useful for creating "pull to refresh" functionality.
//
//-----------------------------------------------------------------------------------
x: function() { return this.iscroll.x; },
y: function() { return this.iscroll.y; },
wrapperW: function() { return this.iscroll.wrapperW; },
wrapperH: function() { return this.iscroll.wrapperH; },
scrollerW: function() { return this.iscroll.scrollerW; },
scrollerH: function() { return this.iscroll.scrollerH; },
// These have setters. Useful for "pull to refresh".
minScrollX: function(val) { if (val !== undefined) { this.iscroll.minScrollX = val; } return this.iscroll.minScrollX; },
minScrollY: function(val) { if (val !== undefined) { this.iscroll.minScrollY = val; } return this.iscroll.minScrollY; },
maxScrollX: function(val) { if (val !== undefined) { this.iscroll.maxScrollX = val; } return this.iscroll.maxScrollX; },
maxScrollY: function(val) { if (val !== undefined) { this.iscroll.maxScrollY = val; } return this.iscroll.maxScrollY; },
//-----------------------------------------------------------------------------------
// Pull-down/Pull-up support
//-----------------------------------------------------------------------------------
// Is pull-down in "pulled" state?
_pullDownIsPulled: function () {
return this.$pullDown.length && this.$pullDown.hasClass(this.options.pullPulledClass);
},
// Is pull-up in "pulled" state?
_pullUpIsPulled: function () {
return this.$pullUp.length && this.$pullUp.hasClass(this.options.pullPulledClass);
},
// Replace the text in a pull block
_replacePullText: function ($pull, text) {
var $label;
if (text) {
$label = $("." + this.options.pullLabelClass, $pull);
if ($label) { $label.text(text); }
}
},
// Reset a pull block to the initial state
_pullSetStateReset: function ($pull, text) {
if ($pull.is("." + this.options.pullLoadingClass + ", ." + this.options.pullPulledClass)) {
$pull.removeClass(this.options.pullPulledClass + " " + this.options.pullLoadingClass);
this._replacePullText($pull, text);
}
},
_pullDownSetStateReset: function(e) {
this._pullSetStateReset(this.$pullDown, this.options.pullDownResetText);
this._triggerWidget("onpulldownreset", e);
},
_pullUpSetStateReset: function(e) {
this._pullSetStateReset(this.$pullUp, this.options.pullUpResetText);
this._triggerWidget("onpullupreset", e);
},
// Set a pull block to pulled state
_pullSetStatePulled: function($pull, text) {
$pull.removeClass(this.options.pullLoadingClass).addClass(this.options.pullPulledClass);
this._replacePullText($pull, text);
},
_pullDownSetStatePulled: function(e) {
this._pullSetStatePulled(this.$pullDown, this.options.pullDownPulledText);
this._triggerWidget("onpulldownpulled", e);
},
_pullUpSetStatePulled: function (e) {
this._pullSetStatePulled(this.$pullUp, this.options.pullUpPulledText);
this._triggerWidget("onpulluppulled", e);
},
// Set a pull block to the loading state
_pullSetStateLoading: function($pull, text) {
$pull.removeClass(this.options.pullPulledClass).addClass(this.options.pullLoadingClass);
this._replacePullText($pull, text);
},
_pullDownSetStateLoading: function (e) {
this._pullSetStateLoading(this.$pullDown, this.options.pullDownLoadingText);
this._triggerWidget("onpulldownloading", e);
},
_pullUpSetStateLoading: function(e) {
this._pullSetStateLoading(this.$pullUp, this.options.pullUpLoadingText);
this._triggerWidget("onpulluploading", e);
},
_pullOnRefresh: function (e) {
// It's debatable if this is the right place to do this. On one hand, it might be best
// to do this in the pullup/down action function. We expect that we will always do a refresh
// after the action, though (unless the action doesn't actually update anything, in which
// case it can still call refresh().) On the other hand, it might be desirable to
// "reset" the pull if a refresh comes along for some other reason. If the content were
// updated because of something other than the user's pull action, then we consider the
// pull moot.
// Reset pull blocks to their initial state
if (this.$pullDown.length) { this._pullDownSetStateReset(e); }
if (this.$pullUp.length) { this._pullUpSetStateReset(e); }
},
_pullOnScrollMove: function (e) {
var pullDownIsPulled, pullUpIsPulled, pullDownHeight, pullDownPast, pullUpHeight, pullUpPast,
y = this.y();
if (this.$pullDown.length) {
pullDownIsPulled = this._pullDownIsPulled();
pullDownHeight = this.options.topOffset;
// User needs to pull down past the top edge of the pulldown element. To prevent false
// triggers from aggressive scrolling, they should have to pull down some additional
// amount. Half the height of the pulldown seems reasonable, but adjust per preference.
pullDownPast = pullDownHeight / 2;
// Set "pulled" state if not pulled and user has pulled past the pulldown element
// by pullDownPast pixels
if (!pullDownIsPulled && y > pullDownPast ) {
this._pullDownSetStatePulled(e);
this.minScrollY(0); // Circumvent top offset so pull-down element doesn't rubber-band
}
// Allow user to "oopsie", and scroll back to cancel and avoid pull-down action
// Cancel if pulled and user has scrolled back to top of pulldown element
else if (pullDownIsPulled && y <= 0) {
this._pullDownSetStateReset(e);
this.minScrollY(-pullDownHeight); // Re-instate top offset
}
}
if (this.$pullUp.length) {
pullUpIsPulled = this._pullUpIsPulled();
pullUpHeight = this.options.bottomOffset;
pullUpPast = pullUpHeight / 2;
if (!pullUpIsPulled && y < this.maxScrollY() - pullUpHeight - pullUpPast ) {
this._pullUpSetStatePulled(e);
this.maxScrollY(this.wrapperH() - this.scrollerH() + this.minScrollY());
}
else if (pullUpIsPulled && y >= this.maxScrollY() ) {
this._pullUpSetStateReset(e);
this.maxScrollY(this.wrapperH() - this.scrollerH() + this.minScrollY() + pullUpHeight);
}
}
},
_pullOnScrollEnd: function (e) {
if (this._pullDownIsPulled(e)) {
this._pullDownSetStateLoading(e);
this._triggerWidget("onpulldown", e);
}
else if (this._pullUpIsPulled(e)) {
this._pullUpSetStateLoading(e);
this._triggerWidget("onpullup", e);
}
}
});
}( jQuery, window, document ));
// Self-init
jQuery(document).bind("pagecreate", function (e) {
"use strict";
// In here, e.target refers to the page that was created (it's the target of the pagecreate event)
// So, we can simply find elements on this page that match a selector of our choosing, and call
// our plugin on them.
// The find() below returns an array of elements within a newly-created page that have
// the data-iscroll attribute. The Widget Factory will enumerate these and call the widget
// _create() function for each member of the array.
// If the array is of zero length, then no _create() fucntion is called.
var elements = jQuery(e.target).find(":jqmData(iscroll)");
elements.iscrollview();
});