Source: basiq.utils.js

/**
 * @module basiq.utils
 * @version 1.0
 * @authors: @smorcuend, @jmanuel_rosa
 * @title basiq (Basic JS library)
 * @description  Prototyping without query selector section
 */
'use strict';

/** @namespace */
var $ = require('./basiq.core');

/**
 * Extend the Basiq object.
 * Merge the contents of an object onto the basiq prototype to provide new basiq instance methods.
 * params[0] = target object | boolean (deep copy)
 */
$.extend = function(objTarget, objExtra) {
  var source = {};
  var obj = objTarget;
  if (objExtra) {
    source = objTarget;
    obj = objExtra;
  }
  for (var prop in obj) {
    if (obj.hasOwnProperty(prop)) {
      source[prop] = obj[prop];
    }
  }
  return source;
};

/**
 * [clone description]
 * @date        2014-07-07
 * @anotherdate 2014-07-07T14:57:43+0100
 * @param       {Object}                 obj [description]
 * @return      {Object}                     [description]
 */
$.clone = function(obj) {
  // if (!$.isPlainObject(obj)) return obj;

  function cloneArrayDeeply() {
    var target = [];
    obj.forEach(function(item) {
      target.push($.clone(item));
    });
    return target;
  }
  if (Array.isArray(obj)) {
    return cloneArrayDeeply();
  }
  return $.extend(obj, {});
};

/**
 * [isPlainObject description]
 * @date        2014-07-07
 * @anotherdate 2014-07-07T14:57:49+0100
 * @param       {Object}                   [description]
 * @return      {Boolean}                  [description]
 */
$.isPlainObject = function(o) {
  return Object(o) === o && Object.getPrototypeOf(o) === Object.prototype;
};

/**
 * [resize event with propagation control]
 */
$.resize = function(callback) {

  var _resizeThrottler = function() {
    // ignore resize events as long as an actualResizeHandler execution is in the queue
    if (!resizeTimeout) {
      resizeTimeout = setTimeout(function() {
        resizeTimeout = null;
        callback();
      }, 500); // The actualResizeHandler will execute at a rate of 2fps (1/timer * 1000)
    }
  };
  window.addEventListener('resize', _resizeThrottler, false);
  var resizeTimeout;

};

/*
 * Generate custom events - Modern browsers
 * @returns {Event}
 */
$.customEvent = function(name, data) {
  //old-fashioned way - For old versions of IE
  if (!document.createEvent) {
    // Create the event
    var event = document.createEvent('Event');
    // Define that the event name is 'build'.
    event.initEvent('build', true, true);
    return event;
  } else {
    return (!data) ? new Event(name) : new CustomEvent(name, data);
  }
};

/**
 * Check Support for some functionality or property
 * @date        2014-04-16
 * @anotherdate 2014-04-16T13:21:07+0100
 * @return      {boolean}
 */
$.supports = (function() {
  var div = document.createElement('div'),
    vendors = 'Ms Moz Webkit'.split(' '),
    len = vendors.length;

  return function(prop) {
    if (prop in div.style) {
      return true;
    }
    prop = prop.replace(/^[a-z]/, function(val) {
      return val.toUpperCase();
    });

    var lenCheck = len;

    while (lenCheck--) {
      if (vendors[lenCheck] + prop in div.style) {
        return true;
      }
    }
    return false;
  };
})();

/* Network functions:
 * ajax ( url, type, data, async, arrayHeaders )
 * @returns Promise
 */
$.ajax = function(url, type, data, async, arrayHeaders, responseType) {

  // Return a new promise.
  return new Promise(function(resolve, reject) {

    try {

      var req = new XMLHttpRequest();
      async = !async ? true: async;
      type = !type ? 'GET' : type;
      req.open(type, url, async);

      if (responseType) {
        req.responseType = responseType;
      } else {
        req.responseType = 'text';
      }

      req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
      if (arrayHeaders) {
        for (var item in arrayHeaders) {
          req.setRequestHeader(item, arrayHeaders[item]);
        }
      }

      //In order to include cookies as part of the request, you need to set the XHR’s .withCredentials property to true
      if ('withCredentials' in req) {
        req.withCredentials = true;
      }

      req.onload = function() {
        //Check the status
        if (req.status === 200) {

          // var dataParsed = null;

          // if (req.responseType !== 'json' && req.getResponseHeader('Content-Type') === 'application/json') {
          //     dataParsed = JSON.parse(req.response);
          // } else {
          //     dataParsed = req.response;
          // }
          //resolve(dataParsed);

          resolve(req);

        } else {
          reject(req);
        }

      };
      // Handle network errors
      req.onerror = function() {
        reject(Error('Network Error'));
      };

      // Make the request
      if (data) {
        req.send(data);
      } else {
        req.send();
      }

    } catch (e) {
      throw new Error(e);
    }

  });

};

/*
 * runLater -> Force event added to the browser bucket event
 * @returns {callback}
 */
$.runLater = function(callback, delay) {
  if (!callback) {
    return false;
  }
  setTimeout(callback, delay || 0);
};

/* Base64 function
 * @returns {log}
 */
$.base64 = {
  encode: function(string) {
    window.btoa(window.unescape(encodeURIComponent(string)));
  },
  decode: function(string, mode) {
    if (mode === 'url') {
      // var modulus = string.length % 4;
      switch (string) {
        case 1:
          throw new Error('$.base64: Invalid token');
        case 2:
          string += '=';
          break;
        case 3:
          string += '=';
          break;
        default:
          break;
      }
    }
    return JSON.parse(decodeURIComponent(window.escape(window.atob(string))));
  }
};

/**
 *
 */
/* addStylesheetRules([
  ['h2', // Also accepts a second argument as an array of arrays instead
    ['color', 'red'],
    ['background-color', 'green', true] // 'true' for !important rules
  ],
  ['.myClass',
    ['background-color', 'yellow']
  ]
]);
type: false, internal
*/
$.addStylesheetRules = function(rules, dom, type, identifier) {

  function _insertContent(styleEl, rules) {
    if (typeof rules === 'string') {
      styleEl.innerHTML = rules;
      return;
    }
    //var s = styleEl.sheet;
    styleEl.innerHTML = '';
    for (var i = 0, rl = rules.length; i < rl; i++) {
      var j = 1,
        rule = rules[i],
        selector = rule[0],
        propStr = '';
      // If the second argument of a rule is an array of arrays, correct our variables.
      // if (Object.prototype.toString.call(rule[1][0]) === '[object Array]') {
      //     rule = rule[1];
      //     j = 0;
      // }
      for (var pl = rule.length; j < pl; j++) {
        var prop = rule[j];
        //s.insertRule(selector, prop[0] + ':' + prop[1] + (prop[2] ? ' !important' : ''), 0);
        propStr += prop[0] + ':' + prop[1] + (prop[2] ? ' !important' : '') + ';\n';
      }
      styleEl.innerHTML += selector + ' {' + propStr + '}\n';
      //s.insertRule(selector + ' {' + propStr + '} ', 0);
    }

  }

  if (undefined !== type && 'DOM' !== type && 'internal' !== type) {
    return false;
  }

  if (undefined === type || 'internal' === type) {
    var doc = dom || document;
    var styleEl = null;
    if ($(identifier, doc).length === 0) {
      styleEl = doc.createElement('style');
      styleEl.type = 'text/css';
      styleEl.id = identifier;
      doc.head.appendChild(styleEl);
      // Apparently some version of Safari needs the following line? I dunno.
      //styleEl.appendChild(doc.createTextNode(''));
      _insertContent(styleEl, rules);
      return true;
    } else {
      return false;
    }
  }

  return false;

};

/**
 * undefined: Se devuelve cuando los argumentos son incorrectos
 * false: Cuando no se cumplen las condiciones necesarias de los métodos
 * true: todo ha ido de forma correcta
 */
$.storage = {
  set: function(key, data) {
    // Los argumentos son obligatorios
    if (undefined === key || 0 === key.length || undefined === data || 0 === data.length) {
      return undefined;
    }

    // Si la clave ya existía, avisa de ello devolviendo falso
    if (false === this.get(key)) {
      return false;
    }

    localStorage.setItem(key, data);
    return true;
  },
  get: function(key) {
    if (undefined === key) {
      return undefined;
    }

    return localStorage.getItem(key);
  },
  remove: function(key) {
    if (undefined === key || 0 === key.length) {
      return undefined;
    }
    if (false === this.get(key)) {
      return false;
    }

    localStorage.removeItem(key);
    return true;
  },
  modify: function(key, data) {
    if (undefined === key || 0 === key.length || undefined === data || 0 === data.length) {
      return undefined;
    }

    localStorage.setItem(key, data);
    return true;
  }
};

/**
 * Find deeply nested objects by attrs = {property: value}
 */
$.findProp = function(items, attrs) {
  function match(value) {
    for (var key in attrs) {
      if (attrs[key] !== value[key]) {
        return false;
      }
    }
    return true;
  }

  function traverse(val) {
    var result;
    val.forEach(function(val) {
      if (match(val)) {
        result = val;
        return false;
      }
      if (val instanceof Object || Array.isArray(val)) {
        result = traverse(val);
      }
      if (result) {
        return false;
      }
    });

    return result;
  }
  return traverse(items);
};

/**
 * [contentLoaded description]
 * @param  {Window}   win [description]
 * @param  {Function} fn  [description]
 * @return {void}       [description]
 */
$.contentLoaded = function(win, fn) {

  var done = false,
    top = true,

    doc = win.document,
    root = doc.documentElement,

    add = doc.addEventListener ? 'addEventListener' : 'attachEvent',
    rem = doc.addEventListener ? 'removeEventListener' : 'detachEvent',
    pre = doc.addEventListener ? '' : 'on',

    init = function(e) {
      if (e.type === 'readystatechange' && doc.readyState !== 'complete') {
        return;
      }
      (e.type === 'load' ? win : doc)[rem](pre + e.type, init, false);
      if (!done && (done = true)) {
        fn.call(win, e.type || e);
      }
    },

    poll = function() {
      try {
        root.doScroll('left');
      } catch (e) {
        setTimeout(poll, 50);
        return;
      }
      init('poll');
    };

  if (doc.readyState === 'complete') {
    fn.call(win, 'lazy');
  } else {
    if (doc.createEventObject && root.doScroll) {
      try {
        top = !win.frameElement;
      } catch (e) {}
      if (top) {
        poll();
      }
    }
    doc[add](pre + 'DOMContentLoaded', init, false);
    doc[add](pre + 'readystatechange', init, false);
    win[add](pre + 'load', init, false);
  }

};

/**
 * [getObjectType description]
 * @date        2014-07-04
 * @anotherdate 2014-07-04T13:14:58+0100
 * @param       {Object}                 obj [description]
 * @return      {String}                     [description]
 */
$.getObjectType = function(obj) {
  return Object.prototype.toString.call(obj);
};

/**
 * [getIndex description]
 * @date        2014-07-22
 * @anotherdate 2014-07-22T14:53:39+0100
 * @param       {NodeObject}                 node [description]
 * @return      {int}                      [description]
 */
$.getIndex = function(node) {
  var n = 0;
  node = node.previousSibling;
  while (node) {
    node = node.previousSibling;
    n++;
  }
  return n;
};

/**
 * [getNodeFromIndex description]
 * @date        2014-07-23
 * @anotherdate 2014-07-23T11:28:19+0100
 * @param       {NodeObject}                 parentNode [description]
 * @param       {int}                 index      [number [1-length]]
 * @return      {Node}                            [return node]
 */
$.getNodeFromIndex = function(parentNode, index) {
  return parentNode.childNodes[index - 1];
};