directive.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. var config = require('./config'),
  2. utils = require('./utils'),
  3. directives = require('./directives'),
  4. filters = require('./filters'),
  5. // Regexes!
  6. KEY_RE = /^[^\|]+/,
  7. ARG_RE = /([^:]+):(.+)$/,
  8. FILTERS_RE = /\|[^\|]+/g,
  9. FILTER_TOKEN_RE = /[^\s']+|'[^']+'/g,
  10. NESTING_RE = /^\^+/,
  11. SINGLE_VAR_RE = /^[\w\.\$]+$/
  12. /**
  13. * Directive class
  14. * represents a single directive instance in the DOM
  15. */
  16. function Directive (definition, expression, rawKey, compiler, node) {
  17. this.compiler = compiler
  18. this.vm = compiler.vm
  19. this.el = node
  20. var isSimple = expression === ''
  21. // mix in properties from the directive definition
  22. if (typeof definition === 'function') {
  23. this[isSimple ? 'bind' : '_update'] = definition
  24. } else {
  25. for (var prop in definition) {
  26. if (prop === 'unbind' || prop === 'update') {
  27. this['_' + prop] = definition[prop]
  28. } else {
  29. this[prop] = definition[prop]
  30. }
  31. }
  32. }
  33. // empty expression, we're done.
  34. if (isSimple) {
  35. this.isSimple = true
  36. return
  37. }
  38. this.expression = expression.trim()
  39. this.rawKey = rawKey
  40. parseKey(this, rawKey)
  41. this.isExp = !SINGLE_VAR_RE.test(this.key)
  42. var filterExps = expression.match(FILTERS_RE)
  43. if (filterExps) {
  44. this.filters = []
  45. var i = 0, l = filterExps.length, filter
  46. for (; i < l; i++) {
  47. filter = parseFilter(filterExps[i], this.compiler)
  48. if (filter) this.filters.push(filter)
  49. }
  50. if (!this.filters.length) this.filters = null
  51. } else {
  52. this.filters = null
  53. }
  54. }
  55. var DirProto = Directive.prototype
  56. /**
  57. * parse a key, extract argument and nesting/root info
  58. */
  59. function parseKey (dir, rawKey) {
  60. var argMatch = rawKey.match(ARG_RE)
  61. var key = argMatch
  62. ? argMatch[2].trim()
  63. : rawKey.trim()
  64. dir.arg = argMatch
  65. ? argMatch[1].trim()
  66. : null
  67. var nesting = key.match(NESTING_RE)
  68. dir.nesting = nesting
  69. ? nesting[0].length
  70. : false
  71. dir.root = key.charAt(0) === '*'
  72. if (dir.nesting) {
  73. key = key.replace(NESTING_RE, '')
  74. } else if (dir.root) {
  75. key = key.slice(1)
  76. }
  77. dir.key = key
  78. }
  79. /**
  80. * parse a filter expression
  81. */
  82. function parseFilter (filter, compiler) {
  83. var tokens = filter.slice(1).match(FILTER_TOKEN_RE)
  84. if (!tokens) return
  85. tokens = tokens.map(function (token) {
  86. return token.replace(/'/g, '').trim()
  87. })
  88. var name = tokens[0],
  89. apply = compiler.getOption('filters', name) || filters[name]
  90. if (!apply) {
  91. utils.warn('Unknown filter: ' + name)
  92. return
  93. }
  94. return {
  95. name : name,
  96. apply : apply,
  97. args : tokens.length > 1
  98. ? tokens.slice(1)
  99. : null
  100. }
  101. }
  102. /**
  103. * called when a new value is set
  104. * for computed properties, this will only be called once
  105. * during initialization.
  106. */
  107. DirProto.update = function (value, init) {
  108. if (!init && value === this.value) return
  109. this.value = value
  110. this.apply(value)
  111. }
  112. /**
  113. * -- computed property only --
  114. * called when a dependency has changed
  115. */
  116. DirProto.refresh = function (value) {
  117. // pass element and viewmodel info to the getter
  118. // enables context-aware bindings
  119. if (value) this.value = value
  120. if (this.isFn) {
  121. value = this.value
  122. } else {
  123. value = this.value.get({
  124. el: this.el,
  125. vm: this.vm
  126. })
  127. if (value !== undefined && value === this.computedValue) return
  128. this.computedValue = value
  129. }
  130. this.apply(value)
  131. }
  132. /**
  133. * Actually invoking the _update from the directive's definition
  134. */
  135. DirProto.apply = function (value) {
  136. this._update(
  137. this.filters
  138. ? this.applyFilters(value)
  139. : value
  140. )
  141. }
  142. /**
  143. * pipe the value through filters
  144. */
  145. DirProto.applyFilters = function (value) {
  146. var filtered = value, filter
  147. for (var i = 0, l = this.filters.length; i < l; i++) {
  148. filter = this.filters[i]
  149. filtered = filter.apply(filtered, filter.args)
  150. }
  151. return filtered
  152. }
  153. /**
  154. * Unbind diretive
  155. * @ param {Boolean} update
  156. * Sometimes we call unbind before an update (i.e. not destroy)
  157. * just to teardown previous stuff, in that case we do not want
  158. * to null everything.
  159. */
  160. DirProto.unbind = function (update) {
  161. // this can be called before the el is even assigned...
  162. if (!this.el) return
  163. if (this._unbind) this._unbind(update)
  164. if (!update) this.vm = this.el = this.binding = this.compiler = null
  165. }
  166. /**
  167. * make sure the directive and expression is valid
  168. * before we create an instance
  169. */
  170. Directive.parse = function (dirname, expression, compiler, node) {
  171. var prefix = config.prefix
  172. if (dirname.indexOf(prefix) === -1) return
  173. dirname = dirname.slice(prefix.length + 1)
  174. var dir = compiler.getOption('directives', dirname) || directives[dirname]
  175. if (!dir) return utils.warn('unknown directive: ' + dirname)
  176. var keyMatch = expression.match(KEY_RE),
  177. rawKey = keyMatch && keyMatch[0].trim()
  178. // have a valid raw key, or be an empty directive
  179. return (rawKey || expression === '')
  180. ? new Directive(dir, expression, rawKey, compiler, node)
  181. : utils.warn('invalid directive expression: ' + expression)
  182. }
  183. module.exports = Directive