| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- 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|undefined} host - transclusion host target
- * @constructor
- */
- function Directive (name, el, vm, descriptor, def, host) {
- // 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 = descriptor.filters
- // private
- this._descriptor = descriptor
- this._host = host
- this._locked = false
- this._bound = false
- this._listeners = null
- // init
- this._bind(def)
- }
- /**
- * Initialize the directive, mixin definition properties,
- * setup the watcher, call definition bind() and update()
- * if present.
- *
- * @param {Object} def
- */
- Directive.prototype._bind = function (def) {
- if (
- (this.name !== 'cloak' || this.vm._isCompiled) &&
- this.el && 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._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-repeat.
- 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
- }
- )
- 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}}"
- */
- 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. v-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 vm = this.vm
- var handler = function () {
- fn.call(vm, vm)
- }
- if (this.filters) {
- handler = 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._checkParam = function (name) {
- var param = this.el.getAttribute(name)
- if (param !== null) {
- this.el.removeAttribute(name)
- param = this.vm.$interpolate(param)
- }
- 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
|