mirror of https://github.com/vuejs/vue.git
new api WIP
This commit is contained in:
parent
f071f87bc7
commit
761b643baa
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title></title>
|
||||
<meta charset="utf-8">
|
||||
<script src="dist/seed.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div sd-template="todo" sd-text="hi" sd-on="click:hello"></div>
|
||||
<script>
|
||||
var Test = Seed.ViewModel.extend({
|
||||
template: 'todo',
|
||||
initialize: function (msg) {
|
||||
this.hi = 'Aloha'
|
||||
},
|
||||
properties: {
|
||||
hello: function () {
|
||||
console.log('Aloha')
|
||||
}
|
||||
}
|
||||
})
|
||||
var app = new Test()
|
||||
document.body.appendChild(app.$el)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -8,11 +8,9 @@ window.addEventListener('hashchange', function () {
|
|||
Seed.broadcast('filterchange')
|
||||
})
|
||||
|
||||
Seed.controller('todos', {
|
||||
var Todos = Seed.ViewModel.extend({
|
||||
|
||||
// initializer, reserved
|
||||
init: function () {
|
||||
window.app = this
|
||||
initialize: function () {
|
||||
// listen for hashtag change
|
||||
this.updateFilter()
|
||||
this.$on('filterchange', this.updateFilter.bind(this))
|
||||
|
|
@ -21,88 +19,91 @@ Seed.controller('todos', {
|
|||
this.remaining = this.todos.filter(filters.active).length
|
||||
},
|
||||
|
||||
// computed properties ----------------------------------------------------
|
||||
total: {get: function () {
|
||||
return this.todos.length
|
||||
}},
|
||||
properties: {
|
||||
|
||||
completed: {get: function () {
|
||||
return this.total - this.remaining
|
||||
}},
|
||||
|
||||
// dynamic context computed property using info from target viewmodel
|
||||
todoFiltered: {get: function (ctx) {
|
||||
return filters[this.filter]({ completed: ctx.vm.completed })
|
||||
}},
|
||||
|
||||
// dynamic context computed property using info from target element
|
||||
filterSelected: {get: function (ctx) {
|
||||
return this.filter === ctx.el.textContent.toLowerCase()
|
||||
}},
|
||||
|
||||
// two-way computed property with both getter and setter
|
||||
allDone: {
|
||||
get: function () {
|
||||
return this.remaining === 0
|
||||
updateFilter: function () {
|
||||
var filter = location.hash.slice(2)
|
||||
this.filter = (filter in filters) ? filter : 'all'
|
||||
},
|
||||
set: function (value) {
|
||||
this.todos.forEach(function (todo) {
|
||||
todo.completed = value
|
||||
})
|
||||
this.remaining = value ? 0 : this.total
|
||||
|
||||
// computed properties ----------------------------------------------------
|
||||
total: {get: function () {
|
||||
return this.todos.length
|
||||
}},
|
||||
|
||||
completed: {get: function () {
|
||||
return this.total - this.remaining
|
||||
}},
|
||||
|
||||
// dynamic context computed property using info from target viewmodel
|
||||
todoFiltered: {get: function (ctx) {
|
||||
return filters[this.filter]({ completed: ctx.vm.completed })
|
||||
}},
|
||||
|
||||
// dynamic context computed property using info from target element
|
||||
filterSelected: {get: function (ctx) {
|
||||
return this.filter === ctx.el.textContent.toLowerCase()
|
||||
}},
|
||||
|
||||
// two-way computed property with both getter and setter
|
||||
allDone: {
|
||||
get: function () {
|
||||
return this.remaining === 0
|
||||
},
|
||||
set: function (value) {
|
||||
this.todos.forEach(function (todo) {
|
||||
todo.completed = value
|
||||
})
|
||||
this.remaining = value ? 0 : this.total
|
||||
todoStorage.save(this.todos)
|
||||
}
|
||||
},
|
||||
|
||||
// event handlers ---------------------------------------------------------
|
||||
addTodo: function () {
|
||||
var value = this.newTodo && this.newTodo.trim()
|
||||
if (value) {
|
||||
this.todos.unshift({ title: value, completed: false })
|
||||
this.newTodo = ''
|
||||
this.remaining++
|
||||
todoStorage.save(this.todos)
|
||||
}
|
||||
},
|
||||
|
||||
removeTodo: function (e) {
|
||||
this.todos.remove(e.vm)
|
||||
this.remaining -= e.vm.completed ? 0 : 1
|
||||
todoStorage.save(this.todos)
|
||||
},
|
||||
|
||||
toggleTodo: function (e) {
|
||||
this.remaining += e.vm.completed ? -1 : 1
|
||||
todoStorage.save(this.todos)
|
||||
},
|
||||
|
||||
editTodo: function (e) {
|
||||
this.beforeEditCache = e.vm.title
|
||||
e.vm.editing = true
|
||||
},
|
||||
|
||||
doneEdit: function (e) {
|
||||
if (!e.vm.editing) return
|
||||
e.vm.editing = false
|
||||
e.vm.title = e.vm.title.trim()
|
||||
if (!e.vm.title) this.removeTodo(e)
|
||||
todoStorage.save(this.todos)
|
||||
},
|
||||
|
||||
cancelEdit: function (e) {
|
||||
e.vm.editing = false
|
||||
e.vm.title = this.beforeEditCache
|
||||
},
|
||||
|
||||
removeCompleted: function () {
|
||||
this.todos = this.todos.filter(filters.active)
|
||||
todoStorage.save(this.todos)
|
||||
}
|
||||
},
|
||||
|
||||
// event handlers ---------------------------------------------------------
|
||||
addTodo: function () {
|
||||
var value = this.newTodo && this.newTodo.trim()
|
||||
if (value) {
|
||||
this.todos.unshift({ title: value, completed: false })
|
||||
this.newTodo = ''
|
||||
this.remaining++
|
||||
todoStorage.save(this.todos)
|
||||
}
|
||||
},
|
||||
|
||||
removeTodo: function (e) {
|
||||
this.todos.remove(e.vm)
|
||||
this.remaining -= e.vm.completed ? 0 : 1
|
||||
todoStorage.save(this.todos)
|
||||
},
|
||||
|
||||
toggleTodo: function (e) {
|
||||
this.remaining += e.vm.completed ? -1 : 1
|
||||
todoStorage.save(this.todos)
|
||||
},
|
||||
|
||||
editTodo: function (e) {
|
||||
this.beforeEditCache = e.vm.title
|
||||
e.vm.editing = true
|
||||
},
|
||||
|
||||
doneEdit: function (e) {
|
||||
if (!e.vm.editing) return
|
||||
e.vm.editing = false
|
||||
e.vm.title = e.vm.title.trim()
|
||||
if (!e.vm.title) this.removeTodo(e)
|
||||
todoStorage.save(this.todos)
|
||||
},
|
||||
|
||||
cancelEdit: function (e) {
|
||||
e.vm.editing = false
|
||||
e.vm.title = this.beforeEditCache
|
||||
},
|
||||
|
||||
removeCompleted: function () {
|
||||
this.todos = this.todos.filter(filters.active)
|
||||
todoStorage.save(this.todos)
|
||||
},
|
||||
|
||||
updateFilter: function () {
|
||||
var filter = location.hash.slice(2)
|
||||
this.filter = (filter in filters) ? filter : 'all'
|
||||
}
|
||||
})
|
||||
|
||||
Seed.bootstrap()
|
||||
var app = new Todos({ el: '#todoapp' })
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
var config = require('./config'),
|
||||
ViewModel = require('./viewmodel'),
|
||||
utils = require('./utils'),
|
||||
Binding = require('./binding'),
|
||||
DirectiveParser = require('./directive-parser'),
|
||||
TextParser = require('./text-parser'),
|
||||
depsParser = require('./deps-parser'),
|
||||
DepsParser = require('./deps-parser'),
|
||||
eventbus = require('./utils').eventbus
|
||||
|
||||
var slice = Array.prototype.slice,
|
||||
|
|
@ -14,15 +14,19 @@ var slice = Array.prototype.slice,
|
|||
* The DOM compiler
|
||||
* scans a DOM node and compile bindings for a ViewModel
|
||||
*/
|
||||
function Compiler (el, options) {
|
||||
function Compiler (vm, options) {
|
||||
|
||||
config.log('\ncreated new Compiler instance.\n')
|
||||
if (typeof el === 'string') {
|
||||
el = document.querySelector(el)
|
||||
utils.log('\ncreated new Compiler instance.\n')
|
||||
|
||||
// copy options
|
||||
options = options || {}
|
||||
for (var op in options) {
|
||||
this[op] = options[op]
|
||||
}
|
||||
|
||||
this.el = el
|
||||
el.compiler = this
|
||||
this.vm = vm
|
||||
vm.$compiler = this
|
||||
this.el = vm.$el
|
||||
this.bindings = {}
|
||||
this.directives = []
|
||||
this.watchers = {}
|
||||
|
|
@ -32,68 +36,37 @@ function Compiler (el, options) {
|
|||
// list of bindings that has dynamic context dependencies
|
||||
this.contextBindings = []
|
||||
|
||||
// copy options
|
||||
options = options || {}
|
||||
for (var op in options) {
|
||||
this[op] = options[op]
|
||||
}
|
||||
|
||||
// check if there's passed in data
|
||||
var dataAttr = config.prefix + '-data',
|
||||
dataId = el.getAttribute(dataAttr),
|
||||
data = (options && options.data) || config.datum[dataId]
|
||||
if (dataId && !data) {
|
||||
config.warn('data "' + dataId + '" is not defined.')
|
||||
}
|
||||
data = data || {}
|
||||
el.removeAttribute(dataAttr)
|
||||
|
||||
// if the passed in data is the viewmodel of a Compiler instance,
|
||||
// make a copy from it
|
||||
if (data instanceof ViewModel) {
|
||||
data = data.$dump()
|
||||
}
|
||||
|
||||
// check if there is a controller associated with this compiler
|
||||
var ctrlID = el.getAttribute(ctrlAttr), controller
|
||||
if (ctrlID) {
|
||||
el.removeAttribute(ctrlAttr)
|
||||
controller = config.controllers[ctrlID]
|
||||
if (controller) {
|
||||
this.controller = controller
|
||||
} else {
|
||||
config.warn('controller "' + ctrlID + '" is not defined.')
|
||||
// copy data if any
|
||||
var data = options.data
|
||||
if (data) {
|
||||
if (data instanceof vm.constructor) {
|
||||
data = utils.dump(data)
|
||||
}
|
||||
for (var key in data) {
|
||||
vm[key] = data[key]
|
||||
}
|
||||
}
|
||||
|
||||
// create the viewmodel object
|
||||
// if the controller has an extended viewmodel contructor, use it;
|
||||
// otherwise, use the original viewmodel constructor.
|
||||
var VMCtor = (controller && controller.ExtendedVM) || ViewModel,
|
||||
viewmodel = this.vm = new VMCtor(this, options)
|
||||
|
||||
// copy data
|
||||
for (var key in data) {
|
||||
viewmodel[key] = data[key]
|
||||
}
|
||||
|
||||
// apply controller initialize function
|
||||
if (controller && controller.init) {
|
||||
controller.init.call(viewmodel)
|
||||
// call user init
|
||||
if (options.initialize) {
|
||||
options.initialize.apply(vm, options.args || [])
|
||||
}
|
||||
|
||||
// now parse the DOM
|
||||
this.compileNode(el, true)
|
||||
this.compileNode(this.el, true)
|
||||
|
||||
// for anything in viewmodel but not binded in DOM, create bindings for them
|
||||
for (key in viewmodel) {
|
||||
if (key.charAt(0) !== '$' && !this.bindings[key]) {
|
||||
for (var key in vm) {
|
||||
if (vm.hasOwnProperty(key) &&
|
||||
key.charAt(0) !== '$' &&
|
||||
!this.bindings[key])
|
||||
{
|
||||
this.createBinding(key)
|
||||
}
|
||||
}
|
||||
|
||||
// extract dependencies for computed properties
|
||||
if (this.computed.length) depsParser.parse(this.computed)
|
||||
if (this.computed.length) DepsParser.parse(this.computed)
|
||||
this.computed = null
|
||||
|
||||
// extract dependencies for computed properties with dynamic context
|
||||
|
|
@ -198,7 +171,7 @@ CompilerProto.compileTextNode = function (node) {
|
|||
* Create binding and attach getter/setter for a key to the viewmodel object
|
||||
*/
|
||||
CompilerProto.createBinding = function (key) {
|
||||
config.log(' created binding: ' + key)
|
||||
utils.log(' created binding: ' + key)
|
||||
var binding = new Binding(this, key)
|
||||
this.bindings[key] = binding
|
||||
if (binding.isComputed) this.computed.push(binding)
|
||||
|
|
@ -304,7 +277,6 @@ CompilerProto.destroy = function () {
|
|||
this.bindings[key].unbind()
|
||||
}
|
||||
// remove el
|
||||
this.el.compiler = null
|
||||
this.el.parentNode.removeChild(this.el)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,19 +2,9 @@ module.exports = {
|
|||
|
||||
prefix : 'sd',
|
||||
debug : false,
|
||||
datum : {},
|
||||
controllers : {},
|
||||
|
||||
interpolateTags : {
|
||||
open : '{{',
|
||||
close : '}}'
|
||||
},
|
||||
|
||||
log: function (msg) {
|
||||
if (this.debug) console.log(msg)
|
||||
},
|
||||
|
||||
warn: function(msg) {
|
||||
if (this.debug) console.warn(msg)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
var Emitter = require('emitter'),
|
||||
config = require('./config'),
|
||||
utils = require('./utils'),
|
||||
observer = new Emitter()
|
||||
|
||||
var dummyEl = document.createElement('div'),
|
||||
|
|
@ -33,11 +34,11 @@ function catchDeps (binding) {
|
|||
*/
|
||||
function filterDeps (binding) {
|
||||
var i = binding.deps.length, dep
|
||||
config.log('\n─ ' + binding.key)
|
||||
utils.log('\n─ ' + binding.key)
|
||||
while (i--) {
|
||||
dep = binding.deps[i]
|
||||
if (!dep.deps.length) {
|
||||
config.log(' └─ ' + dep.key)
|
||||
utils.log(' └─ ' + dep.key)
|
||||
dep.subs.push(binding)
|
||||
} else {
|
||||
binding.deps.splice(i, 1)
|
||||
|
|
@ -47,7 +48,7 @@ function filterDeps (binding) {
|
|||
if (!ctdeps || !config.debug) return
|
||||
i = ctdeps.length
|
||||
while (i--) {
|
||||
config.log(' └─ ctx:' + ctdeps[i])
|
||||
utils.log(' └─ ctx:' + ctdeps[i])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -115,11 +116,11 @@ module.exports = {
|
|||
* parse a list of computed property bindings
|
||||
*/
|
||||
parse: function (bindings) {
|
||||
config.log('\nparsing dependencies...')
|
||||
utils.log('\nparsing dependencies...')
|
||||
observer.isObserving = true
|
||||
bindings.forEach(catchDeps)
|
||||
bindings.forEach(filterDeps)
|
||||
observer.isObserving = false
|
||||
config.log('\ndone.')
|
||||
utils.log('\ndone.')
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
var config = require('./config'),
|
||||
utils = require('./utils'),
|
||||
directives = require('./directives'),
|
||||
filters = require('./filters')
|
||||
|
||||
|
|
@ -188,8 +189,8 @@ module.exports = {
|
|||
var dir = directives[dirname],
|
||||
valid = KEY_RE.test(expression)
|
||||
|
||||
if (!dir) config.warn('unknown directive: ' + dirname)
|
||||
if (!valid) config.warn('invalid directive expression: ' + expression)
|
||||
if (!dir) utils.warn('unknown directive: ' + dirname)
|
||||
if (!valid) utils.warn('invalid directive expression: ' + expression)
|
||||
|
||||
return dir && valid
|
||||
? new Directive(dirname, expression, oneway)
|
||||
|
|
|
|||
83
src/main.js
83
src/main.js
|
|
@ -1,5 +1,4 @@
|
|||
var config = require('./config'),
|
||||
Compiler = require('./compiler'),
|
||||
ViewModel = require('./viewmodel'),
|
||||
directives = require('./directives'),
|
||||
filters = require('./filters'),
|
||||
|
|
@ -7,11 +6,7 @@ var config = require('./config'),
|
|||
utils = require('./utils')
|
||||
|
||||
var eventbus = utils.eventbus,
|
||||
controllers = config.controllers,
|
||||
datum = config.datum,
|
||||
api = {},
|
||||
reserved = ['datum', 'controllers'],
|
||||
booted = false
|
||||
api = {}
|
||||
|
||||
/*
|
||||
* expose utils
|
||||
|
|
@ -25,38 +20,6 @@ api.broadcast = function () {
|
|||
eventbus.emit.apply(eventbus, arguments)
|
||||
}
|
||||
|
||||
/*
|
||||
* Store a piece of plain data in config.datum
|
||||
* so it can be consumed by sd-data
|
||||
*/
|
||||
api.data = function (id, data) {
|
||||
if (!data) return datum[id]
|
||||
datum[id] = data
|
||||
}
|
||||
|
||||
/*
|
||||
* Store a controller function in config.controllers
|
||||
* so it can be consumed by sd-controller
|
||||
*/
|
||||
api.controller = function (id, properties) {
|
||||
if (!properties) return controllers[id]
|
||||
// create a subclass of ViewModel that has the extension methods mixed-in
|
||||
var ExtendedVM = function () {
|
||||
ViewModel.apply(this, arguments)
|
||||
}
|
||||
var p = ExtendedVM.prototype = Object.create(ViewModel.prototype)
|
||||
p.constructor = ExtendedVM
|
||||
for (var prop in properties) {
|
||||
if (prop !== 'init') {
|
||||
p[prop] = properties[prop]
|
||||
}
|
||||
}
|
||||
controllers[id] = {
|
||||
init: properties.init,
|
||||
ExtendedVM: ExtendedVM
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Allows user to create a custom directive
|
||||
*/
|
||||
|
|
@ -79,37 +42,37 @@ api.filter = function (name, fn) {
|
|||
api.config = function (opts) {
|
||||
if (opts) {
|
||||
for (var key in opts) {
|
||||
if (reserved.indexOf(key) === -1) {
|
||||
config[key] = opts[key]
|
||||
}
|
||||
config[key] = opts[key]
|
||||
}
|
||||
}
|
||||
textParser.buildRegex()
|
||||
}
|
||||
|
||||
/*
|
||||
* Compile a single element
|
||||
* Expose the main ViewModel class
|
||||
* and add extend method
|
||||
*/
|
||||
api.compile = function (el) {
|
||||
return new Compiler(el).vm
|
||||
}
|
||||
api.ViewModel = ViewModel
|
||||
|
||||
/*
|
||||
* Bootstrap the whole thing
|
||||
* by creating a Compiler instance for top level nodes
|
||||
* that has either sd-controller or sd-data
|
||||
*/
|
||||
api.bootstrap = function (opts) {
|
||||
if (booted) return
|
||||
api.config(opts)
|
||||
var el,
|
||||
ctrlSlt = '[' + config.prefix + '-controller]',
|
||||
dataSlt = '[' + config.prefix + '-data]'
|
||||
/* jshint boss: true */
|
||||
while (el = document.querySelector(ctrlSlt) || document.querySelector(dataSlt)) {
|
||||
new Compiler(el)
|
||||
ViewModel.extend = function (options) {
|
||||
var ExtendedVM = function (opts) {
|
||||
opts = opts || {}
|
||||
if (options.template) {
|
||||
opts.template = utils.getTemplate(options.template)
|
||||
}
|
||||
if (options.initialize) {
|
||||
opts.initialize = options.initialize
|
||||
}
|
||||
ViewModel.call(this, opts)
|
||||
}
|
||||
booted = true
|
||||
var p = ExtendedVM.prototype = Object.create(ViewModel.prototype)
|
||||
p.constructor = ExtendedVM
|
||||
if (options.properties) {
|
||||
for (var prop in options.properties) {
|
||||
p[prop] = options.properties[prop]
|
||||
}
|
||||
}
|
||||
return ExtendedVM
|
||||
}
|
||||
|
||||
module.exports = api
|
||||
24
src/utils.js
24
src/utils.js
|
|
@ -1,8 +1,12 @@
|
|||
var Emitter = require('emitter'),
|
||||
var config = require('./config'),
|
||||
Emitter = require('emitter'),
|
||||
toString = Object.prototype.toString,
|
||||
aproto = Array.prototype,
|
||||
arrayMutators = ['push','pop','shift','unshift','splice','sort','reverse']
|
||||
|
||||
// hold templates
|
||||
var templates = {}
|
||||
|
||||
var arrayAugmentations = {
|
||||
remove: function (index) {
|
||||
if (typeof index !== 'number') index = index.$index
|
||||
|
|
@ -100,5 +104,23 @@ module.exports = {
|
|||
for (method in arrayAugmentations) {
|
||||
collection[method] = arrayAugmentations[method]
|
||||
}
|
||||
},
|
||||
|
||||
log: function (msg) {
|
||||
if (config.debug) console.log(msg)
|
||||
},
|
||||
|
||||
warn: function(msg) {
|
||||
if (config.debug) console.warn(msg)
|
||||
},
|
||||
|
||||
getTemplate: function (id) {
|
||||
var el = templates[id]
|
||||
if (!el && el !== null) {
|
||||
var selector = '[' + config.prefix + '-template="' + id + '"]'
|
||||
el = templates[id] = document.querySelector(selector)
|
||||
if (el) el.parentNode.removeChild(el)
|
||||
}
|
||||
return el
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,26 @@
|
|||
var utils = require('./utils')
|
||||
var utils = require('./utils'),
|
||||
Compiler = require('./compiler')
|
||||
|
||||
/*
|
||||
* ViewModel exposed to the user that holds data,
|
||||
* computed properties, event handlers
|
||||
* and a few reserved methods
|
||||
*/
|
||||
function ViewModel (compiler, options) {
|
||||
this.$compiler = compiler
|
||||
this.$el = compiler.el
|
||||
this.$index = options.index
|
||||
this.$parent = options.parentCompiler && options.parentCompiler.vm
|
||||
function ViewModel (options) {
|
||||
|
||||
// determine el
|
||||
this.$el = options.template
|
||||
? options.template.cloneNode(true)
|
||||
: typeof options.el === 'string'
|
||||
? document.querySelector(options.el)
|
||||
: options.el
|
||||
|
||||
// possible info inherited as an each item
|
||||
this.$index = options.index
|
||||
this.$parent = options.parentCompiler && options.parentCompiler.vm
|
||||
|
||||
// compile
|
||||
new Compiler(this, options)
|
||||
}
|
||||
|
||||
var VMProto = ViewModel.prototype
|
||||
|
|
@ -49,12 +60,11 @@ VMProto.$watch = function (key, callback) {
|
|||
var self = this
|
||||
// yield and wait for compiler to finish compiling
|
||||
setTimeout(function () {
|
||||
var viewmodel = self.$compiler.vm,
|
||||
binding = self.$compiler.bindings[key],
|
||||
var binding = self.$compiler.bindings[key],
|
||||
i = binding.deps.length,
|
||||
watcher = self.$compiler.watchers[key] = {
|
||||
refresh: function () {
|
||||
callback(viewmodel[key])
|
||||
callback(self[key])
|
||||
},
|
||||
deps: binding.deps
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue