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 '' + url.substring(0, Math.min(full_url.length, 20)) + '...'; }); } 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('
')); // 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, '
') + '
'); } } 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 }; })();