import { adopt, assignNewId, eid, extend, makeInstance, create, register } from '../utils/adopter.js' import { find, findOne } from '../modules/core/selector.js' import { globals } from '../utils/window.js' import { map } from '../utils/utils.js' import { svg, html } from '../modules/core/namespaces.js' import EventTarget from '../types/EventTarget.js' import List from '../types/List.js' import attr from '../modules/core/attr.js' export default class Dom extends EventTarget { constructor (node, attrs) { super() this.node = node this.type = node.nodeName if (attrs && node !== attrs) { this.attr(attrs) } } // Add given element at a position add (element, i) { element = makeInstance(element) // If non-root svg nodes are added we have to remove their namespaces if (element.removeNamespace && this.node instanceof globals.window.SVGElement) { element.removeNamespace() } if (i == null) { this.node.appendChild(element.node) } else if (element.node !== this.node.childNodes[i]) { this.node.insertBefore(element.node, this.node.childNodes[i]) } return this } // Add element to given container and return self addTo (parent, i) { return makeInstance(parent).put(this, i) } // Returns all child elements children () { return new List(map(this.node.children, function (node) { return adopt(node) })) } // Remove all elements in this container clear () { // remove children while (this.node.hasChildNodes()) { this.node.removeChild(this.node.lastChild) } return this } // Clone element clone (deep = true) { // write dom data to the dom so the clone can pickup the data this.writeDataToDom() // clone element and assign new id return assignNewId(this.node.cloneNode(deep)) } // Iterates over all children and invokes a given block each (block, deep) { var children = this.children() var i, il for (i = 0, il = children.length; i < il; i++) { block.apply(children[i], [ i, children ]) if (deep) { children[i].each(block, deep) } } return this } element (nodeName, attrs) { return this.put(new Dom(create(nodeName), attrs)) } // Get first child first () { return adopt(this.node.firstChild) } // Get a element at the given index get (i) { return adopt(this.node.childNodes[i]) } getEventHolder () { return this.node } getEventTarget () { return this.node } // Checks if the given element is a child has (element) { return this.index(element) >= 0 } html (htmlOrFn, outerHTML) { return this.xml(htmlOrFn, outerHTML, html) } // Get / set id id (id) { // generate new id if no id set if (typeof id === 'undefined' && !this.node.id) { this.node.id = eid(this.type) } // dont't set directly with this.node.id to make `null` work correctly return this.attr('id', id) } // Gets index of given element index (element) { return [].slice.call(this.node.childNodes).indexOf(element.node) } // Get the last child last () { return adopt(this.node.lastChild) } // matches the element vs a css selector matches (selector) { const el = this.node const matcher = el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector || null return matcher && matcher.call(el, selector) } // Returns the parent element instance parent (type) { var parent = this // check for parent if (!parent.node.parentNode) return null // get parent element parent = adopt(parent.node.parentNode) if (!type) return parent // loop trough ancestors if type is given do { if (typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent } while ((parent = adopt(parent.node.parentNode))) return parent } // Basically does the same as `add()` but returns the added element instead put (element, i) { element = makeInstance(element) this.add(element, i) return element } // Add element to given container and return container putIn (parent, i) { return makeInstance(parent).add(this, i) } // Remove element remove () { if (this.parent()) { this.parent().removeElement(this) } return this } // Remove a given child removeElement (element) { this.node.removeChild(element.node) return this } // Replace this with element replace (element) { element = makeInstance(element) if (this.node.parentNode) { this.node.parentNode.replaceChild(element.node, this.node) } return element } round (precision = 2, map = null) { const factor = 10 ** precision const attrs = this.attr(map) for (const i in attrs) { if (typeof attrs[i] === 'number') { attrs[i] = Math.round(attrs[i] * factor) / factor } } this.attr(attrs) return this } // Import / Export raw svg svg (svgOrFn, outerSVG) { return this.xml(svgOrFn, outerSVG, svg) } // Return id on string conversion toString () { return this.id() } words (text) { // This is faster than removing all children and adding a new one this.node.textContent = text return this } wrap (node) { const parent = this.parent() if (!parent) { return this.addTo(node) } const position = parent.index(this) return parent.put(node, position).put(this) } // write svgjs data to the dom writeDataToDom () { // dump variables recursively this.each(function () { this.writeDataToDom() }) return this } // Import / Export raw svg xml (xmlOrFn, outerXML, ns) { var well, len, fragment if (xmlOrFn === false) { outerXML = false xmlOrFn = null } // act as getter if no svg string is given if (xmlOrFn == null || typeof xmlOrFn === 'function') { // The default for exports is, that the outerNode is included outerXML = outerXML == null ? true : outerXML // write svgjs data to the dom this.writeDataToDom() let current = this // An export modifier was passed if (xmlOrFn != null) { current = adopt(current.node.cloneNode(true)) // If the user wants outerHTML we need to process this node, too if (outerXML) { const result = xmlOrFn(current) current = result || current // The user does not want this node? Well, then he gets nothing if (result === false) return '' } // Deep loop through all children and apply modifier current.each(function () { const result = xmlOrFn(this) const _this = result || this // If modifier returns false, discard node if (result === false) { this.remove() // If modifier returns new node, use it } else if (result && this !== _this) { this.replace(_this) } }, true) } // Return outer or inner content return outerXML ? current.node.outerHTML : current.node.innerHTML } // Act as setter if we got a string // The default for import is, that the current node is not replaced outerXML = outerXML == null ? false : outerXML // Create temporary holder well = globals.document.createElementNS(ns, 'svg') fragment = globals.document.createDocumentFragment() // Dump raw svg well.innerHTML = xmlOrFn // Transplant nodes into the fragment for (len = well.children.length; len--;) { fragment.appendChild(well.firstElementChild) } const parent = this.parent() // Add the whole fragment at once return outerXML ? this.replace(fragment) && parent : this.add(fragment) } } extend(Dom, { attr, find, findOne }) register(Dom, 'Dom')