var ED = ED || {};
// Utility functions
ED.util = (function() {
// Data structure functions
function each(object, callback) {
if (object === null) return;
if (object instanceof Array) {
for (var i = 0, item; i < object.length; i++) {
callback(object[i], i);
}
} else {
for (var key in object) {
if (object.hasOwnProperty(key)) {
callback(object[key], key);
}
}
}
}
function keys(object) {
var objectKeys = [];
for (var key in object) {
if (object.hasOwnProperty(key)) {
objectKeys.push(key);
}
}
return objectKeys;
}
function values(object) {
var objectValues = [];
for (var key in object) {
if (object.hasOwnProperty(key)) {
objectValues.push(object[key]);
}
}
return objectValues;
}
function inArray(arr, val) {
if (!arr) return false;
for (var i = 0; i < arr.length; i++) {
if (arr[i] === val) {
return true;
}
}
return false;
}
function impl(parent, generator) {
var par = new parent();
var pub;
pub = generator.call(par);
for (var name in pub) {
if (pub.hasOwnProperty(name)) {
par[name] = pub[name];
}
}
return par;
}
// String functions
function toCamelCase(string) {
return string.replace(new RegExp('_(\\w)', 'g'), function(text, letter) {
return letter.toUpperCase();
});
}
function toUnderscore(string) {
return string.replace(new RegExp('([A-Z])', 'g'), function(text, letter) {
return '_' + letter.toLowerCase();
});
}
function linkifyText(string){
if (string) {
string = string.replace(
/((https?\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/gi,
function(url){
var full_url = url;
if (!full_url.match('^https?:\/\/')) {
full_url = 'http://' + full_url;
}
return '<a target="_blank" href="' + full_url + '">' + url.substring(0, Math.min(full_url.length, 20)) + '...</a>';
});
}
return string;
}
function trimText(string) {
return $.trim(string);
}
function truncateText(string, nMaxChars) {
if (string.length <= nMaxChars)
return string;
var xMaxFit = nMaxChars - 3;
var xTruncateAt = string.lastIndexOf(' ', xMaxFit);
if (xTruncateAt == -1 || xTruncateAt < nMaxChars / 2)
xTruncateAt = xMaxFit;
return string.substr(0, xTruncateAt) + "...";
}
function stripHtml(html) {
return html.replace(/<.*?>/g, '');
}
// Browser/feature detection functions
function isIOS() {
return ((navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)));
}
function isAndroid() {
var ua = navigator.userAgent.toLowerCase();
return ua.indexOf("android") > -1;
}
function isSafari() {
return ($.browser.webkit && !(/chrome/.test(navigator.userAgent.toLowerCase())));
}
function isTouchDevice() {
return ('ontouchstart' in window);
}
function isSmallScreen() {
if (!window.orientation) return false;
if (window.orientation === 0) { // portrait
return screen.width < 400;
} else { // landscape
return screen.height < 400;
}
}
// Window & DOM functions
function changePage(url) {
window.location.href = url;
}
function getUrlParam(name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regexS = "[\\?&]"+name+"=([^&#]*)";
var regex = new RegExp( regexS );
var results = regex.exec(unescape(window.location.href));
if( results === null )
return null;
else
return results[1];
}
function getHashParam(name) {
var regexS = name + "=([^&#]*)";
var regex = new RegExp(regexS);
var splitUrl = unescape(window.location.href).split('#');
var hash = ((splitUrl.length > 1) && splitUrl[1]) || '';
var results = regex.exec(hash);
if( results === null )
return null;
else
return results[1];
}
function changeHashParam(name, value) {
var regexS = name + "=([^&#]*)";
var regex = new RegExp(regexS);
var hash = window.location.hash;
var results = regex.exec(hash);
if (getHashParam(name)) {
window.location.hash = window.location.hash.replace(regex, name + '=' + value);
} else {
if (window.location.hash.indexOf('=') > -1) {
window.location.hash += '&';
}
window.location.hash += name + '=' + value;
}
}
function getBrowserInfo() {
if (window.device) {
return device.name + ' | ' + device.phonegap + ' | ' + device.platform + ' | ' + device.uuid + ' | ' + device.version;
} else {
return navigator.userAgent;
}
}
// Abstract on top of Zepto/jQuery differences
function isVisible(elem) {
if ($(elem).isVisible) {
return $(elem).isVisible();
} else {
return $(elem).is(':visible');
}
}
function inView(elem, nearThreshold) {
var viewportHeight = getViewportHeight();
var scrollTop = (document.documentElement.scrollTop ?
document.documentElement.scrollTop :
document.body.scrollTop);
var elemTop = elem.offset().top;
var elemHeight = elem.height();
nearThreshold = nearThreshold || 0;
if ((scrollTop + viewportHeight + nearThreshold) > (elemTop + elemHeight)) {
return true;
}
return false;
}
function getViewportHeight() {
var height = window.innerHeight; // Safari, Opera
var mode = document.compatMode;
if ( (mode || !$.support.boxModel) ) { // IE, Gecko
height = (mode == 'CSS1Compat') ?
document.documentElement.clientHeight : // Standards
document.body.clientHeight; // Quirks
}
return height;
}
function resetScroll(top) {
top = top || 0;
$(document).scrollTop(top);
window.setTimeout(function() {
$(document).scrollTop(top);
}, 10);
}
function detectHash() {
function maybeScrollToHash() {
if (window.location.hash && $(window.location.hash).length) {
var newTop = $(window.location.hash).offset().top - 40;
$(window).scrollTop(newTop);
}
}
$(window).bind('hashchange', function() {
maybeScrollToHash();
});
maybeScrollToHash();
}
function putCursorAtEnd(textarea) {
$(textarea).focus();
if (textarea.setSelectionRange) {
// ... then use it
// (Doesn't work in IE)
// Double the length because Opera is inconsistent about whether a carriage return is one character or two. Sigh.
var len = $(textarea).val().length * 2;
textarea.setSelectionRange(len, len);
} else {
// ... otherwise replace the contents with itself
// (Doesn't work in Google Chrome)
$(textarea).val($(textarea).val());
}
// Scroll to the bottom, in case we're in a tall textarea
// (Necessary for Firefox and Google Chrome)
textarea.scrollTop = 999999;
}
// Time and date functionality
function toDateObject(shortDate) {
var splitDate = shortDate.split('/');
if (splitDate.length != 3) return;
var month = parseInt(splitDate[0], 10) - 1;
var day = parseInt(splitDate[1], 10);
var year = parseInt(splitDate[2], 10);
var date = new Date(year, month, day);
return date;
}
function toShortWeekday(date) {
var days = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
return days[date.getDay()];
}
function toShortDate(date) {
if (typeof date == 'string') date = new Date(date);
if (!date) date = new Date();
return date.format('mm/dd/yyyy'); // or 'shortDate'
}
function toLongDate(date) {
if (date && typeof date == 'string') date = new Date(date);
if (!date) date = new Date();
return date.format('dddd, mmm. d, yyyy'); // or 'fullDate'
}
function toISODate(date) {
function pad(n) {
return n < 10 ? '0' + n : n;
}
return date.getUTCFullYear() + '-' +
pad(date.getUTCMonth()+1) + '-' +
pad(date.getUTCDate()) + 'T' +
pad(date.getUTCHours()) + ':' +
pad(date.getUTCMinutes()) + ':' +
pad(date.getUTCSeconds()) + 'Z';
}
// Inclusive
function getDatesBetween(oldestDate, newestDate) {
var allDates = [];
var currentDate = new Date(oldestDate.getTime());
var num = 0;
while (currentDate <= newestDate) {
allDates.push(toShortDate(currentDate));
currentDate.setDate(currentDate.getDate()+1);
num++;
}
return allDates;
}
function getDatesSince(oldestDate) {
var allDates = [];
var today = new Date();
var currentDate = new Date(oldestDate.getTime());
var num = 0;
while (currentDate < today) {
allDates.push(toShortDate(currentDate));
currentDate.setTime(currentDate.getTime()+(1*24*60*60*1000));
num++;
}
return allDates;
}
function getCurrentTime() {
var nowTime = new Date();
var timeHours = nowTime.getHours();
var timeMinutes = ':00';
if (nowTime.getMinutes() > 15) {
if (nowTime.getMinutes() < 45) {
timeMinutes = ':30';
} else {
timeHours += 1;
}
}
var timeSuffix = 'am';
if (timeHours >= 12) {
timeSuffix = 'pm';
}
if (timeHours > 12) {
timeHours = timeHours - 12;
}
if (timeHours === 0) timeHours = 12;
return {time: (timeHours + timeMinutes), suffix: timeSuffix};
}
function renderTemplate(templateId, data) {
var template;
var html;
templateId = templateId && templateId.replace('#', '');
if (!document.getElementById(templateId)) {
log('Could not find template ' + templateId);
return '';
}
if (window.JsViews) {
template = window.JsViews.template(templateId, document.getElementById(templateId).innerHTML);
html = window.JsViews.render(data || {}, templateId);
} else {
template = $.template(templateId, document.getElementById(templateId).innerHTML);
html = $.render(data || {}, templateId);
}
loadVisibleImages();
return html;
}
function useTouchEvents() {
if (isTouchDevice()) {
if (isAndroid()) return true;
if (isIOS()) return false;
}
return false;
}
function triggerClick(dom) {
if (useTouchEvents()) {
dom.trigger('tap');
} else {
dom.trigger('click');
}
}
ISTOUCHING = false;
function addTouchOrClickHandler(dom, callback, logThis) {
function logAction(message, dom) {
log(message + ' on ' + $(dom).text().replace('\n', ''));
}
if (useTouchEvents()) {
dom.each(function() {
$(this).unbind('tap', callback);
$(this).bind('tap', callback);
$(this).bind('touchstart', function(e) {
e.preventDefault();
//e.stopPropagation();
var item = e.currentTarget;
if (ISTOUCHING) return;
item.moved = false;
ISTOUCHING = true;
item.startX = e.touches[0].pageX;
item.startY = e.touches[0].pageY;
$(item).addClass('active');
});
$(this).bind('touchmove', function(e) {
var item = e.currentTarget;
if (Math.abs(e.touches[0].pageX - item.startX) > 10 ||
Math.abs(e.touches[0].pageY - item.startY) > 10) {
item.moved = true;
$(item).removeClass('active');
}
});
$(this).bind('touchend', function(e) {
var item = e.currentTarget;
ISTOUCHING = false;
if(!item.moved) {
//e.stopPropagation();
//e.preventDefault();
$(item).trigger('tap');
}
setTimeout(function() {
$(item).removeClass('active');
}, 1000);
delete item.moved;
});
});
} else {
dom.unbind('click', callback).bind('click', callback);
}
}
function addClickHandler(dom, callback) {
dom.unbind('click', callback).bind('click', callback);
}
function addWindowScrollHandler(callback) {
$(window).off('scroll', callback).on('scroll', $.throttle(500, callback));
}
function enableElement(dom) {
$(dom).attr('disabled', false).removeAttr('disabled');
}
function disableElement(dom) {
$(dom).attr('disabled', 'disabled');
}
function showModal(dom) {
if ($(dom).modal) {
$(dom).modal('show');
}
}
function hideModal(dom) {
if ($(dom).modal) {
$(dom).modal('hide');
}
}
function addModalShowHandler(dom, callback) {
$(dom).unbind('shown', callback).bind('shown', callback);
}
function addModalHideHandler(dom, callback) {
$(dom).unbind('hidden', callback).bind('hidden', callback);
}
function loadVisibleImages() {
$('img').each(function() {
if (this.src === '' && $(this).attr('data-src') && ED.util.isVisible($(this))) {
if (ED.util.inView($(this), 500)) {
this.src = $(this).attr('data-src');
}
}
});
}
// Logging
var allLogs = [];
function log(something) {
// Store
var storedSomething = something;
if (window.JSON) {
storedSomething = JSON.stringify(something);
}
storedSomething = 'LOG @ ' + new Date().toString() + ': ' + truncateText(storedSomething, 200);
allLogs.push(storedSomething);
$('#mobile-feedback-logs').html(allLogs.reverse().join('<br>'));
// Output
if (window.console) {
if (something instanceof Date) {
something = something.toDateString();
}
if (isIOS() || isAndroid()) {
if (typeof something == 'object') {
something = JSON.stringify(something);
}
something = truncateText(something, 2000);
something = '\nLOG: ' + something;
var stacktrace = '';
if (window.printStackTrace) {
try {
stacktrace = '\n -' + printStackTrace().slice(4).join('\n -');
something += '\nSTACKTRACE:' + stacktrace;
} catch(e) {}
}
if (isIOS()) {
//alert(something);
console.log(something);
} else {
console.log(something);
}
if ($('#logs-viewer').length) {
$('#logs-viewer').prepend(something.replace(/\n/g, '<br>') + '<br>');
}
} else {
console.log(something);
}
}
}
function getLogs() {
return allLogs;
}
var timedEvents = [];
function timeEvent(name) {
timedEvents.push({'name': name || 'unnamed', time: Date.now()});
}
function showTimedEvents() {
var timeText = '';
var lastTime = null;
for (var i = 0; i < timedEvents.length; i++) {
var timedEvent = timedEvents[i];
timeText += 'Event ' + timedEvent.name + ': ' + timedEvent.time;
if (lastTime) timeText += timedEvent.time - lastTime.time + ' after';
timeText += '\\n';
}
log(timeText);
}
return {
// Data structures
each: each,
keys: keys,
values: values,
inArray: inArray,
impl: impl,
// Strings
toCamelCase: toCamelCase,
toUnderscore: toUnderscore,
truncateText: truncateText,
linkifyText: linkifyText,
stripHtml: stripHtml,
trimText: trimText,
// Dates
toLongDate: toLongDate,
toShortDate: toShortDate,
toISODate: toISODate,
toShortWeekday: toShortWeekday,
toDateObject: toDateObject,
getDatesSince: getDatesSince,
getDatesBetween: getDatesBetween,
getCurrentTime: getCurrentTime,
// Window
isAndroid: isAndroid,
isSafari: isSafari,
isIOS: isIOS,
isSmallScreen: isSmallScreen,
isTouchDevice: isTouchDevice,
changePage: changePage,
getUrlParam: getUrlParam,
getHashParam: getHashParam,
changeHashParam: changeHashParam,
getBrowserInfo: getBrowserInfo,
// DOM
inView: inView,
isVisible: isVisible,
detectHash: detectHash,
resetScroll: resetScroll,
putCursorAtEnd: putCursorAtEnd,
renderTemplate: renderTemplate,
addClickHandler: addClickHandler,
addTouchOrClickHandler: addTouchOrClickHandler,
addWindowScrollHandler: addWindowScrollHandler,
enableElement: enableElement,
disableElement: disableElement,
triggerClick: triggerClick,
showModal: showModal,
hideModal: hideModal,
addModalShowHandler: addModalShowHandler,
addModalHideHandler: addModalHideHandler,
loadVisibleImages: loadVisibleImages,
// Logging
log: log,
getLogs: getLogs,
timeEvent: timeEvent,
showTimedEvents: showTimedEvents
};
})();