directive.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. var utils = require('./utils'),
  2. directives = require('./directives'),
  3. // Regexes!
  4. // regex to split multiple directive expressions
  5. // split by commas, but ignore commas within quotes, parens and escapes.
  6. SPLIT_RE = /(?:['"](?:\\.|[^'"])*['"]|\((?:\\.|[^\)])*\)|\\.|[^,])+/g,
  7. // match up to the first single pipe, ignore those within quotes.
  8. KEY_RE = /^(?:['"](?:\\.|[^'"])*['"]|\\.|[^\|]|\|\|)+/,
  9. ARG_RE = /^([\w-$ ]+):(.+)$/,
  10. FILTERS_RE = /\|[^\|]+/g,
  11. FILTER_TOKEN_RE = /[^\s']+|'[^']+'|[^\s"]+|"[^"]+"/g,
  12. NESTING_RE = /^\$(parent|root)\./,
  13. SINGLE_VAR_RE = /^[\w\.$]+$/
  14. /**
  15. * Directive class
  16. * represents a single directive instance in the DOM
  17. */
  18. function Directive (dirname, definition, expression, rawKey, compiler, node) {
  19. this.name = dirname
  20. this.compiler = compiler
  21. this.vm = compiler.vm
  22. this.el = node
  23. this.computeFilters = false
  24. var isEmpty = expression === ''
  25. // mix in properties from the directive definition
  26. if (typeof definition === 'function') {
  27. this[isEmpty ? 'bind' : '_update'] = definition
  28. } else {
  29. for (var prop in definition) {
  30. if (prop === 'unbind' || prop === 'update') {
  31. this['_' + prop] = definition[prop]
  32. } else {
  33. this[prop] = definition[prop]
  34. }
  35. }
  36. }
  37. // empty expression, we're done.
  38. if (isEmpty || this.isEmpty) {
  39. this.isEmpty = true
  40. return
  41. }
  42. this.expression = (
  43. this.isLiteral
  44. ? compiler.eval(expression)
  45. : expression
  46. ).trim()
  47. parseKey(this, rawKey)
  48. var filterExps = this.expression.slice(rawKey.length).match(FILTERS_RE)
  49. if (filterExps) {
  50. this.filters = []
  51. for (var i = 0, l = filterExps.length, filter; i < l; i++) {
  52. filter = parseFilter(filterExps[i], this.compiler)
  53. if (filter) {
  54. this.filters.push(filter)
  55. if (filter.apply.computed) {
  56. // some special filters, e.g. filterBy & orderBy,
  57. // can involve VM properties and they often need to
  58. // be computed.
  59. this.computeFilters = true
  60. }
  61. }
  62. }
  63. if (!this.filters.length) this.filters = null
  64. } else {
  65. this.filters = null
  66. }
  67. this.isExp =
  68. this.computeFilters ||
  69. !SINGLE_VAR_RE.test(this.key) ||
  70. NESTING_RE.test(this.key)
  71. }
  72. var DirProto = Directive.prototype
  73. /**
  74. * parse a key, extract argument and nesting/root info
  75. */
  76. function parseKey (dir, rawKey) {
  77. var key = rawKey
  78. if (rawKey.indexOf(':') > -1) {
  79. var argMatch = rawKey.match(ARG_RE)
  80. key = argMatch
  81. ? argMatch[2].trim()
  82. : key
  83. dir.arg = argMatch
  84. ? argMatch[1].trim()
  85. : null
  86. }
  87. dir.key = key
  88. }
  89. /**
  90. * parse a filter expression
  91. */
  92. function parseFilter (filter, compiler) {
  93. var tokens = filter.slice(1).match(FILTER_TOKEN_RE)
  94. if (!tokens) return
  95. var name = tokens[0],
  96. apply = compiler.getOption('filters', name)
  97. if (!apply) {
  98. utils.warn('Unknown filter: ' + name)
  99. return
  100. }
  101. return {
  102. name : name,
  103. apply : apply,
  104. args : tokens.length > 1
  105. ? tokens.slice(1)
  106. : null
  107. }
  108. }
  109. /**
  110. * called when a new value is set
  111. * for computed properties, this will only be called once
  112. * during initialization.
  113. */
  114. DirProto.update = function (value, init) {
  115. var type = utils.typeOf(value)
  116. if (init || value !== this.value || type === 'Object' || type === 'Array') {
  117. this.value = value
  118. if (this._update) {
  119. this._update(
  120. this.filters && !this.computeFilters
  121. ? this.applyFilters(value)
  122. : value,
  123. init
  124. )
  125. }
  126. }
  127. }
  128. /**
  129. * pipe the value through filters
  130. */
  131. DirProto.applyFilters = function (value) {
  132. var filtered = value, filter
  133. for (var i = 0, l = this.filters.length; i < l; i++) {
  134. filter = this.filters[i]
  135. filtered = filter.apply.apply(this.vm, [filtered].concat(filter.args))
  136. }
  137. return filtered
  138. }
  139. /**
  140. * Unbind diretive
  141. */
  142. DirProto.unbind = function () {
  143. // this can be called before the el is even assigned...
  144. if (!this.el || !this.vm) return
  145. if (this._unbind) this._unbind()
  146. this.vm = this.el = this.binding = this.compiler = null
  147. }
  148. // exposed methods ------------------------------------------------------------
  149. /**
  150. * split a unquoted-comma separated expression into
  151. * multiple clauses
  152. */
  153. Directive.split = function (exp) {
  154. return exp.indexOf(',') > -1
  155. ? exp.match(SPLIT_RE) || ['']
  156. : [exp]
  157. }
  158. /**
  159. * make sure the directive and expression is valid
  160. * before we create an instance
  161. */
  162. Directive.parse = function (dirname, expression, compiler, node) {
  163. var dir = compiler.getOption('directives', dirname) || directives[dirname]
  164. if (!dir) {
  165. utils.warn('unknown directive: ' + dirname)
  166. return
  167. }
  168. var rawKey
  169. if (expression.indexOf('|') > -1) {
  170. var keyMatch = expression.match(KEY_RE)
  171. if (keyMatch) {
  172. rawKey = keyMatch[0].trim()
  173. }
  174. } else {
  175. rawKey = expression.trim()
  176. }
  177. // have a valid raw key, or be an empty directive
  178. if (rawKey || expression === '') {
  179. return new Directive(dirname, dir, expression, rawKey, compiler, node)
  180. } else {
  181. utils.warn('invalid directive expression: ' + expression)
  182. }
  183. }
  184. module.exports = Directive