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} [filters] * @param {Object} def - directive definition object * @param {Function} [linker] - pre-compiled linker function * @constructor */ function Directive (name, el, vm, descriptor, def, linker) { // 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._linker = linker 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() ) { // use raw expression as identifier because filters // make them different watchers var watcher = this.vm._watchers[this.raw] // wrapped updater for context var dir = this var update = this._update = function (val, oldVal) { if (!dir._locked) { dir.update(val, oldVal) } } if (!watcher) { watcher = this.vm._watchers[this.raw] = new Watcher( this.vm, this._watcherExp, update, // callback this.filters, this.twoWay, // need setter, 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