| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- var _ = require('./util')
- var config = require('./config')
- var Watcher = require('./watcher')
- var textParser = require('./parsers/text')
- var expParser = require('./parsers/expression')
- /**
- * A directive links a DOM element with a piece of data,
- * which is the result of evaluating an expression.
- * It registers a watcher with the expression and calls
- * the DOM update function when a change is triggered.
- *
- * @param {String} name
- * @param {Node} el
- * @param {Vue} vm
- * @param {Object} descriptor
- * - {String} expression
- * - {String} [arg]
- * - {Array<Object>} [filters]
- * @param {Object} def - directive definition object
- * @param {Vue} [host] - transclusion host component
- * @param {Object} [scope] - v-for scope
- * @param {Fragment} [frag] - owner fragment
- * @constructor
- */
- // TODO: 1.0.0 cleanup the arguments
- function Directive (name, el, vm, descriptor, def, host, scope, frag, arg, literal) {
- // public
- this.name = name
- this.el = el
- this.vm = vm
- // copy descriptor props
- this.descriptor = descriptor
- this.expression = descriptor.expression
- this.arg = arg || descriptor.arg
- this.filters = descriptor.filters
- // private
- this._def = def
- this._locked = false
- this._bound = false
- this._listeners = null
- // link context
- this._host = host
- this._scope = scope
- this._frag = frag
- // 1.0.0 literal
- this._literal = literal
- }
- /**
- * Initialize the directive, mixin definition properties,
- * setup the watcher, call definition bind() and update()
- * if present.
- *
- * @param {Object} def
- */
- Directive.prototype._bind = function () {
- var def = this._def
- var name = this.name
- if (
- (name !== 'cloak' || this.vm._isCompiled) &&
- this.el && this.el.removeAttribute
- ) {
- this.el.removeAttribute(
- config.prefix + this.name + (this._literal ? ':' : '')
- )
- // 1.0.0: remove bind/on
- // TODO simplify this
- if (name === 'attr') {
- this.el.removeAttribute('bind-' + this.arg)
- } else if (name === 'class' || name === 'style') {
- this.el.removeAttribute('bind-' + name)
- } else if (name === 'on') {
- this.el.removeAttribute('on-' + this.arg)
- } else if (name === 'transition') {
- this.el.removeAttribute(name)
- }
- }
- if (typeof def === 'function') {
- this.update = def
- } else {
- _.extend(this, def)
- }
- this._watcherExp = this.expression
- this._checkDynamicLiteral()
- if (this.bind) {
- this.bind()
- }
- if (this._literal) {
- this.update && this.update(this._descriptor.raw)
- } else if (this._watcherExp &&
- (this.update || this.twoWay) &&
- (!this.isLiteral || this._isDynamicLiteral) &&
- !this._checkStatement()) {
- // wrapped updater for context
- var dir = this
- var update = this._update = this.update
- ? function (val, oldVal) {
- if (!dir._locked) {
- dir.update(val, oldVal)
- }
- }
- : function () {} // noop if no update is provided
- // pre-process hook called before the value is piped
- // through the filters. used in v-for.
- var preProcess = this._preProcess
- ? _.bind(this._preProcess, this)
- : null
- var watcher = this._watcher = new Watcher(
- this.vm,
- this._watcherExp,
- update, // callback
- {
- filters: this.filters,
- twoWay: this.twoWay,
- deep: this.deep,
- preProcess: preProcess,
- scope: this._scope
- }
- )
- if (this._initValue != null) {
- watcher.set(this._initValue)
- } else if (this.update) {
- this.update(watcher.value)
- }
- }
- this._bound = true
- }
- /**
- * check if this is a dynamic literal binding.
- *
- * e.g. v-component="{{currentView}}"
- */
- // TODO: we shouldn't need this in 1.0.0.
- Directive.prototype._checkDynamicLiteral = function () {
- var expression = this.expression
- if (expression && this.isLiteral) {
- var tokens = textParser.parse(expression)
- if (tokens) {
- var exp = textParser.tokensToExp(tokens)
- this.expression = this.vm.$get(exp)
- this._watcherExp = exp
- this._isDynamicLiteral = true
- }
- }
- }
- /**
- * Check if the directive is a function caller
- * and if the expression is a callable one. If both true,
- * we wrap up the expression and use it as the event
- * handler.
- *
- * e.g. on-click="a++"
- *
- * @return {Boolean}
- */
- Directive.prototype._checkStatement = function () {
- var expression = this.expression
- if (
- expression && this.acceptStatement &&
- !expParser.isSimplePath(expression)
- ) {
- var fn = expParser.parse(expression).get
- var scope = this._scope || this.vm
- var handler = function () {
- fn.call(scope, scope)
- }
- if (this.filters) {
- handler = this.vm._applyFilters(handler, null, this.filters)
- }
- this.update(handler)
- return true
- }
- }
- /**
- * Check for an attribute directive param, e.g. lazy
- *
- * @param {String} name
- * @return {String}
- */
- Directive.prototype.param = function (name) {
- var param = this.el.getAttribute(name)
- if (param != null) {
- this.el.removeAttribute(name)
- param = (this._scope || this.vm).$interpolate(param)
- } else {
- param = this.el.getAttribute('bind-' + name)
- if (param != null) {
- this.el.removeAttribute('bind-' + name)
- param = (this._scope || this.vm).$eval(param)
- process.env.NODE_ENV !== 'production' && _.log(
- 'You are using bind- syntax on "' + name + '", which ' +
- 'is a directive param. It will be evaluated only once.'
- )
- }
- }
- return param
- }
- /**
- * Set the corresponding value with the setter.
- * This should only be used in two-way directives
- * e.g. v-model.
- *
- * @param {*} value
- * @public
- */
- Directive.prototype.set = function (value) {
- /* istanbul ignore else */
- if (this.twoWay) {
- this._withLock(function () {
- this._watcher.set(value)
- })
- } else if (process.env.NODE_ENV !== 'production') {
- _.warn(
- 'Directive.set() can only be used inside twoWay' +
- 'directives.'
- )
- }
- }
- /**
- * Execute a function while preventing that function from
- * triggering updates on this directive instance.
- *
- * @param {Function} fn
- */
- Directive.prototype._withLock = function (fn) {
- var self = this
- self._locked = true
- fn.call(self)
- _.nextTick(function () {
- self._locked = false
- })
- }
- /**
- * Convenience method that attaches a DOM event listener
- * to the directive element and autometically tears it down
- * during unbind.
- *
- * @param {String} event
- * @param {Function} handler
- */
- Directive.prototype.on = function (event, handler) {
- _.on(this.el, event, handler)
- ;(this._listeners || (this._listeners = []))
- .push([event, handler])
- }
- /**
- * Teardown the watcher and call unbind.
- */
- Directive.prototype._teardown = function () {
- if (this._bound) {
- this._bound = false
- if (this.unbind) {
- this.unbind()
- }
- if (this._watcher) {
- this._watcher.teardown()
- }
- var listeners = this._listeners
- if (listeners) {
- for (var i = 0; i < listeners.length; i++) {
- _.off(this.el, listeners[i][0], listeners[i][1])
- }
- }
- this.vm = this.el =
- this._watcher = this._listeners = null
- }
- }
- module.exports = Directive
|