diff --git a/Gruntfile.js b/Gruntfile.js
index 0ca3ca0be..77a27ad0f 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -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'),
diff --git a/dist/seed.js b/dist/seed.js
index d7cf1e8da..abe9ef7c4 100644
--- a/dist/seed.js
+++ b/dist/seed.js
@@ -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'
})();
\ No newline at end of file
diff --git a/examples/todomvc/css/app.css b/examples/todomvc/css/app.css
deleted file mode 100644
index c4973259a..000000000
--- a/examples/todomvc/css/app.css
+++ /dev/null
@@ -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;
-}
\ No newline at end of file
diff --git a/examples/todomvc/index.html b/examples/todomvc/index.html
index 787717ec1..9c0a823a7 100644
--- a/examples/todomvc/index.html
+++ b/examples/todomvc/index.html
@@ -4,10 +4,9 @@
Todo
-
-
+
@@ -32,6 +33,7 @@
@@ -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"
>
@@ -61,9 +63,9 @@
{{itemLabel}} left