소스 검색

reintroduce v-style

Evan You 12 년 전
부모
커밋
120af4b774
8개의 변경된 파일218개의 추가작업 그리고 85개의 파일을 삭제
  1. 2 1
      component.json
  2. 137 78
      dist/vue.js
  3. 0 0
      dist/vue.min.js
  4. 2 2
      src/directive.js
  5. 1 0
      src/directives/index.js
  6. 36 0
      src/directives/style.js
  7. 4 4
      test/unit/specs/directive.js
  8. 36 0
      test/unit/specs/directives.js

+ 2 - 1
component.json

@@ -28,7 +28,8 @@
         "src/directives/on.js",
         "src/directives/model.js",
         "src/directives/with.js",
-        "src/directives/html.js"
+        "src/directives/html.js",
+        "src/directives/style.js"
     ],
     "dependencies": {
         "component/emitter": "*"

+ 137 - 78
dist/vue.js

@@ -1,9 +1,5 @@
-/*
- Vue.js v0.8.3
- (c) 2014 Evan You
- License: MIT
-*/
 ;(function(){
+'use strict';
 
 /**
  * Require the given path.
@@ -508,8 +504,13 @@ function inheritOptions (child, parent, topLevel) {
             parentVal = parent[key],
             type = utils.typeOf(val)
         if (topLevel && type === 'Function' && parentVal) {
-            // merge hook functions
-            child[key] = mergeHook(val, parentVal)
+            // merge hook functions into an array
+            child[key] = [val]
+            if (Array.isArray(parentVal)) {
+                child[key] = child[key].concat(parentVal)
+            } else {
+                child[key].push(parentVal)
+            }
         } else if (topLevel && type === 'Object') {
             // merge toplevel object options
             inheritOptions(val, parentVal)
@@ -521,17 +522,6 @@ function inheritOptions (child, parent, topLevel) {
     return child
 }
 
-/**
- *  Merge hook functions
- *  so parent hooks also get called
- */
-function mergeHook (fn, parentFn) {
-    return function (opts) {
-        parentFn.call(this, opts)
-        fn.call(this, opts)
-    }
-}
-
 module.exports = ViewModel
 });
 require.register("vue/src/emitter.js", function(exports, require, module){
@@ -818,9 +808,12 @@ var utils = module.exports = {
         if (hasClassList) {
             el.classList.remove(cls)
         } else {
-            el.className = (' ' + el.className + ' ')
-                .replace(' ' + cls + ' ', '')
-                .trim()
+            var cur = ' ' + el.className + ' ',
+                tar = ' ' + cls + ' '
+            while (cur.indexOf(tar) >= 0) {
+                cur = cur.replace(tar, ' ')
+            }
+            el.className = cur.trim()
         }
     }
 }
@@ -842,7 +835,14 @@ var Emitter     = require('./emitter'),
     makeHash    = utils.hash,
     extend      = utils.extend,
     def         = utils.defProtected,
-    hasOwn      = Object.prototype.hasOwnProperty
+    hasOwn      = Object.prototype.hasOwnProperty,
+
+    // hooks to register
+    hooks = [
+        'created', 'ready',
+        'beforeDestroy', 'afterDestroy',
+        'enteredView', 'leftView'
+    ]
 
 /**
  *  The DOM compiler
@@ -910,7 +910,7 @@ function Compiler (vm, options) {
     }
 
     // beforeCompile hook
-    compiler.execHook('beforeCompile', 'created')
+    compiler.execHook('created')
 
     // the user might have set some props on the vm 
     // so copy it back to the data...
@@ -947,9 +947,7 @@ function Compiler (vm, options) {
     compiler.compile(el, true)
 
     // bind deferred directives (child components)
-    for (var i = 0, l = compiler.deferred.length; i < l; i++) {
-        compiler.bindDirective(compiler.deferred[i])
-    }
+    compiler.deferred.forEach(compiler.bindDirective, compiler)
 
     // extract dependencies for computed properties
     compiler.parseDeps()
@@ -958,7 +956,7 @@ function Compiler (vm, options) {
     compiler.init = false
 
     // post compile / ready hook
-    compiler.execHook('afterCompile', 'ready')
+    compiler.execHook('ready')
 }
 
 var CompilerProto = Compiler.prototype
@@ -1007,11 +1005,13 @@ CompilerProto.setupElement = function (options) {
  *  Setup observer.
  *  The observer listens for get/set/mutate events on all VM
  *  values/objects and trigger corresponding binding updates.
+ *  It also listens for lifecycle hooks.
  */
 CompilerProto.setupObserver = function () {
 
     var compiler = this,
         bindings = compiler.bindings,
+        options  = compiler.options,
         observer = compiler.observer = new Emitter()
 
     // a hash to hold event proxies for each root level key
@@ -1034,6 +1034,27 @@ CompilerProto.setupObserver = function () {
             check(key)
             bindings[key].pub()
         })
+    
+    // register hooks
+    hooks.forEach(function (hook) {
+        var fns = options[hook]
+        if (Array.isArray(fns)) {
+            var i = fns.length
+            // since hooks were merged with child at head,
+            // we loop reversely.
+            while (i--) {
+                register(hook, fns[i])
+            }
+        } else if (fns) {
+            register(hook, fns)
+        }
+    })
+
+    function register (hook, fn) {
+        observer.on('hook:' + hook, function () {
+            fn.call(compiler.vm, options)
+        })
+    }
 
     function check (key) {
         if (!bindings[key]) {
@@ -1086,7 +1107,7 @@ CompilerProto.compile = function (node, root) {
             }
 
         // v-with has 2nd highest priority
-        } else if (!root && ((withKey = utils.attr(node, 'with')) || componentCtor)) {
+        } else if (root !== true && ((withKey = utils.attr(node, 'with')) || componentCtor)) {
 
             directive = Directive.parse('with', withKey || '', compiler, node)
             if (directive) {
@@ -1167,10 +1188,7 @@ CompilerProto.compileNode = function (node) {
     }
     // recursively compile childNodes
     if (node.childNodes.length) {
-        var nodes = slice.call(node.childNodes)
-        for (i = 0, j = nodes.length; i < j; i++) {
-            this.compile(nodes[i])
-        }
+        slice.call(node.childNodes).forEach(this.compile, this)
     }
 }
 
@@ -1185,7 +1203,7 @@ CompilerProto.compileTextNode = function (node) {
 
     for (var i = 0, l = tokens.length; i < l; i++) {
         token = tokens[i]
-        directive = null
+        directive = partialNodes = null
         if (token.key) { // a binding
             if (token.key.charAt(0) === '>') { // a partial
                 partialId = token.key.slice(1).trim()
@@ -1211,6 +1229,8 @@ CompilerProto.compileTextNode = function (node) {
 
         // insert node
         node.parentNode.insertBefore(el, node)
+
+        // bind directive
         if (directive) {
             this.bindDirective(directive)
         }
@@ -1219,10 +1239,7 @@ CompilerProto.compileTextNode = function (node) {
         // will change from the fragment to the correct parentNode.
         // This could affect directives that need access to its element's parentNode.
         if (partialNodes) {
-            for (var j = 0, k = partialNodes.length; j < k; j++) {
-                this.compile(partialNodes[j])
-            }
-            partialNodes = null
+            partialNodes.forEach(this.compile, this)
         }
 
     }
@@ -1413,14 +1430,12 @@ CompilerProto.getOption = function (type, id) {
 }
 
 /**
- *  Execute a user hook
+ *  Emit lifecycle events to trigger hooks
  */
-CompilerProto.execHook = function (id, alt) {
-    var opts = this.options,
-        hook = opts[id] || opts[alt]
-    if (hook) {
-        hook.call(this.vm, opts)
-    }
+CompilerProto.execHook = function (event) {
+    event = 'hook:' + event
+    this.observer.emit(event)
+    this.emitter.emit(event)
 }
 
 /**
@@ -1445,6 +1460,10 @@ CompilerProto.parseDeps = function () {
  */
 CompilerProto.destroy = function () {
 
+    // avoid being called more than once
+    // this is irreversible!
+    if (this.destroyed) return
+
     var compiler = this,
         i, key, dir, instances, binding,
         vm          = compiler.vm,
@@ -1455,10 +1474,6 @@ CompilerProto.destroy = function () {
 
     compiler.execHook('beforeDestroy')
 
-    // unwatch
-    compiler.observer.off()
-    compiler.emitter.off()
-
     // unbind all direcitves
     i = directives.length
     while (i--) {
@@ -1507,7 +1522,13 @@ CompilerProto.destroy = function () {
         vm.$remove()
     }
 
+    this.destroyed = true
+    // emit destroy hook
     compiler.execHook('afterDestroy')
+
+    // finally, unregister all listeners
+    compiler.observer.off()
+    compiler.emitter.off()
 }
 
 // Helpers --------------------------------------------------------------------
@@ -2127,11 +2148,11 @@ var utils      = require('./utils'),
     // match up to the first single pipe, ignore those within quotes.
     KEY_RE          = /^(?:['"](?:\\.|[^'"])*['"]|\\.|[^\|]|\|\|)+/,
 
-    ARG_RE          = /^([\w- ]+):(.+)$/,
+    ARG_RE          = /^([\w-$ ]+):(.+)$/,
     FILTERS_RE      = /\|[^\|]+/g,
     FILTER_TOKEN_RE = /[^\s']+|'[^']+'/g,
     NESTING_RE      = /^\$(parent|root)\./,
-    SINGLE_VAR_RE   = /^[\w\.\$]+$/
+    SINGLE_VAR_RE   = /^[\w\.$]+$/
 
 /**
  *  Directive class
@@ -2174,8 +2195,7 @@ function Directive (definition, expression, rawKey, compiler, node) {
     var filterExps = this.expression.slice(rawKey.length).match(FILTERS_RE)
     if (filterExps) {
         this.filters = []
-        var i = 0, l = filterExps.length, filter
-        for (; i < l; i++) {
+        for (var i = 0, l = filterExps.length, filter; i < l; i++) {
             filter = parseFilter(filterExps[i], this.compiler)
             if (filter) this.filters.push(filter)
         }
@@ -2556,8 +2576,7 @@ module.exports = {
     parse: function (bindings) {
         utils.log('\nparsing dependencies...')
         Observer.shouldGet = true
-        var i = bindings.length
-        while (i--) { catchDeps(bindings[i]) }
+        bindings.forEach(catchDeps)
         Observer.shouldGet = false
         utils.log('\ndone.')
     }
@@ -2858,6 +2877,7 @@ module.exports = {
     'if'      : require('./if'),
     'with'    : require('./with'),
     html      : require('./html'),
+    style     : require('./style'),
 
     attr: function (value) {
         this.el.setAttribute(this.arg, value)
@@ -2979,10 +2999,7 @@ var mutationHandlers = {
     },
 
     unshift: function (m) {
-        var i, l = m.args.length
-        for (i = 0; i < l; i++) {
-            this.buildItem(m.args[i], i)
-        }
+        m.args.forEach(this.buildItem, this)
     },
 
     shift: function () {
@@ -3064,7 +3081,7 @@ module.exports = {
             var method = mutation.method
             mutationHandlers[method].call(self, mutation)
             if (method !== 'push' && method !== 'pop') {
-                self.updateIndexes()
+                self.updateIndex()
             }
             if (method === 'push' || method === 'unshift' || method === 'splice') {
                 self.changed()
@@ -3098,9 +3115,7 @@ module.exports = {
 
         // create child-vms and append to DOM
         if (collection.length) {
-            for (var i = 0, l = collection.length; i < l; i++) {
-                this.buildItem(collection[i], i)
-            }
+            collection.forEach(this.buildItem, this)
             if (!init) this.changed()
         }
     },
@@ -3128,32 +3143,33 @@ module.exports = {
      */
     buildItem: function (data, index) {
 
-        var node    = this.el.cloneNode(true),
-            ctn     = this.container,
+        var el  = this.el.cloneNode(true),
+            ctn = this.container,
+            vms = this.vms,
+            col = this.collection,
             ref, item
 
         // append node into DOM first
         // so v-if can get access to parentNode
         if (data) {
-            ref = this.vms.length > index
-                ? this.vms[index].$el
+            ref = vms.length > index
+                ? vms[index].$el
                 : this.ref
             // make sure it works with v-if
             if (!ref.parentNode) ref = ref.vue_ref
             // process transition info before appending
-            node.vue_trans = utils.attr(node, 'transition', true)
-            transition(node, 1, function () {
-                ctn.insertBefore(node, ref)
+            el.vue_trans = utils.attr(el, 'transition', true)
+            transition(el, 1, function () {
+                ctn.insertBefore(el, ref)
             }, this.compiler)
         }
 
         item = new this.Ctor({
-            el: node,
+            el: el,
             data: data,
             compilerOptions: {
                 repeat: true,
                 repeatIndex: index,
-                repeatCollection: this.collection,
                 parentCompiler: this.compiler,
                 delegator: ctn
             }
@@ -3164,14 +3180,19 @@ module.exports = {
             // let's remove it...
             item.$destroy()
         } else {
-            this.vms.splice(index, 0, item)
+            vms.splice(index, 0, item)
+            // in case `$destroy` is called directly on a repeated vm
+            // make sure the vm's data is properly removed
+            item.$compiler.observer.on('hook:afterDestroy', function () {
+                col.remove(data)
+            })
         }
     },
 
     /**
      *  Update index of each item after a mutation
      */
-    updateIndexes: function () {
+    updateIndex: function () {
         var i = this.vms.length
         while (i--) {
             this.vms[i].$data.$index = i
@@ -3489,14 +3510,52 @@ module.exports = {
     }
 }
 });
+require.register("vue/src/directives/style.js", function(exports, require, module){
+var camelRE = /-([a-z])/g,
+    prefixes = ['webkit', 'moz', 'ms']
+
+function camelReplacer (m) {
+    return m[1].toUpperCase()
+}
+
+module.exports = {
+
+    bind: function () {
+        var prop = this.arg,
+            first = prop.charAt(0)
+        if (first === '$') {
+            // properties that start with $ will be auto-prefixed
+            prop = prop.slice(1)
+            this.prefixed = true
+        } else if (first === '-') {
+            // normal starting hyphens should not be converted
+            prop = prop.slice(1)
+        }
+        this.prop = prop.replace(camelRE, camelReplacer)
+    },
+
+    update: function (value) {
+        var prop = this.prop
+        this.el.style[prop] = value
+        if (this.prefixed) {
+            var i = prefixes.length,
+                prop = prop.charAt(0).toUpperCase() + prop.slice(1)
+            while (i--) {
+                this.el.style[prefixes[i] + prop] = value
+            }
+        }
+    }
+
+}
+});
 require.alias("component-emitter/index.js", "vue/deps/emitter/index.js");
 require.alias("component-emitter/index.js", "emitter/index.js");
 
 require.alias("vue/src/main.js", "vue/index.js");
-if (typeof exports == "object") {
-  module.exports = require("vue");
-} else if (typeof define == "function" && define.amd) {
-  define(function(){ return require("vue"); });
+if (typeof exports == 'object') {
+  module.exports = require('vue');
+} else if (typeof define == 'function' && define.amd) {
+  define(function(){ return require('vue'); });
 } else {
-  this["Vue"] = require("vue");
+  window['Vue'] = require('vue');
 }})();

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
dist/vue.min.js


+ 2 - 2
src/directive.js

@@ -11,11 +11,11 @@ var utils      = require('./utils'),
     // match up to the first single pipe, ignore those within quotes.
     KEY_RE          = /^(?:['"](?:\\.|[^'"])*['"]|\\.|[^\|]|\|\|)+/,
 
-    ARG_RE          = /^([\w- ]+):(.+)$/,
+    ARG_RE          = /^([\w-$ ]+):(.+)$/,
     FILTERS_RE      = /\|[^\|]+/g,
     FILTER_TOKEN_RE = /[^\s']+|'[^']+'/g,
     NESTING_RE      = /^\$(parent|root)\./,
-    SINGLE_VAR_RE   = /^[\w\.\$]+$/
+    SINGLE_VAR_RE   = /^[\w\.$]+$/
 
 /**
  *  Directive class

+ 1 - 0
src/directives/index.js

@@ -9,6 +9,7 @@ module.exports = {
     'if'      : require('./if'),
     'with'    : require('./with'),
     html      : require('./html'),
+    style     : require('./style'),
 
     attr: function (value) {
         this.el.setAttribute(this.arg, value)

+ 36 - 0
src/directives/style.js

@@ -0,0 +1,36 @@
+var camelRE = /-([a-z])/g,
+    prefixes = ['webkit', 'moz', 'ms']
+
+function camelReplacer (m) {
+    return m[1].toUpperCase()
+}
+
+module.exports = {
+
+    bind: function () {
+        var prop = this.arg,
+            first = prop.charAt(0)
+        if (first === '$') {
+            // properties that start with $ will be auto-prefixed
+            prop = prop.slice(1)
+            this.prefixed = true
+        } else if (first === '-') {
+            // normal starting hyphens should not be converted
+            prop = prop.slice(1)
+        }
+        this.prop = prop.replace(camelRE, camelReplacer)
+    },
+
+    update: function (value) {
+        var prop = this.prop
+        this.el.style[prop] = value
+        if (this.prefixed) {
+            var i = prefixes.length,
+                prop = prop.charAt(0).toUpperCase() + prop.slice(1)
+            while (i--) {
+                this.el.style[prefixes[i] + prop] = value
+            }
+        }
+    }
+
+}

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

@@ -157,11 +157,11 @@ describe('UNIT: Directive', function () {
 
         it('should extract correct argument', function () {
             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)
+                e = Directive.parse('text', '$todo:todos + abc', compiler),
+                f = Directive.parse('text', '-todo-fsef:todos | fsf fsef', compiler)
             assert.strictEqual(d.arg, 'todo', 'simple')
-            assert.strictEqual(e.arg, 'todo', 'expression')
-            assert.strictEqual(f.arg, 'todo', 'with filters')
+            assert.strictEqual(e.arg, '$todo', 'expression')
+            assert.strictEqual(f.arg, '-todo-fsef', 'with hyphens and filters')
         })
 
         it('should be able to determine whether the key is an expression', function () {

+ 36 - 0
test/unit/specs/directives.js

@@ -652,6 +652,42 @@ describe('UNIT: Directives', function () {
 
     })
 
+    describe('style', function () {
+        
+        it('should apply a normal style', function () {
+            var d = mockDirective('style')
+            d.arg = 'text-align'
+            d.bind()
+            assert.strictEqual(d.prop, 'textAlign')
+            d.update('center')
+            assert.strictEqual(d.el.style.textAlign, 'center')
+        })
+
+        it('should apply prefixed style', function () {
+            var d = mockDirective('style')
+            d.arg = '-webkit-transform'
+            d.bind()
+            assert.strictEqual(d.prop, 'webkitTransform')
+            d.update('scale(2)')
+            assert.strictEqual(d.el.style.webkitTransform, 'scale(2)')
+        })
+
+        it('should auto prefix styles', function () {
+            var d = mockDirective('style')
+            d.arg = '$transform'
+            d.bind()
+            assert.ok(d.prefixed)
+            assert.strictEqual(d.prop, 'transform')
+            var val = 'scale(2)'
+            d.update(val)
+            assert.strictEqual(d.el.style.transform, val)
+            assert.strictEqual(d.el.style.webkitTransform, val)
+            assert.strictEqual(d.el.style.mozTransform, val)
+            assert.strictEqual(d.el.style.msTransform, val)
+        })
+
+    })
+
 })
 
 function mockDirective (dirName, tag, type) {

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.