Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / web / themes / contrib / bootstrap / js / attributes.js
index dd71c5d4916a2880738772de11c74919ee40f08c..e0c0c441fab5536564430f480da3cfd59c53abe0 100644 (file)
@@ -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 = '';
     var checkPlain = function (str) {
       return str && str.toString().replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;') || '';
     };
-    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(' ');
     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;
   };
 
   /**
    * 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);
   };
 
   /**
    *   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.
    *
    *
    * @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;
   };
 
   /**
    * 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;
   };
 
    */
   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);
     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.
    *
    * @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._);