UNPKG

bootstrap-table

Version:

An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)

599 lines (515 loc) 18.7 kB
/** * Bootstrap Table DOM Manipulation Utility Library * Provides jQuery-style DOM manipulation APIs using native JavaScript * * Security Notice: * - The `create()` method uses innerHTML to parse HTML strings. Always sanitize user input * before passing it to create() to prevent XSS attacks. * - The `html()` method sets innerHTML directly. Use the `text()` method for user-provided content. * - The `attr()` method allows setting arbitrary attributes including event handlers. * Avoid setting event handler attributes (onclick, onerror, etc.) with user-controlled data. */ class DOMHelper { /** * Element selector * @param {string|Element} selector - CSS selector or DOM element * @param {Element} context - Search context, defaults to document * @returns {Element|null} First matched element */ static $ (selector, context = document) { if (typeof selector === 'string') { return context.querySelector(selector) } if (selector instanceof Element) { return selector } return null } /** * Element selector (multiple) * @param {string|Element|NodeList} selector - CSS selector, DOM element, or NodeList * @param {Element} context - Search context, defaults to document * @returns {Element[]} Array of all matched elements. Note: if selector is an Element, returns [Element] */ static $$ (selector, context = document) { if (typeof selector === 'string') { return Array.from(context.querySelectorAll(selector)) } if (selector instanceof NodeList) { return Array.from(selector) } if (selector instanceof Element) { return [selector] } return [] } /** * Create DOM element * @param {string} html - HTML string. Note: This method uses innerHTML and can execute scripts. * Always sanitize user input before passing it to this method. * @returns {Element|null} Created DOM element. Returns null if html is empty, not a string, * or contains only whitespace. */ static create (html) { if (typeof html !== 'string') return null const trimmed = html.trim() if (!trimmed) return null const template = document.createElement('template') template.innerHTML = trimmed return template.content.firstChild } /** * Add CSS class * @param {Element|string} element - DOM element or selector * @param {string} className - Class name to add (space-separated for multiple classes) * @returns {Element|null} The element itself */ static addClass (element, className) { if (typeof element === 'string') element = this.$(element) if (!element || !element.classList) return element if (!className) return element const classes = className.split(' ').filter(c => c) element.classList.add(...classes) return element } /** * Remove CSS class * @param {Element|string} element - DOM element or selector * @param {string} className - Class name to remove (space-separated for multiple classes) * @returns {Element|null} The element itself */ static removeClass (element, className) { if (typeof element === 'string') element = this.$(element) if (!element || !element.classList) return element if (!className) return element const classes = className.split(' ').filter(c => c) element.classList.remove(...classes) return element } /** * Toggle CSS class * @param {Element|string} element - DOM element or selector * @param {string} className - Class name to toggle (space-separated for multiple classes) * @returns {Element|null} The element itself */ static toggleClass (element, className) { if (typeof element === 'string') element = this.$(element) if (!element || !element.classList) return element if (!className) return element const classes = className.split(' ').filter(c => c) classes.forEach(cls => element.classList.toggle(cls)) return element } /** * Check if element has CSS class * @param {Element|string} element - DOM element or selector * @param {string} className - Class name to check * @returns {boolean} Whether the class exists */ static hasClass (element, className) { if (typeof element === 'string') element = this.$(element) if (!element || !element.classList) return false if (!className) return false return element.classList.contains(className) } /** * Get or set attribute * @param {Element|string} element - DOM element or selector * @param {string} name - Attribute name. Warning: Avoid setting event handler attributes * (onclick, onerror, etc.) with user-controlled data to prevent XSS. * @param {string} [value] - Attribute value (omit to get) * @returns {Element|null} Element when setting, or string|null when getting attribute */ static attr (element, name, value) { if (typeof element === 'string') element = this.$(element) if (!element) return value === undefined ? null : element if (value === undefined) { return element.getAttribute(name) } element.setAttribute(name, value) return element } /** * Remove attribute * @param {Element|string} element - DOM element or selector * @param {string} name - Attribute name * @returns {Element|null} The element itself */ static removeAttr (element, name) { if (typeof element === 'string') element = this.$(element) if (!element) return element element.removeAttribute(name) return element } /** * Get or set data attribute * @param {Element|string} element - DOM element or selector * @param {string} key - Data key name * @param {string} [value] - Data value (omit to get) * @returns {(string|undefined) when getting (value omitted); (Element|null|undefined) when setting (value provided)} * Returns the data attribute value (string or undefined) when getting, or the element (or null/undefined if not found) when setting. */ static data (element, key, value) { if (typeof element === 'string') element = this.$(element) if (!element) return value === undefined ? undefined : element if (value === undefined) { return element.dataset[key] } element.dataset[key] = value return element } /** * Append child element * @param {Element|string} parent - Parent element or selector * @param {Element|string} child - Child element or HTML string * @returns {Element|null} Parent element */ static append (parent, child) { if (typeof parent === 'string') parent = this.$(parent) if (typeof child === 'string') child = this.create(child) if (parent && child) { parent.appendChild(child) } return parent } /** * Prepend child element * @param {Element|string} parent - Parent element or selector * @param {Element|string} child - Child element or HTML string * @returns {Element|null} Parent element */ static prepend (parent, child) { if (typeof parent === 'string') parent = this.$(parent) if (typeof child === 'string') child = this.create(child) if (parent && child) { parent.insertBefore(child, parent.firstChild) } return parent } /** * Insert element after target * @param {Element|string} newElement - Element to insert * @param {Element|string} targetElement - Target element * @returns {Element|null} Inserted element */ static insertAfter (newElement, targetElement) { if (typeof targetElement === 'string') targetElement = this.$(targetElement) if (typeof newElement === 'string') newElement = this.create(newElement) if (targetElement && newElement && targetElement.parentNode) { targetElement.parentNode.insertBefore(newElement, targetElement.nextSibling) } return newElement } /** * Insert element before target * @param {Element|string} newElement - Element to insert * @param {Element|string} targetElement - Target element * @returns {Element|null} Inserted element */ static insertBefore (newElement, targetElement) { if (typeof targetElement === 'string') targetElement = this.$(targetElement) if (typeof newElement === 'string') newElement = this.create(newElement) if (targetElement && newElement && targetElement.parentNode) { targetElement.parentNode.insertBefore(newElement, targetElement) } return newElement } /** * Find child elements * @param {Element|string} element - Parent element or selector * @param {string} selector - CSS selector * @returns {Element[]} Array of matched child elements */ static find (element, selector) { if (typeof element === 'string') element = this.$(element) if (!element) return [] return Array.from(element.querySelectorAll(selector)) } /** * Find first matching child element * @param {Element|string} element - Parent element or selector * @param {string} selector - CSS selector * @returns {Element|null} First matched child element */ static findFirst (element, selector) { if (typeof element === 'string') element = this.$(element) if (!element) return null return element.querySelector(selector) } /** * Get or set style * @param {Element|string} element - DOM element or selector * @param {string|Object} property - Property name or property object * @param {string} [value] - Style value (when property is string) * @returns {Element|string|null} Element when setting, style value when getting */ static css (element, property, value) { if (typeof element === 'string') element = this.$(element) if (!element) { return null } if (typeof property === 'object') { // Batch set styles Object.assign(element.style, property) return element } if (value === undefined) { // Get style return getComputedStyle(element)[property] } // Set style element.style[property] = value return element } /** * Get element width * @param {Element|string} element - DOM element or selector * @returns {number} Element width */ static width (element) { if (typeof element === 'string') element = this.$(element) if (!element) return 0 return element.offsetWidth } /** * Get element height * @param {Element|string} element - DOM element or selector * @returns {number} Element height */ static height (element) { if (typeof element === 'string') element = this.$(element) if (!element) return 0 return element.offsetHeight } /** * Get element outer width (including border, optionally including margin) * @param {Element|string} element - DOM element or selector * @param {boolean} [includeMargin=false] - Whether to include margin * @returns {number} Element outer width */ static outerWidth (element, includeMargin = false) { if (typeof element === 'string') element = this.$(element) if (!element) return 0 let width = element.offsetWidth if (includeMargin) { const style = getComputedStyle(element) const marginLeft = parseInt(style.marginLeft, 10) || 0 const marginRight = parseInt(style.marginRight, 10) || 0 width += marginLeft + marginRight } return width } /** * Get element outer height (including border, optionally including margin) * @param {Element|string} element - DOM element or selector * @param {boolean} [includeMargin=false] - Whether to include margin * @returns {number} Element outer height */ static outerHeight (element, includeMargin = false) { if (typeof element === 'string') element = this.$(element) if (!element) return 0 let height = element.offsetHeight if (includeMargin) { const style = getComputedStyle(element) const marginTop = parseInt(style.marginTop, 10) || 0 const marginBottom = parseInt(style.marginBottom, 10) || 0 height += marginTop + marginBottom } return height } /** * Get or set element value * @param {Element|string} element - DOM element or selector * @param {string} [value] - Value (omit to get) * @returns {Element|string|null} Element when setting, current value when getting */ static val (element, value) { if (typeof element === 'string') element = this.$(element) if (!element) return value === undefined ? null : element if (value === undefined) { return element.value } element.value = value return element } /** * Get or set HTML content * @param {Element|string} element - DOM element or selector * @param {string} [content] - HTML content (omit to get). Warning: This method uses innerHTML * and can execute scripts. Use text() for user-provided content. * @returns {Element|string|null} Element when setting, HTML content when getting */ static html (element, content) { if (typeof element === 'string') element = this.$(element) if (!element) return content === undefined ? null : element if (content === undefined) { return element.innerHTML } element.innerHTML = content return element } /** * Get or set text content * @param {Element|string} element - DOM element or selector * @param {string} [content] - Text content (omit to get) * @returns {Element|string|null} Element when setting, text content when getting */ static text (element, content) { if (typeof element === 'string') element = this.$(element) if (!element) return content === undefined ? null : element if (content === undefined) { return element.textContent } element.textContent = content return element } /** * Remove element * @param {Element|string} element - DOM element or selector * @returns {Element|null} Removed element */ static remove (element) { if (typeof element === 'string') element = this.$(element) if (!element || !element.parentNode) return element element.parentNode.removeChild(element) return element } /** * Empty element content * @param {Element|string} element - DOM element or selector * @returns {Element|null} Emptied element */ static empty (element) { if (typeof element === 'string') element = this.$(element) if (!element) return element element.innerHTML = '' return element } /** * Iterate over element collection * @param {Element[]|NodeList|string} elements - Element collection or selector * @param {Function} callback - Callback function with params (index, element) * @returns {Element[]} Element collection */ static each (elements, callback) { if (typeof elements === 'string') { elements = this.$$(elements) } else if (elements instanceof NodeList) { elements = Array.from(elements) } else if (!Array.isArray(elements)) { elements = [elements] } elements.forEach((element, index) => { callback.call(element, index, element) }) return elements } /** * Get parent element * @param {Element|string} element - DOM element or selector * @param {string} [selector] - Parent element selector (optional) * @returns {Element|null} Parent element */ static parent (element, selector) { if (typeof element === 'string') element = this.$(element) if (!element) return null let parent = element.parentElement if (selector) { while (parent && !parent.matches(selector)) { parent = parent.parentElement } } return parent } /** * Get child elements * @param {Element|string} element - DOM element or selector * @param {string} [selector] - Child element selector (optional) * @returns {Element[]} Array of child elements */ static children (element, selector) { if (typeof element === 'string') element = this.$(element) if (!element) return [] let children = Array.from(element.children) if (selector) { children = children.filter(child => child.matches(selector)) } return children } /** * Get next sibling element * @param {Element|string} element - DOM element or selector * @param {string} [selector] - Sibling element selector (optional) * @returns {Element|null} Next sibling element */ static next (element, selector) { if (typeof element === 'string') element = this.$(element) if (!element) return null let next = element.nextElementSibling if (selector) { while (next && !next.matches(selector)) { next = next.nextElementSibling } } return next } /** * Get previous sibling element * @param {Element|string} element - DOM element or selector * @param {string} [selector] - Sibling element selector (optional) * @returns {Element|null} Previous sibling element */ static prev (element, selector) { if (typeof element === 'string') element = this.$(element) if (!element) return null let prev = element.previousElementSibling if (selector) { while (prev && !prev.matches(selector)) { prev = prev.previousElementSibling } } return prev } /** * Get element position relative to document * @param {Element|string} element - DOM element or selector * @returns {Object} Position info {top, left, width, height} */ static offset (element) { if (typeof element === 'string') element = this.$(element) if (!element) return { top: 0, left: 0, width: 0, height: 0 } const rect = element.getBoundingClientRect() return { top: rect.top + window.scrollY, left: rect.left + window.scrollX, width: rect.width, height: rect.height } } /** * Get element position relative to parent * @param {Element|string} element - DOM element or selector * @returns {Object} Position info {top, left} */ static position (element) { if (typeof element === 'string') element = this.$(element) if (!element) return { top: 0, left: 0 } return { top: element.offsetTop, left: element.offsetLeft } } /** * Check if element matches selector * @param {Element|string} element - DOM element or selector * @param {string} selector - CSS selector * @returns {boolean} Whether it matches */ static is (element, selector) { if (typeof element === 'string') element = this.$(element) if (!element) return false return element.matches(selector) } } // Export DOMHelper class export default DOMHelper