Kaynağa Gözat

computed properties!!!

Evan You 12 yıl önce
ebeveyn
işleme
5acc8a2986
8 değiştirilmiş dosya ile 139 ekleme ve 68 silme
  1. 17 10
      examples/todos.html
  2. 26 5
      src/directive-parser.js
  3. 35 9
      src/directives/each.js
  4. 3 3
      src/directives/on.js
  5. 1 3
      src/main.js
  6. 50 35
      src/seed.js
  7. 5 1
      src/textnode-parser.js
  8. 2 2
      test/test.js

+ 17 - 10
examples/todos.html

@@ -42,12 +42,14 @@
             </ul>
             <div id="footer">
                 Total: <span sd-text="total < todos"></span> |
-                Remaining: <span sd-text="remaining"></span> |
-                Completed: <span sd-text="completed < remaining total"></span>
+                Remaining: <span sd-text="remaining < completed"></span> |
+                Completed: <span sd-text="completed"></span>
                 <br>
                 <a class="all" sd-on="click:setFilter">Show All</a> |
                 <a class="remaining" sd-on="click:setFilter">Show Remaining</a> |
                 <a class="completed" sd-on="click:setFilter">Show Completed</a>
+                <br>
+                <a sd-on="click:removeCompleted">Remove Completed</a>
             </div>
 
         </div>
@@ -67,8 +69,8 @@
                 // regular properties
                 scope.todos = todos
                 scope.filter = 'all'
-                scope.remaining = todos.reduce(function (count, todo) {
-                    return count + (todo.done ? 0 : 1)
+                scope.completed = todos.reduce(function (count, todo) {
+                    return count + (todo.done ? 1 : 0)
                 }, 0)
 
                 // computed properties
@@ -76,8 +78,8 @@
                     return scope.todos.length
                 }
 
-                scope.completed = function () {
-                    return scope.todos.length - scope.remaining
+                scope.remaining = function () {
+                    return scope.todos.length - scope.completed
                 }
 
                 // event handlers
@@ -86,23 +88,28 @@
                     if (val) {
                         e.el.value = ''
                         scope.todos.unshift({ text: val, done: false })
-                        scope.remaining++
                     }
                 }
 
                 scope.removeTodo = function (e) {
-                    scope.todos.splice(e.scope.$index, 1)
-                    scope.remaining -= e.scope.done ? 0 : 1
+                    scope.todos.remove(e.scope)
+                    scope.completed -= e.scope.done ? 1 : 0
                 }
 
                 scope.toggleTodo = function (e) {
-                    scope.remaining += e.scope.done ? -1 : 1
+                    scope.completed += e.scope.done ? 1 : -1
                 }
 
                 scope.setFilter = function (e) {
                     scope.filter = e.el.className
                 }
 
+                scope.removeCompleted = function () {
+                    scope.todos = scope.todos.filter(function (todo) {
+                        return !todo.done
+                    })
+                }
+
             })
 
             var app = Seed.bootstrap()

+ 26 - 5
src/directive-parser.js

@@ -6,7 +6,8 @@ var KEY_RE          = /^[^\|<]+/,
     ARG_RE          = /([^:]+):(.+)$/,
     FILTERS_RE      = /\|[^\|<]+/g,
     FILTER_TOKEN_RE = /[^\s']+|'[^']+'/g,
-    DEPS_RE         = /<[^<\|]+/g
+    DEPS_RE         = /<[^<\|]+/g,
+    NESTING_RE      = /^\^+/
 
 // parse a key, extract argument and nesting/root info
 function parseKey (rawKey) {
@@ -22,12 +23,19 @@ function parseKey (rawKey) {
         ? argMatch[1].trim()
         : null
 
-    var nesting = res.key.match(/^\^+/)
+    var nesting = res.key.match(NESTING_RE)
     res.nesting = nesting
         ? nesting[0].length
         : false
 
     res.root = res.key.charAt(0) === '$'
+
+    if (res.nesting) {
+        res.key = res.key.replace(NESTING_RE, '')
+    } else if (res.root) {
+        res.key = res.key.slice(1)
+    }
+
     return res
 }
 
@@ -50,11 +58,11 @@ function parseFilter (filter) {
 
 function Directive (directiveName, expression) {
 
-    var directive = directives[directiveName]
+    var prop, directive = directives[directiveName]
     if (typeof directive === 'function') {
         this._update = directive
     } else {
-        for (var prop in directive) {
+        for (prop in directive) {
             if (prop === 'update') {
                 this['_update'] = directive.update
             } else {
@@ -69,7 +77,7 @@ function Directive (directiveName, expression) {
     var rawKey   = expression.match(KEY_RE)[0],
         keyInfo  = parseKey(rawKey)
 
-    for (var prop in keyInfo) {
+    for (prop in keyInfo) {
         this[prop] = keyInfo[prop]
     }
     
@@ -84,7 +92,19 @@ function Directive (directiveName, expression) {
         : null
 }
 
+// called when a dependency has changed
+Directive.prototype.refresh = function () {
+    if (this.value) {
+        this._update(this.value.call(this.seed.scope))
+    }
+    if (this.binding.refreshDependents) {
+        this.binding.refreshDependents()
+    }
+}
+
+// called when a new value is set
 Directive.prototype.update = function (value) {
+    this.value = value
     // computed property
     if (typeof value === 'function' && !this.fn) {
         value = value()
@@ -94,6 +114,7 @@ Directive.prototype.update = function (value) {
         value = this.applyFilters(value)
     }
     this._update(value)
+    if (this.deps) this.refresh()
 }
 
 Directive.prototype.applyFilters = function (value) {

+ 35 - 9
src/directives/each.js

@@ -1,5 +1,17 @@
 var config = require('../config')
 
+var augmentations = {
+    remove: function (scope) {
+        this.splice(scope.$index, 1)
+    },
+    replace: function (index, data) {
+        if (typeof index !== 'number') {
+            index = index.$index
+        }
+        this.splice(index, 1, data)
+    }
+}
+
 var mutationHandlers = {
     push: function (m) {
         var self = this
@@ -25,21 +37,26 @@ var mutationHandlers = {
         self.reorder()
     },
     splice: function (m) {
-        var self = this
+        var self    = this,
+            index   = m.args[0],
+            removed = m.args[1],
+            added   = m.args.length - 2
         m.result.forEach(function (scope) {
             scope.$destroy()
         })
-        if (m.args.length > 2) {
+        if (added > 0) {
             m.args.slice(2).forEach(function (data, i) {
-                var seed  = self.buildItem(data, i),
-                    index = m.args[0] - m.args[1] + (m.args.length - 1),
-                    ref   = self.collection[index]
-                          ? self.collection[index].$seed.el
+                var seed  = self.buildItem(data, index + i),
+                    pos   = index - removed + added + 1,
+                    ref   = self.collection[pos]
+                          ? self.collection[pos].$seed.el
                           : self.marker
                 self.container.insertBefore(seed.el, ref)
             })
         }
-        self.reorder()
+        if (removed !== added) {
+            self.reorder()
+        }
     },
     sort: function () {
         var self = this
@@ -51,9 +68,10 @@ var mutationHandlers = {
 }
 mutationHandlers.reverse = mutationHandlers.sort
 
-function watchArray (arr, callback) {
+function watchArray (collection, callback) {
+
     Object.keys(mutationHandlers).forEach(function (method) {
-        arr[method] = function () {
+        collection[method] = function () {
             var result = Array.prototype[method].apply(this, arguments)
             callback({
                 method: method,
@@ -62,6 +80,10 @@ function watchArray (arr, callback) {
             })
         }
     })
+
+    for (var method in augmentations) {
+        collection[method] = augmentations[method]
+    }
 }
 
 module.exports = {
@@ -85,6 +107,9 @@ module.exports = {
             if (self.mutationHandlers) {
                 self.mutationHandlers[mutation.method].call(self, mutation)
             }
+            if (self.binding.refreshDependents) {
+                self.binding.refreshDependents()
+            }
         })
         collection.forEach(function (data, i) {
             var seed = self.buildItem(data, i)
@@ -107,6 +132,7 @@ module.exports = {
     },
 
     reorder: function () {
+        console.log('reorder')
         this.collection.forEach(function (scope, i) {
             scope.$index = i
         })

+ 3 - 3
src/directives/on.js

@@ -25,7 +25,7 @@ module.exports = {
 
     fn : true,
 
-    bind: function (handler) {
+    bind: function () {
         if (this.seed.each) {
             this.selector = '[' + this.directiveName + '*="' + this.expression + '"]'
             this.delegator = this.seed.el.parentNode
@@ -47,7 +47,7 @@ module.exports = {
                 delegator[selector] = function (e) {
                     var target = delegateCheck(e.target, delegator, selector)
                     if (target) {
-                        handler({
+                        handler.call(self.seed.scope, {
                             originalEvent : e,
                             el            : target,
                             scope         : target.seed.scope
@@ -61,7 +61,7 @@ module.exports = {
 
             // a normal handler
             this.handler = function (e) {
-                handler({
+                handler.call(self.seed.scope, {
                     originalEvent : e,
                     el            : e.currentTarget,
                     scope         : self.seed.scope

+ 1 - 3
src/main.js

@@ -39,9 +39,7 @@ api.bootstrap = function (opts) {
     if (opts) {
         config.prefix = opts.prefix || config.prefix
     }
-    var app = {},
-        n = 0,
-        el, seed
+    var app = {}, n = 0, el, seed
     while (el = document.querySelector('[' + config.prefix + '-controller]')) {
         seed = new Seed(el)
         if (el.id) {

+ 50 - 35
src/seed.js

@@ -1,12 +1,26 @@
 var Emitter         = require('emitter'),
     config          = require('./config'),
-    DirectiveParser = require('./directive-parser')
+    DirectiveParser = require('./directive-parser'),
+    TextNodeParser  = require('./textnode-parser')
 
 var slice           = Array.prototype.slice,
-    ancestorKeyRE   = /\^/g,
     ctrlAttr        = config.prefix + '-controller',
     eachAttr        = config.prefix + '-each'
 
+function determinScope (key, scope) {
+    if (key.nesting) {
+        var levels = key.nesting
+        while (scope.parentSeed && levels--) {
+            scope = scope.parentSeed
+        }
+    } else if (key.root) {
+        while (scope.parentSeed) {
+            scope = scope.parentSeed
+        }
+    }
+    return scope
+}
+
 function Seed (el, options) {
 
     if (typeof el === 'string') {
@@ -114,7 +128,7 @@ Seed.prototype._compileNode = function (node, root) {
 }
 
 Seed.prototype._compileTextNode = function (node) {
-    return node
+    return TextNodeParser.parse(node)
 }
 
 Seed.prototype._bind = function (node, directive) {
@@ -123,47 +137,26 @@ Seed.prototype._bind = function (node, directive) {
     directive.seed = this
 
     var key = directive.key,
-        snr = this.eachPrefixRE,
-        isEachKey = snr && snr.test(key),
-        scopeOwner = this
+        epr = this.eachPrefixRE,
+        isEachKey = epr && epr.test(key),
+        scope = this
 
     if (isEachKey) {
-        key = key.replace(snr, '')
+        key = directive.key = key.replace(epr, '')
     }
 
-    // handle scope nesting
-    if (snr && !isEachKey) {
-        scopeOwner = this.parentSeed
-    } else {
-        var ancestors = key.match(ancestorKeyRE),
-            root      = key.charAt(0) === '$'
-        if (ancestors) {
-            key = key.replace(ancestorKeyRE, '')
-            var levels = ancestors.length
-            while (scopeOwner.parentSeed && levels--) {
-                scopeOwner = scopeOwner.parentSeed
-            }
-        } else if (root) {
-            key = key.slice(1)
-            while (scopeOwner.parentSeed) {
-                scopeOwner = scopeOwner.parentSeed
-            }
-        }
+    if (epr && !isEachKey) {
+        scope = this.parentSeed
     }
 
-    directive.key = key
-
-    // computed properties
-    if (directive.deps) {
-        directive.deps.forEach(function (dep) {
-            console.log(dep)
-        })
-    }
-
-    var binding = scopeOwner._bindings[key] || scopeOwner._createBinding(key)
+    var ownerScope = determinScope(directive, scope),
+        binding =
+            ownerScope._bindings[key] ||
+            ownerScope._createBinding(key)
 
     // add directive to this binding
     binding.instances.push(directive)
+    directive.binding = binding
 
     // invoke bind hook if exists
     if (directive.bind) {
@@ -175,6 +168,25 @@ Seed.prototype._bind = function (node, directive) {
         directive.update(binding.value)
     }
 
+    // computed properties
+    if (directive.deps) {
+        directive.deps.forEach(function (dep) {
+            var depScope = determinScope(dep, scope),
+                depBinding =
+                    depScope._bindings[dep.key] ||
+                    depScope._createBinding(dep.key)
+            if (!depBinding.dependents) {
+                depBinding.dependents = []
+                depBinding.refreshDependents = function () {
+                    depBinding.dependents.forEach(function (dept) {
+                        dept.refresh()
+                    })
+                }
+            }
+            depBinding.dependents.push(directive)
+        })
+    }
+
 }
 
 Seed.prototype._createBinding = function (key) {
@@ -199,6 +211,9 @@ Seed.prototype._createBinding = function (key) {
             binding.instances.forEach(function (instance) {
                 instance.update(value)
             })
+            if (binding.refreshDependents) {
+                binding.refreshDependents()
+            }
         }
     })
 

+ 5 - 1
src/textnode-parser.js

@@ -1 +1,5 @@
-module.exports = {}
+module.exports = {
+    parse: function (node) {
+        return node
+    }
+}

+ 2 - 2
test/test.js

@@ -1,7 +1,7 @@
 var Seed = require('seed')
 
 describe('Seed', function () {
-    it('should have a extend method', function () {
-        assert.ok(Seed.extend)
+    it('should have a bootstrap method', function () {
+        assert.ok(Seed.bootstrap)
     })
 })