381 lines
9.8 KiB
JavaScript
381 lines
9.8 KiB
JavaScript
(function ($, _) {
|
|
|
|
/**
|
|
* @class Attributes
|
|
*
|
|
* Modifies attributes.
|
|
*
|
|
* @param {Object|Attributes} attributes
|
|
* An object to initialize attributes with.
|
|
*/
|
|
var Attributes = function (attributes) {
|
|
this.data = {};
|
|
this.data['class'] = [];
|
|
this.merge(attributes);
|
|
};
|
|
|
|
/**
|
|
* Renders the attributes object as a string to inject into an HTML element.
|
|
*
|
|
* @return {String}
|
|
* A rendered string suitable for inclusion in HTML markup.
|
|
*/
|
|
Attributes.prototype.toString = function () {
|
|
var output = '';
|
|
var name, value;
|
|
var checkPlain = function (str) {
|
|
return str && str.toString().replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>') || '';
|
|
};
|
|
var data = this.getData();
|
|
for (name in data) {
|
|
if (!data.hasOwnProperty(name)) continue;
|
|
value = data[name];
|
|
if (_.isFunction(value)) value = value();
|
|
if (_.isObject(value)) value = _.values(value);
|
|
if (_.isArray(value)) value = value.join(' ');
|
|
output += ' ' + checkPlain(name) + '="' + checkPlain(value) + '"';
|
|
}
|
|
return output;
|
|
};
|
|
|
|
/**
|
|
* Renders the Attributes object as a plain object.
|
|
*
|
|
* @return {Object}
|
|
* A plain object suitable for inclusion in DOM elements.
|
|
*/
|
|
Attributes.prototype.toPlainObject = function () {
|
|
var object = {};
|
|
var name, value;
|
|
var data = this.getData();
|
|
for (name in data) {
|
|
if (!data.hasOwnProperty(name)) continue;
|
|
value = data[name];
|
|
if (_.isFunction(value)) value = value();
|
|
if (_.isObject(value)) value = _.values(value);
|
|
if (_.isArray(value)) value = value.join(' ');
|
|
object[name] = value;
|
|
}
|
|
return object;
|
|
};
|
|
|
|
/**
|
|
* Add class(es) to the array.
|
|
*
|
|
* @param {string|Array} value
|
|
* An individual class or an array of classes to add.
|
|
*
|
|
* @return {Attributes}
|
|
*
|
|
* @chainable
|
|
*/
|
|
Attributes.prototype.addClass = function (value) {
|
|
var args = Array.prototype.slice.call(arguments);
|
|
this.data['class'] = this.sanitizeClasses(this.data['class'].concat(args));
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Returns whether the requested attribute exists.
|
|
*
|
|
* @param {string} name
|
|
* An attribute name to check.
|
|
*
|
|
* @return {boolean}
|
|
* TRUE or FALSE
|
|
*/
|
|
Attributes.prototype.exists = function (name) {
|
|
return this.data[name] !== void(0) && this.data[name] !== null;
|
|
};
|
|
|
|
/**
|
|
* Retrieve a specific attribute from the array.
|
|
*
|
|
* @param {string} name
|
|
* The specific attribute to retrieve.
|
|
* @param {*} defaultValue
|
|
* (optional) The default value to set if the attribute does not exist.
|
|
*
|
|
* @return {*}
|
|
* A specific attribute value, passed by reference.
|
|
*/
|
|
Attributes.prototype.get = function (name, defaultValue) {
|
|
if (!this.exists(name)) this.data[name] = defaultValue;
|
|
return this.data[name];
|
|
};
|
|
|
|
/**
|
|
* Retrieves a cloned copy of the internal attributes data object.
|
|
*
|
|
* @return {Object}
|
|
*/
|
|
Attributes.prototype.getData = function () {
|
|
return _.extend({}, this.data);
|
|
};
|
|
|
|
/**
|
|
* Retrieves classes from the array.
|
|
*
|
|
* @return {Array}
|
|
* The classes array.
|
|
*/
|
|
Attributes.prototype.getClasses = function () {
|
|
return this.get('class', []);
|
|
};
|
|
|
|
/**
|
|
* Indicates whether a class is present in the array.
|
|
*
|
|
* @param {string|Array} className
|
|
* The class(es) to search for.
|
|
*
|
|
* @return {boolean}
|
|
* TRUE or FALSE
|
|
*/
|
|
Attributes.prototype.hasClass = function (className) {
|
|
className = this.sanitizeClasses(Array.prototype.slice.call(arguments));
|
|
var classes = this.getClasses();
|
|
for (var i = 0, l = className.length; i < l; i++) {
|
|
// If one of the classes fails, immediately return false.
|
|
if (_.indexOf(classes, className[i]) === -1) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Merges multiple values into the array.
|
|
*
|
|
* @param {Attributes|Node|jQuery|Object} object
|
|
* An Attributes object with existing data, a Node DOM element, a jQuery
|
|
* instance or a plain object where the key is the attribute name and the
|
|
* value is the attribute value.
|
|
* @param {boolean} [recursive]
|
|
* Flag determining whether or not to recursively merge key/value pairs.
|
|
*
|
|
* @return {Attributes}
|
|
*
|
|
* @chainable
|
|
*/
|
|
Attributes.prototype.merge = function (object, recursive) {
|
|
// Immediately return if there is nothing to merge.
|
|
if (!object) {
|
|
return this;
|
|
}
|
|
|
|
// Get attributes from a jQuery element.
|
|
if (object instanceof $) {
|
|
object = object[0];
|
|
}
|
|
|
|
// Get attributes from a DOM element.
|
|
if (object instanceof Node) {
|
|
object = Array.prototype.slice.call(object.attributes).reduce(function (attributes, attribute) {
|
|
attributes[attribute.name] = attribute.value;
|
|
return attributes;
|
|
}, {});
|
|
}
|
|
// Get attributes from an Attributes instance.
|
|
else if (object instanceof Attributes) {
|
|
object = object.getData();
|
|
}
|
|
// Otherwise, clone the object.
|
|
else {
|
|
object = _.extend({}, object);
|
|
}
|
|
|
|
// By this point, there should be a valid plain object.
|
|
if (!$.isPlainObject(object)) {
|
|
setTimeout(function () {
|
|
throw new Error('Passed object is not supported: ' + object);
|
|
});
|
|
return this;
|
|
}
|
|
|
|
// Handle classes separately.
|
|
if (object && object['class'] !== void 0) {
|
|
this.addClass(object['class']);
|
|
delete object['class'];
|
|
}
|
|
|
|
if (recursive === void 0 || recursive) {
|
|
this.data = $.extend(true, {}, this.data, object);
|
|
}
|
|
else {
|
|
this.data = $.extend({}, this.data, object);
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Removes an attribute from the array.
|
|
*
|
|
* @param {string} name
|
|
* The name of the attribute to remove.
|
|
*
|
|
* @return {Attributes}
|
|
*
|
|
* @chainable
|
|
*/
|
|
Attributes.prototype.remove = function (name) {
|
|
if (this.exists(name)) delete this.data[name];
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Removes a class from the attributes array.
|
|
*
|
|
* @param {...string|Array} className
|
|
* An individual class or an array of classes to remove.
|
|
*
|
|
* @return {Attributes}
|
|
*
|
|
* @chainable
|
|
*/
|
|
Attributes.prototype.removeClass = function (className) {
|
|
var remove = this.sanitizeClasses(Array.prototype.slice.apply(arguments));
|
|
this.data['class'] = _.without(this.getClasses(), remove);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Replaces a class in the attributes array.
|
|
*
|
|
* @param {string} oldValue
|
|
* The old class to remove.
|
|
* @param {string} newValue
|
|
* The new class. It will not be added if the old class does not exist.
|
|
*
|
|
* @return {Attributes}
|
|
*
|
|
* @chainable
|
|
*/
|
|
Attributes.prototype.replaceClass = function (oldValue, newValue) {
|
|
var classes = this.getClasses();
|
|
var i = _.indexOf(this.sanitizeClasses(oldValue), classes);
|
|
if (i >= 0) {
|
|
classes[i] = newValue;
|
|
this.set('class', classes);
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Ensures classes are flattened into a single is an array and sanitized.
|
|
*
|
|
* @param {...String|Array} classes
|
|
* The class or classes to sanitize.
|
|
*
|
|
* @return {Array}
|
|
* A sanitized array of classes.
|
|
*/
|
|
Attributes.prototype.sanitizeClasses = function (classes) {
|
|
return _.chain(Array.prototype.slice.call(arguments))
|
|
// Flatten in case there's a mix of strings and arrays.
|
|
.flatten()
|
|
|
|
// Split classes that may have been added with a space as a separator.
|
|
.map(function (string) {
|
|
return string.split(' ');
|
|
})
|
|
|
|
// Flatten again since it was just split into arrays.
|
|
.flatten()
|
|
|
|
// Filter out empty items.
|
|
.filter()
|
|
|
|
// Clean the class to ensure it's a valid class name.
|
|
.map(function (value) {
|
|
return Attributes.cleanClass(value);
|
|
})
|
|
|
|
// Ensure classes are unique.
|
|
.uniq()
|
|
|
|
// Retrieve the final value.
|
|
.value();
|
|
};
|
|
|
|
/**
|
|
* Sets an attribute on the array.
|
|
*
|
|
* @param {string} name
|
|
* The name of the attribute to set.
|
|
* @param {*} value
|
|
* The value of the attribute to set.
|
|
*
|
|
* @return {Attributes}
|
|
*
|
|
* @chainable
|
|
*/
|
|
Attributes.prototype.set = function (name, value) {
|
|
var obj = $.isPlainObject(name) ? name : {};
|
|
if (typeof name === 'string') {
|
|
obj[name] = value;
|
|
}
|
|
return this.merge(obj);
|
|
};
|
|
|
|
/**
|
|
* Prepares a string for use as a CSS identifier (element, class, or ID name).
|
|
*
|
|
* Note: this is essentially a direct copy from
|
|
* \Drupal\Component\Utility\Html::cleanCssIdentifier
|
|
*
|
|
* @param {string} identifier
|
|
* The identifier to clean.
|
|
* @param {Object} [filter]
|
|
* An object of string replacements to use on the identifier.
|
|
*
|
|
* @return {string}
|
|
* The cleaned identifier.
|
|
*/
|
|
Attributes.cleanClass = function (identifier, filter) {
|
|
filter = filter || {
|
|
' ': '-',
|
|
'_': '-',
|
|
'/': '-',
|
|
'[': '-',
|
|
']': ''
|
|
};
|
|
|
|
identifier = identifier.toLowerCase();
|
|
|
|
if (filter['__'] === void 0) {
|
|
identifier = identifier.replace('__', '#DOUBLE_UNDERSCORE#');
|
|
}
|
|
|
|
identifier = identifier.replace(Object.keys(filter), Object.keys(filter).map(function(key) { return filter[key]; }));
|
|
|
|
if (filter['__'] === void 0) {
|
|
identifier = identifier.replace('#DOUBLE_UNDERSCORE#', '__');
|
|
}
|
|
|
|
identifier = identifier.replace(/[^\u002D\u0030-\u0039\u0041-\u005A\u005F\u0061-\u007A\u00A1-\uFFFF]/g, '');
|
|
identifier = identifier.replace(['/^[0-9]/', '/^(-[0-9])|^(--)/'], ['_', '__']);
|
|
|
|
return identifier;
|
|
};
|
|
|
|
/**
|
|
* Creates an Attributes instance.
|
|
*
|
|
* @param {object|Attributes} [attributes]
|
|
* An object to initialize attributes with.
|
|
*
|
|
* @return {Attributes}
|
|
* An Attributes instance.
|
|
*
|
|
* @constructor
|
|
*/
|
|
Attributes.create = function (attributes) {
|
|
return new Attributes(attributes);
|
|
};
|
|
|
|
window.Attributes = Attributes;
|
|
|
|
})(window.jQuery, window._);
|