You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Dom.js 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. import {
  2. adopt,
  3. assignNewId,
  4. eid,
  5. extend,
  6. makeInstance,
  7. create,
  8. register
  9. } from '../utils/adopter.js'
  10. import { find, findOne } from '../modules/core/selector.js'
  11. import { globals } from '../utils/window.js'
  12. import { map } from '../utils/utils.js'
  13. import { svg, html } from '../modules/core/namespaces.js'
  14. import EventTarget from '../types/EventTarget.js'
  15. import List from '../types/List.js'
  16. import attr from '../modules/core/attr.js'
  17. export default class Dom extends EventTarget {
  18. constructor (node, attrs) {
  19. super()
  20. this.node = node
  21. this.type = node.nodeName
  22. if (attrs && node !== attrs) {
  23. this.attr(attrs)
  24. }
  25. }
  26. // Add given element at a position
  27. add (element, i) {
  28. element = makeInstance(element)
  29. // If non-root svg nodes are added we have to remove their namespaces
  30. if (element.removeNamespace && this.node instanceof globals.window.SVGElement) {
  31. element.removeNamespace()
  32. }
  33. if (i == null) {
  34. this.node.appendChild(element.node)
  35. } else if (element.node !== this.node.childNodes[i]) {
  36. this.node.insertBefore(element.node, this.node.childNodes[i])
  37. }
  38. return this
  39. }
  40. // Add element to given container and return self
  41. addTo (parent, i) {
  42. return makeInstance(parent).put(this, i)
  43. }
  44. // Returns all child elements
  45. children () {
  46. return new List(map(this.node.children, function (node) {
  47. return adopt(node)
  48. }))
  49. }
  50. // Remove all elements in this container
  51. clear () {
  52. // remove children
  53. while (this.node.hasChildNodes()) {
  54. this.node.removeChild(this.node.lastChild)
  55. }
  56. return this
  57. }
  58. // Clone element
  59. clone (deep = true, assignNewIds = true) {
  60. // write dom data to the dom so the clone can pickup the data
  61. this.writeDataToDom()
  62. // clone element
  63. var nodeClone = this.node.cloneNode(deep);
  64. if (assignNewIds) {
  65. // assign new id
  66. nodeClone = assignNewId(nodeClone);
  67. }
  68. return new this.constructor(nodeClone)
  69. }
  70. // Iterates over all children and invokes a given block
  71. each (block, deep) {
  72. const children = this.children()
  73. let i, il
  74. for (i = 0, il = children.length; i < il; i++) {
  75. block.apply(children[i], [ i, children ])
  76. if (deep) {
  77. children[i].each(block, deep)
  78. }
  79. }
  80. return this
  81. }
  82. element (nodeName, attrs) {
  83. return this.put(new Dom(create(nodeName), attrs))
  84. }
  85. // Get first child
  86. first () {
  87. return adopt(this.node.firstChild)
  88. }
  89. // Get a element at the given index
  90. get (i) {
  91. return adopt(this.node.childNodes[i])
  92. }
  93. getEventHolder () {
  94. return this.node
  95. }
  96. getEventTarget () {
  97. return this.node
  98. }
  99. // Checks if the given element is a child
  100. has (element) {
  101. return this.index(element) >= 0
  102. }
  103. html (htmlOrFn, outerHTML) {
  104. return this.xml(htmlOrFn, outerHTML, html)
  105. }
  106. // Get / set id
  107. id (id) {
  108. // generate new id if no id set
  109. if (typeof id === 'undefined' && !this.node.id) {
  110. this.node.id = eid(this.type)
  111. }
  112. // don't set directly with this.node.id to make `null` work correctly
  113. return this.attr('id', id)
  114. }
  115. // Gets index of given element
  116. index (element) {
  117. return [].slice.call(this.node.childNodes).indexOf(element.node)
  118. }
  119. // Get the last child
  120. last () {
  121. return adopt(this.node.lastChild)
  122. }
  123. // matches the element vs a css selector
  124. matches (selector) {
  125. const el = this.node
  126. const matcher = el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector || null
  127. return matcher && matcher.call(el, selector)
  128. }
  129. // Returns the parent element instance
  130. parent (type) {
  131. let parent = this
  132. // check for parent
  133. if (!parent.node.parentNode) return null
  134. // get parent element
  135. parent = adopt(parent.node.parentNode)
  136. if (!type) return parent
  137. // loop through ancestors if type is given
  138. do {
  139. if (typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent
  140. } while ((parent = adopt(parent.node.parentNode)))
  141. return parent
  142. }
  143. // Basically does the same as `add()` but returns the added element instead
  144. put (element, i) {
  145. element = makeInstance(element)
  146. this.add(element, i)
  147. return element
  148. }
  149. // Add element to given container and return container
  150. putIn (parent, i) {
  151. return makeInstance(parent).add(this, i)
  152. }
  153. // Remove element
  154. remove () {
  155. if (this.parent()) {
  156. this.parent().removeElement(this)
  157. }
  158. return this
  159. }
  160. // Remove a given child
  161. removeElement (element) {
  162. this.node.removeChild(element.node)
  163. return this
  164. }
  165. // Replace this with element
  166. replace (element) {
  167. element = makeInstance(element)
  168. if (this.node.parentNode) {
  169. this.node.parentNode.replaceChild(element.node, this.node)
  170. }
  171. return element
  172. }
  173. round (precision = 2, map = null) {
  174. const factor = 10 ** precision
  175. const attrs = this.attr(map)
  176. for (const i in attrs) {
  177. if (typeof attrs[i] === 'number') {
  178. attrs[i] = Math.round(attrs[i] * factor) / factor
  179. }
  180. }
  181. this.attr(attrs)
  182. return this
  183. }
  184. // Import / Export raw svg
  185. svg (svgOrFn, outerSVG) {
  186. return this.xml(svgOrFn, outerSVG, svg)
  187. }
  188. // Return id on string conversion
  189. toString () {
  190. return this.id()
  191. }
  192. words (text) {
  193. // This is faster than removing all children and adding a new one
  194. this.node.textContent = text
  195. return this
  196. }
  197. wrap (node) {
  198. const parent = this.parent()
  199. if (!parent) {
  200. return this.addTo(node)
  201. }
  202. const position = parent.index(this)
  203. return parent.put(node, position).put(this)
  204. }
  205. // write svgjs data to the dom
  206. writeDataToDom () {
  207. // dump variables recursively
  208. this.each(function () {
  209. this.writeDataToDom()
  210. })
  211. return this
  212. }
  213. // Import / Export raw svg
  214. xml (xmlOrFn, outerXML, ns) {
  215. if (typeof xmlOrFn === 'boolean') {
  216. ns = outerXML
  217. outerXML = xmlOrFn
  218. xmlOrFn = null
  219. }
  220. // act as getter if no svg string is given
  221. if (xmlOrFn == null || typeof xmlOrFn === 'function') {
  222. // The default for exports is, that the outerNode is included
  223. outerXML = outerXML == null ? true : outerXML
  224. // write svgjs data to the dom
  225. this.writeDataToDom()
  226. let current = this
  227. // An export modifier was passed
  228. if (xmlOrFn != null) {
  229. current = adopt(current.node.cloneNode(true))
  230. // If the user wants outerHTML we need to process this node, too
  231. if (outerXML) {
  232. const result = xmlOrFn(current)
  233. current = result || current
  234. // The user does not want this node? Well, then he gets nothing
  235. if (result === false) return ''
  236. }
  237. // Deep loop through all children and apply modifier
  238. current.each(function () {
  239. const result = xmlOrFn(this)
  240. const _this = result || this
  241. // If modifier returns false, discard node
  242. if (result === false) {
  243. this.remove()
  244. // If modifier returns new node, use it
  245. } else if (result && this !== _this) {
  246. this.replace(_this)
  247. }
  248. }, true)
  249. }
  250. // Return outer or inner content
  251. return outerXML
  252. ? current.node.outerHTML
  253. : current.node.innerHTML
  254. }
  255. // Act as setter if we got a string
  256. // The default for import is, that the current node is not replaced
  257. outerXML = outerXML == null ? false : outerXML
  258. // Create temporary holder
  259. const well = create('wrapper', ns)
  260. const fragment = globals.document.createDocumentFragment()
  261. // Dump raw svg
  262. well.innerHTML = xmlOrFn
  263. // Transplant nodes into the fragment
  264. for (let len = well.children.length; len--;) {
  265. fragment.appendChild(well.firstElementChild)
  266. }
  267. const parent = this.parent()
  268. // Add the whole fragment at once
  269. return outerXML
  270. ? this.replace(fragment) && parent
  271. : this.add(fragment)
  272. }
  273. }
  274. extend(Dom, { attr, find, findOne })
  275. register(Dom, 'Dom')