2015-11-19 04:01:05 +08:00
|
|
|
import config from '../config'
|
2015-12-02 03:01:09 +08:00
|
|
|
import { isIE9 } from './env'
|
2015-11-19 04:01:05 +08:00
|
|
|
import { warn } from './debug'
|
|
|
|
|
import { camelize } from './lang'
|
|
|
|
|
import { removeWithTransition } from '../transition/index'
|
2014-08-12 20:59:49 +08:00
|
|
|
|
2015-06-26 03:17:54 +08:00
|
|
|
/**
|
|
|
|
|
* Query an element selector if it's not an element already.
|
|
|
|
|
*
|
|
|
|
|
* @param {String|Element} el
|
|
|
|
|
* @return {Element}
|
|
|
|
|
*/
|
|
|
|
|
|
2015-11-19 04:01:05 +08:00
|
|
|
export function query (el) {
|
2015-06-26 03:17:54 +08:00
|
|
|
if (typeof el === 'string') {
|
|
|
|
|
var selector = el
|
|
|
|
|
el = document.querySelector(el)
|
|
|
|
|
if (!el) {
|
2015-11-19 04:01:05 +08:00
|
|
|
process.env.NODE_ENV !== 'production' && warn(
|
2015-07-08 16:47:20 +08:00
|
|
|
'Cannot find element: ' + selector
|
|
|
|
|
)
|
2015-06-26 03:17:54 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return el
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-23 06:27:06 +08:00
|
|
|
/**
|
|
|
|
|
* Check if a node is in the document.
|
2015-04-06 23:42:56 +08:00
|
|
|
* Note: document.documentElement.contains should work here
|
|
|
|
|
* but always returns false for comment nodes in phantomjs,
|
2015-10-27 22:36:42 +08:00
|
|
|
* making unit tests difficult. This is fixed by doing the
|
2015-04-06 23:42:56 +08:00
|
|
|
* contains() check on the node's parentNode instead of
|
|
|
|
|
* the node itself.
|
2014-08-23 06:27:06 +08:00
|
|
|
*
|
|
|
|
|
* @param {Node} node
|
|
|
|
|
* @return {Boolean}
|
|
|
|
|
*/
|
|
|
|
|
|
2015-11-19 04:01:05 +08:00
|
|
|
export function inDoc (node) {
|
2015-06-03 01:44:11 +08:00
|
|
|
var doc = document.documentElement
|
2015-04-06 23:42:56 +08:00
|
|
|
var parent = node && node.parentNode
|
|
|
|
|
return doc === node ||
|
|
|
|
|
doc === parent ||
|
|
|
|
|
!!(parent && parent.nodeType === 1 && (doc.contains(parent)))
|
2014-08-23 06:27:06 +08:00
|
|
|
}
|
|
|
|
|
|
2014-08-12 20:59:49 +08:00
|
|
|
/**
|
2015-09-23 01:08:21 +08:00
|
|
|
* Get and remove an attribute from a node.
|
2014-08-12 20:59:49 +08:00
|
|
|
*
|
|
|
|
|
* @param {Node} node
|
2015-11-19 04:01:05 +08:00
|
|
|
* @param {String} _attr
|
2014-08-12 20:59:49 +08:00
|
|
|
*/
|
|
|
|
|
|
2015-11-19 04:01:05 +08:00
|
|
|
export function getAttr (node, _attr) {
|
|
|
|
|
var val = node.getAttribute(_attr)
|
2014-08-13 13:30:33 +08:00
|
|
|
if (val !== null) {
|
2015-11-19 04:01:05 +08:00
|
|
|
node.removeAttribute(_attr)
|
2014-08-13 13:30:33 +08:00
|
|
|
}
|
2014-08-12 20:59:49 +08:00
|
|
|
return val
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-06 10:23:54 +08:00
|
|
|
/**
|
2015-09-16 10:15:08 +08:00
|
|
|
* Get an attribute with colon or v-bind: prefix.
|
2015-09-06 10:23:54 +08:00
|
|
|
*
|
|
|
|
|
* @param {Node} node
|
|
|
|
|
* @param {String} name
|
|
|
|
|
* @return {String|null}
|
|
|
|
|
*/
|
|
|
|
|
|
2015-11-19 04:01:05 +08:00
|
|
|
export function getBindAttr (node, name) {
|
|
|
|
|
var val = getAttr(node, ':' + name)
|
2015-09-06 10:23:54 +08:00
|
|
|
if (val === null) {
|
2015-11-19 04:01:05 +08:00
|
|
|
val = getAttr(node, 'v-bind:' + name)
|
2015-09-06 10:23:54 +08:00
|
|
|
}
|
|
|
|
|
return val
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-10 08:29:49 +08:00
|
|
|
/**
|
|
|
|
|
* Check the presence of a bind attribute.
|
|
|
|
|
*
|
|
|
|
|
* @param {Node} node
|
|
|
|
|
* @param {String} name
|
|
|
|
|
* @return {Boolean}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
export function hasBindAttr (node, name) {
|
|
|
|
|
return node.hasAttribute(name) ||
|
|
|
|
|
node.hasAttribute(':' + name) ||
|
|
|
|
|
node.hasAttribute('v-bind:' + name)
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-27 03:56:32 +08:00
|
|
|
/**
|
|
|
|
|
* Insert el before target
|
|
|
|
|
*
|
|
|
|
|
* @param {Element} el
|
2015-04-06 07:46:11 +08:00
|
|
|
* @param {Element} target
|
2014-07-27 03:56:32 +08:00
|
|
|
*/
|
|
|
|
|
|
2015-11-19 04:01:05 +08:00
|
|
|
export function before (el, target) {
|
2014-07-27 03:56:32 +08:00
|
|
|
target.parentNode.insertBefore(el, target)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Insert el after target
|
|
|
|
|
*
|
|
|
|
|
* @param {Element} el
|
2015-04-06 07:46:11 +08:00
|
|
|
* @param {Element} target
|
2014-07-27 03:56:32 +08:00
|
|
|
*/
|
|
|
|
|
|
2015-11-19 04:01:05 +08:00
|
|
|
export function after (el, target) {
|
2014-07-27 03:56:32 +08:00
|
|
|
if (target.nextSibling) {
|
2015-11-19 04:01:05 +08:00
|
|
|
before(el, target.nextSibling)
|
2014-07-27 03:56:32 +08:00
|
|
|
} else {
|
|
|
|
|
target.parentNode.appendChild(el)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Remove el from DOM
|
|
|
|
|
*
|
|
|
|
|
* @param {Element} el
|
|
|
|
|
*/
|
|
|
|
|
|
2015-11-19 04:01:05 +08:00
|
|
|
export function remove (el) {
|
2014-07-27 03:56:32 +08:00
|
|
|
el.parentNode.removeChild(el)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Prepend el to target
|
|
|
|
|
*
|
|
|
|
|
* @param {Element} el
|
2015-04-06 07:46:11 +08:00
|
|
|
* @param {Element} target
|
2014-07-27 03:56:32 +08:00
|
|
|
*/
|
|
|
|
|
|
2015-11-19 04:01:05 +08:00
|
|
|
export function prepend (el, target) {
|
2014-07-27 03:56:32 +08:00
|
|
|
if (target.firstChild) {
|
2015-11-19 04:01:05 +08:00
|
|
|
before(el, target.firstChild)
|
2014-07-27 03:56:32 +08:00
|
|
|
} else {
|
|
|
|
|
target.appendChild(el)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-21 06:46:42 +08:00
|
|
|
/**
|
|
|
|
|
* Replace target with el
|
|
|
|
|
*
|
|
|
|
|
* @param {Element} target
|
|
|
|
|
* @param {Element} el
|
|
|
|
|
*/
|
|
|
|
|
|
2015-11-19 04:01:05 +08:00
|
|
|
export function replace (target, el) {
|
2014-08-21 06:46:42 +08:00
|
|
|
var parent = target.parentNode
|
2014-09-11 11:36:15 +08:00
|
|
|
if (parent) {
|
|
|
|
|
parent.replaceChild(el, target)
|
|
|
|
|
}
|
2014-08-21 06:46:42 +08:00
|
|
|
}
|
|
|
|
|
|
2014-08-31 11:08:00 +08:00
|
|
|
/**
|
|
|
|
|
* Add event listener shorthand.
|
|
|
|
|
*
|
|
|
|
|
* @param {Element} el
|
|
|
|
|
* @param {String} event
|
|
|
|
|
* @param {Function} cb
|
2016-01-30 15:18:07 +08:00
|
|
|
* @param {Boolean} [useCapture]
|
2014-08-31 11:08:00 +08:00
|
|
|
*/
|
|
|
|
|
|
2016-01-30 15:18:07 +08:00
|
|
|
export function on (el, event, cb, useCapture) {
|
|
|
|
|
el.addEventListener(event, cb, useCapture)
|
2014-08-31 11:08:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Remove event listener shorthand.
|
|
|
|
|
*
|
|
|
|
|
* @param {Element} el
|
|
|
|
|
* @param {String} event
|
|
|
|
|
* @param {Function} cb
|
|
|
|
|
*/
|
|
|
|
|
|
2015-11-19 04:01:05 +08:00
|
|
|
export function off (el, event, cb) {
|
2014-08-31 11:08:00 +08:00
|
|
|
el.removeEventListener(event, cb)
|
2014-09-14 07:09:26 +08:00
|
|
|
}
|
|
|
|
|
|
2015-12-02 03:01:09 +08:00
|
|
|
/**
|
|
|
|
|
* In IE9, setAttribute('class') will result in empty class
|
|
|
|
|
* if the element also has the :class attribute; However in
|
|
|
|
|
* PhantomJS, setting `className` does not work on SVG elements...
|
2015-12-05 01:07:03 +08:00
|
|
|
* So we have to do a conditional check here.
|
|
|
|
|
*
|
|
|
|
|
* @param {Element} el
|
|
|
|
|
* @param {String} cls
|
2015-12-02 03:01:09 +08:00
|
|
|
*/
|
|
|
|
|
|
2015-12-17 00:18:06 +08:00
|
|
|
export function setClass (el, cls) {
|
2015-12-05 01:07:03 +08:00
|
|
|
/* istanbul ignore if */
|
2015-12-11 00:49:40 +08:00
|
|
|
if (isIE9 && !(el instanceof SVGElement)) {
|
2015-12-02 03:01:09 +08:00
|
|
|
el.className = cls
|
2015-12-05 01:07:03 +08:00
|
|
|
} else {
|
2015-12-02 03:01:09 +08:00
|
|
|
el.setAttribute('class', cls)
|
|
|
|
|
}
|
2015-12-05 01:07:03 +08:00
|
|
|
}
|
2015-12-02 03:01:09 +08:00
|
|
|
|
2014-09-14 07:09:26 +08:00
|
|
|
/**
|
2014-11-05 02:18:41 +08:00
|
|
|
* Add class with compatibility for IE & SVG
|
2014-09-14 07:09:26 +08:00
|
|
|
*
|
|
|
|
|
* @param {Element} el
|
2015-12-07 10:54:06 +08:00
|
|
|
* @param {String} cls
|
2014-09-14 07:09:26 +08:00
|
|
|
*/
|
|
|
|
|
|
2015-11-19 04:01:05 +08:00
|
|
|
export function addClass (el, cls) {
|
2014-11-05 02:18:41 +08:00
|
|
|
if (el.classList) {
|
|
|
|
|
el.classList.add(cls)
|
|
|
|
|
} else {
|
|
|
|
|
var cur = ' ' + (el.getAttribute('class') || '') + ' '
|
|
|
|
|
if (cur.indexOf(' ' + cls + ' ') < 0) {
|
2015-12-02 03:01:09 +08:00
|
|
|
setClass(el, (cur + cls).trim())
|
2014-11-05 02:18:41 +08:00
|
|
|
}
|
2014-09-14 07:09:26 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2014-11-05 02:18:41 +08:00
|
|
|
* Remove class with compatibility for IE & SVG
|
2014-09-14 07:09:26 +08:00
|
|
|
*
|
|
|
|
|
* @param {Element} el
|
2015-12-07 10:54:06 +08:00
|
|
|
* @param {String} cls
|
2014-09-14 07:09:26 +08:00
|
|
|
*/
|
|
|
|
|
|
2015-11-19 04:01:05 +08:00
|
|
|
export function removeClass (el, cls) {
|
2014-11-05 02:18:41 +08:00
|
|
|
if (el.classList) {
|
|
|
|
|
el.classList.remove(cls)
|
|
|
|
|
} else {
|
|
|
|
|
var cur = ' ' + (el.getAttribute('class') || '') + ' '
|
|
|
|
|
var tar = ' ' + cls + ' '
|
|
|
|
|
while (cur.indexOf(tar) >= 0) {
|
|
|
|
|
cur = cur.replace(tar, ' ')
|
|
|
|
|
}
|
2015-12-02 03:01:09 +08:00
|
|
|
setClass(el, cur.trim())
|
2014-09-14 07:09:26 +08:00
|
|
|
}
|
2015-08-29 23:32:49 +08:00
|
|
|
if (!el.className) {
|
|
|
|
|
el.removeAttribute('class')
|
|
|
|
|
}
|
2014-12-02 08:36:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Extract raw content inside an element into a temporary
|
|
|
|
|
* container div
|
|
|
|
|
*
|
|
|
|
|
* @param {Element} el
|
2015-03-26 06:33:15 +08:00
|
|
|
* @param {Boolean} asFragment
|
2014-12-02 08:36:59 +08:00
|
|
|
* @return {Element}
|
|
|
|
|
*/
|
|
|
|
|
|
2015-11-19 04:01:05 +08:00
|
|
|
export function extractContent (el, asFragment) {
|
2014-12-02 08:36:59 +08:00
|
|
|
var child
|
|
|
|
|
var rawContent
|
2015-05-08 22:47:47 +08:00
|
|
|
/* istanbul ignore if */
|
|
|
|
|
if (
|
2015-11-19 04:01:05 +08:00
|
|
|
isTemplate(el) &&
|
2015-05-08 22:47:47 +08:00
|
|
|
el.content instanceof DocumentFragment
|
|
|
|
|
) {
|
|
|
|
|
el = el.content
|
|
|
|
|
}
|
2014-12-02 08:36:59 +08:00
|
|
|
if (el.hasChildNodes()) {
|
2015-11-19 04:01:05 +08:00
|
|
|
trimNode(el)
|
2015-03-26 06:33:15 +08:00
|
|
|
rawContent = asFragment
|
|
|
|
|
? document.createDocumentFragment()
|
|
|
|
|
: document.createElement('div')
|
2015-07-02 20:59:11 +08:00
|
|
|
/* eslint-disable no-cond-assign */
|
2014-12-02 08:36:59 +08:00
|
|
|
while (child = el.firstChild) {
|
2015-07-02 20:59:11 +08:00
|
|
|
/* eslint-enable no-cond-assign */
|
2014-12-02 08:36:59 +08:00
|
|
|
rawContent.appendChild(child)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return rawContent
|
2015-04-06 07:46:11 +08:00
|
|
|
}
|
2015-06-10 22:39:48 +08:00
|
|
|
|
2015-08-02 05:33:10 +08:00
|
|
|
/**
|
2016-01-29 09:47:54 +08:00
|
|
|
* Trim possible empty head/tail text and comment
|
|
|
|
|
* nodes inside a parent.
|
2015-08-02 05:33:10 +08:00
|
|
|
*
|
|
|
|
|
* @param {Node} node
|
|
|
|
|
*/
|
|
|
|
|
|
2015-11-19 04:01:05 +08:00
|
|
|
export function trimNode (node) {
|
2016-01-29 09:47:54 +08:00
|
|
|
var child
|
|
|
|
|
/* eslint-disable no-sequences */
|
|
|
|
|
while (child = node.firstChild, isTrimmable(child)) {
|
|
|
|
|
node.removeChild(child)
|
|
|
|
|
}
|
|
|
|
|
while (child = node.lastChild, isTrimmable(child)) {
|
|
|
|
|
node.removeChild(child)
|
|
|
|
|
}
|
|
|
|
|
/* eslint-enable no-sequences */
|
2015-08-02 05:33:10 +08:00
|
|
|
}
|
|
|
|
|
|
2016-01-29 09:47:54 +08:00
|
|
|
function isTrimmable (node) {
|
|
|
|
|
return node && (
|
|
|
|
|
(node.nodeType === 3 && !node.data.trim()) ||
|
|
|
|
|
node.nodeType === 8
|
|
|
|
|
)
|
2015-06-17 01:40:00 +08:00
|
|
|
}
|
|
|
|
|
|
2015-06-10 22:39:48 +08:00
|
|
|
/**
|
|
|
|
|
* Check if an element is a template tag.
|
|
|
|
|
* Note if the template appears inside an SVG its tagName
|
|
|
|
|
* will be in lowercase.
|
|
|
|
|
*
|
|
|
|
|
* @param {Element} el
|
|
|
|
|
*/
|
|
|
|
|
|
2015-11-19 04:01:05 +08:00
|
|
|
export function isTemplate (el) {
|
2015-06-10 22:39:48 +08:00
|
|
|
return el.tagName &&
|
|
|
|
|
el.tagName.toLowerCase() === 'template'
|
2015-06-17 01:40:00 +08:00
|
|
|
}
|
2015-07-06 14:19:27 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create an "anchor" for performing dom insertion/removals.
|
|
|
|
|
* This is used in a number of scenarios:
|
2015-07-28 23:19:16 +08:00
|
|
|
* - fragment instance
|
2015-07-06 14:19:27 +08:00
|
|
|
* - v-html
|
|
|
|
|
* - v-if
|
2015-09-02 22:15:30 +08:00
|
|
|
* - v-for
|
2015-07-06 14:19:27 +08:00
|
|
|
* - component
|
|
|
|
|
*
|
|
|
|
|
* @param {String} content
|
|
|
|
|
* @param {Boolean} persist - IE trashes empty textNodes on
|
|
|
|
|
* cloneNode(true), so in certain
|
|
|
|
|
* cases the anchor needs to be
|
|
|
|
|
* non-empty to be persisted in
|
|
|
|
|
* templates.
|
|
|
|
|
* @return {Comment|Text}
|
|
|
|
|
*/
|
|
|
|
|
|
2015-11-19 04:01:05 +08:00
|
|
|
export function createAnchor (content, persist) {
|
2015-11-11 02:53:15 +08:00
|
|
|
var anchor = config.debug
|
2015-07-06 14:19:27 +08:00
|
|
|
? document.createComment(content)
|
|
|
|
|
: document.createTextNode(persist ? ' ' : '')
|
2015-11-11 02:53:15 +08:00
|
|
|
anchor.__vue_anchor = true
|
|
|
|
|
return anchor
|
2015-07-06 14:19:27 +08:00
|
|
|
}
|
2015-09-11 23:32:00 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Find a component ref attribute that starts with $.
|
|
|
|
|
*
|
|
|
|
|
* @param {Element} node
|
|
|
|
|
* @return {String|undefined}
|
|
|
|
|
*/
|
|
|
|
|
|
2015-09-20 04:48:56 +08:00
|
|
|
var refRE = /^v-ref:/
|
2015-11-19 04:01:05 +08:00
|
|
|
export function findRef (node) {
|
2015-09-11 23:32:00 +08:00
|
|
|
if (node.hasAttributes()) {
|
|
|
|
|
var attrs = node.attributes
|
|
|
|
|
for (var i = 0, l = attrs.length; i < l; i++) {
|
|
|
|
|
var name = attrs[i].name
|
|
|
|
|
if (refRE.test(name)) {
|
2015-11-19 04:01:05 +08:00
|
|
|
return camelize(name.replace(refRE, ''))
|
2015-09-11 23:32:00 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-10-22 02:06:13 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Map a function to a range of nodes .
|
|
|
|
|
*
|
|
|
|
|
* @param {Node} node
|
|
|
|
|
* @param {Node} end
|
|
|
|
|
* @param {Function} op
|
|
|
|
|
*/
|
|
|
|
|
|
2015-11-19 04:01:05 +08:00
|
|
|
export function mapNodeRange (node, end, op) {
|
2015-10-22 02:06:13 +08:00
|
|
|
var next
|
|
|
|
|
while (node !== end) {
|
|
|
|
|
next = node.nextSibling
|
|
|
|
|
op(node)
|
|
|
|
|
node = next
|
|
|
|
|
}
|
|
|
|
|
op(end)
|
|
|
|
|
}
|
2015-10-23 04:08:31 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Remove a range of nodes with transition, store
|
|
|
|
|
* the nodes in a fragment with correct ordering,
|
|
|
|
|
* and call callback when done.
|
|
|
|
|
*
|
|
|
|
|
* @param {Node} start
|
|
|
|
|
* @param {Node} end
|
|
|
|
|
* @param {Vue} vm
|
|
|
|
|
* @param {DocumentFragment} frag
|
|
|
|
|
* @param {Function} cb
|
|
|
|
|
*/
|
|
|
|
|
|
2015-11-19 04:01:05 +08:00
|
|
|
export function removeNodeRange (start, end, vm, frag, cb) {
|
2015-10-23 04:08:31 +08:00
|
|
|
var done = false
|
|
|
|
|
var removed = 0
|
|
|
|
|
var nodes = []
|
2015-11-19 04:01:05 +08:00
|
|
|
mapNodeRange(start, end, function (node) {
|
2015-10-23 04:08:31 +08:00
|
|
|
if (node === end) done = true
|
|
|
|
|
nodes.push(node)
|
2015-11-19 04:01:05 +08:00
|
|
|
removeWithTransition(node, vm, onRemoved)
|
2015-10-23 04:08:31 +08:00
|
|
|
})
|
|
|
|
|
function onRemoved () {
|
|
|
|
|
removed++
|
|
|
|
|
if (done && removed >= nodes.length) {
|
|
|
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
|
|
|
frag.appendChild(nodes[i])
|
|
|
|
|
}
|
|
|
|
|
cb && cb()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|