dom.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. import config from '../config'
  2. import { isIE9 } from './env'
  3. import { warn } from './debug'
  4. import { camelize } from './lang'
  5. import { removeWithTransition } from '../transition/index'
  6. /**
  7. * Query an element selector if it's not an element already.
  8. *
  9. * @param {String|Element} el
  10. * @return {Element}
  11. */
  12. export function query (el) {
  13. if (typeof el === 'string') {
  14. var selector = el
  15. el = document.querySelector(el)
  16. if (!el) {
  17. process.env.NODE_ENV !== 'production' && warn(
  18. 'Cannot find element: ' + selector
  19. )
  20. }
  21. }
  22. return el
  23. }
  24. /**
  25. * Check if a node is in the document.
  26. * Note: document.documentElement.contains should work here
  27. * but always returns false for comment nodes in phantomjs,
  28. * making unit tests difficult. This is fixed by doing the
  29. * contains() check on the node's parentNode instead of
  30. * the node itself.
  31. *
  32. * @param {Node} node
  33. * @return {Boolean}
  34. */
  35. export function inDoc (node) {
  36. var doc = document.documentElement
  37. var parent = node && node.parentNode
  38. return doc === node ||
  39. doc === parent ||
  40. !!(parent && parent.nodeType === 1 && (doc.contains(parent)))
  41. }
  42. /**
  43. * Get and remove an attribute from a node.
  44. *
  45. * @param {Node} node
  46. * @param {String} _attr
  47. */
  48. export function getAttr (node, _attr) {
  49. var val = node.getAttribute(_attr)
  50. if (val !== null) {
  51. node.removeAttribute(_attr)
  52. }
  53. return val
  54. }
  55. /**
  56. * Get an attribute with colon or v-bind: prefix.
  57. *
  58. * @param {Node} node
  59. * @param {String} name
  60. * @return {String|null}
  61. */
  62. export function getBindAttr (node, name) {
  63. var val = getAttr(node, ':' + name)
  64. if (val === null) {
  65. val = getAttr(node, 'v-bind:' + name)
  66. }
  67. return val
  68. }
  69. /**
  70. * Insert el before target
  71. *
  72. * @param {Element} el
  73. * @param {Element} target
  74. */
  75. export function before (el, target) {
  76. target.parentNode.insertBefore(el, target)
  77. }
  78. /**
  79. * Insert el after target
  80. *
  81. * @param {Element} el
  82. * @param {Element} target
  83. */
  84. export function after (el, target) {
  85. if (target.nextSibling) {
  86. before(el, target.nextSibling)
  87. } else {
  88. target.parentNode.appendChild(el)
  89. }
  90. }
  91. /**
  92. * Remove el from DOM
  93. *
  94. * @param {Element} el
  95. */
  96. export function remove (el) {
  97. el.parentNode.removeChild(el)
  98. }
  99. /**
  100. * Prepend el to target
  101. *
  102. * @param {Element} el
  103. * @param {Element} target
  104. */
  105. export function prepend (el, target) {
  106. if (target.firstChild) {
  107. before(el, target.firstChild)
  108. } else {
  109. target.appendChild(el)
  110. }
  111. }
  112. /**
  113. * Replace target with el
  114. *
  115. * @param {Element} target
  116. * @param {Element} el
  117. */
  118. export function replace (target, el) {
  119. var parent = target.parentNode
  120. if (parent) {
  121. parent.replaceChild(el, target)
  122. }
  123. }
  124. /**
  125. * Add event listener shorthand.
  126. *
  127. * @param {Element} el
  128. * @param {String} event
  129. * @param {Function} cb
  130. */
  131. export function on (el, event, cb) {
  132. el.addEventListener(event, cb)
  133. }
  134. /**
  135. * Remove event listener shorthand.
  136. *
  137. * @param {Element} el
  138. * @param {String} event
  139. * @param {Function} cb
  140. */
  141. export function off (el, event, cb) {
  142. el.removeEventListener(event, cb)
  143. }
  144. /**
  145. * In IE9, setAttribute('class') will result in empty class
  146. * if the element also has the :class attribute; However in
  147. * PhantomJS, setting `className` does not work on SVG elements...
  148. * So we have to do a conditional check here.
  149. *
  150. * @param {Element} el
  151. * @param {String} cls
  152. */
  153. function setClass (el, cls) {
  154. /* istanbul ignore if */
  155. if (isIE9 && el.hasOwnProperty('className')) {
  156. el.className = cls
  157. } else {
  158. el.setAttribute('class', cls)
  159. }
  160. }
  161. /**
  162. * Add class with compatibility for IE & SVG
  163. *
  164. * @param {Element} el
  165. * @param {String} cls
  166. */
  167. export function addClass (el, cls) {
  168. if (el.classList) {
  169. el.classList.add(cls)
  170. } else {
  171. var cur = ' ' + (el.getAttribute('class') || '') + ' '
  172. if (cur.indexOf(' ' + cls + ' ') < 0) {
  173. setClass(el, (cur + cls).trim())
  174. }
  175. }
  176. }
  177. /**
  178. * Remove class with compatibility for IE & SVG
  179. *
  180. * @param {Element} el
  181. * @param {String} cls
  182. */
  183. export function removeClass (el, cls) {
  184. if (el.classList) {
  185. el.classList.remove(cls)
  186. } else {
  187. var cur = ' ' + (el.getAttribute('class') || '') + ' '
  188. var tar = ' ' + cls + ' '
  189. while (cur.indexOf(tar) >= 0) {
  190. cur = cur.replace(tar, ' ')
  191. }
  192. setClass(el, cur.trim())
  193. }
  194. if (!el.className) {
  195. el.removeAttribute('class')
  196. }
  197. }
  198. /**
  199. * Extract raw content inside an element into a temporary
  200. * container div
  201. *
  202. * @param {Element} el
  203. * @param {Boolean} asFragment
  204. * @return {Element}
  205. */
  206. export function extractContent (el, asFragment) {
  207. var child
  208. var rawContent
  209. /* istanbul ignore if */
  210. if (
  211. isTemplate(el) &&
  212. el.content instanceof DocumentFragment
  213. ) {
  214. el = el.content
  215. }
  216. if (el.hasChildNodes()) {
  217. trimNode(el)
  218. rawContent = asFragment
  219. ? document.createDocumentFragment()
  220. : document.createElement('div')
  221. /* eslint-disable no-cond-assign */
  222. while (child = el.firstChild) {
  223. /* eslint-enable no-cond-assign */
  224. rawContent.appendChild(child)
  225. }
  226. }
  227. return rawContent
  228. }
  229. /**
  230. * Trim possible empty head/tail textNodes inside a parent.
  231. *
  232. * @param {Node} node
  233. */
  234. export function trimNode (node) {
  235. trim(node, node.firstChild)
  236. trim(node, node.lastChild)
  237. }
  238. function trim (parent, node) {
  239. if (node && node.nodeType === 3 && !node.data.trim()) {
  240. parent.removeChild(node)
  241. }
  242. }
  243. /**
  244. * Check if an element is a template tag.
  245. * Note if the template appears inside an SVG its tagName
  246. * will be in lowercase.
  247. *
  248. * @param {Element} el
  249. */
  250. export function isTemplate (el) {
  251. return el.tagName &&
  252. el.tagName.toLowerCase() === 'template'
  253. }
  254. /**
  255. * Create an "anchor" for performing dom insertion/removals.
  256. * This is used in a number of scenarios:
  257. * - fragment instance
  258. * - v-html
  259. * - v-if
  260. * - v-for
  261. * - component
  262. *
  263. * @param {String} content
  264. * @param {Boolean} persist - IE trashes empty textNodes on
  265. * cloneNode(true), so in certain
  266. * cases the anchor needs to be
  267. * non-empty to be persisted in
  268. * templates.
  269. * @return {Comment|Text}
  270. */
  271. export function createAnchor (content, persist) {
  272. var anchor = config.debug
  273. ? document.createComment(content)
  274. : document.createTextNode(persist ? ' ' : '')
  275. anchor.__vue_anchor = true
  276. return anchor
  277. }
  278. /**
  279. * Find a component ref attribute that starts with $.
  280. *
  281. * @param {Element} node
  282. * @return {String|undefined}
  283. */
  284. var refRE = /^v-ref:/
  285. export function findRef (node) {
  286. if (node.hasAttributes()) {
  287. var attrs = node.attributes
  288. for (var i = 0, l = attrs.length; i < l; i++) {
  289. var name = attrs[i].name
  290. if (refRE.test(name)) {
  291. return camelize(name.replace(refRE, ''))
  292. }
  293. }
  294. }
  295. }
  296. /**
  297. * Map a function to a range of nodes .
  298. *
  299. * @param {Node} node
  300. * @param {Node} end
  301. * @param {Function} op
  302. */
  303. export function mapNodeRange (node, end, op) {
  304. var next
  305. while (node !== end) {
  306. next = node.nextSibling
  307. op(node)
  308. node = next
  309. }
  310. op(end)
  311. }
  312. /**
  313. * Remove a range of nodes with transition, store
  314. * the nodes in a fragment with correct ordering,
  315. * and call callback when done.
  316. *
  317. * @param {Node} start
  318. * @param {Node} end
  319. * @param {Vue} vm
  320. * @param {DocumentFragment} frag
  321. * @param {Function} cb
  322. */
  323. export function removeNodeRange (start, end, vm, frag, cb) {
  324. var done = false
  325. var removed = 0
  326. var nodes = []
  327. mapNodeRange(start, end, function (node) {
  328. if (node === end) done = true
  329. nodes.push(node)
  330. removeWithTransition(node, vm, onRemoved)
  331. })
  332. function onRemoved () {
  333. removed++
  334. if (done && removed >= nodes.length) {
  335. for (var i = 0; i < nodes.length; i++) {
  336. frag.appendChild(nodes[i])
  337. }
  338. cb && cb()
  339. }
  340. }
  341. }