BaseComponent

declaration
 BaseComponent 

BaseComponent object. This isn't a method because it can't be instantiated directly
like other components. No fancy "extends" method or anything because extension happens
happens through composition not inheritance.

var BaseComponent = {

requestAnimationFrame

property
 requestAnimationFrame 

Request animation frame

requestAnimationFrame: raf,

cancelAnimationFrame

property
 cancelAnimationFrame 

Cancel anmation frame

cancelAnimationFrame: caf,

breakpoints

property
 breakpoints 

Breakpoints being used in the CSS.

breakpoints: {
  xs: {
    min: 0,
    max: 543
  },
  sm: {
    min: 544,
    max: 795
  },
  md: {
    min: 796,
    max: 1047
  },
  lg: {
    min: 1048,
    max: 1799
  },
  xl: {
    min: 1800,
    max: Infinity
  }
},

exportjQuery

method
 exportjQuery() 

Option name Type Description
Obj Mixed
name String

Export as a jQuery plugin.

exportjQuery: function(Obj, name) {

  if (!root.jQuery) {
    return;
  }

  var fnName = 'spark' + name;

  root.jQuery.fn[fnName] = function(method, options) {
    return this.each(function() {
      return BaseComponent.loadOrCreateJQueryInstance(fnName, Obj, this, method, options);
    });
  };
},

loadOrCreateJQueryInstance

method
 loadOrCreateJQueryInstance() 

Option name Type Description
fnName String

The name of the jQuery function.

Ctor Function

The constructor

el Element
method String, Object

The method to invoke on the instance. This can be omitted and options will take its place.

options Object
return Object

Load a cached instance of a component for use with jQuery. This way, calling $('...').sparkTextInput()
doesn't create a new instance each time, but rather returns an existing instance (if signatures match).
If the instance doesn't already exist, create it and cache it. To remove a cached instance of a jQuery
plugin, use $('...').removeData().

loadOrCreateJQueryInstance: function(fnName, Ctor, el, method, options) {

  var methodIsString = typeof method === 'string';
  options = methodIsString ? options : method;
  method = methodIsString ? method : null;

  // Find a cached instance
  var dataName = 'spark.' + fnName;
  var $el = $(el);
  var cachedInstance = $el.data(dataName);
  var instance;

  // We have a cached instance
  if (cachedInstance) {
    instance = cachedInstance;
  }
  // Create and cache
  else {
    instance = new Ctor(el, options);
    $el.data(dataName, instance);
  }

  // If we have a method to call, do so.
  if (method) {

    // Pitch a fit if this is a private method.
    if (method[0] === '_') {
      throw new Error('Cannot access private method "' + method + '" on the ' + fnName + ' class.');
    }

    // Fail if this method doesn't exist.
    if (typeof instance[method] !== 'function') {
      throw new Error('No method "' + method + '" is defined on the ' + fnName + ' class.');
    }

    instance[method](options);
  }

  return instance;
},

setParams

method
 setParams() 

Option name Type Description
params Object
force Boolean

Force setting even if the param is not whitelisted.

Set a hash of parameters if they're whitelisted or we're told to force the set.
This is used to set initial values as well as set passed parameters.

setParams: function(params, force) {

  for (var i in params) {
    if (this._whitelistedParams.indexOf(i) !== -1 || force) {
      this[i] = params[i];
    }
  }
},

unsetParams

method
 unsetParams() 

Option name Type Description
keys Array, Object
scope Object

The object to unset the params from. Defaults to this.

Unset all parameters.

unsetParams: function(keys, scope) {

  // If passed an object just get the keys.
  keys = keys instanceof Array ? keys : Object.keys(keys);

  scope = scope || this;

  var i = 0;
  var len = keys.length;

  for (; i < len; i++) {
    delete scope[keys[i]];
  }
},

toggleClass

method
 toggleClass() 

Option name Type Description
el Element, Array

An element or array of elements to update.

name String
enable Boolean

Toggle a class on an element given a condition.

toggleClass: function(el, name, enable) {

  if (!el) {
    return;
  }

  // If we're passed an array, toggle the class on each.
  if (el instanceof NodeList || el instanceof Array) {

    for (var i = 0, len = el.length; i < len; i++) {
      BaseComponent.toggleClass(el[i], name, enable);
    }

    return;
  }

  var action;
  if (enable !== undefined) {
    enable = typeof enable === 'function' ? enable.call(null, el) : enable;
    action = enable ? 'add' : 'remove';
  } else {
    action = BaseComponent.hasClass(el, name) ? 'remove' : 'add';
  }

  return BaseComponent[action + 'Class'](el, name);
},

addClass

method
 addClass() 

Option name Type Description
el Element, Array

An element or array of elements to update.

name String

Add a class on an element.

addClass: function(el, name) {

  if (arguments.length === 2 && typeof name === 'string') {
    name = BaseComponent.trim(name).split(ws);
  } else {
    name = name instanceof Array ? name : Array.prototype.slice.call(arguments, 1);
  }

  // optimize for best, most common case
  if (name.length === 1 && el.classList) {
    if (name[0]) el.classList.add(name[0]);
    return el;
  }

  var toAdd = [];
  var i = 0;
  var l = name.length;
  var item;
  var clsName = typeof el.className === 'string' ? el.className : (el.getAttribute ? el.getAttribute('class') : '');

  // see if we have anything to add
  for (; i < l; i++) {
    item = name[i];
    if (item && !BaseComponent.hasClass(clsName, item)) {
      toAdd.push(item);
    }
  }

  if (toAdd.length) {
    if (typeof el.className === 'string') {
      el.className = BaseComponent.trim((clsName + ' ' + toAdd.join(' ')).replace(cleanup, ' '));
    } else if (el.setAttribute) {
      el.setAttribute('class', BaseComponent.trim((clsName + ' ' + toAdd.join(' ')).replace(cleanup, ' ')));
    }
  }

  return el;
},

removeClass

method
 removeClass() 

Option name Type Description
el Element, Array

An element or array of elements to update.

name String

Remove a class on an element.

removeClass: function(el, name) {

  if (arguments.length === 2 && typeof name === 'string') {
    name = BaseComponent.trim(name).split(ws);
  } else {
    name = name instanceof Array ? name : Array.prototype.slice.call(arguments, 1);
  }

  // optimize for best, most common case
  if (name.length === 1 && el.classList) {
    if (name[0]) el.classList.remove(name[0]);
    return el;
  }

  // store two copies
  var clsName = ' ' + (typeof el.className === 'string' ? el.className : (el.getAttribute ? el.getAttribute('class') : '')) + ' ';
  var result = clsName;
  var current;
  var start;
  for (var i = 0, l = name.length; i < l; i++) {
    current = name[i];
    start = current ? result.indexOf(' ' + current + ' ') : -1;
    if (start !== -1) {
      start += 1;
      result = result.slice(0, start) + result.slice(start + current.length);
    }
  }

  // only write if modified
  if (clsName !== result) {
    if (typeof el.className === 'string') {
      el.className = BaseComponent.trim(result.replace(cleanup, ' '));
    } else if (el.setAttribute) {
      el.setAttribute('class', BaseComponent.trim(result.replace(cleanup, ' ')));
    }
  }

  return el;
},

hasClass

method
 hasClass() 

Option name Type Description
el Element, String
name String
return Boolean

See if an element has a class.

hasClass: function(el, name) {
  var cName = (typeof el === 'object' ? el.className || ((el.getAttribute && el.getAttribute('class')) || '') : el || '').replace(/[\t\r\n\f]/g, ' ');
  return (' ' + cName + ' ').indexOf(' ' + name + ' ') !== -1;
},

getChildIndex

method
 getChildIndex() 

Option name Type Description
nodes NodeList

A nodelist.

child Element

A node.

Get the index of a child element.

getChildIndex: function(nodes, child) {
  return Array.prototype.indexOf.call(nodes, child);
},

elementHasParent

method
 elementHasParent() 

Option name Type Description
child Element
possibleParent Element
return Boolean

See if an element has another element for a parent.

elementHasParent: function(child, possibleParent) {

  var parent = child.parentNode;

  while (parent) {

    if (parent === possibleParent) {
      return true;
    }

    parent = parent.parentNode;
  }

  return false;
},

elementMatches

method
 elementMatches() 

Option name Type Description
el Element
query String
return Boolean

See if an element matches a query selector.

elementMatches: function(el, query) {

  if (vendorMatch) return vendorMatch.call(el, query);

  var nodes = el.parentNode.querySelectorAll(query);

  for (var i = 0; i < nodes.length; i++) {
    if (nodes[i] === el) return true;
  }

  return false;
},

getElementMatchingParent

method
 getElementMatchingParent() 

Option name Type Description
parent Element
query String
limitEl Array, Element

The last element we should check.

return Boolean, Element

See if an element has another element for a parent.

getElementMatchingParent: function(parent, query, limitEl) {

  limitEl = limitEl instanceof Array ? limitEl : [limitEl || document.body];

  while (parent) {

    if (BaseComponent.elementMatches(parent, query)) {
      return parent;
    }

    if (limitEl.indexOf(parent) !== -1) {
      return false;
    }

    parent = parent.parentNode;
  }

  return false;
},

getElementMatchingParents

method
 getElementMatchingParents() 

Option name Type Description
parent Element
query String
limitEl Element

The last element we should check.

return Boolean, Array

See if an element has parents which match a query.

getElementMatchingParents: function(parent, query, limitEl) {

  var list = [];

  while ((parent = BaseComponent.getElementMatchingParent(parent.parentNode, query, limitEl))) {
    list.push(parent);
  }

  return list;
},

getElementOffset

method
 getElementOffset() 

Option name Type Description
el Element
lessScroll Boolean

Subtract the scroll value

return Object

Get the offset position of the element.

getElementOffset: function(el, lessScroll) {

  var rect = {
    top: 0,
    left: 0
  };

  // Native implementation
  if (el.getBoundingClientRect) {

    var bounding = el.getBoundingClientRect();
    rect.left = bounding.left;
    rect.top = bounding.top;

    if (!lessScroll) {
      rect.left += typeof window.scrollX !== 'undefined' ? window.scrollX : window.pageXOffset;
      rect.top += typeof window.scrollY !== 'undefined' ? window.scrollY : window.pageYOffset;
    }
  } else {
    var x = 0,
      y = 0;
    do {
      x += el.offsetLeft + (lessScroll ? el.scrollLeft : 0);
      y += el.offsetTop + (lessScroll ? el.scrollTop : 0);
    }
    while ((el = el.offsetParent));

    rect.left = x;
    rect.top = y;
  }

  return rect;
},

getNodeListIndex

method
 getNodeListIndex() 

Option name Type Description
els NodeList
el Node

Get the index of an element in a nodelist.

getNodeListIndex: function(els, el) {
  return Array.prototype.indexOf.call(els, el);
},

trim

method
 trim() 

Option name Type Description
str String

Trim whitespace on a string.

trim: function(str) {
  return str.replace(trimRE, '');
},

round

method
 round() 

Option name Type Description
num Number
len Number

Round to a given number of decimal places.

round: function(num, len) {
  len = len !== undefined ? len : 2;
  var x = Math.pow(10, len);
  return Math.round(num * x) / x;
},

appendChildren

method
 appendChildren() 

Option name Type Description
el Element
children Array
empty Boolean

Empty the node before adding children?

Append an array of children to a node.

appendChildren: function(el, children, empty) {

  empty = empty === undefined ? false : empty;

  if (empty) {
    el.textContent = '';
  }

  var domList = children instanceof window.HTMLCollection;

  if (domList) {
    while (children.length) {
      el.appendChild(children[0]);
    }
  } else {

    var i = 0;
    var len = children.length;

    for (; i < len; i++) {
      if (children[i]) {
        el.appendChild(children[i]);
      }
    }
  }
},

insertBefore

method
 insertBefore() 

Option name Type Description
el Element
beforeEl Element
children Array

Insert an array of elements before a node.

insertBefore: function(el, beforeEl, children) {

  var i = 0;
  var len = children.length;

  for (; i < len; i++) {
    el.insertBefore(children[i], beforeEl);
  }
},

getBreakpoint

method
 getBreakpoint() 

Option name Type Description
width Number

Find the active breakpoint.

getBreakpoint: function(width, breakpoints) {

  breakpoints = breakpoints || BaseComponent.breakpoints;

  var i;

  for (i in breakpoints) {
    if (width >= breakpoints[i].min && width <= breakpoints[i].max) {
      return i;
    }
  }
},

remove

method
 remove() 

Option name Type Description
leaveElement Boolean

Leave the element intact.

Remove the component from the DOM and prepare for garbage collection by dereferencing values.

remove: function(leaveElement) {

  (this._removeEventListeners || noop)();

  if (!leaveElement && this.el.parentNode) {
    this.el.parentNode.removeChild(this.el);
  }

  this._unsetParams(this.defaults);
},

triggerEvent

method
 triggerEvent() 

Option name Type Description
el Element
name String

Trigger a DOM event on an element.

triggerEvent: function(el, name) {

  var event;

  if (document.createEvent) {
    event = document.createEvent('HTMLEvents');
    event.initEvent(name, true, true);
    event.eventName = name;
    el.dispatchEvent(event);
  } else {
    event = document.createEventObject();
    event.eventType = name;
    event.eventName = name;
    el.fireEvent('on' + event.eventType, event);
  }
},

scrollWindowTo

method
 scrollWindowTo() 

Option name Type Description
params Object

Scroll the window to a specific element or position.

scrollWindowTo: function(params) {

  params = params || {};

  var offset;
  var x;
  var y;

  if (params instanceof HTMLElement) {
    offset = BaseComponent.getElementOffset(params);
    x = offset.left;
    y = offset.top;
    params = arguments[1] || {};
  } else {
    x = params.x || 0;
    y = params.y || 0;
  }

  BaseComponent.tween({
    target: window,
    prop: 'scrollTo',
    start: [window.scrollX, window.scrollY],
    end: [x, y],
    duration: params.duration,
    callback: params.callback
  });
},

tween

method
 tween() 

Option name Type Description
params Object
return Long

Tween from one value to another.

tween: function(params) {

  params = params || {};

  var begin;
  var obj = params.target;

  if (!obj) {
    throw new Error('Cannot tween without a target!');
  }

  var prop = typeof params.prop === 'string' ? [params.prop] : params.prop;
  var start = typeof params.start === 'number' ? [params.start] : params.start;
  var end = typeof params.end === 'number' ? [params.end] : params.end;
  var duration = params.duration || 250;
  var callback = params.callback || noop;

  // Ensure we have the same number of start and end properties.
  if (start.length !== end.length) {
    throw new Error('Cannot tween two different sets of parameters!');
  }

  var f = function(ts) {

    // Keep track of when we start
    if (!begin)
      begin = ts;

    // Progress
    var prog = ts - begin;

    // Percentage complete
    var per = Math.min(prog / duration, 1);

    // Adjust the values for the percentage complete.
    var args = [];
    var i = 0;
    var len = start.length;
    for (; i < len; i++) {
      args[i] = start[i] + ((end[i] - start[i]) * per);
    }

    // Apply the values for each property.
    i = 0;
    len = prop.length;
    var arg;
    for (; i < len; i++) {

      // If this is the last property but we have more arguments, set them all.
      arg = i + 1 === len && args.length - 1 > i ? args.slice(i) : args[i];

      if (typeof obj[prop[i]] === 'function') {
        obj[prop[i]].apply(obj, arg);
      } else {
        obj[prop[i]] = arg;
      }
    }

    // Keep going if we have more to do.
    if (prog < duration)
      raf(f);
    else
      callback();
  };

  return raf(f);
},

copyAttributes

method
 copyAttributes() 

Option name Type Description
a Element
b Element

Copy all of the attributes from one element to another.

copyAttributes: function(a, b) {

  var i = 0;
  var len = a.attributes.length;

  for (; i < len; i++) {
    b.setAttribute(a.attributes[i].name, a.attributes[i].value);
  }
},

wrapElement

method
 wrapElement() 

Option name Type Description
el Element
wrapper Element
return Element

Wrap an element with another element

wrapElement: function(el, wrapper) {
  wrapper = wrapper || document.createElement('div');
  if (el.nextSibling) {
    el.parentNode.insertBefore(wrapper, el.nextSibling);
  } else {
    el.parentNode.appendChild(wrapper);
  }
  return wrapper.appendChild(el);
},

range

method
 range() 

Option name Type Description
start Number
stop Number
step Number

Optional

Create a range of numbers.

range: function(start, stop, step) {
  if (stop == null) {
    stop = start || 0;
    start = 0;
  }
  if (!step) {
    step = stop < start ? -1 : 1;
  }

  var length = Math.max(Math.ceil((stop - start) / step), 0);
  var range = new Array(length);

  for (var idx = 0; idx < length; idx++, start += step) {
    range[idx] = start;
  }

  return range;
},

getSiblingBefore

method
 getSiblingBefore() 

Option name Type Description
el Element
query String
return Element, Null

Get a nearest sibling before the given element which matches
the given query selector.

getSiblingBefore: function(el, query) {

  while ((el = el.previousElementSibling)) {
    if (BaseComponent.elementMatches(el, query)) {
      return el;
    }
  }

  return null;
},

getSiblingAfter

method
 getSiblingAfter() 

Option name Type Description
el Element
query String
return Element, Null

Get a nearest sibling after the given element which matches
the given query selector.

getSiblingAfter: function(el, query) {

  while ((el = el.nextElementSibling)) {
    if (BaseComponent.elementMatches(el, query)) {
      return el;
    }
  }

  return null;
},

getMatchingChild

method
 getMatchingChild() 

Option name Type Description
el Element
query String
return Element, Null

Get a child that matches the selector.

getMatchingChild: function(el, query) {

  var i = 0;
  var len = el.children.length;

  for (; i < len; i++) {
    if (BaseComponent.elementMatches(el.children[i], query)) {
      return el.children[i];
    }
  }

  return null;
},

getElementMatchingChildren

method
 getElementMatchingChildren() 

Option name Type Description
el Element
query String
return List

See if an element has children which match a query.

getElementMatchingChildren: function(el, query) {

  var list = [];
  var i = 0;
  var len = el.children.length;

  for (; i < len; i++) {
    if (BaseComponent.elementMatches(el.children[i], query)) {
      list.push(el.children[i]);
    }
  }

  return list;
},
  };

  return BaseComponent;
}));