computed properties now have access to context scope and element

This commit is contained in:
Evan You 2013-08-11 01:50:32 -04:00
parent 1a842d18f2
commit 8eedfeacf9
15 changed files with 328 additions and 129 deletions

View File

@ -67,7 +67,7 @@ module.exports = function( grunt ) {
grunt.loadNpmTasks( 'grunt-component-build' )
grunt.loadNpmTasks( 'grunt-mocha' )
grunt.registerTask( 'test', ['mocha'] )
grunt.registerTask( 'default', ['jshint', 'component_build:build'] )
grunt.registerTask( 'default', ['jshint', 'component_build:build', 'concat:dev'] )
grunt.registerTask( 'concat', function (version) {
var fs = require('fs'),

161
dist/seed.js vendored
View File

@ -456,6 +456,14 @@ module.exports = {
interpolateTags : {
open : '{{',
close : '}}'
},
log: function (msg) {
if (this.debug) console.log(msg)
},
warn: function(msg) {
if (this.debug) console.warn(msg)
}
}
});
@ -568,6 +576,8 @@ var slice = Array.prototype.slice,
*/
function Seed (el, options) {
config.log('\ncreated new Seed instance.\n')
if (typeof el === 'string') {
el = document.querySelector(el)
}
@ -587,8 +597,8 @@ function Seed (el, options) {
var dataAttr = config.prefix + '-data',
dataId = el.getAttribute(dataAttr),
data = (options && options.data) || config.datum[dataId]
if (config.debug && dataId && !data) {
console.warn('data "' + dataId + '" is not defined.')
if (dataId && !data) {
config.warn('data "' + dataId + '" is not defined.')
}
data = data || {}
el.removeAttribute(dataAttr)
@ -600,10 +610,11 @@ function Seed (el, options) {
}
// initialize the scope object
var scope = this.scope = new Scope(this, options)
var key,
scope = this.scope = new Scope(this, options)
// copy data
for (var key in data) {
for (key in data) {
scope[key] = data[key]
}
@ -614,16 +625,23 @@ function Seed (el, options) {
var factory = config.controllers[ctrlID]
if (factory) {
factory(this.scope)
} else if (config.debug) {
console.warn('controller "' + ctrlID + '" is not defined.')
} else {
config.warn('controller "' + ctrlID + '" is not defined.')
}
}
// now parse the DOM
this._compileNode(el, true)
// for anything in scope but not binded in DOM, create bindings for them
for (key in scope) {
if (key.charAt(0) !== '$' && !this._bindings[key]) {
this._createBinding(key)
}
}
// extract dependencies for computed properties
depsParser.parse(this._computed)
if (this._computed.length) depsParser.parse(this._computed)
delete this._computed
}
@ -756,6 +774,7 @@ SeedProto._bind = function (directive) {
* Create binding and attach getter/setter for a key to the scope object
*/
SeedProto._createBinding = function (key) {
config.log(' created binding: ' + key)
var binding = new Binding(this, key)
this._bindings[key] = binding
if (binding.isComputed) this._computed.push(binding)
@ -895,6 +914,7 @@ var utils = require('./utils'),
*/
function Binding (seed, key) {
this.seed = seed
this.scope = seed.scope
this.key = key
var path = key.split('.')
this.inspect(utils.getNestedValue(seed.scope, path))
@ -942,7 +962,10 @@ BindingProto.def = function (scope, path) {
observer.emit('get', self)
}
return self.isComputed
? self.value.get()
? self.value.get({
el: self.seed.el,
scope: self.seed.scope
})
: self.value
},
set: function (value) {
@ -1073,7 +1096,12 @@ DirProto.update = function (value) {
* computed properties only
*/
DirProto.refresh = function () {
var value = this.value.get()
// pass element and scope info to the getter
// enables powerful context-aware bindings
var value = this.value.get({
el: this.el,
scope: this.seed.scope
})
if (value === this.computedValue) return
this.computedValue = value
this.apply(value)
@ -1180,10 +1208,8 @@ module.exports = {
var dir = directives[dirname],
valid = KEY_RE.test(expression)
if (config.debug) {
if (!dir) console.warn('unknown directive: ' + dirname)
if (!valid) console.warn('invalid directive expression: ' + expression)
}
if (!dir) config.warn('unknown directive: ' + dirname)
if (!valid) config.warn('invalid directive expression: ' + expression)
return dir && valid
? new Directive(dirname, expression, oneway)
@ -1236,8 +1262,14 @@ module.exports = {
});
require.register("seed/src/deps-parser.js", function(exports, require, module){
var Emitter = require('emitter'),
config = require('./config'),
observer = new Emitter()
var dummyEl = document.createElement('div'),
ARGS_RE = /^function\s*?\((.+)\)/,
SCOPE_RE_STR = '\\.scope\\.[\\.A-Za-z0-9_$]+',
noop = function () {}
/*
* Auto-extract the dependencies of a computed property
* by recording the getters triggered when evaluating it.
@ -1250,7 +1282,10 @@ function catchDeps (binding) {
observer.on('get', function (dep) {
binding.deps.push(dep)
})
binding.value.get()
binding.value.get({
scope: createDummyScope(binding.value.get),
el: dummyEl
})
observer.off('get')
}
@ -1260,9 +1295,11 @@ function catchDeps (binding) {
*/
function filterDeps (binding) {
var i = binding.deps.length, dep
config.log('\n─ ' + binding.key)
while (i--) {
dep = binding.deps[i]
if (!dep.deps.length) {
config.log(' └─' + dep.key)
dep.subs.push.apply(dep.subs, binding.instances)
} else {
binding.deps.splice(i, 1)
@ -1270,6 +1307,38 @@ function filterDeps (binding) {
}
}
/*
* We need to invoke each binding's getter for dependency parsing,
* but we don't know what sub-scope properties the user might try
* to access in that getter. To avoid thowing an error or forcing
* the user to guard against an undefined argument, we staticly
* analyze the function to extract any possible nested properties
* the user expects the target scope to possess. They are all assigned
* a noop function so they can be invoked with no real harm.
*/
function createDummyScope (fn) {
var scope = {},
str = fn.toString()
var args = str.match(ARGS_RE)
if (!args) return scope
var argRE = new RegExp(args[1] + SCOPE_RE_STR, 'g'),
matches = str.match(argRE)
if (!matches) return scope
var i = matches.length, j, path, key, level
while (i--) {
level = scope
path = matches[i].slice(args[1].length + 7).split('.')
j = 0
while (j < path.length) {
key = path[j]
if (!level[key]) level[key] = noop
level = level[key]
j++
}
}
return scope
}
module.exports = {
/*
@ -1281,10 +1350,12 @@ module.exports = {
* parse a list of computed property bindings
*/
parse: function (bindings) {
config.log('\nparsing dependencies...')
observer.isObserving = true
bindings.forEach(catchDeps)
bindings.forEach(filterDeps)
observer.isObserving = false
config.log('\ndone.')
}
}
});
@ -1296,26 +1367,32 @@ var keyCodes = {
up: 38,
left: 37,
right: 39,
down: 40
down: 40,
esc: 27
}
module.exports = {
trim: function (value) {
return value ? value.toString().trim() : ''
},
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
},
uppercase: function (value) {
return value.toString().toUpperCase()
return value ? value.toString().toUpperCase() : ''
},
lowercase: function (value) {
return value.toString().toLowerCase()
return value ? value.toString().toLowerCase() : ''
},
currency: function (value, args) {
if (!value) return value
if (!value) return ''
var sign = (args && args[0]) || '$',
i = value % 3,
f = '.' + value.toFixed(2).slice(-2),
@ -1324,12 +1401,13 @@ module.exports = {
},
key: function (handler, args) {
if (!handler) return
var code = keyCodes[args[0]]
if (!code) {
code = parseInt(args[0], 10)
}
return function (e) {
if (e.originalEvent.keyCode === code) {
if (e.keyCode === code) {
handler.call(this, e)
}
}
@ -1368,7 +1446,6 @@ module.exports = {
},
focus: function (value) {
// yield so it work when toggling visibility
var el = this.el
setTimeout(function () {
el[value ? 'focus' : 'blur']()
@ -1397,7 +1474,7 @@ module.exports = {
el.addEventListener('change', this.change)
},
update: function (value) {
this.el.value = value
this.el.value = value ? value : ''
},
unbind: function () {
if (this.oneway) return
@ -1480,8 +1557,7 @@ var mutationHandlers = {
push: function (m) {
var self = this
m.args.forEach(function (data, i) {
var seed = self.buildItem(data, self.collection.length + i)
self.container.insertBefore(seed.el, self.ref)
self.buildItem(self.ref, data, self.collection.length + i)
})
},
@ -1492,11 +1568,10 @@ var mutationHandlers = {
unshift: function (m) {
var self = this
m.args.forEach(function (data, i) {
var seed = self.buildItem(data, i),
ref = self.collection.length > m.args.length
var ref = self.collection.length > m.args.length
? self.collection[m.args.length].$el
: self.ref
self.container.insertBefore(seed.el, ref)
self.buildItem(ref, data, i)
})
self.updateIndexes()
},
@ -1517,12 +1592,11 @@ var mutationHandlers = {
})
if (added > 0) {
m.args.slice(2).forEach(function (data, i) {
var seed = self.buildItem(data, index + i),
pos = index - removed + added + 1,
var pos = index - removed + added + 1,
ref = self.collection[pos]
? self.collection[pos].$el
: self.ref
self.container.insertBefore(seed.el, ref)
self.buildItem(ref, index + i)
})
}
if (removed !== added) {
@ -1570,15 +1644,15 @@ module.exports = {
// create child-seeds and append to DOM
collection.forEach(function (data, i) {
var seed = self.buildItem(data, i)
self.container.insertBefore(seed.el, self.ref)
self.buildItem(self.ref, data, i)
})
},
buildItem: function (data, index) {
buildItem: function (ref, data, index) {
var node = this.el.cloneNode(true)
this.container.insertBefore(node, ref)
var Seed = require('../seed'),
node = this.el.cloneNode(true)
var spore = new Seed(node, {
spore = new Seed(node, {
each: true,
eachPrefix: this.arg + '.',
parentSeed: this.seed,
@ -1587,7 +1661,6 @@ module.exports = {
delegator: this.container
})
this.collection[index] = spore.scope
return spore
},
updateIndexes: function () {
@ -1660,11 +1733,9 @@ module.exports = {
dHandler = delegator.sd_dHandlers[identifier] = function (e) {
var target = delegateCheck(e.target, delegator, identifier)
if (target) {
handler.call(seed.scope, {
el: target,
scope: target.sd_scope,
originalEvent: e
})
e.el = target
e.scope = target.sd_scope
handler.call(seed.scope, e)
}
}
dHandler.event = event
@ -1674,11 +1745,9 @@ module.exports = {
// a normal, single element handler
this.handler = function (e) {
handler.call(seed.scope, {
el: e.currentTarget,
scope: seed.scope,
originalEvent: e
})
e.el = e.currentTarget
e.scope = seed.scope
handler.call(seed.scope, e)
}
this.el.addEventListener(event, this.handler)
@ -1697,5 +1766,5 @@ require.alias("component-indexof/index.js", "component-emitter/deps/indexof/inde
require.alias("seed/src/main.js", "seed/index.js");
window.Seed = window.Seed || require('seed')
Seed.version = '0.1.1'
Seed.version = 'dev'
})();

View File

@ -1,13 +0,0 @@
#todoapp.all [href="#/all"],
#todoapp.active [href="#/active"],
#todoapp.completed [href="#/completed"] {
font-weight: bold;
}
#todoapp.active .todo.completed {
display: none;
}
#todoapp.completed .todo:not(.completed) {
display: none;
}

View File

@ -4,10 +4,9 @@
<title>Todo</title>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="common/base.css">
<link rel="stylesheet" type="text/css" href="css/app.css">
</head>
<body>
<section id="todoapp" sd-controller="Todos" sd-class="filter">
<section id="todoapp" sd-controller="Todos">
<header id="header">
<h1>todos</h1>
@ -15,8 +14,10 @@
<input
id="new-todo"
autofocus
sd-on="keyup:addTodo | key enter"
autocomplete="off"
placeholder="What needs to be done?"
sd-value="newTodo"
sd-on="keyup:addTodo | key enter"
>
</header>
@ -32,6 +33,7 @@
<li
class="todo"
sd-each="todo:todos"
sd-show="filterTodo"
sd-class="completed:todo.completed, editing:todo.editing"
>
<div class="view">
@ -48,7 +50,7 @@
class="edit"
type="text"
sd-focus="todo.editing"
sd-on="blur:stopEdit, keyup:stopEdit | key enter"
sd-on="blur:doneEdit, keyup:doneEdit | key enter, keyup:cancelEdit | key esc"
sd-value="todo.title"
>
</li>
@ -61,9 +63,9 @@
<strong sd-text="remaining"></strong> {{itemLabel}} left
</span>
<ul id="filters">
<li><a href="#/all">All</a></li>
<li><a href="#/active">Active</a></li>
<li><a href="#/completed">Completed</a></li>
<li><a href="#/all" sd-class="selected:checkFilter">All</a></li>
<li><a href="#/active" sd-class="selected:checkFilter">Active</a></li>
<li><a href="#/completed" sd-class="selected:checkFilter">Completed</a></li>
</ul>
<button id="clear-completed" sd-on="click:removeCompleted" sd-show="completed">
Remove Completed ({{completed}})

View File

@ -1,12 +1,16 @@
var storageKey = 'todos-seedjs',
storedData = JSON.parse(localStorage.getItem(storageKey))
todos = JSON.parse(localStorage.getItem(storageKey)) || [],
filters = {
all: function () { return true },
active: function (v) { return !v },
completed: function (v) { return v }
}
Seed.controller('Todos', function (scope) {
// regular properties -----------------------------------------------------
scope.todos = Array.isArray(storedData) ? storedData : []
scope.filter = location.hash.slice(2) || 'all'
scope.remaining = scope.todos.reduce(function (n, todo) {
scope.todos = todos
scope.remaining = todos.reduce(function (n, todo) {
return n + (todo.completed ? 0 : 1)
}, 0)
@ -23,6 +27,17 @@ Seed.controller('Todos', function (scope) {
return scope.remaining > 1 ? 'items' : 'item'
}}
// computed property using info from target scope
scope.filterTodo = {get: function (e) {
return filters[scope.filter](e.scope.completed)
}}
// computed property using info from target element
scope.checkFilter = {get: function (e) {
return scope.filter === e.el.textContent.toLowerCase()
}}
// two-way computed property with both getter and setter
scope.allDone = {
get: function () {
return scope.remaining === 0
@ -36,47 +51,66 @@ Seed.controller('Todos', function (scope) {
}
// event handlers ---------------------------------------------------------
scope.addTodo = function (e) {
if (e.el.value) {
scope.todos.unshift({ title: e.el.value, completed: false })
e.el.value = ''
scope.addTodo = function () {
if (scope.newTodo.trim()) {
scope.todos.unshift({ title: scope.newTodo.trim(), completed: false })
scope.newTodo = ''
scope.remaining++
sync()
}
}
scope.removeTodo = function (e) {
scope.todos.remove(e.scope)
scope.remaining -= e.scope.completed ? 0 : 1
sync()
}
scope.updateCount = function (e) {
scope.remaining += e.scope.completed ? -1 : 1
sync()
}
var beforeEditCache
scope.edit = function (e) {
beforeEditCache = e.scope.title
e.scope.editing = true
}
scope.stopEdit = function (e) {
scope.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)
sync()
}
scope.cancelEdit = function (e) {
e.scope.editing = false
setTimeout(function () {
e.scope.title = beforeEditCache
}, 0)
}
scope.removeCompleted = function () {
if (scope.completed === 0) return
scope.todos = scope.todos.filter(function (todo) {
return !todo.completed
})
sync()
}
// listen for hash change
window.addEventListener('hashchange', function () {
scope.filter = location.hash.slice(2)
})
// filters
updateFilter()
window.addEventListener('hashchange', updateFilter)
function updateFilter () {
if (!location.hash) return
scope.filter = location.hash.slice(2) || 'all'
}
// persist data on leave
window.addEventListener('beforeunload', function () {
// data persistence using localStorage
function sync () {
localStorage.setItem(storageKey, scope.$serialize('todos'))
})
}
})

View File

@ -11,6 +11,7 @@ var utils = require('./utils'),
*/
function Binding (seed, key) {
this.seed = seed
this.scope = seed.scope
this.key = key
var path = key.split('.')
this.inspect(utils.getNestedValue(seed.scope, path))
@ -58,7 +59,10 @@ BindingProto.def = function (scope, path) {
observer.emit('get', self)
}
return self.isComputed
? self.value.get()
? self.value.get({
el: self.seed.el,
scope: self.seed.scope
})
: self.value
},
set: function (value) {

View File

@ -8,5 +8,13 @@ module.exports = {
interpolateTags : {
open : '{{',
close : '}}'
},
log: function (msg) {
if (this.debug) console.log(msg)
},
warn: function(msg) {
if (this.debug) console.warn(msg)
}
}

View File

@ -1,6 +1,12 @@
var Emitter = require('emitter'),
config = require('./config'),
observer = new Emitter()
var dummyEl = document.createElement('div'),
ARGS_RE = /^function\s*?\((.+)\)/,
SCOPE_RE_STR = '\\.scope\\.[\\.A-Za-z0-9_$]+',
noop = function () {}
/*
* Auto-extract the dependencies of a computed property
* by recording the getters triggered when evaluating it.
@ -13,7 +19,10 @@ function catchDeps (binding) {
observer.on('get', function (dep) {
binding.deps.push(dep)
})
binding.value.get()
binding.value.get({
scope: createDummyScope(binding.value.get),
el: dummyEl
})
observer.off('get')
}
@ -23,9 +32,11 @@ function catchDeps (binding) {
*/
function filterDeps (binding) {
var i = binding.deps.length, dep
config.log('\n─ ' + binding.key)
while (i--) {
dep = binding.deps[i]
if (!dep.deps.length) {
config.log(' └─' + dep.key)
dep.subs.push.apply(dep.subs, binding.instances)
} else {
binding.deps.splice(i, 1)
@ -33,6 +44,38 @@ function filterDeps (binding) {
}
}
/*
* We need to invoke each binding's getter for dependency parsing,
* but we don't know what sub-scope properties the user might try
* to access in that getter. To avoid thowing an error or forcing
* the user to guard against an undefined argument, we staticly
* analyze the function to extract any possible nested properties
* the user expects the target scope to possess. They are all assigned
* a noop function so they can be invoked with no real harm.
*/
function createDummyScope (fn) {
var scope = {},
str = fn.toString()
var args = str.match(ARGS_RE)
if (!args) return scope
var argRE = new RegExp(args[1] + SCOPE_RE_STR, 'g'),
matches = str.match(argRE)
if (!matches) return scope
var i = matches.length, j, path, key, level
while (i--) {
level = scope
path = matches[i].slice(args[1].length + 7).split('.')
j = 0
while (j < path.length) {
key = path[j]
if (!level[key]) level[key] = noop
level = level[key]
j++
}
}
return scope
}
module.exports = {
/*
@ -44,9 +87,11 @@ module.exports = {
* parse a list of computed property bindings
*/
parse: function (bindings) {
config.log('\nparsing dependencies...')
observer.isObserving = true
bindings.forEach(catchDeps)
bindings.forEach(filterDeps)
observer.isObserving = false
config.log('\ndone.')
}
}

View File

@ -62,7 +62,12 @@ DirProto.update = function (value) {
* computed properties only
*/
DirProto.refresh = function () {
var value = this.value.get()
// pass element and scope info to the getter
// enables powerful context-aware bindings
var value = this.value.get({
el: this.el,
scope: this.seed.scope
})
if (value === this.computedValue) return
this.computedValue = value
this.apply(value)
@ -169,10 +174,8 @@ module.exports = {
var dir = directives[dirname],
valid = KEY_RE.test(expression)
if (config.debug) {
if (!dir) console.warn('unknown directive: ' + dirname)
if (!valid) console.warn('invalid directive expression: ' + expression)
}
if (!dir) config.warn('unknown directive: ' + dirname)
if (!valid) config.warn('invalid directive expression: ' + expression)
return dir && valid
? new Directive(dirname, expression, oneway)

View File

@ -9,8 +9,7 @@ var mutationHandlers = {
push: function (m) {
var self = this
m.args.forEach(function (data, i) {
var seed = self.buildItem(data, self.collection.length + i)
self.container.insertBefore(seed.el, self.ref)
self.buildItem(self.ref, data, self.collection.length + i)
})
},
@ -21,11 +20,10 @@ var mutationHandlers = {
unshift: function (m) {
var self = this
m.args.forEach(function (data, i) {
var seed = self.buildItem(data, i),
ref = self.collection.length > m.args.length
var ref = self.collection.length > m.args.length
? self.collection[m.args.length].$el
: self.ref
self.container.insertBefore(seed.el, ref)
self.buildItem(ref, data, i)
})
self.updateIndexes()
},
@ -46,12 +44,11 @@ var mutationHandlers = {
})
if (added > 0) {
m.args.slice(2).forEach(function (data, i) {
var seed = self.buildItem(data, index + i),
pos = index - removed + added + 1,
var pos = index - removed + added + 1,
ref = self.collection[pos]
? self.collection[pos].$el
: self.ref
self.container.insertBefore(seed.el, ref)
self.buildItem(ref, index + i)
})
}
if (removed !== added) {
@ -99,15 +96,15 @@ module.exports = {
// create child-seeds and append to DOM
collection.forEach(function (data, i) {
var seed = self.buildItem(data, i)
self.container.insertBefore(seed.el, self.ref)
self.buildItem(self.ref, data, i)
})
},
buildItem: function (data, index) {
buildItem: function (ref, data, index) {
var node = this.el.cloneNode(true)
this.container.insertBefore(node, ref)
var Seed = require('../seed'),
node = this.el.cloneNode(true)
var spore = new Seed(node, {
spore = new Seed(node, {
each: true,
eachPrefix: this.arg + '.',
parentSeed: this.seed,
@ -116,7 +113,6 @@ module.exports = {
delegator: this.container
})
this.collection[index] = spore.scope
return spore
},
updateIndexes: function () {

View File

@ -28,7 +28,6 @@ module.exports = {
},
focus: function (value) {
// yield so it work when toggling visibility
var el = this.el
setTimeout(function () {
el[value ? 'focus' : 'blur']()
@ -57,7 +56,7 @@ module.exports = {
el.addEventListener('change', this.change)
},
update: function (value) {
this.el.value = value
this.el.value = value ? value : ''
},
unbind: function () {
if (this.oneway) return

View File

@ -44,11 +44,9 @@ module.exports = {
dHandler = delegator.sd_dHandlers[identifier] = function (e) {
var target = delegateCheck(e.target, delegator, identifier)
if (target) {
handler.call(seed.scope, {
el: target,
scope: target.sd_scope,
originalEvent: e
})
e.el = target
e.scope = target.sd_scope
handler.call(seed.scope, e)
}
}
dHandler.event = event
@ -58,11 +56,9 @@ module.exports = {
// a normal, single element handler
this.handler = function (e) {
handler.call(seed.scope, {
el: e.currentTarget,
scope: seed.scope,
originalEvent: e
})
e.el = e.currentTarget
e.scope = seed.scope
handler.call(seed.scope, e)
}
this.el.addEventListener(event, this.handler)

View File

@ -5,26 +5,32 @@ var keyCodes = {
up: 38,
left: 37,
right: 39,
down: 40
down: 40,
esc: 27
}
module.exports = {
trim: function (value) {
return value ? value.toString().trim() : ''
},
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
},
uppercase: function (value) {
return value.toString().toUpperCase()
return value ? value.toString().toUpperCase() : ''
},
lowercase: function (value) {
return value.toString().toLowerCase()
return value ? value.toString().toLowerCase() : ''
},
currency: function (value, args) {
if (!value) return value
if (!value) return ''
var sign = (args && args[0]) || '$',
i = value % 3,
f = '.' + value.toFixed(2).slice(-2),
@ -33,12 +39,13 @@ module.exports = {
},
key: function (handler, args) {
if (!handler) return
var code = keyCodes[args[0]]
if (!code) {
code = parseInt(args[0], 10)
}
return function (e) {
if (e.originalEvent.keyCode === code) {
if (e.keyCode === code) {
handler.call(this, e)
}
}

View File

@ -15,6 +15,8 @@ var slice = Array.prototype.slice,
*/
function Seed (el, options) {
config.log('\ncreated new Seed instance.\n')
if (typeof el === 'string') {
el = document.querySelector(el)
}
@ -34,8 +36,8 @@ function Seed (el, options) {
var dataAttr = config.prefix + '-data',
dataId = el.getAttribute(dataAttr),
data = (options && options.data) || config.datum[dataId]
if (config.debug && dataId && !data) {
console.warn('data "' + dataId + '" is not defined.')
if (dataId && !data) {
config.warn('data "' + dataId + '" is not defined.')
}
data = data || {}
el.removeAttribute(dataAttr)
@ -47,10 +49,11 @@ function Seed (el, options) {
}
// initialize the scope object
var scope = this.scope = new Scope(this, options)
var key,
scope = this.scope = new Scope(this, options)
// copy data
for (var key in data) {
for (key in data) {
scope[key] = data[key]
}
@ -61,16 +64,23 @@ function Seed (el, options) {
var factory = config.controllers[ctrlID]
if (factory) {
factory(this.scope)
} else if (config.debug) {
console.warn('controller "' + ctrlID + '" is not defined.')
} else {
config.warn('controller "' + ctrlID + '" is not defined.')
}
}
// now parse the DOM
this._compileNode(el, true)
// for anything in scope but not binded in DOM, create bindings for them
for (key in scope) {
if (key.charAt(0) !== '$' && !this._bindings[key]) {
this._createBinding(key)
}
}
// extract dependencies for computed properties
depsParser.parse(this._computed)
if (this._computed.length) depsParser.parse(this._computed)
delete this._computed
}
@ -203,6 +213,7 @@ SeedProto._bind = function (directive) {
* Create binding and attach getter/setter for a key to the scope object
*/
SeedProto._createBinding = function (key) {
config.log(' created binding: ' + key)
var binding = new Binding(this, key)
this._bindings[key] = binding
if (binding.isComputed) this._computed.push(binding)

38
test.js Normal file
View File

@ -0,0 +1,38 @@
var ARGS_RE = /^function\s*?\((.+)\)/,
SCOPE_RE_STR = '\\.scope\\.[\\.A-Za-z0-9_$]+',
noop = function () {}
function test (fn) {
var s = Date.now()
var scope = {},
str = fn.toString()
console.log(Date.now() - s)
var args = str.match(ARGS_RE)
console.log(Date.now() - s)
if (!args) return scope
var argRE = new RegExp(args[1] + SCOPE_RE_STR, 'g'),
matches = str.match(argRE)
console.log(Date.now() - s)
if (!matches) return scope
var i = matches.length, j, path, key, level
while (i--) {
level = scope
path = matches[i].slice(args[1].length + 7).split('.')
j = 0
while (j < path.length) {
key = path[j]
if (!level[key]) level[key] = noop
level = level[key]
j++
}
}
console.log(scope)
console.log(Date.now() - s)
return scope
}
process.nextTick(function () {
test(function (e) {
return e.scope.a
})
})