directive-parser.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. var config = require('./config'),
  2. directives = require('./directives'),
  3. filters = require('./filters')
  4. var KEY_RE = /^[^\|<]+/,
  5. ARG_RE = /([^:]+):(.+)$/,
  6. FILTERS_RE = /\|[^\|<]+/g,
  7. FILTER_TOKEN_RE = /[^\s']+|'[^']+'/g,
  8. INVERSE_RE = /^!/,
  9. NESTING_RE = /^\^+/,
  10. ONEWAY_RE = /-oneway$/
  11. /*
  12. * Directive class
  13. * represents a single directive instance in the DOM
  14. */
  15. function Directive (directiveName, expression, oneway) {
  16. var prop,
  17. definition = directives[directiveName]
  18. // mix in properties from the directive definition
  19. if (typeof definition === 'function') {
  20. this._update = definition
  21. } else {
  22. this._update = definition.update
  23. for (prop in definition) {
  24. if (prop !== 'update') {
  25. this[prop] = definition[prop]
  26. }
  27. }
  28. }
  29. this.oneway = !!oneway
  30. this.directiveName = directiveName
  31. this.expression = expression.trim()
  32. this.rawKey = expression.match(KEY_RE)[0].trim()
  33. this.parseKey(this.rawKey)
  34. var filterExps = expression.match(FILTERS_RE)
  35. this.filters = filterExps
  36. ? filterExps.map(parseFilter)
  37. : null
  38. }
  39. var DirProto = Directive.prototype
  40. /*
  41. * called when a new value is set
  42. * for computed properties, this will only be called once
  43. * during initialization.
  44. */
  45. DirProto.update = function (value) {
  46. if (value && (value === this.value)) return
  47. this.value = value
  48. this.apply(value)
  49. }
  50. /*
  51. * -- computed property only --
  52. * called when a dependency has changed
  53. */
  54. DirProto.refresh = function () {
  55. // pass element and scope info to the getter
  56. // enables powerful context-aware bindings
  57. var value = this.value.get({
  58. el: this.el,
  59. scope: this.seed.scope
  60. })
  61. if (value === this.computedValue) return
  62. this.computedValue = value
  63. this.apply(value)
  64. this.binding.pub()
  65. }
  66. /*
  67. * Actually invoking the _update from the directive's definition
  68. */
  69. DirProto.apply = function (value) {
  70. if (this.inverse) value = !value
  71. this._update(
  72. this.filters
  73. ? this.applyFilters(value)
  74. : value
  75. )
  76. }
  77. /*
  78. * pipe the value through filters
  79. */
  80. DirProto.applyFilters = function (value) {
  81. var filtered = value, filter
  82. for (var i = 0, l = this.filters.length; i < l; i++) {
  83. filter = this.filters[i]
  84. if (!filter.apply) throw new Error('Unknown filter: ' + filter.name)
  85. filtered = filter.apply(filtered, filter.args)
  86. }
  87. return filtered
  88. }
  89. /*
  90. * parse a key, extract argument and nesting/root info
  91. */
  92. DirProto.parseKey = function (rawKey) {
  93. var argMatch = rawKey.match(ARG_RE)
  94. var key = argMatch
  95. ? argMatch[2].trim()
  96. : rawKey.trim()
  97. this.arg = argMatch
  98. ? argMatch[1].trim()
  99. : null
  100. this.inverse = INVERSE_RE.test(key)
  101. if (this.inverse) {
  102. key = key.slice(1)
  103. }
  104. var nesting = key.match(NESTING_RE)
  105. this.nesting = nesting
  106. ? nesting[0].length
  107. : false
  108. this.root = key.charAt(0) === '$'
  109. if (this.nesting) {
  110. key = key.replace(NESTING_RE, '')
  111. } else if (this.root) {
  112. key = key.slice(1)
  113. }
  114. this.key = key
  115. }
  116. /*
  117. * parse a filter expression
  118. */
  119. function parseFilter (filter) {
  120. var tokens = filter.slice(1)
  121. .match(FILTER_TOKEN_RE)
  122. .map(function (token) {
  123. return token.replace(/'/g, '').trim()
  124. })
  125. return {
  126. name : tokens[0],
  127. apply : filters[tokens[0]],
  128. args : tokens.length > 1
  129. ? tokens.slice(1)
  130. : null
  131. }
  132. }
  133. module.exports = {
  134. /*
  135. * make sure the directive and expression is valid
  136. * before we create an instance
  137. */
  138. parse: function (dirname, expression) {
  139. var prefix = config.prefix
  140. if (dirname.indexOf(prefix) === -1) return null
  141. dirname = dirname.slice(prefix.length + 1)
  142. var oneway = ONEWAY_RE.test(dirname)
  143. if (oneway) {
  144. dirname = dirname.slice(0, -7)
  145. }
  146. var dir = directives[dirname],
  147. valid = KEY_RE.test(expression)
  148. if (!dir) config.warn('unknown directive: ' + dirname)
  149. if (!valid) config.warn('invalid directive expression: ' + expression)
  150. return dir && valid
  151. ? new Directive(dirname, expression, oneway)
  152. : null
  153. }
  154. }