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 = {
Request animation frame
requestAnimationFrame: raf,
Cancel anmation frame
cancelAnimationFrame: caf,
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
}
},
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);
});
};
},
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;
},
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];
}
}
},
Option name | Type | Description |
---|---|---|
keys | Array, Object | |
scope | Object | The object to unset the params from. Defaults to |
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]];
}
},
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);
},
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;
},
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;
},
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;
},
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);
},
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;
},
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;
},
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;
},
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;
},
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;
},
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);
},
Option name | Type | Description |
---|---|---|
str | String |
Trim whitespace on a string.
trim: function(str) {
return str.replace(trimRE, '');
},
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;
},
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]);
}
}
}
},
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);
}
},
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;
}
}
},
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);
},
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);
}
},
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
});
},
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);
},
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);
}
},
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);
},
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;
},
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;
},
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;
},
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;
},
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;
}));