Browse Source

functional tests pass

Evan You 12 năm trước cách đây
mục cha
commit
8ce2b3dbde
4 tập tin đã thay đổi với 81 bổ sung183 xóa
  1. 31 14
      src/compiler.js
  2. 41 162
      src/directive.js
  3. 9 6
      src/text-parser.js
  4. 0 1
      test/unit/specs/api.js

+ 31 - 14
src/compiler.js

@@ -465,11 +465,11 @@ CompilerProto.checkPriorityDir = function (dirname, node, root) {
         root !== true &&
         (Ctor = this.resolveComponent(node, undefined, true))
     ) {
-        directive = Directive.build(dirname, '', this, node)
+        directive = this.parseDirective(dirname, '', node)
         directive.Ctor = Ctor
     } else {
         expression = utils.attr(node, dirname)
-        directive = expression && Directive.build(dirname, expression, this, node)
+        directive = expression && this.parseDirective(dirname, expression, node)
     }
     if (directive) {
         if (root === true) {
@@ -525,7 +525,7 @@ CompilerProto.compileElement = function (node, root) {
         var prefix = config.prefix + '-',
             attrs = slice.call(node.attributes),
             params = this.options.paramAttributes,
-            attr, isDirective, exps, exp, directive, dirname
+            attr, isDirective, exp, directives, directive, dirname
 
         for (i = 0, l = attrs.length; i < l; i++) {
 
@@ -535,26 +535,24 @@ CompilerProto.compileElement = function (node, root) {
             if (attr.name.indexOf(prefix) === 0) {
                 // a directive - split, parse and bind it.
                 isDirective = true
-                exps = Directive.split(attr.value)
+                dirname = attr.name.slice(prefix.length)
+                // build with multiple: true
+                directives = this.parseDirective(dirname, attr.value, node, true)
                 // loop through clauses (separated by ",")
                 // inside each attribute
-                for (j = 0, k = exps.length; j < k; j++) {
-                    exp = exps[j]
-                    dirname = attr.name.slice(prefix.length)
-                    directive = Directive.build(dirname, exp, this, node)
-
+                for (j = 0, k = directives.length; j < k; j++) {
+                    directive = directives[j]
                     if (dirname === 'with') {
                         this.bindDirective(directive, this.parent)
                     } else {
                         this.bindDirective(directive)
                     }
-                    
                 }
             } else if (config.interpolate) {
                 // non directive attribute, check interpolation tags
                 exp = TextParser.parseAttr(attr.value)
                 if (exp) {
-                    directive = Directive.build('attr', attr.name + ':' + exp, this, node)
+                    directive = this.parseDirective('attr', attr.name + ':' + exp, node)
                     if (params && params.indexOf(attr.name) > -1) {
                         // a param attribute... we should use the parent binding
                         // to avoid circular updates like size={{size}}
@@ -595,14 +593,14 @@ CompilerProto.compileTextNode = function (node) {
         if (token.key) { // a binding
             if (token.key.charAt(0) === '>') { // a partial
                 el = document.createComment('ref')
-                directive = Directive.build('partial', token.key.slice(1), this, el)
+                directive = this.parseDirective('partial', token.key.slice(1), el)
             } else {
                 if (!token.html) { // text binding
                     el = document.createTextNode('')
-                    directive = Directive.build('text', token.key, this, el)
+                    directive = this.parseDirective('text', token.key, el)
                 } else { // html binding
                     el = document.createComment(config.prefix + '-html')
-                    directive = Directive.build('html', token.key, this, el)
+                    directive = this.parseDirective('html', token.key, el)
                 }
             }
         } else { // a plain string
@@ -618,6 +616,25 @@ CompilerProto.compileTextNode = function (node) {
     node.parentNode.removeChild(node)
 }
 
+/**
+ *  Parse a directive name/value pair into one or more
+ *  directive instances
+ */
+CompilerProto.parseDirective = function (name, value, el, multiple) {
+    var compiler = this,
+        definition = compiler.getOption('directives', name)
+    if (definition) {
+        // parse into AST-like objects
+        var asts = Directive.parse(value)
+        return multiple
+            ? asts.map(build)
+            : build(asts[0])
+    }
+    function build (ast) {
+        return new Directive(name, ast, definition, compiler, el)
+    }
+}
+
 /**
  *  Add a directive instance to the correct binding & viewmodel
  */

+ 41 - 162
src/directive.js

@@ -1,11 +1,6 @@
-var utils      = require('./utils'),
-    dirId      = 1,
-
-    // match up to the first single pipe, ignore those within quotes.
-    KEY_RE          = /^(?:['"](?:\\.|[^'"])*['"]|\\.|[^\|]|\|\|)+/,
-    ARG_RE          = /^([\w-$ ]+):(.+)$/,
-    FILTERS_RE      = /\|[^\|]+/g,
-    FILTER_TOKEN_RE = /[^\s']+|'[^']+'|[^\s"]+|"[^"]+"/g,
+var dirId           = 1,
+    ARG_RE          = /^[\w-]+$/,
+    FILTER_TOKEN_RE = /[^\s'"]+|'[^']+'|"[^"]+"/g,
     NESTING_RE      = /^\$(parent|root)\./,
     SINGLE_VAR_RE   = /^[\w\.$]+$/,
     QUOTE_RE        = /"/g
@@ -14,16 +9,19 @@ var utils      = require('./utils'),
  *  Directive class
  *  represents a single directive instance in the DOM
  */
-function Directive (dirname, definition, expression, rawKey, compiler, node) {
+function Directive (name, ast, definition, compiler, el) {
 
     this.id             = dirId++
-    this.name           = dirname
+    this.name           = name
     this.compiler       = compiler
     this.vm             = compiler.vm
-    this.el             = node
+    this.el             = el
     this.computeFilters = false
+    this.key            = ast.key
+    this.arg            = ast.arg
+    this.expression     = ast.expression
 
-    var isEmpty   = expression === ''
+    var isEmpty = this.expression === ''
 
     // mix in properties from the directive definition
     if (typeof definition === 'function') {
@@ -46,15 +44,11 @@ function Directive (dirname, definition, expression, rawKey, compiler, node) {
 
     this.expression = (
         this.isLiteral
-            ? compiler.eval(expression)
-            : expression
+            ? compiler.eval(this.expression)
+            : this.expression
     ).trim()
-    
-    var parsed = Directive.parseArg(rawKey)
-    this.key = parsed.key
-    this.arg = parsed.arg
-    
-    var filters = Directive.parseFilters(this.expression.slice(rawKey.length)),
+
+    var filters = ast.filters,
         filter, fn, i, l, computed
     if (filters) {
         this.filters = []
@@ -133,8 +127,8 @@ DirProto.unbind = function () {
 // Exposed static methods -----------------------------------------------------
 
 /**
- *  split a unquoted-comma separated expression into
- *  multiple clauses
+ *  Parse a directive string into an Array of
+ *  AST-like objects representing directives
  */
 Directive.parse = function (str) {
 
@@ -147,7 +141,8 @@ Directive.parse = function (str) {
         argIndex = 0,
         dirs     = [],
         dir      = {},
-        lastFilterIndex = 0
+        lastFilterIndex = 0,
+        arg
 
     for (var c, i = 0, l = str.length; i < l; i++) {
         c = str.charAt(i)
@@ -165,12 +160,15 @@ Directive.parse = function (str) {
             begin = argIndex = lastFilterIndex = i + 1
         } else if (c === ':' && !dir.key && !dir.arg) {
             // argument
-            argIndex = i + 1
-            dir.arg = str.slice(begin, i).trim()
+            arg = str.slice(begin, i).trim()
+            if (ARG_RE.test(arg)) {
+                argIndex = i + 1
+                dir.arg = str.slice(begin, i).trim()
+            }
         } else if (c === '|' && str.charAt(i + 1) !== '|') {
-            if (!dir.key) {
+            if (dir.key === undefined) {
                 // first filter, end of key
-                lastFilterIndex = i
+                lastFilterIndex = i + 1
                 dir.key = str.slice(argIndex, i).trim()
             } else {
                 // already has filter
@@ -194,127 +192,40 @@ Directive.parse = function (str) {
             curly--
         }
     }
-    if (begin !== i) {
+    if (i === 0 || begin !== i) {
         pushDir()
     }
 
     function pushDir () {
         dir.expression = str.slice(begin, i).trim()
-        if (!dir.key) {
+        if (dir.key === undefined) {
             dir.key = str.slice(argIndex, i).trim()
         } else if (lastFilterIndex !== begin) {
             pushFilter()
         }
-        dirs.push(dir)
+        if (i === 0 || dir.key) {
+            dirs.push(dir)
+        }
     }
 
     function pushFilter () {
-        (dir.filters = dir.filters || [])
-            .push(str.slice(lastFilterIndex + 1, i).trim())
+        var exp = str.slice(lastFilterIndex, i).trim(),
+            filter
+        if (exp) {
+            filter = {}
+            var tokens = exp.match(FILTER_TOKEN_RE)
+            filter.name = tokens[0]
+            filter.args = tokens.length > 1 ? tokens.slice(1) : null
+        }
+        if (filter) {
+            (dir.filters = dir.filters || []).push(filter)
+        }
         lastFilterIndex = i + 1
     }
 
     return dirs
 }
 
-// function split (str) {
-//     var inSingle = false,
-//         inDouble = false,
-//         curly    = 0,
-//         square   = 0,
-//         paren    = 0,
-//         begin    = 0,
-//         end      = 0,
-//         res      = []
-//     for (var c, i = 0, l = str.length; i < l; i++) {
-//         c = str.charAt(i)
-//         if (inSingle) {
-//             if (c === "'") {
-//                 inSingle = !inSingle
-//             }
-//             end++
-//         } else if (inDouble) {
-//             if (c === '"') {
-//                 inDouble = !inDouble
-//             }
-//             end++
-//         } else if (c === ',' && !paren && !curly && !square) {
-//             res.push(str.slice(begin, end))
-//             begin = end = i + 1
-//         } else {
-//             if (c === '"') {
-//                 inDouble = true
-//             } else if (c === "'") {
-//                 inSingle = true
-//             } else if (c === '(') {
-//                 paren++
-//             } else if (c === ')') {
-//                 paren--
-//             } else if (c === '[') {
-//                 square++
-//             } else if (c === ']') {
-//                 square--
-//             } else if (c === '{') {
-//                 curly++
-//             } else if (c === '}') {
-//                 curly--
-//             }
-//             end++
-//         }
-//     }
-//     if (begin !== end) {
-//         res.push(str.slice(begin, end))
-//     }
-//     return res
-// }
-
-// /**
-//  *  parse a key, extract argument
-//  */
-// Directive.parseArg = function (rawKey) {
-//     var key = rawKey,
-//         arg = null
-//     if (rawKey.indexOf(':') > -1) {
-//         var argMatch = rawKey.match(ARG_RE)
-//         key = argMatch
-//             ? argMatch[2].trim()
-//             : key
-//         arg = argMatch
-//             ? argMatch[1].trim()
-//             : arg
-//     }
-//     return {
-//         key: key,
-//         arg: arg
-//     }
-// }
-
-// /**
-//  *  parse a the filters
-//  */
-// Directive.parseFilters = function (exp) {
-//     if (exp.indexOf('|') < 0) {
-//         return
-//     }
-//     var filters = exp.match(FILTERS_RE),
-//         res, i, l, tokens
-//     if (filters) {
-//         res = []
-//         for (i = 0, l = filters.length; i < l; i++) {
-//             tokens = filters[i].slice(1).match(FILTER_TOKEN_RE)
-//             if (tokens) {
-//                 res.push({
-//                     name: tokens[0],
-//                     args: tokens.length > 1
-//                         ? tokens.slice(1)
-//                         : null
-//                 })
-//             }
-//         }
-//     }
-//     return res
-// }
-
 /**
  *  Inline computed filters so they become part
  *  of the expression
@@ -345,36 +256,4 @@ function escapeQuote (v) {
         : v
 }
 
-/**
- *  Parse the key from a directive raw expression
- */
-Directive.parseKey = function (expression) {
-    if (expression.indexOf('|') > -1) {
-        var keyMatch = expression.match(KEY_RE)
-        if (keyMatch) {
-            return keyMatch[0].trim()
-        }
-    } else {
-        return expression.trim()
-    }
-}
-
-/**
- *  make sure the directive and expression is valid
- *  before we create an instance
- */
-Directive.build = function (dirname, expression, compiler, node) {
-
-    var dir = compiler.getOption('directives', dirname)
-    if (!dir) return
-
-    var rawKey = Directive.parseKey(expression)
-    // have a valid raw key, or be an empty directive
-    if (rawKey || expression === '') {
-        return new Directive(dirname, dir, expression, rawKey, compiler, node)
-    } else {
-        utils.warn('Invalid directive expression: ' + expression)
-    }
-}
-
 module.exports = Directive

+ 9 - 6
src/text-parser.js

@@ -76,12 +76,15 @@ function parseAttr (attr) {
  *  so that we can combine everything into a huge expression
  */
 function inlineFilters (key) {
-    var filters = Directive.parseFilters(key)
-    if (filters) {
-        key = Directive.inlineFilters(
-            Directive.parseKey(key),
-            filters
-        )
+    if (key.indexOf('|') > -1) {
+        var dirs = Directive.parse(key),
+            dir = dirs && dirs[0]
+        if (dir && dir.filters) {
+            key = Directive.inlineFilters(
+                dir.key,
+                dir.filters
+            )
+        }
     }
     return '(' + key + ')'
 }

+ 0 - 1
test/unit/specs/api.js

@@ -238,7 +238,6 @@ describe('API', function () {
                 isEmpty: true,
                 bind: function () {
                     emptyCalled = true
-                    assert.notOk(this.expression)
                     assert.notOk(this.binding)
                 },
                 update: function () {}