Skip to content

Instantly share code, notes, and snippets.

@pamelafox
Created April 13, 2012 17:44
Show Gist options
  • Select an option

  • Save pamelafox/2378697 to your computer and use it in GitHub Desktop.

Select an option

Save pamelafox/2378697 to your computer and use it in GitHub Desktop.

Revisions

  1. pamelafox revised this gist Apr 13, 2012. No changes.
  2. pamelafox created this gist Apr 13, 2012.
    639 changes: 639 additions & 0 deletions util.js
    Original file line number Diff line number Diff line change
    @@ -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
    };
    })();