"use strict"; /** * Methods for getting and modifying attributes. * * @module cheerio/attributes */ Object.defineProperty(exports, "__esModule", { value: true }); exports.toggleClass = exports.removeClass = exports.addClass = exports.hasClass = exports.removeAttr = exports.val = exports.data = exports.prop = exports.attr = void 0; var static_js_1 = require("../static.js"); var utils_js_1 = require("../utils.js"); var domutils_1 = require("domutils"); var hasOwn = Object.prototype.hasOwnProperty; var rspace = /\s+/; var dataAttrPrefix = 'data-'; /* * Lookup table for coercing string data-* attributes to their corresponding * JavaScript primitives */ var primitives = { null: null, true: true, false: false, }; // Attributes that are booleans var rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i; // Matches strings that look like JSON objects or arrays var rbrace = /^{[^]*}$|^\[[^]*]$/; function getAttr(elem, name, xmlMode) { var _a; if (!elem || !(0, utils_js_1.isTag)(elem)) return undefined; (_a = elem.attribs) !== null && _a !== void 0 ? _a : (elem.attribs = {}); // Return the entire attribs object if no attribute specified if (!name) { return elem.attribs; } if (hasOwn.call(elem.attribs, name)) { // Get the (decoded) attribute return !xmlMode && rboolean.test(name) ? name : elem.attribs[name]; } // Mimic the DOM and return text content as value for `option's` if (elem.name === 'option' && name === 'value') { return (0, static_js_1.text)(elem.children); } // Mimic DOM with default value for radios/checkboxes if (elem.name === 'input' && (elem.attribs['type'] === 'radio' || elem.attribs['type'] === 'checkbox') && name === 'value') { return 'on'; } return undefined; } /** * Sets the value of an attribute. The attribute will be deleted if the value is `null`. * * @private * @param el - The element to set the attribute on. * @param name - The attribute's name. * @param value - The attribute's value. */ function setAttr(el, name, value) { if (value === null) { removeAttribute(el, name); } else { el.attribs[name] = "".concat(value); } } function attr(name, value) { // Set the value (with attr map support) if (typeof name === 'object' || value !== undefined) { if (typeof value === 'function') { if (typeof name !== 'string') { { throw new Error('Bad combination of arguments.'); } } return (0, utils_js_1.domEach)(this, function (el, i) { if ((0, utils_js_1.isTag)(el)) setAttr(el, name, value.call(el, i, el.attribs[name])); }); } return (0, utils_js_1.domEach)(this, function (el) { if (!(0, utils_js_1.isTag)(el)) return; if (typeof name === 'object') { Object.keys(name).forEach(function (objName) { var objValue = name[objName]; setAttr(el, objName, objValue); }); } else { setAttr(el, name, value); } }); } return arguments.length > 1 ? this : getAttr(this[0], name, this.options.xmlMode); } exports.attr = attr; /** * Gets a node's prop. * * @private * @category Attributes * @param el - Element to get the prop of. * @param name - Name of the prop. * @returns The prop's value. */ function getProp(el, name, xmlMode) { return name in el ? // @ts-expect-error TS doesn't like us accessing the value directly here. el[name] : !xmlMode && rboolean.test(name) ? getAttr(el, name, false) !== undefined : getAttr(el, name, xmlMode); } /** * Sets the value of a prop. * * @private * @param el - The element to set the prop on. * @param name - The prop's name. * @param value - The prop's value. */ function setProp(el, name, value, xmlMode) { if (name in el) { // @ts-expect-error Overriding value el[name] = value; } else { setAttr(el, name, !xmlMode && rboolean.test(name) ? (value ? '' : null) : "".concat(value)); } } function prop(name, value) { var _this = this; var _a; if (typeof name === 'string' && value === undefined) { var el = this[0]; if (!el || !(0, utils_js_1.isTag)(el)) return undefined; switch (name) { case 'style': { var property_1 = this.css(); var keys = Object.keys(property_1); keys.forEach(function (p, i) { property_1[i] = p; }); property_1.length = keys.length; return property_1; } case 'tagName': case 'nodeName': { return el.name.toUpperCase(); } case 'href': case 'src': { var prop_1 = (_a = el.attribs) === null || _a === void 0 ? void 0 : _a[name]; /* eslint-disable node/no-unsupported-features/node-builtins */ if (typeof URL !== 'undefined' && ((name === 'href' && (el.tagName === 'a' || el.name === 'link')) || (name === 'src' && (el.tagName === 'img' || el.tagName === 'iframe' || el.tagName === 'audio' || el.tagName === 'video' || el.tagName === 'source'))) && prop_1 !== undefined && this.options.baseURI) { return new URL(prop_1, this.options.baseURI).href; } /* eslint-enable node/no-unsupported-features/node-builtins */ return prop_1; } case 'innerText': { return (0, domutils_1.innerText)(el); } case 'textContent': { return (0, domutils_1.textContent)(el); } case 'outerHTML': return this.clone().wrap('').parent().html(); case 'innerHTML': return this.html(); default: return getProp(el, name, this.options.xmlMode); } } if (typeof name === 'object' || value !== undefined) { if (typeof value === 'function') { if (typeof name === 'object') { throw new Error('Bad combination of arguments.'); } return (0, utils_js_1.domEach)(this, function (el, i) { if ((0, utils_js_1.isTag)(el)) { setProp(el, name, value.call(el, i, getProp(el, name, _this.options.xmlMode)), _this.options.xmlMode); } }); } return (0, utils_js_1.domEach)(this, function (el) { if (!(0, utils_js_1.isTag)(el)) return; if (typeof name === 'object') { Object.keys(name).forEach(function (key) { var val = name[key]; setProp(el, key, val, _this.options.xmlMode); }); } else { setProp(el, name, value, _this.options.xmlMode); } }); } return undefined; } exports.prop = prop; /** * Sets the value of a data attribute. * * @private * @param el - The element to set the data attribute on. * @param name - The data attribute's name. * @param value - The data attribute's value. */ function setData(el, name, value) { var _a; var elem = el; (_a = elem.data) !== null && _a !== void 0 ? _a : (elem.data = {}); if (typeof name === 'object') Object.assign(elem.data, name); else if (typeof name === 'string' && value !== undefined) { elem.data[name] = value; } } /** * Read the specified attribute from the equivalent HTML5 `data-*` attribute, * and (if present) cache the value in the node's internal data store. If no * attribute name is specified, read _all_ HTML5 `data-*` attributes in this manner. * * @private * @category Attributes * @param el - Element to get the data attribute of. * @param name - Name of the data attribute. * @returns The data attribute's value, or a map with all of the data attributes. */ function readData(el, name) { var domNames; var jsNames; var value; if (name == null) { domNames = Object.keys(el.attribs).filter(function (attrName) { return attrName.startsWith(dataAttrPrefix); }); jsNames = domNames.map(function (domName) { return (0, utils_js_1.camelCase)(domName.slice(dataAttrPrefix.length)); }); } else { domNames = [dataAttrPrefix + (0, utils_js_1.cssCase)(name)]; jsNames = [name]; } for (var idx = 0; idx < domNames.length; ++idx) { var domName = domNames[idx]; var jsName = jsNames[idx]; if (hasOwn.call(el.attribs, domName) && !hasOwn.call(el.data, jsName)) { value = el.attribs[domName]; if (hasOwn.call(primitives, value)) { value = primitives[value]; } else if (value === String(Number(value))) { value = Number(value); } else if (rbrace.test(value)) { try { value = JSON.parse(value); } catch (e) { /* Ignore */ } } el.data[jsName] = value; } } return name == null ? el.data : value; } function data(name, value) { var _a; var elem = this[0]; if (!elem || !(0, utils_js_1.isTag)(elem)) return; var dataEl = elem; (_a = dataEl.data) !== null && _a !== void 0 ? _a : (dataEl.data = {}); // Return the entire data object if no data specified if (!name) { return readData(dataEl); } // Set the value (with attr map support) if (typeof name === 'object' || value !== undefined) { (0, utils_js_1.domEach)(this, function (el) { if ((0, utils_js_1.isTag)(el)) { if (typeof name === 'object') setData(el, name); else setData(el, name, value); } }); return this; } if (hasOwn.call(dataEl.data, name)) { return dataEl.data[name]; } return readData(dataEl, name); } exports.data = data; function val(value) { var querying = arguments.length === 0; var element = this[0]; if (!element || !(0, utils_js_1.isTag)(element)) return querying ? undefined : this; switch (element.name) { case 'textarea': return this.text(value); case 'select': { var option = this.find('option:selected'); if (!querying) { if (this.attr('multiple') == null && typeof value === 'object') { return this; } this.find('option').removeAttr('selected'); var values = typeof value !== 'object' ? [value] : value; for (var i = 0; i < values.length; i++) { this.find("option[value=\"".concat(values[i], "\"]")).attr('selected', ''); } return this; } return this.attr('multiple') ? option.toArray().map(function (el) { return (0, static_js_1.text)(el.children); }) : option.attr('value'); } case 'input': case 'option': return querying ? this.attr('value') : this.attr('value', value); } return undefined; } exports.val = val; /** * Remove an attribute. * * @private * @param elem - Node to remove attribute from. * @param name - Name of the attribute to remove. */ function removeAttribute(elem, name) { if (!elem.attribs || !hasOwn.call(elem.attribs, name)) return; delete elem.attribs[name]; } /** * Splits a space-separated list of names to individual names. * * @category Attributes * @param names - Names to split. * @returns - Split names. */ function splitNames(names) { return names ? names.trim().split(rspace) : []; } /** * Method for removing attributes by `name`. * * @category Attributes * @example * * ```js * $('.pear').removeAttr('class').html(); * //=>
  • Pear
  • * * $('.apple').attr('id', 'favorite'); * $('.apple').removeAttr('id class').html(); * //=>
  • Apple
  • * ``` * * @param name - Name of the attribute. * @returns The instance itself. * @see {@link https://api.jquery.com/removeAttr/} */ function removeAttr(name) { var attrNames = splitNames(name); var _loop_1 = function (i) { (0, utils_js_1.domEach)(this_1, function (elem) { if ((0, utils_js_1.isTag)(elem)) removeAttribute(elem, attrNames[i]); }); }; var this_1 = this; for (var i = 0; i < attrNames.length; i++) { _loop_1(i); } return this; } exports.removeAttr = removeAttr; /** * Check to see if _any_ of the matched elements have the given `className`. * * @category Attributes * @example * * ```js * $('.pear').hasClass('pear'); * //=> true * * $('apple').hasClass('fruit'); * //=> false * * $('li').hasClass('pear'); * //=> true * ``` * * @param className - Name of the class. * @returns Indicates if an element has the given `className`. * @see {@link https://api.jquery.com/hasClass/} */ function hasClass(className) { return this.toArray().some(function (elem) { var clazz = (0, utils_js_1.isTag)(elem) && elem.attribs['class']; var idx = -1; if (clazz && className.length) { while ((idx = clazz.indexOf(className, idx + 1)) > -1) { var end = idx + className.length; if ((idx === 0 || rspace.test(clazz[idx - 1])) && (end === clazz.length || rspace.test(clazz[end]))) { return true; } } } return false; }); } exports.hasClass = hasClass; /** * Adds class(es) to all of the matched elements. Also accepts a `function`. * * @category Attributes * @example * * ```js * $('.pear').addClass('fruit').html(); * //=>
  • Pear
  • * * $('.apple').addClass('fruit red').html(); * //=>
  • Apple
  • * ``` * * @param value - Name of new class. * @returns The instance itself. * @see {@link https://api.jquery.com/addClass/} */ function addClass(value) { // Support functions if (typeof value === 'function') { return (0, utils_js_1.domEach)(this, function (el, i) { if ((0, utils_js_1.isTag)(el)) { var className = el.attribs['class'] || ''; addClass.call([el], value.call(el, i, className)); } }); } // Return if no value or not a string or function if (!value || typeof value !== 'string') return this; var classNames = value.split(rspace); var numElements = this.length; for (var i = 0; i < numElements; i++) { var el = this[i]; // If selected element isn't a tag, move on if (!(0, utils_js_1.isTag)(el)) continue; // If we don't already have classes — always set xmlMode to false here, as it doesn't matter for classes var className = getAttr(el, 'class', false); if (!className) { setAttr(el, 'class', classNames.join(' ').trim()); } else { var setClass = " ".concat(className, " "); // Check if class already exists for (var j = 0; j < classNames.length; j++) { var appendClass = "".concat(classNames[j], " "); if (!setClass.includes(" ".concat(appendClass))) setClass += appendClass; } setAttr(el, 'class', setClass.trim()); } } return this; } exports.addClass = addClass; /** * Removes one or more space-separated classes from the selected elements. If no * `className` is defined, all classes will be removed. Also accepts a `function`. * * @category Attributes * @example * * ```js * $('.pear').removeClass('pear').html(); * //=>
  • Pear
  • * * $('.apple').addClass('red').removeClass().html(); * //=>
  • Apple
  • * ``` * * @param name - Name of the class. If not specified, removes all elements. * @returns The instance itself. * @see {@link https://api.jquery.com/removeClass/} */ function removeClass(name) { // Handle if value is a function if (typeof name === 'function') { return (0, utils_js_1.domEach)(this, function (el, i) { if ((0, utils_js_1.isTag)(el)) { removeClass.call([el], name.call(el, i, el.attribs['class'] || '')); } }); } var classes = splitNames(name); var numClasses = classes.length; var removeAll = arguments.length === 0; return (0, utils_js_1.domEach)(this, function (el) { if (!(0, utils_js_1.isTag)(el)) return; if (removeAll) { // Short circuit the remove all case as this is the nice one el.attribs['class'] = ''; } else { var elClasses = splitNames(el.attribs['class']); var changed = false; for (var j = 0; j < numClasses; j++) { var index = elClasses.indexOf(classes[j]); if (index >= 0) { elClasses.splice(index, 1); changed = true; /* * We have to do another pass to ensure that there are not duplicate * classes listed */ j--; } } if (changed) { el.attribs['class'] = elClasses.join(' '); } } }); } exports.removeClass = removeClass; /** * Add or remove class(es) from the matched elements, depending on either the * class's presence or the value of the switch argument. Also accepts a `function`. * * @category Attributes * @example * * ```js * $('.apple.green').toggleClass('fruit green red').html(); * //=>
  • Apple
  • * * $('.apple.green').toggleClass('fruit green red', true).html(); * //=>
  • Apple
  • * ``` * * @param value - Name of the class. Can also be a function. * @param stateVal - If specified the state of the class. * @returns The instance itself. * @see {@link https://api.jquery.com/toggleClass/} */ function toggleClass(value, stateVal) { // Support functions if (typeof value === 'function') { return (0, utils_js_1.domEach)(this, function (el, i) { if ((0, utils_js_1.isTag)(el)) { toggleClass.call([el], value.call(el, i, el.attribs['class'] || '', stateVal), stateVal); } }); } // Return if no value or not a string or function if (!value || typeof value !== 'string') return this; var classNames = value.split(rspace); var numClasses = classNames.length; var state = typeof stateVal === 'boolean' ? (stateVal ? 1 : -1) : 0; var numElements = this.length; for (var i = 0; i < numElements; i++) { var el = this[i]; // If selected element isn't a tag, move on if (!(0, utils_js_1.isTag)(el)) continue; var elementClasses = splitNames(el.attribs['class']); // Check if class already exists for (var j = 0; j < numClasses; j++) { // Check if the class name is currently defined var index = elementClasses.indexOf(classNames[j]); // Add if stateValue === true or we are toggling and there is no value if (state >= 0 && index < 0) { elementClasses.push(classNames[j]); } else if (state <= 0 && index >= 0) { // Otherwise remove but only if the item exists elementClasses.splice(index, 1); } } el.attribs['class'] = elementClasses.join(' '); } return this; } exports.toggleClass = toggleClass; //# sourceMappingURL=attributes.js.map