'use strict';
|
|
var cssSelect = require('css-select');
|
|
var svgoCssSelectAdapter = require('./css-select-adapter');
|
var cssSelectOpts = {
|
xmlMode: true,
|
adapter: svgoCssSelectAdapter
|
};
|
|
var JSAPI = module.exports = function(data, parentNode) {
|
Object.assign(this, data);
|
if (parentNode) {
|
Object.defineProperty(this, 'parentNode', {
|
writable: true,
|
value: parentNode
|
});
|
}
|
};
|
|
/**
|
* Perform a deep clone of this node.
|
*
|
* @return {Object} element
|
*/
|
JSAPI.prototype.clone = function() {
|
var node = this;
|
var nodeData = {};
|
|
Object.keys(node).forEach(function(key) {
|
if (key !== 'class' && key !== 'style' && key !== 'content') {
|
nodeData[key] = node[key];
|
}
|
});
|
|
// Deep-clone node data.
|
nodeData = JSON.parse(JSON.stringify(nodeData));
|
|
// parentNode gets set to a proper object by the parent clone,
|
// but it needs to be true/false now to do the right thing
|
// in the constructor.
|
var clonedNode = new JSAPI(nodeData, !!node.parentNode);
|
|
if (node.class) {
|
clonedNode.class = node.class.clone(clonedNode);
|
}
|
if (node.style) {
|
clonedNode.style = node.style.clone(clonedNode);
|
}
|
if (node.content) {
|
clonedNode.content = node.content.map(function(childNode) {
|
var clonedChild = childNode.clone();
|
clonedChild.parentNode = clonedNode;
|
return clonedChild;
|
});
|
}
|
|
return clonedNode;
|
};
|
|
/**
|
* Determine if item is an element
|
* (any, with a specific name or in a names array).
|
*
|
* @param {String|Array} [param] element name or names arrays
|
* @return {Boolean}
|
*/
|
JSAPI.prototype.isElem = function(param) {
|
|
if (!param) return !!this.elem;
|
|
if (Array.isArray(param)) return !!this.elem && (param.indexOf(this.elem) > -1);
|
|
return !!this.elem && this.elem === param;
|
|
};
|
|
/**
|
* Renames an element
|
*
|
* @param {String} name new element name
|
* @return {Object} element
|
*/
|
JSAPI.prototype.renameElem = function(name) {
|
|
if (name && typeof name === 'string')
|
this.elem = this.local = name;
|
|
return this;
|
|
};
|
|
/**
|
* Determine if element is empty.
|
*
|
* @return {Boolean}
|
*/
|
JSAPI.prototype.isEmpty = function() {
|
|
return !this.content || !this.content.length;
|
|
};
|
|
/**
|
* Find the closest ancestor of the current element.
|
* @param elemName
|
*
|
* @return {?Object}
|
*/
|
JSAPI.prototype.closestElem = function(elemName) {
|
var elem = this;
|
|
while ((elem = elem.parentNode) && !elem.isElem(elemName));
|
|
return elem;
|
};
|
|
/**
|
* Changes content by removing elements and/or adding new elements.
|
*
|
* @param {Number} start Index at which to start changing the content.
|
* @param {Number} n Number of elements to remove.
|
* @param {Array|Object} [insertion] Elements to add to the content.
|
* @return {Array} Removed elements.
|
*/
|
JSAPI.prototype.spliceContent = function(start, n, insertion) {
|
|
if (arguments.length < 2) return [];
|
|
if (!Array.isArray(insertion))
|
insertion = Array.apply(null, arguments).slice(2);
|
|
insertion.forEach(function(inner) { inner.parentNode = this }, this);
|
|
return this.content.splice.apply(this.content, [start, n].concat(insertion));
|
|
|
};
|
|
/**
|
* Determine if element has an attribute
|
* (any, or by name or by name + value).
|
*
|
* @param {String} [name] attribute name
|
* @param {String} [val] attribute value (will be toString()'ed)
|
* @return {Boolean}
|
*/
|
JSAPI.prototype.hasAttr = function(name, val) {
|
|
if (!this.attrs || !Object.keys(this.attrs).length) return false;
|
|
if (!arguments.length) return !!this.attrs;
|
|
if (val !== undefined) return !!this.attrs[name] && this.attrs[name].value === val.toString();
|
|
return !!this.attrs[name];
|
|
};
|
|
/**
|
* Determine if element has an attribute by local name
|
* (any, or by name or by name + value).
|
*
|
* @param {String} [localName] local attribute name
|
* @param {Number|String|RegExp|Function} [val] attribute value (will be toString()'ed or executed, otherwise ignored)
|
* @return {Boolean}
|
*/
|
JSAPI.prototype.hasAttrLocal = function(localName, val) {
|
|
if (!this.attrs || !Object.keys(this.attrs).length) return false;
|
|
if (!arguments.length) return !!this.attrs;
|
|
var callback;
|
|
switch (val != null && val.constructor && val.constructor.name) {
|
case 'Number': // same as String
|
case 'String': callback = stringValueTest; break;
|
case 'RegExp': callback = regexpValueTest; break;
|
case 'Function': callback = funcValueTest; break;
|
default: callback = nameTest;
|
}
|
return this.someAttr(callback);
|
|
function nameTest(attr) {
|
return attr.local === localName;
|
}
|
|
function stringValueTest(attr) {
|
return attr.local === localName && val == attr.value;
|
}
|
|
function regexpValueTest(attr) {
|
return attr.local === localName && val.test(attr.value);
|
}
|
|
function funcValueTest(attr) {
|
return attr.local === localName && val(attr.value);
|
}
|
|
};
|
|
/**
|
* Get a specific attribute from an element
|
* (by name or name + value).
|
*
|
* @param {String} name attribute name
|
* @param {String} [val] attribute value (will be toString()'ed)
|
* @return {Object|Undefined}
|
*/
|
JSAPI.prototype.attr = function(name, val) {
|
|
if (!this.hasAttr() || !arguments.length) return undefined;
|
|
if (val !== undefined) return this.hasAttr(name, val) ? this.attrs[name] : undefined;
|
|
return this.attrs[name];
|
|
};
|
|
/**
|
* Get computed attribute value from an element
|
*
|
* @param {String} name attribute name
|
* @return {Object|Undefined}
|
*/
|
JSAPI.prototype.computedAttr = function(name, val) {
|
/* jshint eqnull: true */
|
if (!arguments.length) return;
|
|
for (var elem = this; elem && (!elem.hasAttr(name) || !elem.attr(name).value); elem = elem.parentNode);
|
|
if (val != null) {
|
return elem ? elem.hasAttr(name, val) : false;
|
} else if (elem && elem.hasAttr(name)) {
|
return elem.attrs[name].value;
|
}
|
|
};
|
|
/**
|
* Remove a specific attribute.
|
*
|
* @param {String|Array} name attribute name
|
* @param {String} [val] attribute value
|
* @return {Boolean}
|
*/
|
JSAPI.prototype.removeAttr = function(name, val, recursive) {
|
|
if (!arguments.length) return false;
|
|
if (Array.isArray(name)) {
|
name.forEach(this.removeAttr, this);
|
return false;
|
}
|
|
if (!this.hasAttr(name)) return false;
|
|
if (!recursive && val && this.attrs[name].value !== val) return false;
|
|
delete this.attrs[name];
|
|
if (!Object.keys(this.attrs).length) delete this.attrs;
|
|
return true;
|
|
};
|
|
/**
|
* Add attribute.
|
*
|
* @param {Object} [attr={}] attribute object
|
* @return {Object|Boolean} created attribute or false if no attr was passed in
|
*/
|
JSAPI.prototype.addAttr = function(attr) {
|
attr = attr || {};
|
|
if (attr.name === undefined ||
|
attr.prefix === undefined ||
|
attr.local === undefined
|
) return false;
|
|
this.attrs = this.attrs || {};
|
this.attrs[attr.name] = attr;
|
|
if(attr.name === 'class') { // newly added class attribute
|
this.class.hasClass();
|
}
|
|
if(attr.name === 'style') { // newly added style attribute
|
this.style.hasStyle();
|
}
|
|
return this.attrs[attr.name];
|
|
};
|
|
/**
|
* Iterates over all attributes.
|
*
|
* @param {Function} callback callback
|
* @param {Object} [context] callback context
|
* @return {Boolean} false if there are no any attributes
|
*/
|
JSAPI.prototype.eachAttr = function(callback, context) {
|
|
if (!this.hasAttr()) return false;
|
|
for (var name in this.attrs) {
|
callback.call(context, this.attrs[name]);
|
}
|
|
return true;
|
|
};
|
|
/**
|
* Tests whether some attribute passes the test.
|
*
|
* @param {Function} callback callback
|
* @param {Object} [context] callback context
|
* @return {Boolean} false if there are no any attributes
|
*/
|
JSAPI.prototype.someAttr = function(callback, context) {
|
|
if (!this.hasAttr()) return false;
|
|
for (var name in this.attrs) {
|
if (callback.call(context, this.attrs[name])) return true;
|
}
|
|
return false;
|
|
};
|
|
/**
|
* Evaluate a string of CSS selectors against the element and returns matched elements.
|
*
|
* @param {String} selectors CSS selector(s) string
|
* @return {Array} null if no elements matched
|
*/
|
JSAPI.prototype.querySelectorAll = function(selectors) {
|
|
var matchedEls = cssSelect(selectors, this, cssSelectOpts);
|
|
return matchedEls.length > 0 ? matchedEls : null;
|
|
};
|
|
/**
|
* Evaluate a string of CSS selectors against the element and returns only the first matched element.
|
*
|
* @param {String} selectors CSS selector(s) string
|
* @return {Array} null if no element matched
|
*/
|
JSAPI.prototype.querySelector = function(selectors) {
|
|
return cssSelect.selectOne(selectors, this, cssSelectOpts);
|
|
};
|
|
/**
|
* Test if a selector matches a given element.
|
*
|
* @param {String} selector CSS selector string
|
* @return {Boolean} true if element would be selected by selector string, false if it does not
|
*/
|
JSAPI.prototype.matches = function(selector) {
|
|
return cssSelect.is(this, selector, cssSelectOpts);
|
|
};
|