Переглянути джерело

add support for interpolation in html attributes

Evan You 12 роки тому
батько
коміт
a2e287f0b9

+ 34 - 19
src/compiler.js

@@ -242,7 +242,7 @@ CompilerProto.compile = function (node, root) {
         if (repeatExp = utils.attr(node, 'repeat')) {
 
             // repeat block cannot have v-id at the same time.
-            directive = Directive.parse(config.attrs.repeat, repeatExp, compiler, node)
+            directive = Directive.parse('repeat', repeatExp, compiler, node)
             if (directive) {
                 compiler.bindDirective(directive)
             }
@@ -250,7 +250,7 @@ CompilerProto.compile = function (node, root) {
         // v-component has 2nd highest priority
         } else if (!root && (componentExp = utils.attr(node, 'component'))) {
 
-            directive = Directive.parse(config.attrs.component, componentExp, compiler, node)
+            directive = Directive.parse('component', componentExp, compiler, node)
             if (directive) {
                 // component directive is a bit different from the others.
                 // when it has no argument, it should be treated as a
@@ -293,28 +293,44 @@ CompilerProto.compile = function (node, root) {
  *  Compile a normal node
  */
 CompilerProto.compileNode = function (node) {
-    var i, j, attrs = node.attributes
+    var i, j,
+        attrs = node.attributes,
+        prefix = config.prefix + '-'
     // parse if has attributes
     if (attrs && attrs.length) {
-        var attr, valid, exps, exp
+        var attr, isDirective, exps, exp, directive
         // loop through all attributes
         i = attrs.length
         while (i--) {
             attr = attrs[i]
-            valid = false
-            exps = Directive.split(attr.value)
-            // loop through clauses (separated by ",")
-            // inside each attribute
-            j = exps.length
-            while (j--) {
-                exp = exps[j]
-                var directive = Directive.parse(attr.name, exp, this, node)
-                if (directive) {
-                    valid = true
-                    this.bindDirective(directive)
+            isDirective = false
+
+            if (attr.name.indexOf(prefix) === 0) {
+                // a directive - split, parse and bind it.
+                isDirective = true
+                exps = Directive.split(attr.value)
+                // loop through clauses (separated by ",")
+                // inside each attribute
+                j = exps.length
+                while (j--) {
+                    exp = exps[j]
+                    directive = Directive.parse(attr.name.slice(prefix.length), exp, this, node)
+                    if (directive) {
+                        this.bindDirective(directive)
+                    }
+                }
+            } else {
+                // non directive attribute, check interpolation tags
+                exp = TextParser.parseAttr(attr.value)
+                if (exp) {
+                    directive = Directive.parse('attr', attr.name + ':' + exp, this, node)
+                    if (directive) {
+                        this.bindDirective(directive)
+                    }
                 }
             }
-            if (valid) node.removeAttribute(attr.name)
+
+            if (isDirective) node.removeAttribute(attr.name)
         }
     }
     // recursively compile childNodes
@@ -332,8 +348,7 @@ CompilerProto.compileNode = function (node) {
 CompilerProto.compileTextNode = function (node) {
     var tokens = TextParser.parse(node.nodeValue)
     if (!tokens) return
-    var dirname = config.attrs.text,
-        el, token, directive
+    var el, token, directive
     for (var i = 0, l = tokens.length; i < l; i++) {
         token = tokens[i]
         if (token.key) { // a binding
@@ -346,7 +361,7 @@ CompilerProto.compileTextNode = function (node) {
                 }
             } else { // a binding
                 el = document.createTextNode('')
-                directive = Directive.parse(dirname, token.key, this, el)
+                directive = Directive.parse('text', token.key, this, el)
                 if (directive) {
                     this.bindDirective(directive)
                 }

+ 1 - 6
src/directive.js

@@ -1,5 +1,4 @@
-var config     = require('./config'),
-    utils      = require('./utils'),
+var utils      = require('./utils'),
     directives = require('./directives'),
     filters    = require('./filters'),
 
@@ -201,10 +200,6 @@ Directive.split = function (exp) {
  */
 Directive.parse = function (dirname, expression, compiler, node) {
 
-    var prefix = config.prefix + '-'
-    if (dirname.indexOf(prefix) !== 0) return
-    dirname = dirname.slice(prefix.length)
-
     var dir = compiler.getOption('directives', dirname) || directives[dirname]
     if (!dir) return utils.warn('unknown directive: ' + dirname)
 

+ 32 - 18
src/text-parser.js

@@ -1,22 +1,36 @@
 var BINDING_RE = /\{\{(.+?)\}\}/
 
-module.exports = {
+/**
+ *  Parse a piece of text, return an array of tokens
+ */
+function parse (text) {
+    if (!BINDING_RE.test(text)) return null
+    var m, i, tokens = []
+    /* jshint boss: true */
+    while (m = text.match(BINDING_RE)) {
+        i = m.index
+        if (i > 0) tokens.push(text.slice(0, i))
+        tokens.push({ key: m[1].trim() })
+        text = text.slice(i + m[0].length)
+    }
+    if (text.length) tokens.push(text)
+    return tokens
+}
 
-    /**
-     *  Parse a piece of text, return an array of tokens
-     */
-    parse: function (text) {
-        if (!BINDING_RE.test(text)) return null
-        var m, i, tokens = []
-        /* jshint boss: true */
-        while (m = text.match(BINDING_RE)) {
-            i = m.index
-            if (i > 0) tokens.push(text.slice(0, i))
-            tokens.push({ key: m[1].trim() })
-            text = text.slice(i + m[0].length)
-        }
-        if (text.length) tokens.push(text)
-        return tokens
+/**
+ *  Parse an attribute value with possible interpolation tags
+ *  return a Directive-friendly expression
+ */
+function parseAttr (attr) {
+    var tokens = parse(attr)
+    if (!tokens) return null
+    var res = [], token
+    for (var i = 0, l = tokens.length; i < l; i++) {
+        token = tokens[i]
+        res.push(token.key || ('"' + token + '"'))
     }
-    
-}
+    return res.join('+')
+}
+
+exports.parse = parse
+exports.parseAttr = parseAttr

+ 10 - 1
test/functional/fixtures/expression.html

@@ -23,8 +23,10 @@
             <button v-on="click: ok = !ok" class="toggle">toggle</button>
             <button v-on="click: noMsg = 'Nah'" class="change">change</button>
         </div>
+        <div id="attrs" data-test="hi {{msg}} ha"></div>
         <script>
-        Vue.config({debug:true})
+            Vue.config({debug:true})
+
             var normal = new Vue({
                 el: '#normal',
                 data: {
@@ -54,6 +56,13 @@
                     noMsg: 'NO'
                 }
             })
+
+            var attrs = new Vue({
+                el: '#attrs',
+                data: {
+                    msg: 'ho'
+                }
+            })
         </script>
     </body>
 </html>

+ 14 - 2
test/functional/specs/expression.js

@@ -1,6 +1,6 @@
-/* global normal */
+/* global normal, attrs */
 
-casper.test.begin('Expression', 19, function (test) {
+casper.test.begin('Expression', 21, function (test) {
     
     casper
     .start('./fixtures/expression.html')
@@ -11,6 +11,10 @@ casper.test.begin('Expression', 19, function (test) {
         test.assertField('two', 'World')
         test.assertField('three', 'Hi')
         test.assertField('four', 'Ho')
+        // attrs
+        test.assertEval(function () {
+            return document.getElementById('attrs').dataset.test === 'hi ho ha'
+        })
     })
     .thenEvaluate(function () {
         // setting value
@@ -67,6 +71,14 @@ casper.test.begin('Expression', 19, function (test) {
     .thenClick('#conditional .change', function () {
         test.assertSelectorHasText('#conditional p', 'Nah')
     })
+    .thenEvaluate(function () {
+        attrs.msg = 'hoho'
+    })
+    .then(function () {
+        test.assertEval(function () {
+            return document.getElementById('attrs').dataset.test === 'hi hoho ha'
+        })
+    })
     .run(function () {
         test.done()
     })

+ 38 - 38
test/unit/specs/directive.js

@@ -86,27 +86,27 @@ describe('UNIT: Directive', function () {
         })
 
         it('should return undefined if directive is unknown', function () {
-            var d = Directive.parse('v-directive-that-does-not-exist', 'abc', compiler)
+            var d = Directive.parse('directive-that-does-not-exist', 'abc', compiler)
             assert.ok(d === undefined)
         })
 
         it('should return undefined if the expression is invalid', function () {
-            var e = Directive.parse('v-text', '  ', compiler),
-                f = Directive.parse('v-text', '|', compiler),
-                g = Directive.parse('v-text', '  |  ', compiler)
+            var e = Directive.parse('text', '  ', compiler),
+                f = Directive.parse('text', '|', compiler),
+                g = Directive.parse('text', '  |  ', compiler)
             assert.strictEqual(e, undefined, 'spaces')
             assert.strictEqual(f, undefined, 'single pipe')
             assert.strictEqual(g, undefined, 'pipe with spaces')
         })
 
         it('should return a simple Directive if expression is empty', function () {
-            var d = Directive.parse('v-text', '', compiler)
+            var d = Directive.parse('text', '', compiler)
             assert.ok(d instanceof Directive)
             assert.ok(d.isSimple)
         })
 
         it('should return an instance of Directive if args are good', function () {
-            var d = Directive.parse('v-text', 'abc', compiler)
+            var d = Directive.parse('text', 'abc', compiler)
             assert.ok(d instanceof Directive)
         })
 
@@ -126,12 +126,12 @@ describe('UNIT: Directive', function () {
         directives.obj = obj
         
         it('should copy the definition as _update if the def is a function', function () {
-            var d = Directive.parse('v-test', 'abc', compiler)
+            var d = Directive.parse('test', 'abc', compiler)
             assert.strictEqual(d._update, test)
         })
 
         it('should copy methods if the def is an object', function () {
-            var d = Directive.parse('v-obj', 'abc', compiler)
+            var d = Directive.parse('obj', 'abc', compiler)
             assert.strictEqual(d._update, obj.update, 'update should be copied as _update')
             assert.strictEqual(d._unbind, obj.unbind, 'unbind should be copied as _unbind')
             assert.strictEqual(d.bind, obj.bind)
@@ -140,36 +140,36 @@ describe('UNIT: Directive', function () {
 
         it('should trim the expression', function () {
             var exp = ' fsfsef   | fsef a  ',
-                d = Directive.parse('v-text', exp, compiler)
+                d = Directive.parse('text', exp, compiler)
             assert.strictEqual(d.expression, exp.trim())
         })
 
         it('should extract correct key', function () {
-            var d = Directive.parse('v-text', '"fsefse | fsefsef" && bc', compiler),
-                e = Directive.parse('v-text', '"fsefsf & fsefs" | test', compiler),
-                f = Directive.parse('v-text', '"fsef:fsefsf" || ff', compiler)
+            var d = Directive.parse('text', '"fsefse | fsefsef" && bc', compiler),
+                e = Directive.parse('text', '"fsefsf & fsefs" | test', compiler),
+                f = Directive.parse('text', '"fsef:fsefsf" || ff', compiler)
             assert.strictEqual(d.key, '"fsefse | fsefsef" && bc', 'pipe inside quotes and &&')
             assert.strictEqual(e.key, '"fsefsf & fsefs"', '& inside quotes with filter')
             assert.strictEqual(f.key, '"fsef:fsefsf" || ff', ': inside quotes and ||')
         })
 
         it('should extract correct argument', function () {
-            var d = Directive.parse('v-text', 'todo:todos', compiler),
-                e = Directive.parse('v-text', 'todo:todos + abc', compiler),
-                f = Directive.parse('v-text', 'todo:todos | fsf fsef', compiler)
+            var d = Directive.parse('text', 'todo:todos', compiler),
+                e = Directive.parse('text', 'todo:todos + abc', compiler),
+                f = Directive.parse('text', 'todo:todos | fsf fsef', compiler)
             assert.strictEqual(d.arg, 'todo', 'simple')
             assert.strictEqual(e.arg, 'todo', 'expression')
             assert.strictEqual(f.arg, 'todo', 'with filters')
         })
 
         it('should be able to determine whether the key is an expression', function () {
-            var d = Directive.parse('v-text', 'abc', compiler),
-                e = Directive.parse('v-text', '!abc', compiler),
-                f = Directive.parse('v-text', 'abc + bcd * 5 / 2', compiler),
-                g = Directive.parse('v-text', 'abc && (bcd || eee)', compiler),
-                h = Directive.parse('v-text', 'test(abc)', compiler),
-                i = Directive.parse('v-text', 'a.b', compiler),
-                j = Directive.parse('v-text', 'a.$b', compiler)
+            var d = Directive.parse('text', 'abc', compiler),
+                e = Directive.parse('text', '!abc', compiler),
+                f = Directive.parse('text', 'abc + bcd * 5 / 2', compiler),
+                g = Directive.parse('text', 'abc && (bcd || eee)', compiler),
+                h = Directive.parse('text', 'test(abc)', compiler),
+                i = Directive.parse('text', 'a.b', compiler),
+                j = Directive.parse('text', 'a.$b', compiler)
             assert.ok(!d.isExp, 'non-expression')
             assert.ok(e.isExp, 'negation')
             assert.ok(f.isExp, 'math')
@@ -180,11 +180,11 @@ describe('UNIT: Directive', function () {
         })
 
         it('should have a filter prop of null if no filters are present', function () {
-            var d = Directive.parse('v-text', 'abc', compiler),
-                e = Directive.parse('v-text', 'abc |', compiler),
-                f = Directive.parse('v-text', 'abc ||', compiler),
-                g = Directive.parse('v-text', 'abc | | ', compiler),
-                h = Directive.parse('v-text', 'abc | unknown | nothing at all | whaaat', compiler)
+            var d = Directive.parse('text', 'abc', compiler),
+                e = Directive.parse('text', 'abc |', compiler),
+                f = Directive.parse('text', 'abc ||', compiler),
+                g = Directive.parse('text', 'abc | | ', compiler),
+                h = Directive.parse('text', 'abc | unknown | nothing at all | whaaat', compiler)
             assert.strictEqual(d.filters, null)
             assert.strictEqual(e.filters, null, 'single')
             assert.strictEqual(f.filters, null, 'double')
@@ -193,7 +193,7 @@ describe('UNIT: Directive', function () {
         })
 
         it('should extract correct filters (single filter)', function () {
-            var d = Directive.parse('v-text', 'abc || a + "b|c" | uppercase', compiler),
+            var d = Directive.parse('text', 'abc || a + "b|c" | uppercase', compiler),
                 f = d.filters[0]
             assert.strictEqual(f.name, 'uppercase')
             assert.strictEqual(f.args, null)
@@ -201,7 +201,7 @@ describe('UNIT: Directive', function () {
         })
 
         it('should extract correct filters (single filter with args)', function () {
-            var d = Directive.parse('v-text', 'abc + \'b | c | d\' | pluralize item \'arg with spaces\'', compiler),
+            var d = Directive.parse('text', 'abc + \'b | c | d\' | pluralize item \'arg with spaces\'', compiler),
                 f = d.filters[0]
             assert.strictEqual(f.name, 'pluralize', 'name')
             assert.strictEqual(f.args.length, 2, 'args length')
@@ -211,7 +211,7 @@ describe('UNIT: Directive', function () {
 
         it('should extract correct filters (multiple filters)', function () {
             // intentional double pipe
-            var d = Directive.parse('v-text', 'abc | uppercase | pluralize item || lowercase', compiler),
+            var d = Directive.parse('text', 'abc | uppercase | pluralize item || lowercase', compiler),
                 f1 = d.filters[0],
                 f2 = d.filters[1],
                 f3 = d.filters[2]
@@ -227,7 +227,7 @@ describe('UNIT: Directive', function () {
     describe('.applyFilters()', function () {
         
         it('should work', function () {
-            var d = Directive.parse('v-text', 'abc | pluralize item | capitalize', compiler),
+            var d = Directive.parse('text', 'abc | pluralize item | capitalize', compiler),
                 v = d.applyFilters(2)
             assert.strictEqual(v, 'Items')
         })
@@ -241,13 +241,13 @@ describe('UNIT: Directive', function () {
         directives.applyTest = applyTest
 
         it('should invole the _update function', function () {
-            var d = Directive.parse('v-applyTest', 'abc', compiler)
+            var d = Directive.parse('applyTest', 'abc', compiler)
             d.apply(12345)
             assert.strictEqual(test, 12345)
         })
         
         it('should apply the filter if there is any', function () {
-            var d = Directive.parse('v-applyTest', 'abc | currency £', compiler)
+            var d = Directive.parse('applyTest', 'abc | currency £', compiler)
             d.apply(12345)
             assert.strictEqual(test, '£12,345.00')
         })
@@ -256,7 +256,7 @@ describe('UNIT: Directive', function () {
 
     describe('.update()', function () {
         
-        var d = Directive.parse('v-text', 'abc', compiler),
+        var d = Directive.parse('text', 'abc', compiler),
             applied = false
         d.apply = function () {
             applied = true
@@ -284,7 +284,7 @@ describe('UNIT: Directive', function () {
 
     describe('.refresh()', function () {
         
-        var d = Directive.parse('v-text', 'abc', compiler),
+        var d = Directive.parse('text', 'abc', compiler),
             applied = false,
             el = 1, vm = 2,
             value = {
@@ -322,7 +322,7 @@ describe('UNIT: Directive', function () {
 
     describe('.unbind()', function () {
         
-        var d = Directive.parse('v-text', 'abc', compiler),
+        var d = Directive.parse('text', 'abc', compiler),
             unbound = false,
             val
         d._unbind = function (v) {
@@ -357,7 +357,7 @@ describe('UNIT: Directive', function () {
                 called++
             }
             Vue.directive('simple-dir-test1', call)
-            var d = Directive.parse('v-simple-dir-test1', '', compiler)
+            var d = Directive.parse('simple-dir-test1', '', compiler)
             d.bind()
             assert.strictEqual(called, 1)
         })
@@ -371,7 +371,7 @@ describe('UNIT: Directive', function () {
                 bind: call,
                 unbind: call
             })
-            var d = Directive.parse('v-simple-dir-test2', '', compiler, true)
+            var d = Directive.parse('simple-dir-test2', '', compiler, true)
             d.bind()
             d.unbind()
             assert.strictEqual(called, 2)

+ 19 - 0
test/unit/specs/misc.js

@@ -17,6 +17,25 @@ describe('Misc Features', function () {
         })
     })
 
+    describe('expression inside attributes', function () {
+        it('should interpolate the attribute', function (done) {
+            var v = new Vue({
+                attributes: {
+                    test: 'one {{msg}} three'
+                },
+                data: {
+                    msg: 'two'
+                }
+            })
+            assert.strictEqual(v.$el.getAttribute('test'), 'one two three')
+            v.msg = '2'
+            nextTick(function () {
+                assert.strictEqual(v.$el.getAttribute('test'), 'one 2 three')
+                done()
+            })
+        })
+    })
+
     describe('computed properties', function () {
         it('should be accessible like a normal attribtue', function () {
             var b = 2

+ 11 - 1
test/unit/specs/text-parser.js

@@ -9,7 +9,7 @@ describe('UNIT: TextNode Parser', function () {
             assert.strictEqual(result, null)
         })
 
-        it('should ignore escapped tags', function () {
+        it('should ignore escaped tags', function () {
             var result = TextParser.parse('test {{key}} &#123;&#123;hello&#125;&#125;')
             assert.strictEqual(result.length, 3)
             assert.strictEqual(result[2], ' &#123;&#123;hello&#125;&#125;')
@@ -50,4 +50,14 @@ describe('UNIT: TextNode Parser', function () {
 
     })
 
+    describe('.parseAttr()', function () {
+    
+        it('should return Directive.parse friendly expression', function () {
+            assert.strictEqual(TextParser.parseAttr('{{msg}}'), 'msg')
+            assert.strictEqual(TextParser.parseAttr('{{msg + "123"}}'), 'msg + "123"')
+            assert.strictEqual(TextParser.parseAttr('ha {{msg + "123"}} ho'), '"ha "+msg + "123"+" ho"')
+        })
+
+    })
+
 })

+ 2 - 1
test/unit/specs/utils.js

@@ -6,7 +6,8 @@ describe('UNIT: Utils', function () {
     try {
         require('non-existent')
     } catch (e) {
-        console.log('testing require fail')
+        // testing require fail
+        // for code coverage
     }
     
     describe('hash', function () {