X-Git-Url: http://www.aleph1.co.uk/gitweb/?p=yaffs-website;a=blobdiff_plain;f=web%2Fthemes%2Fcontrib%2Fbootstrap%2Fjs%2Fattributes.js;fp=web%2Fthemes%2Fcontrib%2Fbootstrap%2Fjs%2Fattributes.js;h=e0c0c441fab5536564430f480da3cfd59c53abe0;hp=dd71c5d4916a2880738772de11c74919ee40f08c;hb=af6d1fb995500ae68849458ee10d66abbdcfb252;hpb=680c79a86e3ed402f263faeac92e89fb6d9edcc0 diff --git a/web/themes/contrib/bootstrap/js/attributes.js b/web/themes/contrib/bootstrap/js/attributes.js index dd71c5d49..e0c0c441f 100644 --- a/web/themes/contrib/bootstrap/js/attributes.js +++ b/web/themes/contrib/bootstrap/js/attributes.js @@ -1,21 +1,24 @@ (function ($, _) { /** - * Class to help modify attributes. + * @class Attributes * - * @param {object} object - * An object to initialize attributes with. + * Modifies attributes. * - * @constructor + * @param {Object|Attributes} attributes + * An object to initialize attributes with. */ - var Attributes = function (object) { - this.data = object && _.isObject(object) && _.clone(object) || {}; + 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. * - * @returns {string} + * @return {String} + * A rendered string suitable for inclusion in HTML markup. */ Attributes.prototype.toString = function () { var output = ''; @@ -23,9 +26,10 @@ var checkPlain = function (str) { return str && str.toString().replace(/&/g, '&').replace(/"/g, '"').replace(//g, '>') || ''; }; - for (name in this.data) { - if (!this.data.hasOwnProperty(name)) continue; - value = this.data[name]; + 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(' '); @@ -34,6 +38,27 @@ 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. * @@ -45,9 +70,8 @@ * @chainable */ Attributes.prototype.addClass = function (value) { - var classes = this.getClasses(); - value = [].concat(classes, value); - this.set('class', _.uniq(value)); + var args = Array.prototype.slice.call(arguments); + this.data['class'] = this.sanitizeClasses(this.data['class'].concat(args)); return this; }; @@ -83,10 +107,10 @@ /** * Retrieves a cloned copy of the internal attributes data object. * - * @returns {Object} + * @return {Object} */ Attributes.prototype.getData = function () { - return _.clone(this.data); + return _.extend({}, this.data); }; /** @@ -96,32 +120,37 @@ * The classes array. */ Attributes.prototype.getClasses = function () { - var classes = [].concat(this.get('class', [])); - return _.uniq(classes); + return this.get('class', []); }; /** * Indicates whether a class is present in the array. * - * @param {string|Array} name + * @param {string|Array} className * The class(es) to search for. * * @return {boolean} * TRUE or FALSE */ - Attributes.prototype.hasClass = function (name) { - name = [].concat(name); + Attributes.prototype.hasClass = function (className) { + className = this.sanitizeClasses(Array.prototype.slice.call(arguments)); var classes = this.getClasses(); - var found = false; - _.each(name, function (value) { if (_.indexOf(classes, value) !== -1) found = true; }); - return found; + 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 {object} values - * An associative key/value 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. * @@ -129,14 +158,54 @@ * * @chainable */ - Attributes.prototype.merge = function (values, recursive) { - values = values instanceof Attributes ? values.getData() : values; - if (recursive === void(0) || recursive) { - this.data = $.extend(true, {}, this.data, values); + 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 { - $.extend(this.data, values); + this.data = $.extend({}, this.data, object); } + return this; }; @@ -158,15 +227,16 @@ /** * Removes a class from the attributes array. * - * @param {string|Array} value + * @param {...string|Array} className * An individual class or an array of classes to remove. * * @return {Attributes} * * @chainable */ - Attributes.prototype.removeClass = function (value) { - this.set('class', _.without(this.getClasses(), [].concat(value))); + Attributes.prototype.removeClass = function (className) { + var remove = this.sanitizeClasses(Array.prototype.slice.apply(arguments)); + this.data['class'] = _.without(this.getClasses(), remove); return this; }; @@ -184,7 +254,7 @@ */ Attributes.prototype.replaceClass = function (oldValue, newValue) { var classes = this.getClasses(); - var i = _.indexOf(oldValue, classes); + var i = _.indexOf(this.sanitizeClasses(oldValue), classes); if (i >= 0) { classes[i] = newValue; this.set('class', classes); @@ -192,6 +262,43 @@ 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. * @@ -205,24 +312,69 @@ * @chainable */ Attributes.prototype.set = function (name, value) { - this.data[name] = value; - return this; + 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 = identifier.replace(Object.keys(filter), Object.keys(filter).map(function(key) { return filter[key]; }), identifier); + + if (filter['__'] === void 0) { + identifier = identifier.replace('#DOUBLE_UNDERSCORE#', '__', identifier); + } + + identifier = identifier.replace(/[^\u002D\u0030-\u0039\u0041-\u005A\u005F\u0061-\u007A\u00A1-\uFFFF]/g, ''); + identifier = identifier.replace(['/^[0-9]/', '/^(-[0-9])|^(--)/'], ['_', '__'], identifier); + + return identifier; }; /** * Creates an Attributes instance. * - * @param {object|Attributes} object + * @param {object|Attributes} [attributes] * An object to initialize attributes with. * - * @returns {Attributes} - * - * @global + * @return {Attributes} + * An Attributes instance. * * @constructor */ - window.Attributes = function (object) { - return object instanceof Attributes ? object : new Attributes(object); + Attributes.create = function (attributes) { + return new Attributes(attributes); }; + window.Attributes = Attributes; + })(window.jQuery, window._);