kotarok
1/23/2017 - 12:34 PM

Array based tiny jQuery subset alternative with minimum number of methods + useful functionalities. Less than 1kb gzipped.

Array based tiny jQuery subset alternative with minimum number of methods + useful functionalities. Less than 1kb gzipped.

!function(a){"use strict";var b=function(a){return a=a.replace(/^\s+/,""),a.replace(/[\W]+([a-z0-9])(\w*)/gi,function(a,b,c){return b.toUpperCase()+c})},c=function(a,b){return"function"==typeof b?new d(a).forEach(function(a,c){b(a,c)}):new d(a)},d=function(a){return a?this.push(a):this},e=c.fn=d.prototype=[];e.item=function(a){return c(this[a])},e.find=function(a){var b=new d;return this.forEach(function(c){b.push(c.querySelectorAll(a))}),b},e.push=function(b){var c=[];return b instanceof HTMLElement||b===a||b===document?c=[b]:"string"==typeof b?[].forEach.call(document.querySelectorAll(b),function(a){c.push(a)}):c=b,c.forEach(function(a){[].push.call(this,a)}.bind(this)),this},e.delayEach=function(a,b){var c=0;if(b)var d=setInterval(function(){a(this[c],c),c++,c>=this.length&&clearInterval(d)}.bind(this),b);else this.forEach(function(b){a(b)});return this},e.oprtClass_=function(a,b){b=[].slice.call(b);var c="number"==typeof b[b.length-1]?b.pop():0;return this.delayEach(function(c){c.classList[a].apply(c.classList,b)},c),this},e.addClass=function(){return this.oprtClass_("add",arguments)},e.removeClass=function(){return this.oprtClass_("remove",arguments)},e.toggleClass=function(a,b,c){return"number"==typeof b&&(c=b,b=void 0),this.oprtClass_("toggle",[a,b,c])},e.hasClass=function(a){return this[0].classList.contains(a)};var f=function(a,b,c,d){b.split(/,\s*/).forEach(function(b){a[c+"EventListener"](b,d)})};e.on=function(a,b){return this.forEach(function(c){f(c,a,"add",b.bind(c))}),this},e.off=function(a,b){return this.forEach(function(c){f(c,a,"remove",b.bind(c))}),this},e.one=function(a,b){return this.forEach(function(c){var e=function(){b.apply(c),f(c,a,"remove",e)};f(c,a,"add",e)}),this},e.defineScrollObserver=function(b,d){var e=d=d||0;return"number"==typeof d&&Math.abs(d)>1?d=function(a,b,c){return c>b.top+e}:"function"!=typeof d&&(d=function(a,b,c){return c*e>b.top}),this.forEach(function(e){c(a).on("load,scroll,resize",function(){var f=c(e).rect(),g=a.innerHeight;if(d(e,f,g)){var h=new Event(b);h.rect=f,h.vh=g,e.dispatchEvent(h)}})}),this},e.parents=function(){for(var a=[],b=this[0];b=b.parentNode;)b instanceof HTMLElement&&a.push(b);return c(a)},e.parent=function(){var a=this[0].parentNode;return a instanceof HTMLElement?c(a):void 0},e.prop=function(a,b){return"string"==typeof b?(this.forEach(function(c){c[a]=b}),this):this[0][a]},e.html=function(a){return void 0!==a?(this.forEach(function(b){b.innerHTML=a}),this):this[0].innderHTML},e.attr=function(a,b){return void 0!==b?(this.forEach(function(c){c.setAttribute(a,b)}),this):this[0].getAttribute(a)},e.data=function(a,c){return a=b(a),void 0!==c?(this.forEach(function(b){b.dataset[a]=c}),this):this[0].dataset[a]},e.val=function(a){return void 0!==a?(this.forEach(function(b){b.value=a}),this):this[0].value?this[0].value:void 0},e.style=function(a,c){return void 0!==c?this.forEach(function(d){d.style[b(a)]=c}):a instanceof Object?this.forEach(function(c){Object.keys(a).forEach(function(d){c.style[b(d)]=a[d]})}):this[0].style[b(a)]},e.insert=function(a,b){return this.forEach(function(c){"string"==typeof b?c.insertAdjacentHTML(a,b):b instanceof HTMLElement&&c.insertAdjacentElement(a,b)}),this},e.remove=function(){this.forEach(function(a){a.parentNode.removeChild(a)})},e.rect=function(){var a=this[0].getBoundingClientRect();return a.x=this[0].offsetLeft,a.y=this[0].offsetTop,a},e.unique=function(){return this.filter(function(a,b,c){return c.indexOf(a)===b})},["reverse","slice","filter"].forEach(function(a){e[a]=function(){return c([][a].apply(this,arguments))}}),a.nq=c}(this);
(function(window) {
  'use strict';

  var camelize = function(str) {
    str = str.replace(/^\s+/, '');
    return str.replace(/[\W]+([a-z0-9])(\w*)/ig, function(match, p1, p2) {
      return p1.toUpperCase() + p2;
    });
  };

  /**
   * Main function to create NqObj object.
   * @global
   * @param  {String} q [description]
   * @param  {Function} f Optional. If it's given, passed to forEach.
   * @return {NqObj}
   */
  var nq = function(q, f) {
    if (typeof f === 'function') {
      return new NqObj(q).forEach(function(el, i) {f(el, i);});
    } else {
      return new NqObj(q);
    }
  };


  /**
   * NqObj
   * @global
   * @constructor
   * @param {String|Array|NodeList|HTMLElement} q Given elements are added to NqObj.
   */
  var NqObj = function(q) {
    return q? this.push(q): this;
  };

  var nqp = nq.fn = NqObj.prototype = [];

  /**
   * Returns new NqObj with specified element. Just like NodeList.item()
   * @param  {Number} i Index number.
   * @return {Object}   New NqObj only has specified element.
   */
  nqp.item = function(i) {
    return nq(this[i]);
  };

  /**
   * Just like jQ.find(). Find matched elements from descendant of original collection.
   * @param {String|Array|NodeList|HTMLElement} q CSS selector or Element or Elements
   * @return {Object}   New NqObj.
   */
  nqp.find = function(q) {
    var newNq = new NqObj();
    this.forEach(function(el) {
      newNq.push(el.querySelectorAll(q));
    });
    return newNq;
  };

  /**
   * Add new elements to collection.
   * @param {Array|NodeList|NqObj|HTMLElement|window|document} q CSS selector or Element or Elements
   * @return {Object}   self
   */
  nqp.push = function(q) {
    var r = [];
    if (q instanceof HTMLElement || q === window || q === document) {
      r = [q];
    } else if (typeof q === 'string') {
      [].forEach.call(document.querySelectorAll(q),function(el){
        r.push(el);
      });
    } else {
      r = q;
    }
    r.forEach(function(el) {
      [].push.call(this, el);
    }.bind(this));
    return this;
  };

  /**
   * delayEach is forEach with interval
   * @param  {Function} f      function to iterate
   * @param  {Number} interval Interval in millisecond
   * @return {Object}          self
   */
  nqp.delayEach = function(f, interval) {
    var i = 0;
    if (interval) {
      var timer = setInterval(function() {
        f(this[i], i);
        i++;
        if (i >= this.length) {
          clearInterval(timer);
        }
      }.bind(this), interval);
    } else {
      this.forEach(function(el) {
        f(el);
      });
    }
    return this;
  };

  /**
   * (methodName, className[, className...][, interval])
   * @private
   * @param  {String} method Method Name
   * @param  {String | Number} args   className[, className...][, interval]
   * @return {Object}        Self
   */
  nqp.oprtClass_ = function(method, args){
    args = [].slice.call(args);
    var interval = (typeof args[args.length - 1] === 'number') ? args.pop() : 0;
    this.delayEach(function(el) {
      el.classList[method].apply(el.classList, args);
    }, interval);
    return this;
  };

  /**
   * Shorthand for HTMLElement##classList#add, and interval
   * @param  {String}  className[, className...][, interval]
   * @return {Object}  Self
   */
  nqp.addClass = function() {
    return this.oprtClass_('add', arguments);
  };

  /**
   * Shorthand for HTMLElement##classList#remove, and interval
   * @param  {String}  className[, className...][, interval]
   * @return {Object}  Self
   */
  nqp.removeClass = function() {
    return this.oprtClass_('remove', arguments);
  };

  /**
   * Shorthand for cHTMLElement#lassList#toggle, and interval
   * @param  {String}  className[, condition function...][, interval]
   * @return {Object}  Self
   */
  nqp.toggleClass = function(className, condition, interval) {
    if (typeof condition === 'number') {
      interval = condition;
      condition = undefined;
    }
    return this.oprtClass_('toggle', [className, condition, interval]);
  };

  /**
   * Shorthand for HTMLElement#classList#contains
   * @param  {String}  className className
   * @return {Object}  Self
   */
  nqp.hasClass = function(className) {
    return this[0].classList.contains(className);
  };

  var treatMultipleEventListner_ = function(el, eventType, listenerType, fn) {
    eventType.split(/,\s*/).forEach(function(e) {
      el[listenerType + 'EventListener'](e, fn);
    });
  };

  /**
   * Shorthand for Element#addEventListener
   * @param  {Event}  e Event type
   * @param  {Function}  f Event handler function
   * @return {Object}  Self
   */
  nqp.on = function(eventType, f) {
    this.forEach(function(el) {
      treatMultipleEventListner_(el, eventType, 'add', f.bind(el));
    });
    return this;
  };

  /**
   * Shorthand for Element#removeEventListener
   * @param  {Event}  e Event type
   * @param  {Function}  f Function to remove
   * @return {Object}  Self
   */
  nqp.off = function(eventType, f) {
    this.forEach(function(el) {
      treatMultipleEventListner_(el, eventType, 'remove', f.bind(el));
    });
    return this;
  };

  nqp.one = function(eventType, f) {
    this.forEach(function(el) {
      var eventName;
      var fnWrapper = function() {
        f.apply(el);
        treatMultipleEventListner_(el, eventType, 'remove', fnWrapper);
      };
      treatMultipleEventListner_(el, eventType, 'add', fnWrapper);
    });
    return this;
  };

  nqp.defineScrollObserver = function(eventName, condition) {
    var offset = condition = condition || 0;

    // var getScrollContainer_ = function(el) {
    //   var scrollContainer = $(el)[0].find(function(el) {
    //     return el.scrollHeight > el.offsetHeight || el.scrollWidth > el.offsetWidth;
    //   });
    //   return (scrollContainer instanceof HTMLBodyElement)? window: scrollContainer;
    // };

    if (typeof condition === 'number' && Math.abs(condition) > 1) {
      condition = function(el, rect, vh) {
        return vh > rect.top + offset;
      };
    } else if (typeof condition !== 'function') {
      condition = function(el, rect, vh) {
        return vh * offset > rect.top;
      };
    }
    this.forEach(function(el) {
      nq(window).on('load,scroll,resize', function() {
        var rect = nq(el).rect(), vh = window.innerHeight;
        if (condition(el, rect, vh)) {
          var e = new Event(eventName);
          e.rect = rect;
          e.vh = vh;
          el.dispatchEvent(e);
        }
      });
    });
    return this;
  };

  nqp.parents = function() {
    var els = [], el = this[0];
    while ((el = el.parentNode)) {
      if(el instanceof HTMLElement){
        els.push(el);
      }
    }
    return nq(els);
  };

  nqp.parent = function() {
    var el = this[0].parentNode;
    return (el instanceof HTMLElement)? nq(el): undefined;
  };

  /**
   * Porperty accessor
   * @param  {String} n Property name
   * @param  {String | Number} v Value to set
   * @return {Object}   Self
   */
  nqp.prop = function(n, v) {
    // this condition statement must check if it's string.
    // Because there's a case passing '' to reset property v.
    if (typeof v === 'string') {
      this.forEach(function(el) {
        el[n] = v;
      });
      return this;
    } else {
      return this[0][n];
    }
  };

  /**
   * Short hand for HTMLElement#innreHTML
   * @param  {String} v HTML source string
   * @return {Object | String}   Self | innerHTML value
   */
  nqp.html = function(v) {
    if (v !== undefined) {
      this.forEach(function(el) {
        el.innerHTML = v;
      });
      return this;
    } else {
      return this[0].innderHTML;
    }
  };

  /**
   * Short hand for HTMLElement#set/getAttribute
   * @param  {String} n Attribute name
   * @param  {String | Number} v Attribute value
   * @return {Object | String}   Self | Attribute value
   */
  nqp.attr = function(n, v) {
    if (v !== undefined) {
      this.forEach(function(el) {
        el.setAttribute(n, v);
      });
      return this;
    } else {
      return this[0].getAttribute(n);
    }
  };

  /**
   * Short hand for HTMLElement#dataset
   * @param  {String} n Data set name
   * @param  {String | Number} v Data content to set
   * @return {Object | String}   Self | Date content
   */
  nqp.data = function(n, v) {
    n = camelize(n);
    if (v !== undefined) {
      this.forEach(function(el) {
        el.dataset[n] = v;
      });
      return this;
    } else {
      return this[0].dataset[n];
    }
  };

  /**
   * Form element value accessor
   * @param  {String | Number} v Form field value
   * @return {Object | String}   Self | Form field value
   */
  nqp.val = function(v) {
    if (v !== undefined) {
      this.forEach(function(el) {
        el.value = v;
      });
      return this;
    } else {
      if (this[0].value) {
        return this[0].value;
      }
    }
  };

  /**
   * Style attribute accessor
   * @param  {String} n CSS property name | Object of CSS rulesets
   * @param  {String | Number} v Form field value
   * @return {Object | String}   Self | Style property value
   */
  nqp.style = function(n, v) {
    if (v !== undefined) {
      return this.forEach(function(el) {
        el.style[camelize(n)] = v;
      });
    } else if (n instanceof Object) {
      return this.forEach(function(el) {
        Object.keys(n).forEach(function(prop) {
          el.style[camelize(prop)] = n[prop];
        });
      });
    } else {
      // return getComputedStyle(this[0])[camelize(n)];
      return this[0].style[camelize(n)];
    }
  };

  /**
   * Shorthand for .insertAdjacentHTML
   * @param  {String} position (beforebegin, afterbegin, beforeend, afterend)
   * @param  {HTMLElement | NqObj} target   Target element
   * @return {[type]}          [description]
   */
  nqp.insert = function(position, insertee) {
    this.forEach(function(el) {
      if (typeof insertee === 'string') {
        el.insertAdjacentHTML(position, insertee);
      } else if (insertee instanceof HTMLElement) {
        el.insertAdjacentElement(position, insertee);
      }
    });
    return this;
  };

  /**
   * Remove elemet themselves
   * @return {undefined}
   */
  nqp.remove = function() {
    this.forEach(function(el) {
      el.parentNode.removeChild(el);
    });
  };

  /**
   * Get DOMRect
   * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIDOMClientRect
   * @return {Object} DOMRect object
   */
  nqp.rect = function() {
    var rect = this[0].getBoundingClientRect();
    rect.x = this[0].offsetLeft;
    rect.y = this[0].offsetTop;
    return rect;
  };

  /**
   * Uniques collection.
   * @return {Object} self
   */
  nqp.unique = function() {
    return this.filter(function(value, index, el) {
      return el.indexOf(value) === index;
    });
  };

  /**
   * Reverse collection order
   * @name reverse
   * @method
   * @public
   * @return {Object} self
   */

  /**
   * Work as Array#slice
   * @name slice
   * @method
   * @public
   * @return {Object} self
   */
  ['reverse','slice', 'filter']
  .forEach(function(m) {
    nqp[m] = function() {
      return nq([][m].apply(this, arguments));
    };
  });

  window.nq = nq;
})(this);