mirror of https://github.com/vuejs/vue.git
nested controllers
This commit is contained in:
parent
39760f49b7
commit
f1ed54bc84
|
|
@ -0,0 +1,20 @@
|
|||
WIP, playing with data binding
|
||||
|
||||
### Template
|
||||
|
||||
### Controller
|
||||
|
||||
- Nested Controllers and accessing parent scope
|
||||
- Controller inheritance
|
||||
|
||||
### Data
|
||||
|
||||
### Data Binding
|
||||
|
||||
### Filters
|
||||
|
||||
### Computed Properties
|
||||
|
||||
### Custom Filter
|
||||
|
||||
### Custom Directive
|
||||
13
TODO.md
13
TODO.md
|
|
@ -1,4 +1,9 @@
|
|||
- nested controllers - but how to inherit scope?
|
||||
- improve arrayWatcher
|
||||
- parse textNodes
|
||||
- computed properties
|
||||
- complete arrayWatcher
|
||||
|
||||
- computed properties (through invoking functions, need to rework the setter triggering mechanism using emitter)
|
||||
|
||||
- the data object passed in should become an absolute source of truth, so multiple controllers can bind to the same data (i.e. second seed using it should insert dependency instead of overwriting it)
|
||||
|
||||
- nested properties in scope (kinda hard but try)
|
||||
|
||||
- parse textNodes
|
||||
|
|
@ -13,7 +13,6 @@
|
|||
"src/textnode-parser.js",
|
||||
"src/directives.js",
|
||||
"src/filters.js",
|
||||
"src/controllers.js",
|
||||
"src/watch-array.js"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Nested Controllers</title>
|
||||
<style type="text/css">
|
||||
div {
|
||||
padding-left: 10px;
|
||||
}
|
||||
</style>
|
||||
<script src="../dist/seed.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div sd-controller="Grandpa">
|
||||
<p sd-text="name"></p>
|
||||
|
||||
<div sd-controller="Dad">
|
||||
<p><span sd-text="name"></span>, son of <span sd-text="^name"></span></p>
|
||||
|
||||
<div sd-controller="Son">
|
||||
<p><span sd-text="name"></span>, son of <span sd-text="^name"></span></p>
|
||||
|
||||
<div sd-controller="Baby">
|
||||
<p><span sd-text="name"></span>, son of <span sd-text="^name"></span>, grandson of <span sd-text="^^name"></span> and great-grandson of <span sd-text="$name"></span></p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var Seed = require('seed')
|
||||
|
||||
Seed.controller('Grandpa', function (scope, seed) {
|
||||
scope.name = 'John'
|
||||
})
|
||||
|
||||
Seed.controller('Dad', function (scope, seed) {
|
||||
scope.name = 'Jack'
|
||||
})
|
||||
|
||||
Seed.controller('Son', function (scope, seed) {
|
||||
scope.name = 'Jason'
|
||||
})
|
||||
|
||||
Seed.controller('Baby', function (scope, seed) {
|
||||
scope.name = 'James'
|
||||
})
|
||||
|
||||
Seed.bootstrap()
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Todo</title>
|
||||
<meta charset="utf-8">
|
||||
<script src="dist/seed.js"></script>
|
||||
<style type="text/css">
|
||||
.red {
|
||||
color: red;
|
||||
}
|
||||
<head>
|
||||
<title>Todo</title>
|
||||
<meta charset="utf-8">
|
||||
<script src="../dist/seed.js"></script>
|
||||
<style type="text/css">
|
||||
.red {
|
||||
color: red;
|
||||
}
|
||||
.done {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
|
@ -26,55 +26,61 @@
|
|||
#app.completed .completed {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" sd-class="filter" sd-controller="TodoList">
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" sd-controller="Todos" sd-class="filter">
|
||||
<div>
|
||||
<input placeholder="What needs to be done?" sd-on="change:addTodo">
|
||||
</div>
|
||||
<ul sd-show="todos">
|
||||
<li class="todo" sd-each="todo:todos" sd-class="done:todo.done">
|
||||
<li class="todo" sd-each="todo:todos" sd-class="done:todo.done">
|
||||
<input type="checkbox" sd-checked="todo.done" sd-on="change:toggleTodo">
|
||||
<span sd-text="todo.text"></span>
|
||||
<a sd-on="click:removeTodo">X</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div id="footer">
|
||||
Remaining: <span sd-text="remaining"></span><br>
|
||||
Total: <span sd-text="total < todos"></span> |
|
||||
Remaining: <span sd-text="remaining"></span> |
|
||||
Completed: <span sd-text="completed < remaining total"></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>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
<script>
|
||||
|
||||
var data = {
|
||||
todos: [
|
||||
{
|
||||
text: '1!',
|
||||
done: false
|
||||
},
|
||||
{
|
||||
text: '2!',
|
||||
done: false
|
||||
},
|
||||
{
|
||||
text: '3!',
|
||||
done: true
|
||||
}
|
||||
]
|
||||
}
|
||||
var Seed = require('seed')
|
||||
|
||||
var Seed = require('seed')
|
||||
var todos = [
|
||||
{ text: 'make nesting controllers work', done: true },
|
||||
{ text: 'complete ArrayWatcher', done: false },
|
||||
{ text: 'computed properties', done: false },
|
||||
{ text: 'parse textnodes', done: false }
|
||||
]
|
||||
|
||||
Seed.controller('TodoList', function (scope, seed) {
|
||||
Seed.controller('Todos', function (scope, seed) {
|
||||
|
||||
// regular properties
|
||||
scope.todos = todos
|
||||
scope.filter = 'all'
|
||||
scope.remaining = scope.todos.reduce(function (count, todo) {
|
||||
return count + (todo.done ? 0 : 1)
|
||||
}, 0)
|
||||
|
||||
// computed properties
|
||||
scope.total = function () {
|
||||
return scope.todos.length
|
||||
}
|
||||
|
||||
scope.completed = function () {
|
||||
return scope.todos.length - scope.remaining
|
||||
}
|
||||
|
||||
// event handlers
|
||||
scope.addTodo = function (e) {
|
||||
var text = e.el.value
|
||||
if (text) {
|
||||
|
|
@ -88,8 +94,7 @@
|
|||
}
|
||||
|
||||
scope.removeTodo = function (e) {
|
||||
var i = e.seed.eachIndex
|
||||
scope.todos.splice(i, 1)
|
||||
scope.todos.splice(e.seed.index, 1)
|
||||
scope.remaining -= e.seed.scope.done ? 0 : 1
|
||||
}
|
||||
|
||||
|
|
@ -100,13 +105,11 @@
|
|||
scope.setFilter = function (e) {
|
||||
scope.filter = e.el.className
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
var app = Seed.bootstrap({
|
||||
el: '#app',
|
||||
data: data
|
||||
})
|
||||
Seed.bootstrap()
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1 +0,0 @@
|
|||
module.exports = {}
|
||||
|
|
@ -83,6 +83,7 @@ module.exports = {
|
|||
})
|
||||
this.childSeeds = []
|
||||
}
|
||||
if (!Array.isArray(collection)) return
|
||||
watchArray(collection, this.mutate.bind(this))
|
||||
var self = this
|
||||
collection.forEach(function (item, i) {
|
||||
|
|
@ -95,11 +96,12 @@ module.exports = {
|
|||
buildItem: function (data, index, collection) {
|
||||
var Seed = require('./seed'),
|
||||
node = this.el.cloneNode(true)
|
||||
var spore = new Seed(node, data, {
|
||||
eachPrefix: this.arg,
|
||||
var spore = new Seed(node, {
|
||||
eachPrefixRE: new RegExp('^' + this.arg + '.'),
|
||||
parentSeed: this.seed,
|
||||
eachIndex: index,
|
||||
eachCollection: collection
|
||||
index: index,
|
||||
eachCollection: collection,
|
||||
data: data
|
||||
})
|
||||
this.container.insertBefore(node, this.marker)
|
||||
collection[index] = spore.scope
|
||||
|
|
|
|||
58
src/main.js
58
src/main.js
|
|
@ -1,14 +1,17 @@
|
|||
var config = require('./config'),
|
||||
Seed = require('./seed'),
|
||||
directives = require('./directives'),
|
||||
filters = require('./filters'),
|
||||
controllers = require('./controllers')
|
||||
filters = require('./filters')
|
||||
|
||||
Seed.config = config
|
||||
var controllers = config.controllers = {},
|
||||
datum = config.datum = {},
|
||||
api = {}
|
||||
|
||||
// API
|
||||
|
||||
Seed.extend = function (opts) {
|
||||
api.config = config
|
||||
|
||||
api.extend = function (opts) {
|
||||
var Spore = function () {
|
||||
Seed.apply(this, arguments)
|
||||
for (var prop in this.extensions) {
|
||||
|
|
@ -26,39 +29,42 @@ Seed.extend = function (opts) {
|
|||
return Spore
|
||||
}
|
||||
|
||||
Seed.controller = function (id, extensions) {
|
||||
api.data = function (id, data) {
|
||||
if (!data) return datum[id]
|
||||
if (datum[id]) {
|
||||
console.warn('data object "' + id + '"" already exists and has been overwritten.')
|
||||
}
|
||||
datum[id] = data
|
||||
}
|
||||
|
||||
api.controller = function (id, extensions) {
|
||||
if (!extensions) return controllers[id]
|
||||
if (controllers[id]) {
|
||||
console.warn('controller "' + id + '" was already registered and has been overwritten.')
|
||||
console.warn('controller "' + id + '" already exists and has been overwritten.')
|
||||
}
|
||||
controllers[id] = extensions
|
||||
}
|
||||
|
||||
Seed.bootstrap = function (seeds) {
|
||||
if (!Array.isArray(seeds)) seeds = [seeds]
|
||||
var instances = []
|
||||
seeds.forEach(function (seed) {
|
||||
var el = seed.el
|
||||
if (typeof el === 'string') {
|
||||
el = document.querySelector(el)
|
||||
api.bootstrap = function () {
|
||||
var app = {},
|
||||
n = 0,
|
||||
el, seed
|
||||
while (el = document.querySelector('[' + config.prefix + '-controller]')) {
|
||||
seed = new Seed(el)
|
||||
if (el.id) {
|
||||
app['$' + el.id] = seed
|
||||
}
|
||||
if (!el) console.warn('invalid element or selector: ' + seed.el)
|
||||
instances.push(new Seed(el, seed.data, seed.options))
|
||||
})
|
||||
return instances.length > 1
|
||||
? instances
|
||||
: instances[0]
|
||||
n++
|
||||
}
|
||||
return n > 1 ? app : seed
|
||||
}
|
||||
|
||||
Seed.directive = function (name, fn) {
|
||||
api.directive = function (name, fn) {
|
||||
directives[name] = fn
|
||||
}
|
||||
|
||||
Seed.filter = function (name, fn) {
|
||||
api.filter = function (name, fn) {
|
||||
filters[name] = fn
|
||||
}
|
||||
|
||||
// alias for an alternative API
|
||||
Seed.plant = Seed.controller
|
||||
Seed.sprout = Seed.bootstrap
|
||||
|
||||
module.exports = Seed
|
||||
module.exports = api
|
||||
70
src/seed.js
70
src/seed.js
|
|
@ -1,15 +1,17 @@
|
|||
var Emitter = require('emitter'),
|
||||
config = require('./config'),
|
||||
controllers = require('./controllers'),
|
||||
DirectiveParser = require('./directive-parser')
|
||||
|
||||
var slice = Array.prototype.slice
|
||||
|
||||
var ancestorKeyRE = /\^/g,
|
||||
rootKeyRE = /^\$/
|
||||
|
||||
// lazy init
|
||||
var ctrlAttr,
|
||||
eachAttr
|
||||
|
||||
function Seed (el, data, options) {
|
||||
function Seed (el, options) {
|
||||
|
||||
// refresh
|
||||
ctrlAttr = config.prefix + '-controller'
|
||||
|
|
@ -21,31 +23,39 @@ function Seed (el, data, options) {
|
|||
|
||||
el.seed = this
|
||||
this.el = el
|
||||
this.scope = data
|
||||
this._bindings = {}
|
||||
this.components = {}
|
||||
|
||||
if (options) {
|
||||
this.parentSeed = options.parentSeed
|
||||
this.eachPrefixRE = new RegExp('^' + options.eachPrefix + '.')
|
||||
this.eachIndex = options.eachIndex
|
||||
for (var op in options) {
|
||||
this[op] = options[op]
|
||||
}
|
||||
}
|
||||
|
||||
var key
|
||||
// initiate the scope
|
||||
var dataPrefix = config.prefix + '-data'
|
||||
this.scope =
|
||||
(options && options.data)
|
||||
|| config.datum[el.getAttribute(dataPrefix)]
|
||||
|| {}
|
||||
el.removeAttribute(dataPrefix)
|
||||
|
||||
// keep a temporary copy for all the real data
|
||||
// so we can overwrite the passed in data object
|
||||
// with getter/setters.
|
||||
var key
|
||||
this._dataCopy = {}
|
||||
for (key in data) {
|
||||
this._dataCopy[key] = data[key]
|
||||
for (key in this.scope) {
|
||||
this._dataCopy[key] = this.scope[key]
|
||||
}
|
||||
|
||||
// if has controller
|
||||
var ctrlID = el.getAttribute(ctrlAttr),
|
||||
controller = null
|
||||
if (ctrlID) {
|
||||
controller = controllers[ctrlID]
|
||||
controller = config.controllers[ctrlID]
|
||||
if (!controller) console.warn('controller ' + ctrlID + ' is not defined.')
|
||||
el.removeAttribute(ctrlAttr)
|
||||
if (!controller) throw new Error('controller ' + ctrlID + ' is not defined.')
|
||||
}
|
||||
|
||||
// process nodes for directives
|
||||
|
|
@ -93,7 +103,13 @@ Seed.prototype._compileNode = function (node, root) {
|
|||
|
||||
} else if (ctrlExp && !root) { // nested controllers
|
||||
|
||||
// TODO need to be clever here!
|
||||
var id = node.id,
|
||||
seed = new Seed(node, {
|
||||
parentSeed: self
|
||||
})
|
||||
if (id) {
|
||||
self['$' + id] = seed
|
||||
}
|
||||
|
||||
} else if (node.attributes && node.attributes.length) { // normal node (non-controller)
|
||||
|
||||
|
|
@ -133,11 +149,29 @@ Seed.prototype._bind = function (node, directive) {
|
|||
snr = this.eachPrefixRE,
|
||||
isEachKey = snr && snr.test(key),
|
||||
scopeOwner = this
|
||||
// TODO make scope chain work on nested controllers
|
||||
|
||||
if (isEachKey) {
|
||||
key = key.replace(snr, '')
|
||||
} else if (snr) {
|
||||
}
|
||||
|
||||
// handle scope nesting
|
||||
if (snr && !isEachKey) {
|
||||
scopeOwner = this.parentSeed
|
||||
} else {
|
||||
var ancestors = key.match(ancestorKeyRE),
|
||||
root = key.match(rootKeyRE)
|
||||
if (ancestors) {
|
||||
key = key.replace(ancestorKeyRE, '')
|
||||
var levels = ancestors.length
|
||||
while (scopeOwner.parentSeed && levels--) {
|
||||
scopeOwner = scopeOwner.parentSeed
|
||||
}
|
||||
} else if (root) {
|
||||
key = key.replace(rootKeyRE, '')
|
||||
while (scopeOwner.parentSeed) {
|
||||
scopeOwner = scopeOwner.parentSeed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
directive.key = key
|
||||
|
|
@ -179,14 +213,6 @@ Seed.prototype._createBinding = function (key) {
|
|||
return binding
|
||||
}
|
||||
|
||||
Seed.prototype.dump = function () {
|
||||
var data = {}
|
||||
for (var key in this._bindings) {
|
||||
data[key] = this._bindings[key].value
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
Seed.prototype.destroy = function () {
|
||||
for (var key in this._bindings) {
|
||||
this._bindings[key].instances.forEach(unbind)
|
||||
|
|
|
|||
Loading…
Reference in New Issue