Просмотр исходного кода

ViewModel.extend() should also extend Object options

Evan You 12 лет назад
Родитель
Сommit
ec86b9faf9
7 измененных файлов с 163 добавлено и 91 удалено
  1. 1 0
      TODO.md
  2. 2 1
      src/compiler.js
  3. 5 1
      src/directives/each.js
  4. 7 2
      src/exp-parser.js
  5. 67 36
      src/main.js
  6. 4 10
      src/utils.js
  7. 77 41
      test/unit/specs/api.js

+ 1 - 0
TODO.md

@@ -1,4 +1,5 @@
 - sd-partial
+- $index on sd-each VMs
 - transition effects
 - component examples
 - tests

+ 2 - 1
src/compiler.js

@@ -184,7 +184,8 @@ CompilerProto.compileNode = function (node, root) {
         } else if (vmExp && !root) { // nested ViewModels
 
             node.removeAttribute(vmAttr)
-            var ChildVM = utils.getVM(vmExp)
+            var opts = compiler.vm.constructor.options,
+                ChildVM = (opts && opts.vms && opts.vms[vmExp]) || utils.vms[vmExp]
             if (ChildVM) {
                 new ChildVM({
                     el: node,

+ 5 - 1
src/directives/each.js

@@ -126,7 +126,11 @@ module.exports = {
         var node = this.el.cloneNode(true),
             ctn  = this.container,
             vmID = node.getAttribute(config.prefix + '-viewmodel'),
-            ChildVM = utils.getVM(vmID) || ViewModel,
+            opts = this.vm.constructor.options,
+            ChildVM =
+                (opts && opts.vms && opts.vms[vmID]) ||
+                utils.vms[vmID] ||
+                ViewModel,
             wrappedData = {}
         wrappedData[this.arg] = data || {}
         var item = new ChildVM({

+ 7 - 2
src/exp-parser.js

@@ -25,8 +25,9 @@ function getVariables (code) {
         .replace(KEYWORDS_RE, '')
         .replace(NUMBER_RE, '')
         .replace(BOUNDARY_RE, '')
-    code = code ? code.split(/,+/) : []
     return code
+        ? code.split(/,+/)
+        : []
 }
 
 module.exports = {
@@ -48,7 +49,11 @@ module.exports = {
             if (hash[v]) continue
             hash[v] = v
             // push assignment
-            args.push(v + '=this.$get("' + v + '")')
+            args.push(v + (
+                v.charAt(0) === '$'
+                    ? '=this.' + v
+                    : '=this.$get("' + v + '")'
+                ))
         }
         args = 'var ' + args.join(',') + ';return ' + exp
         /* jshint evil: true */

+ 67 - 36
src/main.js

@@ -7,29 +7,45 @@ var config      = require('./config'),
     api         = {}
 
 /*
- *  Allows user to create a custom directive
+ *  Set config options
  */
-api.directive = function (name, fn) {
-    if (!fn) return directives[name]
-    directives[name] = fn
+api.config = function (opts) {
+    if (opts) {
+        utils.extend(config, opts)
+        textParser.buildRegex()
+    }
 }
 
 /*
- *  Allows user to create a custom filter
+ *  Allows user to register/retrieve a directive definition
  */
-api.filter = function (name, fn) {
-    if (!fn) return filters[name]
-    filters[name] = fn
+api.directive = function (id, fn) {
+    if (!fn) return directives[id]
+    directives[id] = fn
 }
 
 /*
- *  Set config options
+ *  Allows user to register/retrieve a filter function
  */
-api.config = function (opts) {
-    if (opts) {
-        utils.extend(config, opts)
-        textParser.buildRegex()
-    }
+api.filter = function (id, fn) {
+    if (!fn) return filters[id]
+    filters[id] = fn
+}
+
+/*
+ *  Allows user to register/retrieve a ViewModel constructor
+ */
+api.vm = function (id, Ctor) {
+    if (!Ctor) return utils.vms[id]
+    utils.vms[id] = Ctor
+}
+
+/*
+ *  Allows user to register/retrieve a template partial
+ */
+api.partial = function (id, partial) {
+    if (!partial) return utils.partials[id]
+    utils.partials[id] = partial
 }
 
 api.ViewModel = ViewModel
@@ -40,33 +56,21 @@ ViewModel.extend = extend
  *  and add extend method
  */
 function extend (options) {
-    var ParentVM = this,
-        ExtendedVM = function (opts) {
-            opts = opts || {}
-            // extend instance data
-            if (options.data) {
-                opts.data = opts.data || {}
-                utils.extend(opts.data, options.data)
-            }
-            // copy in constructor options, but instance options
-            // have priority.
-            for (var key in options) {
-                if (key !== 'props' &&
-                    key !== 'data' &&
-                    key !== 'template' &&
-                    key !== 'el') {
-                    opts[key] = opts[key] || options[key]
-                }
-            }
-            ParentVM.call(this, opts)
-        }
-    // inherit from ViewModel
+    var ParentVM = this
+    // inherit options
+    options = inheritOptions(options, ParentVM.options, true)
+    var ExtendedVM = function (opts) {
+        opts = inheritOptions(opts, options, true)
+        ParentVM.call(this, opts)
+    }
+    // inherit prototype props
     var proto = ExtendedVM.prototype = Object.create(ParentVM.prototype)
     utils.defProtected(proto, 'constructor', ExtendedVM)
     // copy prototype props
     if (options.props) {
         utils.extend(proto, options.props, function (key) {
-            return !(key in ParentVM.prototype)
+            // do not overwrite the ancestor ViewModel prototype methods
+            return !(key in ViewModel.prototype)
         })
     }
     // convert template to documentFragment
@@ -80,6 +84,33 @@ function extend (options) {
     return ExtendedVM
 }
 
+/*
+ *  Inherit options
+ *
+ *  For options such as `data`, `vms`, `directives`, 'partials',
+ *  they should be further extended. However extending should only
+ *  be done at top level.
+ *  
+ *  `props` is an exception because it's handled directly on the
+ *  prototype.
+ *
+ *  `el` is an exception because it's not allowed as an
+ *  extension option, but only as an instance option.
+ */
+function inheritOptions (child, parent, topLevel) {
+    child = child || {}
+    if (!parent) return child
+    for (var key in parent) {
+        if (key === 'el' || key === 'props') continue
+        if (!child[key]) { // child has priority
+            child[key] = parent[key]
+        } else if (topLevel && utils.typeOf(child[key]) === 'Object') {
+            inheritOptions(child[key], parent[key], false)
+        }
+    }
+    return child
+}
+
 /*
  *  Convert a string template to a dom fragment
  */

+ 4 - 10
src/utils.js

@@ -1,9 +1,11 @@
 var config    = require('./config'),
-    toString  = Object.prototype.toString,
-    VMs       = {}
+    toString  = Object.prototype.toString
 
 module.exports = {
 
+    vms: {},
+    partials: {},
+
     /*
      *  Define an ienumerable property
      *  This avoids it being included in JSON.stringify
@@ -29,14 +31,6 @@ module.exports = {
         }
     },
 
-    registerVM: function (id, VM) {
-        VMs[id] = VM
-    },
-
-    getVM: function (id) {
-        return VMs[id]
-    },
-
     log: function () {
         if (config.debug) console.log.apply(console, arguments)
         return this

+ 77 - 41
test/unit/specs/api.js

@@ -1,5 +1,48 @@
 describe('UNIT: API', function () {
 
+    describe('config()', function () {
+        
+        it('should work when changing prefix', function () {
+            var testId = 'config-1'
+            seed.config({
+                prefix: 'test'
+            })
+            mock(testId, '<span test-text="test"></span>')
+            new seed.ViewModel({
+                el: '#' + testId,
+                data: { test: testId }
+            })
+            assert.strictEqual($('#' + testId + ' span'), testId)
+        })
+
+        it('should work when changing interpolate tags', function () {
+            var testId = 'config-2'
+            seed.config({
+                interpolateTags: {
+                    open: '<%',
+                    close: '%>'
+                }
+            })
+            mock(testId, '<% test %>')
+            new seed.ViewModel({
+                el: '#' + testId,
+                data: { test: testId }
+            })
+            assert.strictEqual($('#' + testId), testId)
+        })
+
+        after(function () {
+            seed.config({
+                prefix: 'sd',
+                interpolateTags: {
+                    open: '{{',
+                    close: '}}'
+                }
+            })
+        })
+
+    })
+
     describe('filter()', function () {
 
         var reverse = function (input) {
@@ -78,6 +121,18 @@ describe('UNIT: API', function () {
 
     })
 
+    describe('vm()', function () {
+        it('should be tested', function () {
+            assert.ok(false)
+        })
+    })
+
+    describe('partial()', function () {
+        it('should be tested', function () {
+            assert.ok(false)
+        })
+    })
+
     describe('ViewModel.extend()', function () {
         
         it('should return a subclass of seed.ViewModel', function () {
@@ -93,13 +148,25 @@ describe('UNIT: API', function () {
             })
             var Child = Parent.extend({
                 data: {
-                    test2: 'ho'
+                    test2: 'ho',
+                    test3: {
+                        hi: 1
+                    }
                 }
             })
             assert.strictEqual(Child.super, Parent)
-            var child = new Child()
+            var child = new Child({
+                data: {
+                    test3: {
+                        ho: 2
+                    }
+                }
+            })
             assert.strictEqual(child.test, 'hi')
             assert.strictEqual(child.test2, 'ho')
+            // should overwrite past 1 level deep
+            assert.strictEqual(child.test3.ho, 2)
+            assert.notOk(child.test3.hi)
         })
 
         describe('Options', function () {
@@ -305,49 +372,18 @@ describe('UNIT: API', function () {
 
             })
 
-        })
-
-    })
-
-    describe('config()', function () {
-        
-        it('should work when changing prefix', function () {
-            var testId = 'config-1'
-            seed.config({
-                prefix: 'test'
-            })
-            mock(testId, '<span test-text="test"></span>')
-            new seed.ViewModel({
-                el: '#' + testId,
-                data: { test: testId }
+            describe('vms', function () {
+                it('should be tested', function () {
+                    assert.ok(false)
+                })
             })
-            assert.strictEqual($('#' + testId + ' span'), testId)
-        })
 
-        it('should work when changing interpolate tags', function () {
-            var testId = 'config-2'
-            seed.config({
-                interpolateTags: {
-                    open: '<%',
-                    close: '%>'
-                }
-            })
-            mock(testId, '<% test %>')
-            new seed.ViewModel({
-                el: '#' + testId,
-                data: { test: testId }
+            describe('partials', function () {
+                it('should be tested', function () {
+                    assert.ok(false)
+                })
             })
-            assert.strictEqual($('#' + testId), testId)
-        })
 
-        after(function () {
-            seed.config({
-                prefix: 'sd',
-                interpolateTags: {
-                    open: '{{',
-                    close: '}}'
-                }
-            })
         })
 
     })