github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/public/libs/vue-1.0.24/src/directive.js (about)

     1  import {
     2    extend,
     3    bind,
     4    on,
     5    off,
     6    getAttr,
     7    getBindAttr,
     8    camelize,
     9    hyphenate,
    10    nextTick,
    11    warn
    12  } from './util/index'
    13  import Watcher from './watcher'
    14  import { parseExpression, isSimplePath } from './parsers/expression'
    15  
    16  function noop () {}
    17  
    18  /**
    19   * A directive links a DOM element with a piece of data,
    20   * which is the result of evaluating an expression.
    21   * It registers a watcher with the expression and calls
    22   * the DOM update function when a change is triggered.
    23   *
    24   * @param {Object} descriptor
    25   *                 - {String} name
    26   *                 - {Object} def
    27   *                 - {String} expression
    28   *                 - {Array<Object>} [filters]
    29   *                 - {Object} [modifiers]
    30   *                 - {Boolean} literal
    31   *                 - {String} attr
    32   *                 - {String} arg
    33   *                 - {String} raw
    34   *                 - {String} [ref]
    35   *                 - {Array<Object>} [interp]
    36   *                 - {Boolean} [hasOneTime]
    37   * @param {Vue} vm
    38   * @param {Node} el
    39   * @param {Vue} [host] - transclusion host component
    40   * @param {Object} [scope] - v-for scope
    41   * @param {Fragment} [frag] - owner fragment
    42   * @constructor
    43   */
    44  
    45  export default function Directive (descriptor, vm, el, host, scope, frag) {
    46    this.vm = vm
    47    this.el = el
    48    // copy descriptor properties
    49    this.descriptor = descriptor
    50    this.name = descriptor.name
    51    this.expression = descriptor.expression
    52    this.arg = descriptor.arg
    53    this.modifiers = descriptor.modifiers
    54    this.filters = descriptor.filters
    55    this.literal = this.modifiers && this.modifiers.literal
    56    // private
    57    this._locked = false
    58    this._bound = false
    59    this._listeners = null
    60    // link context
    61    this._host = host
    62    this._scope = scope
    63    this._frag = frag
    64    // store directives on node in dev mode
    65    if (process.env.NODE_ENV !== 'production' && this.el) {
    66      this.el._vue_directives = this.el._vue_directives || []
    67      this.el._vue_directives.push(this)
    68    }
    69  }
    70  
    71  /**
    72   * Initialize the directive, mixin definition properties,
    73   * setup the watcher, call definition bind() and update()
    74   * if present.
    75   */
    76  
    77  Directive.prototype._bind = function () {
    78    var name = this.name
    79    var descriptor = this.descriptor
    80  
    81    // remove attribute
    82    if (
    83      (name !== 'cloak' || this.vm._isCompiled) &&
    84      this.el && this.el.removeAttribute
    85    ) {
    86      var attr = descriptor.attr || ('v-' + name)
    87      this.el.removeAttribute(attr)
    88    }
    89  
    90    // copy def properties
    91    var def = descriptor.def
    92    if (typeof def === 'function') {
    93      this.update = def
    94    } else {
    95      extend(this, def)
    96    }
    97  
    98    // setup directive params
    99    this._setupParams()
   100  
   101    // initial bind
   102    if (this.bind) {
   103      this.bind()
   104    }
   105    this._bound = true
   106  
   107    if (this.literal) {
   108      this.update && this.update(descriptor.raw)
   109    } else if (
   110      (this.expression || this.modifiers) &&
   111      (this.update || this.twoWay) &&
   112      !this._checkStatement()
   113    ) {
   114      // wrapped updater for context
   115      var dir = this
   116      if (this.update) {
   117        this._update = function (val, oldVal) {
   118          if (!dir._locked) {
   119            dir.update(val, oldVal)
   120          }
   121        }
   122      } else {
   123        this._update = noop
   124      }
   125      var preProcess = this._preProcess
   126        ? bind(this._preProcess, this)
   127        : null
   128      var postProcess = this._postProcess
   129        ? bind(this._postProcess, this)
   130        : null
   131      var watcher = this._watcher = new Watcher(
   132        this.vm,
   133        this.expression,
   134        this._update, // callback
   135        {
   136          filters: this.filters,
   137          twoWay: this.twoWay,
   138          deep: this.deep,
   139          preProcess: preProcess,
   140          postProcess: postProcess,
   141          scope: this._scope
   142        }
   143      )
   144      // v-model with inital inline value need to sync back to
   145      // model instead of update to DOM on init. They would
   146      // set the afterBind hook to indicate that.
   147      if (this.afterBind) {
   148        this.afterBind()
   149      } else if (this.update) {
   150        this.update(watcher.value)
   151      }
   152    }
   153  }
   154  
   155  /**
   156   * Setup all param attributes, e.g. track-by,
   157   * transition-mode, etc...
   158   */
   159  
   160  Directive.prototype._setupParams = function () {
   161    if (!this.params) {
   162      return
   163    }
   164    var params = this.params
   165    // swap the params array with a fresh object.
   166    this.params = Object.create(null)
   167    var i = params.length
   168    var key, val, mappedKey
   169    while (i--) {
   170      key = hyphenate(params[i])
   171      mappedKey = camelize(key)
   172      val = getBindAttr(this.el, key)
   173      if (val != null) {
   174        // dynamic
   175        this._setupParamWatcher(mappedKey, val)
   176      } else {
   177        // static
   178        val = getAttr(this.el, key)
   179        if (val != null) {
   180          this.params[mappedKey] = val === '' ? true : val
   181        }
   182      }
   183    }
   184  }
   185  
   186  /**
   187   * Setup a watcher for a dynamic param.
   188   *
   189   * @param {String} key
   190   * @param {String} expression
   191   */
   192  
   193  Directive.prototype._setupParamWatcher = function (key, expression) {
   194    var self = this
   195    var called = false
   196    var unwatch = (this._scope || this.vm).$watch(expression, function (val, oldVal) {
   197      self.params[key] = val
   198      // since we are in immediate mode,
   199      // only call the param change callbacks if this is not the first update.
   200      if (called) {
   201        var cb = self.paramWatchers && self.paramWatchers[key]
   202        if (cb) {
   203          cb.call(self, val, oldVal)
   204        }
   205      } else {
   206        called = true
   207      }
   208    }, {
   209      immediate: true,
   210      user: false
   211    })
   212    ;(this._paramUnwatchFns || (this._paramUnwatchFns = [])).push(unwatch)
   213  }
   214  
   215  /**
   216   * Check if the directive is a function caller
   217   * and if the expression is a callable one. If both true,
   218   * we wrap up the expression and use it as the event
   219   * handler.
   220   *
   221   * e.g. on-click="a++"
   222   *
   223   * @return {Boolean}
   224   */
   225  
   226  Directive.prototype._checkStatement = function () {
   227    var expression = this.expression
   228    if (
   229      expression && this.acceptStatement &&
   230      !isSimplePath(expression)
   231    ) {
   232      var fn = parseExpression(expression).get
   233      var scope = this._scope || this.vm
   234      var handler = function (e) {
   235        scope.$event = e
   236        fn.call(scope, scope)
   237        scope.$event = null
   238      }
   239      if (this.filters) {
   240        handler = scope._applyFilters(handler, null, this.filters)
   241      }
   242      this.update(handler)
   243      return true
   244    }
   245  }
   246  
   247  /**
   248   * Set the corresponding value with the setter.
   249   * This should only be used in two-way directives
   250   * e.g. v-model.
   251   *
   252   * @param {*} value
   253   * @public
   254   */
   255  
   256  Directive.prototype.set = function (value) {
   257    /* istanbul ignore else */
   258    if (this.twoWay) {
   259      this._withLock(function () {
   260        this._watcher.set(value)
   261      })
   262    } else if (process.env.NODE_ENV !== 'production') {
   263      warn(
   264        'Directive.set() can only be used inside twoWay' +
   265        'directives.'
   266      )
   267    }
   268  }
   269  
   270  /**
   271   * Execute a function while preventing that function from
   272   * triggering updates on this directive instance.
   273   *
   274   * @param {Function} fn
   275   */
   276  
   277  Directive.prototype._withLock = function (fn) {
   278    var self = this
   279    self._locked = true
   280    fn.call(self)
   281    nextTick(function () {
   282      self._locked = false
   283    })
   284  }
   285  
   286  /**
   287   * Convenience method that attaches a DOM event listener
   288   * to the directive element and autometically tears it down
   289   * during unbind.
   290   *
   291   * @param {String} event
   292   * @param {Function} handler
   293   * @param {Boolean} [useCapture]
   294   */
   295  
   296  Directive.prototype.on = function (event, handler, useCapture) {
   297    on(this.el, event, handler, useCapture)
   298    ;(this._listeners || (this._listeners = []))
   299      .push([event, handler])
   300  }
   301  
   302  /**
   303   * Teardown the watcher and call unbind.
   304   */
   305  
   306  Directive.prototype._teardown = function () {
   307    if (this._bound) {
   308      this._bound = false
   309      if (this.unbind) {
   310        this.unbind()
   311      }
   312      if (this._watcher) {
   313        this._watcher.teardown()
   314      }
   315      var listeners = this._listeners
   316      var i
   317      if (listeners) {
   318        i = listeners.length
   319        while (i--) {
   320          off(this.el, listeners[i][0], listeners[i][1])
   321        }
   322      }
   323      var unwatchFns = this._paramUnwatchFns
   324      if (unwatchFns) {
   325        i = unwatchFns.length
   326        while (i--) {
   327          unwatchFns[i]()
   328        }
   329      }
   330      if (process.env.NODE_ENV !== 'production' && this.el) {
   331        this.el._vue_directives.$remove(this)
   332      }
   333      this.vm = this.el = this._watcher = this._listeners = null
   334    }
   335  }