|
|
@@ -0,0 +1,639 @@ |
|
|
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 |
|
|
}; |
|
|
})(); |