| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- 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
- * @constructor
- */
- function Directive (name, el, vm, descriptor, def) {
- // public
- this.name = name
- this.el = el
- this.vm = vm
- // copy descriptor props
- this.raw = descriptor.raw
- this.expression = descriptor.expression
- this.arg = descriptor.arg
- this.filters = _.resolveFilters(vm, descriptor.filters)
- // private
- this._locked = false
- this._bound = false
- // init
- this._bind(def)
- }
- var p = Directive.prototype
- /**
- * Initialize the directive, mixin definition properties,
- * setup the watcher, call definition bind() and update()
- * if present.
- *
- * @param {Object} def
- */
- p._bind = function (def) {
- if (this.name !== 'cloak' && this.el.removeAttribute) {
- this.el.removeAttribute(config.prefix + this.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.update && this._watcherExp &&
- (!this.isLiteral || this._isDynamicLiteral) &&
- !this._checkStatement()
- ) {
- // wrapped updater for context
- var dir = this
- var update = this._update = function (val, oldVal) {
- if (!dir._locked) {
- dir.update(val, oldVal)
- }
- }
- // use raw expression as identifier because filters
- // make them different watchers
- var watcher = this.vm._watchers[this.raw]
- // v-repeat always creates a new watcher because it has
- // a special filter that's bound to its directive
- // instance.
- if (!watcher || this.name === 'repeat') {
- watcher = this.vm._watchers[this.raw] = new Watcher(
- this.vm,
- this._watcherExp,
- update, // callback
- {
- filters: this.filters,
- twoWay: this.twoWay,
- deep: this.deep
- }
- )
- } else {
- watcher.addCb(update)
- }
- this._watcher = watcher
- if (this._initValue != null) {
- watcher.set(this._initValue)
- } else {
- this.update(watcher.value)
- }
- }
- this._bound = true
- }
- /**
- * check if this is a dynamic literal binding.
- *
- * e.g. v-component="{{currentView}}"
- */
- p._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. v-on="click: a++"
- *
- * @return {Boolean}
- */
- p._checkStatement = function () {
- var expression = this.expression
- if (
- expression && this.acceptStatement &&
- !expParser.pathTestRE.test(expression)
- ) {
- var fn = expParser.parse(expression).get
- var vm = this.vm
- var handler = function () {
- fn.call(vm, vm)
- }
- if (this.filters) {
- handler = _.applyFilters(
- handler,
- this.filters.read,
- vm
- )
- }
- this.update(handler)
- return true
- }
- }
- /**
- * Check for an attribute directive param, e.g. lazy
- *
- * @param {String} name
- * @return {String}
- */
- p._checkParam = function (name) {
- var param = this.el.getAttribute(name)
- if (param !== null) {
- this.el.removeAttribute(name)
- }
- return param
- }
- /**
- * Teardown the watcher and call unbind.
- */
- p._teardown = function () {
- if (this._bound) {
- if (this.unbind) {
- this.unbind()
- }
- var watcher = this._watcher
- if (watcher && watcher.active) {
- watcher.removeCb(this._update)
- if (!watcher.active) {
- this.vm._watchers[this.raw] = null
- }
- }
- this._bound = false
- this.vm = this.el = this._watcher = null
- }
- }
- /**
- * Set the corresponding value with the setter.
- * This should only be used in two-way directives
- * e.g. v-model.
- *
- * @param {*} value
- * @param {Boolean} lock - prevent wrtie triggering update.
- * @public
- */
- p.set = function (value, lock) {
- if (this.twoWay) {
- if (lock) {
- this._locked = true
- }
- this._watcher.set(value)
- if (lock) {
- var self = this
- _.nextTick(function () {
- self._locked = false
- })
- }
- }
- }
- module.exports = Directive
|