mo-framework
Version:
Hybrid mobile app framework with generators
280 lines (272 loc) • 7.53 kB
JavaScript
/*!
* v0.3.0
* Copyright (c) 2014 Jarid Margolin
* bouncefix.js is open sourced under the MIT license.
*/
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], function () {
return (root.returnExportsGlobal = factory());
});
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like enviroments that support module.exports,
// like Node.
module.exports = factory();
} else {
root['bouncefix'] = factory();
}
}(this, function () {
/*
* dom-event.js
*
* (C) 2014 Jarid Margolin
* MIT LICENCE
*
*/
var domEvent;
domEvent = function () {
// ----------------------------------------------------------------------------
// DOMEvent
//
// Convenient class used to work with addEventListener.
// ----------------------------------------------------------------------------
function DOMEvent(el, eventName, handler, context) {
// Make args available to instance
this.el = el;
this.eventName = eventName;
this.handler = handler;
this.context = context;
// Attach
this.add();
}
//
// Handler that manages context, and normalizes both
// preventDefault and stopPropagation.
//
DOMEvent.prototype._handler = function (e, context) {
// Copy props to new evt object. This is shallow.
// Only done so that I can modify stopPropagation
// and preventDefault.
var evt = {};
for (var k in e) {
evt[k] = e[k];
}
// Normalize stopPropagation
evt.stopPropagation = function () {
if (e.stopPropagation) {
e.stopPropagation();
} else {
e.cancelBubble = true;
}
};
// Normalize preventDefault
evt.preventDefault = function () {
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false;
}
};
// Call with context and modified evt.
this.handler.call(this.context || context, evt);
};
//
// Add the `EventListener`. This method is called internally in
// the constructor. It can also be used to re-attach a listener
// that was previously removed.
//
DOMEvent.prototype.add = function () {
// Cache this
var self = this;
// Cache handler so it can be removed.
self.cachedHandler = function (e) {
self._handler.call(self, e, this);
};
// Modified handler
self.el.addEventListener(self.eventName, self.cachedHandler, false);
};
//
// Remove the `EventListener`
//
DOMEvent.prototype.remove = function () {
this.el.removeEventListener(this.eventName, this.cachedHandler);
};
// ----------------------------------------------------------------------------
// Expose
// ----------------------------------------------------------------------------
return DOMEvent;
}();
/*
* utils.js
*
* (C) 2014 Jarid Margolin
* MIT LICENCE
*
*/
var utils;
utils = function () {
// ----------------------------------------------------------------------------
// Utils
// ----------------------------------------------------------------------------
var utils = {};
//
// Search nodes to find target el. Return if exists
//
utils.getTargetedEl = function (el, className) {
while (true) {
// We found it, exit
if (el.classList.contains(className)) {
break;
}
// Else keep climbing up tree
if (el = el.parentElement) {
continue;
}
// Not found
break;
}
return el;
};
//
// Return true or false depending on if content
// is scrollable
//
utils.isScrollable = function (el) {
return el.scrollHeight > el.offsetHeight;
};
//
// Keep scrool from hitting end bounds
//
utils.scrollToEnd = function (el) {
var curPos = el.scrollTop, height = el.offsetHeight, scroll = el.scrollHeight;
// If at top, bump down 1px
if (curPos <= 0) {
el.scrollTop = 1;
}
// If at bottom, bump up 1px
if (curPos + height >= scroll) {
el.scrollTop = scroll - height - 1;
}
};
// ----------------------------------------------------------------------------
// Expose
// ----------------------------------------------------------------------------
return utils;
}();
/*
* fix.js
*
* (C) 2014 Jarid Margolin
* MIT LICENCE
*
*/
var fix;
fix = function (DOMEvent, utils) {
// ----------------------------------------------------------------------------
// Fix
//
// Class Constructor - Called with new BounceFix(el)
// Responsible for setting up required instance
// variables, and listeners.
// ----------------------------------------------------------------------------
var Fix = function (className) {
// Make sure it is created correctly
if (!(this instanceof Fix)) {
return new Fix(className);
}
// Without className there is nothing to fix
if (!className) {
throw new Error('"className" argument is required');
}
// Add className to instance
this.className = className;
// On touchstart check for block. On end, cleanup
this.startListener = new DOMEvent(document, 'touchstart', this.touchStart, this);
this.endListener = new DOMEvent(document, 'touchend', this.touchEnd, this);
};
//
// touchstart handler
//
Fix.prototype.touchStart = function (evt) {
// Get target
var el = utils.getTargetedEl(evt.target, this.className);
// If el scrollable
if (el && utils.isScrollable(el)) {
return utils.scrollToEnd(el);
}
// Else block touchmove
if (el && !this.moveListener) {
this.moveListener = new DOMEvent(el, 'touchmove', this.touchMove, this);
}
};
//
// If this event is called, we block scrolling
// by preventing default behavior.
//
Fix.prototype.touchMove = function (evt) {
evt.preventDefault();
};
//
// On touchend we need to remove and listeners
// we may have added.
//
Fix.prototype.touchEnd = function (evt) {
if (this.moveListener) {
this.moveListener.remove();
delete this.moveListener;
}
};
//
// touchend handler
//
Fix.prototype.remove = function () {
this.startListener.remove();
this.endListener.remove();
};
// ----------------------------------------------------------------------------
// Expose
// ----------------------------------------------------------------------------
return Fix;
}(domEvent, utils);
/*
* bouncefix.js
*
* (C) 2014 Jarid Margolin
* MIT LICENCE
*
*/
var bouncefix;
bouncefix = function (Fix) {
// ----------------------------------------------------------------------------
// Bouncefix
//
// Stop full body elastic scroll bounce when scrolling inside
// nested containers (IOS)
// ----------------------------------------------------------------------------
var bouncefix = { cache: {} };
//
// Add/Create new instance
//
bouncefix.add = function (className) {
if (!this.cache[className]) {
this.cache[className] = new Fix(className);
}
};
//
// Delete/Remove instance
//
bouncefix.remove = function (className) {
if (this.cache[className]) {
this.cache[className].remove();
delete this.cache[className];
}
};
// ----------------------------------------------------------------------------
// Expose
// ----------------------------------------------------------------------------
return bouncefix;
}(fix);
return bouncefix;
}));