Sabre Spark

BaseComponent

The base class for Spark JS components. Contains methods used across multiple components.

Option name Type Description
module components/base.js

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.

Option name Type Description
var BaseComponent = {

requestAnimationFrame

property
 requestAnimationFrame 

Request animation frame

Option name Type Description
requestAnimationFrame: raf,

cancelAnimationFrame

property
 cancelAnimationFrame 

Cancel anmation frame

Option name Type Description
cancelAnimationFrame: caf,

breakpoints

property
 breakpoints 

Breakpoints being used in the CSS.

Option name Type Description
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() 

Export as a jQuery plugin.

Option name Type Description
Obj Mixed
name String
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() 

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().

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

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.

Option name Type Description
params Object
force Boolean

Force setting even if the param is not whitelisted.

setParams: function(params, force) {

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

unsetParams

method
 unsetParams() 

Unset all parameters.

Option name Type Description
keys Array, Object
scope Object

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

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

Toggle a class on an element given a condition.

Option name Type Description
el Element, Array

An element or array of elements to update.

name String
enable Boolean
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() 

Add a class on an element.

Option name Type Description
el Element, Array

An element or array of elements to update.

name String
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() 

Remove a class on an element.

Option name Type Description
el Element, Array

An element or array of elements to update.

name String
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() 

See if an element has a class.

Option name Type Description
el Element, String
name String
return Boolean
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() 

Get the index of a child element.

Option name Type Description
nodes NodeList

A nodelist.

child Element

A node.

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

elementHasParent

method
 elementHasParent() 

See if an element has another element for a parent.

Option name Type Description
child Element
possibleParent Element
return Boolean
elementHasParent: function(child, possibleParent) {

  var parent = child.parentNode;

  while (parent) {

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

    parent = parent.parentNode;
  }

  return false;
},

elementMatches

method
 elementMatches() 

See if an element matches a query selector.

Option name Type Description
el Element
query String
return Boolean
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() 

See if an element has another element for a parent.

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

The last element we should check.

return Boolean, Element
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() 

See if an element has parents which match a query.

Option name Type Description
parent Element
query String
limitEl Element

The last element we should check.

return Boolean, Array
getElementMatchingParents: function(parent, query, limitEl) {

  var list = [];

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

  return list;
},

getElementOffset

method
 getElementOffset() 

Get the offset position of the element.

Option name Type Description
el Element
lessScroll Boolean

Subtract the scroll value

return Object
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() 

Get the index of an element in a nodelist.

Option name Type Description
els NodeList
el Node
getNodeListIndex: function(els, el) {
  return Array.prototype.indexOf.call(els, el);
},

trim

method
 trim() 

Trim whitespace on a string.

Option name Type Description
str String
trim: function(str) {
  return str.replace(trimRE, '');
},

round

method
 round() 

Round to a given number of decimal places.

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

appendChildren

method
 appendChildren() 

Append an array of children to a node.

Option name Type Description
el Element
children Array
empty Boolean

Empty the node before adding children?

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

Insert an array of elements before a node.

Option name Type Description
el Element
beforeEl Element
children Array
insertBefore: function(el, beforeEl, children) {

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

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

getBreakpoint

method
 getBreakpoint() 

Find the active breakpoint.

Option name Type Description
width Number
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() 

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

Option name Type Description
leaveElement Boolean

Leave the element intact.

remove: function(leaveElement) {

  (this._removeEventListeners || noop)();

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

  this._unsetParams(this.defaults);
},

triggerEvent

method
 triggerEvent() 

Trigger a DOM event on an element.

Option name Type Description
el Element
name String
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() 

Scroll the window to a specific element or position.

Option name Type Description
params Object
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() 

Tween from one value to another.

Option name Type Description
params Object
return Long
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() 

Copy all of the attributes from one element to another.

Option name Type Description
a Element
b Element
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() 

Wrap an element with another element

Option name Type Description
el Element
wrapper Element
return 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() 

Create a range of numbers.

Option name Type Description
start Number
stop Number
step Number

Optional

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

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

Option name Type Description
el Element
query String
return Element, Null
getSiblingBefore: function(el, query) {

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

  return null;
},

getSiblingAfter

method
 getSiblingAfter() 

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

Option name Type Description
el Element
query String
return Element, Null
getSiblingAfter: function(el, query) {

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

  return null;
},

getMatchingChild

method
 getMatchingChild() 

Get a child that matches the selector.

Option name Type Description
el Element
query String
return Element, Null
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() 

See if an element has children which match a query.

Option name Type Description
el Element
query String
return List
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;
}));