1(function ($, _) { 2 3 /** 4 * @class Attributes 5 * 6 * Modifies attributes. 7 * 8 * @param {Object|Attributes} attributes 9 * An object to initialize attributes with. 10 */ 11 var Attributes = function (attributes) { 12 this.data = {}; 13 this.data['class'] = []; 14 this.merge(attributes); 15 }; 16 17 /** 18 * Renders the attributes object as a string to inject into an HTML element. 19 * 20 * @return {String} 21 * A rendered string suitable for inclusion in HTML markup. 22 */ 23 Attributes.prototype.toString = function () { 24 var output = ''; 25 var name, value; 26 var checkPlain = function (str) { 27 return str && str.toString().replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>') || ''; 28 }; 29 var data = this.getData(); 30 for (name in data) { 31 if (!data.hasOwnProperty(name)) continue; 32 value = data[name]; 33 if (_.isFunction(value)) value = value(); 34 if (_.isObject(value)) value = _.values(value); 35 if (_.isArray(value)) value = value.join(' '); 36 output += ' ' + checkPlain(name) + '="' + checkPlain(value) + '"'; 37 } 38 return output; 39 }; 40 41 /** 42 * Renders the Attributes object as a plain object. 43 * 44 * @return {Object} 45 * A plain object suitable for inclusion in DOM elements. 46 */ 47 Attributes.prototype.toPlainObject = function () { 48 var object = {}; 49 var name, value; 50 var data = this.getData(); 51 for (name in data) { 52 if (!data.hasOwnProperty(name)) continue; 53 value = data[name]; 54 if (_.isFunction(value)) value = value(); 55 if (_.isObject(value)) value = _.values(value); 56 if (_.isArray(value)) value = value.join(' '); 57 object[name] = value; 58 } 59 return object; 60 }; 61 62 /** 63 * Add class(es) to the array. 64 * 65 * @param {string|Array} value 66 * An individual class or an array of classes to add. 67 * 68 * @return {Attributes} 69 * 70 * @chainable 71 */ 72 Attributes.prototype.addClass = function (value) { 73 var args = Array.prototype.slice.call(arguments); 74 this.data['class'] = this.sanitizeClasses(this.data['class'].concat(args)); 75 return this; 76 }; 77 78 /** 79 * Returns whether the requested attribute exists. 80 * 81 * @param {string} name 82 * An attribute name to check. 83 * 84 * @return {boolean} 85 * TRUE or FALSE 86 */ 87 Attributes.prototype.exists = function (name) { 88 return this.data[name] !== void(0) && this.data[name] !== null; 89 }; 90 91 /** 92 * Retrieve a specific attribute from the array. 93 * 94 * @param {string} name 95 * The specific attribute to retrieve. 96 * @param {*} defaultValue 97 * (optional) The default value to set if the attribute does not exist. 98 * 99 * @return {*} 100 * A specific attribute value, passed by reference. 101 */ 102 Attributes.prototype.get = function (name, defaultValue) { 103 if (!this.exists(name)) this.data[name] = defaultValue; 104 return this.data[name]; 105 }; 106 107 /** 108 * Retrieves a cloned copy of the internal attributes data object. 109 * 110 * @return {Object} 111 */ 112 Attributes.prototype.getData = function () { 113 return _.extend({}, this.data); 114 }; 115 116 /** 117 * Retrieves classes from the array. 118 * 119 * @return {Array} 120 * The classes array. 121 */ 122 Attributes.prototype.getClasses = function () { 123 return this.get('class', []); 124 }; 125 126 /** 127 * Indicates whether a class is present in the array. 128 * 129 * @param {string|Array} className 130 * The class(es) to search for. 131 * 132 * @return {boolean} 133 * TRUE or FALSE 134 */ 135 Attributes.prototype.hasClass = function (className) { 136 className = this.sanitizeClasses(Array.prototype.slice.call(arguments)); 137 var classes = this.getClasses(); 138 for (var i = 0, l = className.length; i < l; i++) { 139 // If one of the classes fails, immediately return false. 140 if (_.indexOf(classes, className[i]) === -1) { 141 return false; 142 } 143 } 144 return true; 145 }; 146 147 /** 148 * Merges multiple values into the array. 149 * 150 * @param {Attributes|Node|jQuery|Object} object 151 * An Attributes object with existing data, a Node DOM element, a jQuery 152 * instance or a plain object where the key is the attribute name and the 153 * value is the attribute value. 154 * @param {boolean} [recursive] 155 * Flag determining whether or not to recursively merge key/value pairs. 156 * 157 * @return {Attributes} 158 * 159 * @chainable 160 */ 161 Attributes.prototype.merge = function (object, recursive) { 162 // Immediately return if there is nothing to merge. 163 if (!object) { 164 return this; 165 } 166 167 // Get attributes from a jQuery element. 168 if (object instanceof $) { 169 object = object[0]; 170 } 171 172 // Get attributes from a DOM element. 173 if (object instanceof Node) { 174 object = Array.prototype.slice.call(object.attributes).reduce(function (attributes, attribute) { 175 attributes[attribute.name] = attribute.value; 176 return attributes; 177 }, {}); 178 } 179 // Get attributes from an Attributes instance. 180 else if (object instanceof Attributes) { 181 object = object.getData(); 182 } 183 // Otherwise, clone the object. 184 else { 185 object = _.extend({}, object); 186 } 187 188 // By this point, there should be a valid plain object. 189 if (!$.isPlainObject(object)) { 190 setTimeout(function () { 191 throw new Error('Passed object is not supported: ' + object); 192 }); 193 return this; 194 } 195 196 // Handle classes separately. 197 if (object && object['class'] !== void 0) { 198 this.addClass(object['class']); 199 delete object['class']; 200 } 201 202 if (recursive === void 0 || recursive) { 203 this.data = $.extend(true, {}, this.data, object); 204 } 205 else { 206 this.data = $.extend({}, this.data, object); 207 } 208 209 return this; 210 }; 211 212 /** 213 * Removes an attribute from the array. 214 * 215 * @param {string} name 216 * The name of the attribute to remove. 217 * 218 * @return {Attributes} 219 * 220 * @chainable 221 */ 222 Attributes.prototype.remove = function (name) { 223 if (this.exists(name)) delete this.data[name]; 224 return this; 225 }; 226 227 /** 228 * Removes a class from the attributes array. 229 * 230 * @param {...string|Array} className 231 * An individual class or an array of classes to remove. 232 * 233 * @return {Attributes} 234 * 235 * @chainable 236 */ 237 Attributes.prototype.removeClass = function (className) { 238 var remove = this.sanitizeClasses(Array.prototype.slice.apply(arguments)); 239 this.data['class'] = _.without(this.getClasses(), remove); 240 return this; 241 }; 242 243 /** 244 * Replaces a class in the attributes array. 245 * 246 * @param {string} oldValue 247 * The old class to remove. 248 * @param {string} newValue 249 * The new class. It will not be added if the old class does not exist. 250 * 251 * @return {Attributes} 252 * 253 * @chainable 254 */ 255 Attributes.prototype.replaceClass = function (oldValue, newValue) { 256 var classes = this.getClasses(); 257 var i = _.indexOf(this.sanitizeClasses(oldValue), classes); 258 if (i >= 0) { 259 classes[i] = newValue; 260 this.set('class', classes); 261 } 262 return this; 263 }; 264 265 /** 266 * Ensures classes are flattened into a single is an array and sanitized. 267 * 268 * @param {...String|Array} classes 269 * The class or classes to sanitize. 270 * 271 * @return {Array} 272 * A sanitized array of classes. 273 */ 274 Attributes.prototype.sanitizeClasses = function (classes) { 275 return _.chain(Array.prototype.slice.call(arguments)) 276 // Flatten in case there's a mix of strings and arrays. 277 .flatten() 278 279 // Split classes that may have been added with a space as a separator. 280 .map(function (string) { 281 return string.split(' '); 282 }) 283 284 // Flatten again since it was just split into arrays. 285 .flatten() 286 287 // Filter out empty items. 288 .filter() 289
290 // Clean the class to ensure it's a valid class name. 291 .map(function (value) { 292 return Attributes.cleanClass(value); 293 }) 294 295 // Ensure classes are unique. 296 .uniq() 297 298 // Retrieve the final value. 299 .value(); 300 }; 301 302 /** 303 * Sets an attribute on the array. 304 * 305 * @param {string} name 306 * The name of the attribute to set. 307 * @param {*} value 308 * The value of the attribute to set. 309 * 310 * @return {Attributes} 311 * 312 * @chainable 313 */ 314 Attributes.prototype.set = function (name, value) { 315 var obj = $.isPlainObject(name) ? name : {}; 316 if (typeof name === 'string') { 317 obj[name] = value; 318 } 319 return this.merge(obj); 320 }; 321 322 /** 323 * Prepares a string for use as a CSS identifier (element, class, or ID name). 324 * 325 * Note: this is essentially a direct copy from 326 * \Drupal\Component\Utility\Html::cleanCssIdentifier 327 * 328 * @param {string} identifier 329 * The identifier to clean. 330 * @param {Object} [filter] 331 * An object of string replacements to use on the identifier. 332 * 333 * @return {string} 334 * The cleaned identifier. 335 */ 336 Attributes.cleanClass = function (identifier, filter) { 337 filter = filter || { 338 ' ': '-', 339 '_': '-', 340 '/': '-', 341 '[': '-', 342 ']': '' 343 }; 344 345 identifier = identifier.toLowerCase(); 346 347 if (filter['__'] === void 0) { 348 identifier = identifier.replace('__', '#DOUBLE_UNDERSCORE#'); 349 } 350 351 identifier = identifier.replace(Object.keys(filter), Object.keys(filter).map(function(key) { return filter[key]; })); 352 353 if (filter['__'] === void 0) { 354 identifier = identifier.replace('#DOUBLE_UNDERSCORE#', '__'); 355 } 356
357 identifier = identifier.replace(/[^\u002D\u0030-\u0039\u0041-\u005A\u005F\u0061-\u007A\u00A1-\uFFFF]/g, ''); 358 identifier = identifier.replace(['/^[0-9]/', '/^(-[0-9])|^(--)/'], ['_', '__']); 359 360 return identifier; 361 }; 362 363 /** 364 * Creates an Attributes instance. 365 * 366 * @param {object|Attributes} [attributes] 367 * An object to initialize attributes with. 368 * 369 * @return {Attributes} 370 * An Attributes instance. 371 * 372 * @constructor 373 */ 374 Attributes.create = function (attributes) { 375 return new Attributes(attributes); 376 }; 377 378 window.Attributes = Attributes; 379 380})(window.jQuery, window._);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.