examinedliving
10/11/2013 - 1:19 PM

util.js

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
  };
})();