mirror of https://github.com/vuejs/vue.git
computed properties now have access to context scope and element
This commit is contained in:
parent
1a842d18f2
commit
8eedfeacf9
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
})();
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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}})
|
||||
|
|
|
|||
|
|
@ -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'))
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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.')
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 () {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
25
src/seed.js
25
src/seed.js
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
})
|
||||
Loading…
Reference in New Issue