directive.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. var _ = require('./util')
  2. var config = require('./config')
  3. var Watcher = require('./watcher')
  4. var textParser = require('./parsers/text')
  5. var expParser = require('./parsers/expression')
  6. /**
  7. * A directive links a DOM element with a piece of data,
  8. * which is the result of evaluating an expression.
  9. * It registers a watcher with the expression and calls
  10. * the DOM update function when a change is triggered.
  11. *
  12. * @param {String} name
  13. * @param {Node} el
  14. * @param {Vue} vm
  15. * @param {Object} descriptor
  16. * - {String} expression
  17. * - {String} [arg]
  18. * - {Array<Object>} [filters]
  19. * @param {Object} def - directive definition object
  20. * @constructor
  21. */
  22. function Directive (name, el, vm, descriptor, def) {
  23. // public
  24. this.name = name
  25. this.el = el
  26. this.vm = vm
  27. // copy descriptor props
  28. this.raw = descriptor.raw
  29. this.expression = descriptor.expression
  30. this.arg = descriptor.arg
  31. this.filters = _.resolveFilters(vm, descriptor.filters)
  32. // private
  33. this._locked = false
  34. this._bound = false
  35. // init
  36. this._bind(def)
  37. }
  38. var p = Directive.prototype
  39. /**
  40. * Initialize the directive, mixin definition properties,
  41. * setup the watcher, call definition bind() and update()
  42. * if present.
  43. *
  44. * @param {Object} def
  45. */
  46. p._bind = function (def) {
  47. if (this.name !== 'cloak' && this.el.removeAttribute) {
  48. this.el.removeAttribute(config.prefix + this.name)
  49. }
  50. if (typeof def === 'function') {
  51. this.update = def
  52. } else {
  53. _.extend(this, def)
  54. }
  55. this._watcherExp = this.expression
  56. this._checkDynamicLiteral()
  57. if (this.bind) {
  58. this.bind()
  59. }
  60. if (
  61. this.update && this._watcherExp &&
  62. (!this.isLiteral || this._isDynamicLiteral) &&
  63. !this._checkStatement()
  64. ) {
  65. // wrapped updater for context
  66. var dir = this
  67. var update = this._update = function (val, oldVal) {
  68. if (!dir._locked) {
  69. dir.update(val, oldVal)
  70. }
  71. }
  72. // use raw expression as identifier because filters
  73. // make them different watchers
  74. var watcher = this.vm._watchers[this.raw]
  75. // v-repeat always creates a new watcher because it has
  76. // a special filter that's bound to its directive
  77. // instance.
  78. if (!watcher || this.name === 'repeat') {
  79. watcher = this.vm._watchers[this.raw] = new Watcher(
  80. this.vm,
  81. this._watcherExp,
  82. update, // callback
  83. {
  84. filters: this.filters,
  85. twoWay: this.twoWay,
  86. deep: this.deep
  87. }
  88. )
  89. } else {
  90. watcher.addCb(update)
  91. }
  92. this._watcher = watcher
  93. if (this._initValue != null) {
  94. watcher.set(this._initValue)
  95. } else {
  96. this.update(watcher.value)
  97. }
  98. }
  99. this._bound = true
  100. }
  101. /**
  102. * check if this is a dynamic literal binding.
  103. *
  104. * e.g. v-component="{{currentView}}"
  105. */
  106. p._checkDynamicLiteral = function () {
  107. var expression = this.expression
  108. if (expression && this.isLiteral) {
  109. var tokens = textParser.parse(expression)
  110. if (tokens) {
  111. var exp = textParser.tokensToExp(tokens)
  112. this.expression = this.vm.$get(exp)
  113. this._watcherExp = exp
  114. this._isDynamicLiteral = true
  115. }
  116. }
  117. }
  118. /**
  119. * Check if the directive is a function caller
  120. * and if the expression is a callable one. If both true,
  121. * we wrap up the expression and use it as the event
  122. * handler.
  123. *
  124. * e.g. v-on="click: a++"
  125. *
  126. * @return {Boolean}
  127. */
  128. p._checkStatement = function () {
  129. var expression = this.expression
  130. if (
  131. expression && this.acceptStatement &&
  132. !expParser.pathTestRE.test(expression)
  133. ) {
  134. var fn = expParser.parse(expression).get
  135. var vm = this.vm
  136. var handler = function () {
  137. fn.call(vm, vm)
  138. }
  139. if (this.filters) {
  140. handler = _.applyFilters(
  141. handler,
  142. this.filters.read,
  143. vm
  144. )
  145. }
  146. this.update(handler)
  147. return true
  148. }
  149. }
  150. /**
  151. * Check for an attribute directive param, e.g. lazy
  152. *
  153. * @param {String} name
  154. * @return {String}
  155. */
  156. p._checkParam = function (name) {
  157. var param = this.el.getAttribute(name)
  158. if (param !== null) {
  159. this.el.removeAttribute(name)
  160. }
  161. return param
  162. }
  163. /**
  164. * Teardown the watcher and call unbind.
  165. */
  166. p._teardown = function () {
  167. if (this._bound) {
  168. if (this.unbind) {
  169. this.unbind()
  170. }
  171. var watcher = this._watcher
  172. if (watcher && watcher.active) {
  173. watcher.removeCb(this._update)
  174. if (!watcher.active) {
  175. this.vm._watchers[this.raw] = null
  176. }
  177. }
  178. this._bound = false
  179. this.vm = this.el = this._watcher = null
  180. }
  181. }
  182. /**
  183. * Set the corresponding value with the setter.
  184. * This should only be used in two-way directives
  185. * e.g. v-model.
  186. *
  187. * @param {*} value
  188. * @param {Boolean} lock - prevent wrtie triggering update.
  189. * @public
  190. */
  191. p.set = function (value, lock) {
  192. if (this.twoWay) {
  193. if (lock) {
  194. this._locked = true
  195. }
  196. this._watcher.set(value)
  197. if (lock) {
  198. var self = this
  199. _.nextTick(function () {
  200. self._locked = false
  201. })
  202. }
  203. }
  204. }
  205. module.exports = Directive