Przeglądaj źródła

component refactor

Evan You 12 lat temu
rodzic
commit
628c42cc9f

+ 2 - 1
component.json

@@ -24,7 +24,8 @@
         "src/directives/if.js",
         "src/directives/repeat.js",
         "src/directives/on.js",
-        "src/directives/model.js"
+        "src/directives/model.js",
+        "src/directives/component.js"
     ],
     "dependencies": {
         "component/emitter": "*"

+ 30 - 37
src/compiler.js

@@ -71,8 +71,9 @@ function Compiler (vm, options) {
 
     // set parent VM
     // and register child id on parent
-    var childId = utils.attr(el, 'id')
+    var childId = utils.attr(el, 'component-id')
     if (parent) {
+        parent.childCompilers.push(compiler)
         def(vm, '$parent', parent.vm)
         if (childId) {
             compiler.childId = childId
@@ -217,18 +218,20 @@ CompilerProto.setupObserver = function () {
  */
 CompilerProto.compile = function (node, root) {
 
-    var compiler = this
+    var compiler = this,
+        nodeType = node.nodeType,
+        tagName  = node.tagName
 
-    if (node.nodeType === 1) { // a normal node
+    if (nodeType === 1 && tagName !== 'SCRIPT') { // a normal node
 
         // skip anything with v-pre
         if (utils.attr(node, 'pre') !== null) return
 
         // special attributes to check
         var repeatExp,
-            componentId,
+            componentExp,
             partialId,
-            customElementFn = compiler.getOption('elements', node.tagName.toLowerCase())
+            directive
 
         // It is important that we access these attributes
         // procedurally because the order matters.
@@ -242,21 +245,25 @@ CompilerProto.compile = function (node, root) {
         if (repeatExp = utils.attr(node, 'repeat')) {
 
             // repeat block cannot have v-id at the same time.
-            var directive = Directive.parse(config.attrs.repeat, repeatExp, compiler, node)
+            directive = Directive.parse(config.attrs.repeat, repeatExp, compiler, node)
             if (directive) {
                 compiler.bindDirective(directive)
             }
 
-        // custom elements has 2nd highest priority
-        } else if (!root && customElementFn) {
-
-            addChild(customElementFn)
-
-        // v-component has 3rd highest priority
-        } else if (!root && (componentId = utils.attr(node, 'component'))) {
+        // v-component has 2nd highest priority
+        } else if (!root && (componentExp = utils.attr(node, 'component'))) {
 
-            var ChildVM = compiler.getOption('components', componentId)
-            if (ChildVM) addChild(ChildVM)
+            directive = Directive.parse(config.attrs.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
+                // simple directive with its key as the argument.
+                if (componentExp.indexOf(':') === -1) {
+                    directive.isSimple = true
+                    directive.arg = directive.key
+                }
+                compiler.bindDirective(directive)
+            }
 
         } else {
 
@@ -277,27 +284,12 @@ CompilerProto.compile = function (node, root) {
             compiler.compileNode(node)
         }
 
-    } else if (node.nodeType === 3) { // text node
+    } else if (nodeType === 3) { // text node
 
         compiler.compileTextNode(node)
 
     }
 
-    function addChild (Ctor) {
-        if (utils.isConstructor(Ctor)) {
-            var child = new Ctor({
-                el: node,
-                child: true,
-                compilerOptions: {
-                    parentCompiler: compiler
-                }
-            })
-            compiler.childCompilers.push(child.$compiler)
-        } else {
-            // simply call the function
-            Ctor(node)
-        }
-    }
 }
 
 /**
@@ -412,13 +404,13 @@ CompilerProto.bindDirective = function (directive) {
     binding.instances.push(directive)
     directive.binding = binding
 
-    var value = binding.value
     // invoke bind hook if exists
     if (directive.bind) {
-        directive.bind(value)
+        directive.bind()
     }
 
     // set initial value
+    var value = binding.value
     if (value !== undefined) {
         if (binding.isComputed) {
             directive.refresh(value)
@@ -454,11 +446,11 @@ CompilerProto.createBinding = function (key, isExp, isFn) {
         bindings[key] = binding
         // make sure the key exists in the object so it can be observed
         // by the Observer!
-        Observer.ensurePath(compiler.vm, key)
         if (binding.root) {
             // this is a root level binding. we need to define getter/setters for it.
             compiler.define(key, binding)
         } else {
+            Observer.ensurePath(compiler.vm, key)
             var parentKey = key.slice(0, key.lastIndexOf('.'))
             if (!hasOwn.call(bindings, parentKey)) {
                 // this is a nested value binding, but the binding for its parent
@@ -488,9 +480,10 @@ CompilerProto.define = function (key, binding) {
         // computed property
         compiler.markComputed(binding)
     } else if (type === 'Object' || type === 'Array') {
-        // observe objects later, becase there might be more keys
-        // to be added to it. we also want to emit all the set events
-        // after all values are available.
+        // observe objects later, because there might be more keys
+        // to be added to it during Observer.ensurePath().
+        // we also want to emit all the set events after all values
+        // are available.
         compiler.observables.push(binding)
     }
 

+ 3 - 3
src/deps-parser.js

@@ -9,10 +9,10 @@ var Emitter  = require('./emitter'),
 function catchDeps (binding) {
     if (binding.isFn) return
     utils.log('\n─ ' + binding.key)
-    var depsHash = utils.hash()
+    var has = []
     observer.on('get', function (dep) {
-        if (depsHash[dep.key]) return
-        depsHash[dep.key] = 1
+        if (has.indexOf(dep) > -1) return
+        has.push(dep)
         utils.log('  └─ ' + dep.key)
         binding.deps.push(dep)
         dep.subs.push(binding)

+ 40 - 0
src/directives/component.js

@@ -0,0 +1,40 @@
+var utils = require('../utils')
+
+module.exports = {
+
+    bind: function () {
+        if (this.isSimple) {
+            this.build()
+        }
+    },
+
+    update: function (value) {
+        if (!this.component) {
+            this.build(value)
+        } else {
+            this.component.model = value
+        }
+    },
+
+    build: function (value) {
+        var Ctor = this.compiler.getOption('components', this.arg)
+        if (!Ctor) utils.warn('unknown component: ' + this.arg)
+        var options = {
+                el: this.el,
+                compilerOptions: {
+                    parentCompiler: this.compiler
+                }
+            }
+        if (value) {
+            options.scope = {
+                model: value
+            }
+        }
+        this.component = new Ctor(options)
+    },
+
+    unbind: function () {
+        this.component.$destroy()
+    }
+
+}

+ 5 - 4
src/directives/index.js

@@ -3,10 +3,11 @@ var utils      = require('../utils'),
 
 module.exports = {
 
-    on     : require('./on'),
-    repeat : require('./repeat'),
-    model  : require('./model'),
-    'if'   : require('./if'),
+    on        : require('./on'),
+    repeat    : require('./repeat'),
+    model     : require('./model'),
+    'if'      : require('./if'),
+    component : require('./component'),
 
     attr: function (value) {
         this.el.setAttribute(this.arg, value)

+ 1 - 10
src/main.js

@@ -42,15 +42,6 @@ ViewModel.component = function (id, Ctor) {
     return this
 }
 
-/**
- *  Allows user to register/retrieve a Custom element constructor
- */
-ViewModel.element = function (id, Ctor) {
-    if (!Ctor) return utils.elements[id]
-    utils.elements[id] = utils.toConstructor(Ctor)
-    return this
-}
-
 /**
  *  Allows user to register/retrieve a template partial
  */
@@ -141,12 +132,12 @@ function inheritOptions (child, parent, topLevel) {
  *  that are used in compilation.
  */
 var specialAttributes = [
-    'id',
     'pre',
     'text',
     'repeat',
     'partial',
     'component',
+    'component-id',
     'transition'
 ]
 

+ 3 - 1
src/observer.js

@@ -214,10 +214,12 @@ function ensurePath (obj, key) {
         if (!obj[sec]) obj[sec] = {}
         obj = obj[sec]
     }
-    if (typeOf(obj) === 'Object') {
+    var type = typeOf(obj)
+    if (type === 'Object' || type === 'Array') {
         sec = path[i]
         if (!(sec in obj)) obj[sec] = undefined
     }
+    return obj[sec]
 }
 
 module.exports = {

+ 2 - 14
src/utils.js

@@ -22,7 +22,6 @@ var utils = module.exports = {
     components  : makeHash(),
     partials    : makeHash(),
     transitions : makeHash(),
-    elements    : makeHash(),
 
     /**
      *  get an attribute and remove it.
@@ -141,11 +140,6 @@ var utils = module.exports = {
                 : null
     },
 
-    isConstructor: function (obj) {
-        ViewModel = ViewModel || require('./viewmodel')
-        return obj.prototype instanceof ViewModel || obj === ViewModel
-    },
-
     /**
      *  convert certain option values to the desired format.
      */
@@ -153,18 +147,12 @@ var utils = module.exports = {
         var components = options.components,
             partials   = options.partials,
             template   = options.template,
-            elements   = options.elements,
             key
         if (components) {
             for (key in components) {
                 components[key] = utils.toConstructor(components[key])
             }
         }
-        if (elements) {
-            for (key in elements) {
-                elements[key] = utils.toConstructor(elements[key])
-            }
-        }
         if (partials) {
             for (key in partials) {
                 partials[key] = utils.toFragment(partials[key])
@@ -185,11 +173,11 @@ var utils = module.exports = {
     },
     
     /**
-     *  warnings, thrown in all cases
+     *  warnings, traces by default
+     *  can be suppressed by `silent` option.
      */
     warn: function() {
         if (!config.silent && console) {
-            console.trace()
             console.warn(join.call(arguments, ' '))
         }
     }

+ 0 - 40
test/functional/fixtures/custom-element.html

@@ -1,40 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-    <head>
-        <title>Custom Elements</title>
-        <meta charset="utf-8">
-        <script src="../../../dist/vue.js"></script>
-    </head>
-    <body>
-        <my-element>afsefsefse</my-element>
-        <cool>hmm</cool>
-        <wow></wow>
-        <script>
-            // global custom element with option object + replace
-            Vue.element('my-element', {
-                replace: true,
-                className: 'test',
-                template: '<div>{{msg}}</div>'
-            })
-            new Vue({
-                el:'body',
-                scope: {
-                    msg: 'hihi',
-                },
-                elements: {
-                    // private custom element with simple function
-                    cool: function (el) {
-                        el.className = 'cool'
-                        el.innerHTML = 'This is cool'
-                    },
-                    // private custom element with constructor
-                    wow: Vue.extend({
-                        ready: function () {
-                            this.$el.textContent = 'this is wow'
-                        }
-                    })
-                }
-            })
-        </script>
-    </body>
-</html>

+ 12 - 3
test/functional/fixtures/encapsulation.html

@@ -11,15 +11,21 @@
             <div class="filter">{{filterMsg | nodigits}}</div>
             <div class="partial" v-partial="partial-test"></div>
             <div class="vm" v-component="vm-test">{{vmMsg}}</div>
+            <div class="vm-w-model" v-component="vm-w-model:vmData">{{msg + model.msg}}</div>
         </div>
         <script>
             var T = Vue.extend({
                 components: {
-                    'vm-test': Vue.extend({
+                    'vm-test': {
                         scope: {
                             vmMsg: 'component works'
                         }
-                    })
+                    },
+                    'vm-w-model': {
+                        scope : {
+                            msg: 'component with model '
+                        }
+                    }
                 },
                 partials: {
                     'partial-test': '{{partialMsg}}'
@@ -40,7 +46,10 @@
                 scope: {
                     dirMsg: 'directive',
                     filterMsg: 'fi43l132ter5 w12345orks',
-                    partialMsg: 'partial works'
+                    partialMsg: 'partial works',
+                    vmData: {
+                        msg: 'works'
+                    }
                 }
             })
         </script>

+ 1 - 1
test/functional/fixtures/nested-props.html

@@ -24,7 +24,7 @@
             var data = { c: 555 }
             var Demo = Vue.extend({
                 lazy: true,
-                ready: function () {
+                created: function () {
                     this.msg = 'Yoyoyo'
                     this.a = data
                 },

+ 2 - 1
test/functional/fixtures/nested-repeat.html

@@ -11,7 +11,7 @@
                 <li v-repeat="item : items" v-class="'list-' + $index">
                     <ul>
                         <li v-repeat="subitem : item.items" v-class="'list-' + $index">
-                            {{$parent.$index + '.' + $index + ' : ' + item.title + '<-' + subitem.title}}
+                            {{$parent.$index + '.' + $index + ' : ' + item.title + '&lt;-' + subitem.title}}
                         </li>
                     </ul>
                 </li>
@@ -24,6 +24,7 @@
             <button id="b1-1" v-on="click: items[1].items[1].title = 'hi'">2.2</button>
         </div>
         <script>
+        Vue.config({debug:true})
         var items = [
             { title: 0, items: [{title:0}, {title:1}] },
             { title: 1, items: [{title:0}, {title:1}] }

+ 0 - 13
test/functional/specs/custom-element.js

@@ -1,13 +0,0 @@
-casper.test.begin('Custom Elements', 3, function (test) {
-    
-    casper
-    .start('./fixtures/custom-element.html', function () {
-        test.assertSelectorHasText('div.test', 'hihi')
-        test.assertSelectorHasText('cool.cool', 'This is cool')
-        test.assertSelectorHasText('wow', 'this is wow')
-    })
-    .run(function () {
-        test.done()
-    })
-
-})

+ 2 - 1
test/functional/specs/encapsulation.js

@@ -1,4 +1,4 @@
-casper.test.begin('Component Encapsulation', 4, function (test) {
+casper.test.begin('Component Encapsulation', 5, function (test) {
     
     casper
     .start('./fixtures/encapsulation.html', function () {
@@ -6,6 +6,7 @@ casper.test.begin('Component Encapsulation', 4, function (test) {
         test.assertSelectorHasText('.filter', 'filter works')
         test.assertSelectorHasText('.partial', 'partial works')
         test.assertSelectorHasText('.vm', 'component works')
+        test.assertSelectorHasText('.vm-w-model', 'component with model works')
     })
     .run(function () {
         test.done()

+ 3 - 121
test/unit/specs/api.js

@@ -71,8 +71,8 @@ describe('UNIT: API', function () {
             var testId = 'directive-2',
                 msg = 'wowaaaa?'
             dirTest = {
-                bind: function (value) {
-                    this.el.setAttribute(testId + 'bind', value + 'bind')
+                bind: function () {
+                    this.el.setAttribute(testId + 'bind', 'bind')
                 },
                 update: function (value) {
                     this.el.setAttribute(testId + 'update', value + 'update')
@@ -88,7 +88,7 @@ describe('UNIT: API', function () {
                     scope: { test: msg }
                 }),
                 el = document.querySelector('#' + testId + ' span')
-            assert.strictEqual(el.getAttribute(testId + 'bind'), msg + 'bind', 'should have called bind()')
+            assert.strictEqual(el.getAttribute(testId + 'bind'), 'bind', 'should have called bind()')
             assert.strictEqual(el.getAttribute(testId + 'update'), msg + 'update', 'should have called update()')
             vm.$destroy() // assuming this works
             assert.notOk(el.getAttribute(testId + 'bind'), 'should have called unbind()')
@@ -143,64 +143,6 @@ describe('UNIT: API', function () {
 
     })
 
-    describe('element()', function () {
-        
-        var testId = 'api-element-test',
-            testId2 = testId + '2',
-            testId3 = testId + '3',
-            opts = {
-                className: 'hihi',
-                scope: { hi: 'ok' }
-            },
-            Test = Vue.extend(opts),
-            utils = require('vue/src/utils')
-
-        it('should register a Custom Element constructor', function () {
-            Vue.element(testId, Test)
-            assert.strictEqual(utils.elements[testId], Test)
-        })
-
-        it('should also work with option objects', function () {
-            Vue.element(testId2, opts)
-            assert.ok(utils.elements[testId2].prototype instanceof Vue)
-        })
-
-        it('should accept simple function as-is', function () {
-            var fn = function (el) {
-                el.className = 'hihi3'
-                el.textContent = 'ok3'
-            }
-            Vue.element(testId3, fn)
-            assert.strictEqual(utils.elements[testId3], fn)
-        })
-
-        it('should retrieve the VM if has only one arg', function () {
-            assert.strictEqual(Vue.element(testId), Test)
-        })
-
-        it('should work with custom tag names', function () {
-
-            mock(testId, '<' + testId + '>{{hi}}</' + testId + '>')
-            var t = new Vue({ el: '#' + testId }),
-                child = t.$el.querySelector(testId)
-            assert.strictEqual(child.className, 'hihi')
-            assert.strictEqual(child.textContent, 'ok')
-
-            mock(testId2, '<' + testId2 + '>{{hi}}</' + testId2 + '>')
-            var t2 = new Vue({ el: '#' + testId2 }),
-                child2 = t2.$el.querySelector(testId2)
-            assert.strictEqual(child2.className, 'hihi')
-            assert.strictEqual(child2.textContent, 'ok')
-
-            mock(testId3, '<' + testId3 + '></' + testId3 + '>')
-            var t3 = new Vue({ el: '#' + testId3 }),
-                child3 = t3.$el.querySelector(testId3)
-            assert.strictEqual(child3.className, 'hihi3')
-            assert.strictEqual(child3.textContent, 'ok3')
-        })
-
-    })
-
     describe('partial()', function () {
 
         var testId = 'api-partial-test',
@@ -633,66 +575,6 @@ describe('UNIT: API', function () {
                 })
 
             })
-            
-            describe('elements', function () {
-                
-                it('should allow the VM to use private custom elements', function () {
-                    var Child = Vue.extend({
-                        scope: {
-                            name: 'child'
-                        }
-                    })
-                    var Parent = Vue.extend({
-                        template: '<p>{{name}}</p><child>{{name}}</child>',
-                        scope: {
-                            name: 'dad'
-                        },
-                        elements: {
-                            child: Child
-                        }
-                    })
-                    var p = new Parent()
-                    assert.strictEqual(p.$el.querySelector('p').textContent, 'dad')
-                    assert.strictEqual(p.$el.querySelector('child').textContent, 'child')
-                })
-
-                it('should work with plain option object', function () {
-                    var Parent = Vue.extend({
-                        template: '<p>{{name}}</p><child>{{name}}</child>',
-                        scope: {
-                            name: 'dad'
-                        },
-                        elements: {
-                            child: {
-                                scope: {
-                                    name: 'child'
-                                }
-                            }
-                        }
-                    })
-                    var p = new Parent()
-                    assert.strictEqual(p.$el.querySelector('p').textContent, 'dad')
-                    assert.strictEqual(p.$el.querySelector('child').textContent, 'child')
-                })
-
-                it('should work with a simple function', function () {
-                    var Parent = Vue.extend({
-                        template: '<p>{{name}}</p><child></child>',
-                        scope: {
-                            name: 'dad'
-                        },
-                        elements: {
-                            child: function (el) {
-                                el.textContent = 'child'
-                            }
-                        }
-                    })
-                    var p = new Parent()
-                    assert.strictEqual(p.$el.querySelector('p').textContent, 'dad')
-                    assert.strictEqual(p.$el.querySelector('child').textContent, 'child')
-                })
-
-            })
 
             describe('partials', function () {
                 

+ 6 - 2
test/unit/specs/directives.js

@@ -569,7 +569,11 @@ describe('UNIT: Directives', function () {
 
     })
 
-    describe('id', function () {
+    describe('component', function () {
+        // body...
+    })
+
+    describe('component-id', function () {
         
         it('should register a VM isntance on its parent\'s $', function () {
             var called = false
@@ -581,7 +585,7 @@ describe('UNIT: Directives', function () {
                 }
             })
             var t = new Vue({
-                template: '<div v-component="child" v-id="hihi"></div>',
+                template: '<div v-component="child" v-component-id="hihi"></div>',
                 components: {
                     child: Child
                 }

+ 0 - 6
test/unit/specs/utils.js

@@ -209,9 +209,6 @@ describe('UNIT: Utils', function () {
                 a: { scope: { data: 1 } },
                 b: { scope: { data: 2 } }
             },
-            elements: {
-                c: { scope: { data: 3 }}
-            },
             template: '<a>{{hi}}</a>'
         }
 
@@ -240,9 +237,6 @@ describe('UNIT: Utils', function () {
             assert.strictEqual(components.a.options.scope.data, 1)
             assert.ok(components.b.prototype instanceof Vue)
             assert.strictEqual(components.b.options.scope.data, 2)
-            var elements = options.elements
-            assert.ok(elements.c.prototype instanceof Vue)
-            assert.strictEqual(elements.c.options.scope.data, 3)
         })
 
     })