Browse Source

put properties on the scope prototype!

Evan You 13 years ago
parent
commit
cc64365323
5 changed files with 112 additions and 84 deletions
  1. 67 64
      examples/todomvc/js/app.js
  2. 5 2
      src/binding.js
  3. 3 2
      src/directives/on.js
  4. 18 3
      src/main.js
  5. 19 13
      src/seed.js

+ 67 - 64
examples/todomvc/js/app.js

@@ -1,102 +1,105 @@
+var filters = {
+    all: function () { return true },
+    active: function (todo) { return !todo.completed },
+    completed: function (todo) { return todo.completed }
+}
+
 window.addEventListener('hashchange', function () {
     Seed.broadcast('filterchange')
 })
 
-Seed.controller('todos', function (scope) {
+Seed.controller('todos', {
 
-    // filters ----------------------------------------------------------------
-    var filters = {
-        all: function () { return true },
-        active: function (todo) { return !todo.completed },
-        completed: function (todo) { return todo.completed }
-    }
-    var updateFilter = function () {
-        var filter = location.hash.slice(2)
-        scope.filter = (filter in filters) ? filter : 'all'
-    }
-    updateFilter()
-    scope.$on('filterchange', updateFilter)
-
-    // regular properties -----------------------------------------------------
-    scope.todos = todoStorage.fetch()
-    scope.remaining = scope.todos.filter(filters.active).length
+    // initializer, reserved
+    init: function () {
+        // listen for hashtag change
+        this.updateFilter()
+        this.$on('filterchange', this.updateFilter.bind(this))
+        // instance properties
+        this.todos = todoStorage.fetch()
+        this.remaining = this.todos.filter(filters.active).length
+    },
 
     // computed properties ----------------------------------------------------
-    scope.total = {get: function () {
-        return scope.todos.length
-    }}
+    total: {get: function () {
+        return this.todos.length
+    }},
 
-    scope.completed = {get: function () {
-        return scope.total - scope.remaining
-    }}
+    completed: {get: function () {
+        return this.total - this.remaining
+    }},
 
     // dynamic context computed property using info from target scope
-    scope.todoFiltered = {get: function (ctx) {
-        return filters[scope.filter](ctx.scope)
-    }}
+    todoFiltered: {get: function (ctx) {
+        return filters[this.filter](ctx.scope)
+    }},
 
     // dynamic context computed property using info from target element
-    scope.filterSelected = {get: function (ctx) {
-        return scope.filter === ctx.el.textContent.toLowerCase()
-    }}
+    filterSelected: {get: function (ctx) {
+        return this.filter === ctx.el.textContent.toLowerCase()
+    }},
 
     // two-way computed property with both getter and setter
-    scope.allDone = {
+    allDone: {
         get: function () {
-            return scope.remaining === 0
+            return this.remaining === 0
         },
         set: function (value) {
-            scope.remaining = value ? 0 : scope.total
-            scope.todos.forEach(function (todo) {
+            this.remaining = value ? 0 : this.total
+            this.todos.forEach(function (todo) {
                 todo.completed = value
             })
         }
-    }
+    },
 
     // event handlers ---------------------------------------------------------
-    scope.addTodo = function () {
-        var value = scope.newTodo && scope.newTodo.trim()
+    addTodo: function () {
+        var value = this.newTodo && this.newTodo.trim()
         if (value) {
-            scope.todos.unshift({ title: value, completed: false })
-            scope.newTodo = ''
-            scope.remaining++
-            todoStorage.save(scope.todos)
+            this.todos.unshift({ title: value, completed: false })
+            this.newTodo = ''
+            this.remaining++
+            todoStorage.save(this.todos)
         }
-    }
+    },
 
-    scope.removeTodo = function (e) {
-        scope.todos.remove(e.scope)
-        scope.remaining -= e.scope.completed ? 0 : 1
-        todoStorage.save(scope.todos)
-    }
+    removeTodo: function (e) {
+        this.todos.remove(e.scope)
+        this.remaining -= e.scope.completed ? 0 : 1
+        todoStorage.save(this.todos)
+    },
 
-    scope.toggleTodo = function (e) {
-        scope.remaining += e.scope.completed ? -1 : 1
-        todoStorage.save(scope.todos)
-    }
+    toggleTodo: function (e) {
+        this.remaining += e.scope.completed ? -1 : 1
+        todoStorage.save(this.todos)
+    },
 
-    var beforeEditCache
-    scope.editTodo = function (e) {
-        beforeEditCache = e.scope.title
+    editTodo: function (e) {
+        this.beforeEditCache = e.scope.title
         e.scope.editing = true
-    }
+    },
 
-    scope.doneEdit = function (e) {
+    doneEdit: function (e) {
         if (!e.scope.editing) return
         e.scope.editing = false
         e.scope.title = e.scope.title.trim()
-        if (!e.scope.title) scope.removeTodo(e)
-        todoStorage.save(scope.todos)
-    }
+        if (!e.scope.title) this.removeTodo(e)
+        todoStorage.save(this.todos)
+    },
 
-    scope.cancelEdit = function (e) {
+    cancelEdit: function (e) {
         e.scope.editing = false
-        e.scope.title = beforeEditCache
-    }
+        e.scope.title = this.beforeEditCache
+    },
+
+    removeCompleted: function () {
+        this.todos = this.todos.filter(filters.active)
+        todoStorage.save(this.todos)
+    },
 
-    scope.removeCompleted = function () {
-        scope.todos = scope.todos.filter(filters.active)
-        todoStorage.save(scope.todos)
+    updateFilter: function () {
+        var filter = location.hash.slice(2)
+        this.filter = (filter in filters) ? filter : 'all'
     }
 })
 

+ 5 - 2
src/binding.js

@@ -35,6 +35,8 @@ BindingProto.inspect = function (value) {
             var l = Object.keys(value).length
             if (l === 1 || (l === 2 && value.set)) {
                 self.isComputed = true // computed property
+                value.get = value.get.bind(self.scope)
+                if (value.set) value.set = value.set.bind(self.scope)
             }
         }
     } else if (type === 'Array') {
@@ -52,6 +54,7 @@ BindingProto.inspect = function (value) {
  */
 BindingProto.def = function (scope, path) {
     var self = this,
+        seed = self.seed,
         key = path[0]
     if (path.length === 1) {
         // here we are! at the end of the path!
@@ -63,8 +66,8 @@ BindingProto.def = function (scope, path) {
                 }
                 return self.isComputed
                     ? self.value.get({
-                        el: self.seed.el,
-                        scope: self.seed.scope
+                        el: seed.el,
+                        scope: seed.scope
                     })
                     : self.value
             },

+ 3 - 2
src/directives/on.js

@@ -28,7 +28,8 @@ module.exports = {
         if (!handler) return
 
         var seed  = this.seed,
-            event = this.arg
+            event = this.arg,
+            ownerScope = this.binding.seed.scope
 
         if (seed.each && event !== 'blur' && event !== 'blur') {
 
@@ -46,7 +47,7 @@ module.exports = {
                 if (target) {
                     e.el = target
                     e.scope = target.sd_scope
-                    handler.call(seed.scope, e)
+                    handler.call(ownerScope, e)
                 }
             }
             dHandler.event = event

+ 18 - 3
src/main.js

@@ -1,5 +1,6 @@
 var config      = require('./config'),
     Seed        = require('./seed'),
+    Scope       = require('./scope'),
     directives  = require('./directives'),
     filters     = require('./filters'),
     textParser  = require('./text-parser'),
@@ -37,9 +38,23 @@ api.data = function (id, data) {
  *  Store a controller function in config.controllers
  *  so it can be consumed by sd-controller
  */
-api.controller = function (id, extensions) {
-    if (!extensions) return controllers[id]
-    controllers[id] = extensions
+api.controller = function (id, properties) {
+    if (!properties) return controllers[id]
+    // create a subclass of Scope that has the extension methods mixed-in
+    var ExtendedScope = function () {
+        Scope.apply(this, arguments)
+    }
+    var p = ExtendedScope.prototype = Object.create(Scope.prototype)
+    p.constructor = ExtendedScope
+    for (var prop in properties) {
+        if (prop !== 'init') {
+            p[prop] = properties[prop]
+        }
+    }
+    controllers[id] = {
+        init: properties.init,
+        ExtendedScope: ExtendedScope
+    }
 }
 
 /*

+ 19 - 13
src/seed.js

@@ -54,27 +54,33 @@ function Seed (el, options) {
         data = data.$dump()
     }
 
-    // initialize the scope object
-    var key,
-        scope = this.scope = new Scope(this, options)
-
-    // copy data
-    for (key in data) {
-        scope[key] = data[key]
-    }
-
-    // if has controller function, apply it so we have all the user definitions
-    var ctrlID = el.getAttribute(ctrlAttr)
+    // check if there is a controller associated with this seed
+    var ctrlID = el.getAttribute(ctrlAttr), controller
     if (ctrlID) {
         el.removeAttribute(ctrlAttr)
-        var controller = config.controllers[ctrlID]
+        controller = config.controllers[ctrlID]
         if (controller) {
             this._controller = controller
-            controller(this.scope)
         } else {
             config.warn('controller "' + ctrlID + '" is not defined.')
         }
     }
+    
+    // create the scope object
+    // if the controller has an extended scope contructor, use it;
+    // otherwise, use the original scope constructor.
+    var ScopeConstructor = (controller && controller.ExtendedScope) || Scope,
+        scope = this.scope = new ScopeConstructor(this, options)
+
+    // copy data
+    for (key in data) {
+        scope[key] = data[key]
+    }
+
+    // apply controller initialize function
+    if (controller && controller.init) {
+        controller.init.call(scope)
+    }
 
     // now parse the DOM
     this._compileNode(el, true)