mirror of https://github.com/vuejs/vue.git
templates
This commit is contained in:
parent
caed31fd02
commit
d2779aad82
|
|
@ -6,68 +6,69 @@
|
|||
div:not(#grandpa) {
|
||||
padding-left: 15px;
|
||||
}
|
||||
p {
|
||||
position: relative;
|
||||
}
|
||||
p:not(.ancestor):before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -22px;
|
||||
content: "└ ";
|
||||
color: #F00;
|
||||
}
|
||||
</style>
|
||||
<script src="../dist/seed.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="grandpa" data-name="Andy">
|
||||
<p class="ancestor" sd-text="name"></p>
|
||||
<div id="grandpa" sd-viewmodel="man" data-name="Andy" data-family="Johnson">
|
||||
<p class="ancestor">{{name}} {{family}}</p>
|
||||
|
||||
<div sd-viewmodel="man" data-name="Jack">
|
||||
<p><span sd-text="name"></span>, son of <span sd-text="^name"></span></p>
|
||||
<p>{{name}}, son of {{^name}}</p>
|
||||
|
||||
<div sd-viewmodel="man" data-name="Mike">
|
||||
<p><span sd-text="name"></span>, son of <span sd-text="^name"></span></p>
|
||||
<p>{{name}}, son of {{^name}}</p>
|
||||
|
||||
<div sd-viewmodel="man" data-name="Tim">
|
||||
<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 sd-viewmodel="man" data-name="Tim" sd-template="offspring">
|
||||
</div>
|
||||
|
||||
<div sd-viewmodel="man" data-name="Tom">
|
||||
<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 sd-viewmodel="man" data-name="Tom" sd-template="offspring">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div sd-viewmodel="man" data-name="Jason">
|
||||
<p><span sd-text="name"></span>, son of <span sd-text="^name"></span></p>
|
||||
<p>{{name}}, son of {{^name}}</p>
|
||||
|
||||
<div sd-viewmodel="man" data-name="Andrew">
|
||||
<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 sd-viewmodel="man" data-name="Andrew" sd-template="offspring">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/sd-template" sd-template-id="offspring">
|
||||
<p>
|
||||
{{name}}, son of {{^name}},
|
||||
<br>
|
||||
grandson of {{^^name}},
|
||||
<br>
|
||||
great-grandson of {{$name}},
|
||||
<br>
|
||||
and offspring of family {{family}}.
|
||||
</p>
|
||||
</script>
|
||||
<script src="../dist/seed.js"></script>
|
||||
<script>
|
||||
seed.config({ debug: true })
|
||||
|
||||
var Man = seed.ViewModel.extend({
|
||||
id: 'man',
|
||||
init: function () {
|
||||
this.name = this.$el.dataset.name
|
||||
var family = this.$el.dataset.family
|
||||
if (family) {
|
||||
this.family = family
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var demo = new Man({ el: '#grandpa' })
|
||||
|
||||
seed.bootstrap('#grandpa')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -3,25 +3,34 @@
|
|||
<head>
|
||||
<title></title>
|
||||
<meta charset="utf-8">
|
||||
<script src="../dist/seed.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div sd-template="todo">
|
||||
|
||||
<div id="hawaii" sd-template="test"></div>
|
||||
|
||||
<script type="text/sd-template" sd-template-id="test">
|
||||
<p>{{hi}}!</p>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script src="../dist/seed.js"></script>
|
||||
<script>
|
||||
var Test = seed.ViewModel.extend({
|
||||
template: 'todo',
|
||||
init: function (msg) {
|
||||
this.hi = msg
|
||||
}
|
||||
// compile an existing node with sd-template directive
|
||||
// will replace its innerHTML with the template's content
|
||||
var hawaii = new seed.ViewModel({
|
||||
el: '#hawaii'
|
||||
})
|
||||
|
||||
var hawaii = new Test({ args: ['Aloha'] }),
|
||||
china = new Test({ args: ['你好'] })
|
||||
|
||||
document.body.appendChild(hawaii.$el)
|
||||
// if using template without an existing node
|
||||
// the VM's $el will be a clone of the template's content
|
||||
// and you will have to manually append it into DOM.
|
||||
// this is mostly to allow users to create VMs dynamically.
|
||||
var china = new seed.ViewModel({
|
||||
template: 'test'
|
||||
})
|
||||
document.body.appendChild(china.$el)
|
||||
|
||||
hawaii.hi = 'Aloha'
|
||||
china.hi = '你好'
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
232
src/compiler.js
232
src/compiler.js
|
|
@ -18,8 +18,6 @@ var vmAttr, eachAttr
|
|||
*/
|
||||
function Compiler (vm, options) {
|
||||
|
||||
utils.log('\nnew Compiler instance: ', vm.$el, '\n')
|
||||
|
||||
// need to refresh this everytime we compile
|
||||
eachAttr = config.prefix + '-each'
|
||||
vmAttr = config.prefix + '-viewmodel'
|
||||
|
|
@ -30,17 +28,52 @@ function Compiler (vm, options) {
|
|||
this[op] = options[op]
|
||||
}
|
||||
|
||||
// determine el
|
||||
var tpl = options.template,
|
||||
el = options.el
|
||||
el = typeof el === 'string'
|
||||
? document.querySelector(el)
|
||||
: el
|
||||
if (el) {
|
||||
var tplExp = tpl || el.getAttribute(config.prefix + '-template')
|
||||
if (tplExp) {
|
||||
el.innerHTML = utils.getTemplate(tplExp) || ''
|
||||
el.removeAttribute(config.prefix + '-template')
|
||||
}
|
||||
} else if (tpl) {
|
||||
var template = utils.getTemplate(tpl)
|
||||
if (template) {
|
||||
var tplHolder = document.createElement('div')
|
||||
tplHolder.innerHTML = template
|
||||
el = tplHolder.childNodes[0]
|
||||
}
|
||||
}
|
||||
|
||||
utils.log('\nnew VM instance: ', el, '\n')
|
||||
|
||||
// set el
|
||||
vm.$el = el
|
||||
// link it up!
|
||||
vm.$compiler = this
|
||||
// possible info inherited as an each item
|
||||
vm.$index = options.index
|
||||
vm.$parent = options.parentCompiler && options.parentCompiler.vm
|
||||
|
||||
// now for the compiler itself...
|
||||
this.vm = vm
|
||||
vm.$compiler = this
|
||||
this.el = vm.$el
|
||||
this.bindings = {}
|
||||
this.observer = new Emitter()
|
||||
this.directives = []
|
||||
this.watchers = {}
|
||||
// list of computed properties that need to parse dependencies for
|
||||
this.computed = []
|
||||
// list of bindings that has dynamic context dependencies
|
||||
this.contextBindings = []
|
||||
this.computed = [] // computed props to parse deps from
|
||||
this.contextBindings = [] // computed props with dynamic context
|
||||
|
||||
// prototypal inheritance of bindings
|
||||
var parent = this.parentCompiler
|
||||
this.bindings = parent
|
||||
? Object.create(parent.bindings)
|
||||
: {}
|
||||
this.rootCompiler = parent
|
||||
? getRoot(parent)
|
||||
: this
|
||||
|
||||
// setup observer
|
||||
this.setupObserver()
|
||||
|
|
@ -77,16 +110,49 @@ function Compiler (vm, options) {
|
|||
|
||||
var CompilerProto = Compiler.prototype
|
||||
|
||||
/*
|
||||
* Setup observer.
|
||||
* The observer listens for get/set/mutate events on all VM
|
||||
* values/objects and trigger corresponding binding updates.
|
||||
*/
|
||||
CompilerProto.setupObserver = function () {
|
||||
|
||||
var compiler = this,
|
||||
bindings = this.bindings,
|
||||
observer = this.observer = new Emitter()
|
||||
|
||||
// a hash to hold event proxies for each root level key
|
||||
// so they can be referenced and removed later
|
||||
observer.proxies = {}
|
||||
|
||||
// add own listeners which trigger binding updates
|
||||
observer
|
||||
.on('get', function (key) {
|
||||
if (DepsParser.observer.isObserving) {
|
||||
DepsParser.observer.emit('get', bindings[key])
|
||||
}
|
||||
})
|
||||
.on('set', function (key, val) {
|
||||
if (!bindings[key]) compiler.createBinding(key)
|
||||
bindings[key].update(val)
|
||||
})
|
||||
.on('mutate', function (key) {
|
||||
bindings[key].refresh()
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Actually parse the DOM nodes for directives, create bindings,
|
||||
* and parse dependencies afterwards. For the dependency extraction to work,
|
||||
* this has to happen after all user-set values are present in the VM.
|
||||
*/
|
||||
CompilerProto.compile = function () {
|
||||
|
||||
var key,
|
||||
vm = this.vm,
|
||||
computed = this.computed,
|
||||
contextBindings = this.contextBindings
|
||||
|
||||
// parse the DOM
|
||||
this.compileNode(this.el, true)
|
||||
|
||||
|
|
@ -107,39 +173,6 @@ CompilerProto.compile = function () {
|
|||
// extract dependencies for computed properties with dynamic context
|
||||
if (contextBindings.length) this.bindContexts(contextBindings)
|
||||
this.contextBindings = null
|
||||
|
||||
utils.log('\ncompilation done.\n')
|
||||
}
|
||||
|
||||
/*
|
||||
* Setup observer.
|
||||
* The observer listens for get/set/mutate events on all VM
|
||||
* values/objects and trigger corresponding binding updates.
|
||||
*/
|
||||
CompilerProto.setupObserver = function () {
|
||||
|
||||
var bindings = this.bindings,
|
||||
observer = this.observer,
|
||||
compiler = this
|
||||
|
||||
// a hash to hold event proxies for each root level key
|
||||
// so they can be referenced and removed later
|
||||
observer.proxies = {}
|
||||
|
||||
// add own listeners which trigger binding updates
|
||||
observer
|
||||
.on('get', function (key) {
|
||||
if (DepsParser.observer.isObserving) {
|
||||
DepsParser.observer.emit('get', bindings[key])
|
||||
}
|
||||
})
|
||||
.on('set', function (key, val) {
|
||||
if (!bindings[key]) compiler.createBinding(key)
|
||||
bindings[key].update(val)
|
||||
})
|
||||
.on('mutate', function (key) {
|
||||
bindings[key].refresh()
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -169,6 +202,7 @@ CompilerProto.compileNode = function (node, root) {
|
|||
|
||||
} else if (vmExp && !root) { // nested ViewModels
|
||||
|
||||
node.removeAttribute(vmAttr)
|
||||
var ChildVM = utils.getVM(vmExp)
|
||||
if (ChildVM) {
|
||||
new ChildVM({
|
||||
|
|
@ -241,10 +275,63 @@ CompilerProto.compileTextNode = function (node) {
|
|||
node.parentNode.removeChild(node)
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a directive instance to the correct binding & viewmodel
|
||||
*/
|
||||
CompilerProto.bindDirective = function (directive) {
|
||||
|
||||
this.directives.push(directive)
|
||||
directive.compiler = this
|
||||
directive.vm = this.vm
|
||||
|
||||
var key = directive.key,
|
||||
compiler = traceOwnerCompiler(directive, this)
|
||||
|
||||
var binding
|
||||
if (compiler.vm.hasOwnProperty(key)) {
|
||||
// if the value is present in the target VM, we create the binding on its compiler
|
||||
binding = compiler.bindings.hasOwnProperty(key)
|
||||
? compiler.bindings[key]
|
||||
: compiler.createBinding(key)
|
||||
} else {
|
||||
// due to prototypal inheritance of bindings, if a key doesn't exist here,
|
||||
// it doesn't exist in the whole prototype chain. Therefore in that case
|
||||
// we create the new binding at the root level.
|
||||
binding = compiler.bindings[key] || this.rootCompiler.createBinding(key)
|
||||
}
|
||||
|
||||
binding.instances.push(directive)
|
||||
directive.binding = binding
|
||||
|
||||
// for newly inserted sub-VMs (each items), need to bind deps
|
||||
// because they didn't get processed when the parent compiler
|
||||
// was binding dependencies.
|
||||
var i, dep
|
||||
if (binding.contextDeps) {
|
||||
i = binding.contextDeps.length
|
||||
while (i--) {
|
||||
dep = this.bindings[binding.contextDeps[i]]
|
||||
dep.subs.push(directive)
|
||||
}
|
||||
}
|
||||
|
||||
// invoke bind hook if exists
|
||||
if (directive.bind) {
|
||||
directive.bind(binding.value)
|
||||
}
|
||||
|
||||
// set initial value
|
||||
directive.update(binding.value)
|
||||
if (binding.isComputed) {
|
||||
directive.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create binding and attach getter/setter for a key to the viewmodel object
|
||||
*/
|
||||
CompilerProto.createBinding = function (key) {
|
||||
|
||||
utils.log(' created binding: ' + key)
|
||||
|
||||
var bindings = this.bindings,
|
||||
|
|
@ -320,58 +407,6 @@ CompilerProto.define = function (key, binding) {
|
|||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a directive instance to the correct binding & viewmodel
|
||||
*/
|
||||
CompilerProto.bindDirective = function (directive) {
|
||||
|
||||
this.directives.push(directive)
|
||||
directive.compiler = this
|
||||
directive.vm = this.vm
|
||||
|
||||
var key = directive.key,
|
||||
compiler = this
|
||||
|
||||
// deal with each block
|
||||
if (this.each) {
|
||||
if (key.indexOf(this.eachPrefix) === 0) {
|
||||
key = directive.key = key.replace(this.eachPrefix, '')
|
||||
} else {
|
||||
compiler = this.parentCompiler
|
||||
}
|
||||
}
|
||||
|
||||
// deal with nesting
|
||||
compiler = traceOwnerCompiler(directive, compiler)
|
||||
var binding = compiler.bindings[key] || compiler.createBinding(key)
|
||||
|
||||
binding.instances.push(directive)
|
||||
directive.binding = binding
|
||||
|
||||
// for newly inserted sub-VMs (each items), need to bind deps
|
||||
// because they didn't get processed when the parent compiler
|
||||
// was binding dependencies.
|
||||
var i, dep
|
||||
if (binding.contextDeps) {
|
||||
i = binding.contextDeps.length
|
||||
while (i--) {
|
||||
dep = this.bindings[binding.contextDeps[i]]
|
||||
dep.subs.push(directive)
|
||||
}
|
||||
}
|
||||
|
||||
// invoke bind hook if exists
|
||||
if (directive.bind) {
|
||||
directive.bind(binding.value)
|
||||
}
|
||||
|
||||
// set initial value
|
||||
directive.update(binding.value)
|
||||
if (binding.isComputed) {
|
||||
directive.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Process subscriptions for computed properties that has
|
||||
* dynamic context dependencies
|
||||
|
|
@ -439,4 +474,11 @@ function traceOwnerCompiler (key, compiler) {
|
|||
return compiler
|
||||
}
|
||||
|
||||
/*
|
||||
* shorthand for getting root compiler
|
||||
*/
|
||||
function getRoot (compiler) {
|
||||
return traceOwnerCompiler({ root: true }, compiler)
|
||||
}
|
||||
|
||||
module.exports = Compiler
|
||||
|
|
@ -81,6 +81,11 @@ module.exports = {
|
|||
this.ref = document.createComment('sd-each-' + this.arg)
|
||||
ctn.insertBefore(this.ref, this.el)
|
||||
ctn.removeChild(this.el)
|
||||
this.collection = null
|
||||
this.vms = null
|
||||
this.mutationListener = (function (mutation) {
|
||||
mutationHandlers[mutation.method].call(this, mutation)
|
||||
}).bind(this)
|
||||
},
|
||||
|
||||
update: function (collection) {
|
||||
|
|
@ -95,12 +100,11 @@ module.exports = {
|
|||
this.buildItem(this.ref, null, null)
|
||||
}
|
||||
this.collection = collection
|
||||
this.vms = []
|
||||
|
||||
// listen for collection mutation events
|
||||
// the collection has been augmented during Binding.set()
|
||||
collection.__observer__.on('mutate', (function (mutation) {
|
||||
mutationHandlers[mutation.method].call(this, mutation)
|
||||
}).bind(this))
|
||||
collection.__observer__.on('mutate', this.mutationListener)
|
||||
|
||||
// create child-seeds and append to DOM
|
||||
for (var i = 0, l = collection.length; i < l; i++) {
|
||||
|
|
@ -120,11 +124,13 @@ module.exports = {
|
|||
eachPrefix: this.arg + '.',
|
||||
parentCompiler: this.compiler,
|
||||
index: index,
|
||||
data: data,
|
||||
delegator: this.container
|
||||
delegator: this.container,
|
||||
data: {
|
||||
todo: data
|
||||
}
|
||||
})
|
||||
if (index !== null) {
|
||||
this.collection[index] = item
|
||||
if (index) {
|
||||
this.vms[index] = item
|
||||
} else {
|
||||
item.$destroy()
|
||||
}
|
||||
|
|
@ -139,7 +145,7 @@ module.exports = {
|
|||
|
||||
unbind: function () {
|
||||
if (this.collection) {
|
||||
this.collection.off('mutate')
|
||||
this.collection.off('mutate', this.mutationListener)
|
||||
var i = this.collection.length
|
||||
while (i--) {
|
||||
this.collection[i].$destroy()
|
||||
|
|
|
|||
23
src/main.js
23
src/main.js
|
|
@ -48,6 +48,23 @@ api.config = function (opts) {
|
|||
textParser.buildRegex()
|
||||
}
|
||||
|
||||
/*
|
||||
* Angular style bootstrap
|
||||
*/
|
||||
api.bootstrap = function (el) {
|
||||
el = (typeof el === 'string'
|
||||
? document.querySelector(el)
|
||||
: el) || document.body
|
||||
var Ctor = ViewModel,
|
||||
vmAttr = config.prefix + '-viewmodel',
|
||||
vmExp = el.getAttribute(vmAttr)
|
||||
if (vmExp) {
|
||||
Ctor = utils.getVM(vmExp)
|
||||
el.removeAttribute(vmAttr)
|
||||
}
|
||||
return new Ctor({ el: el })
|
||||
}
|
||||
|
||||
/*
|
||||
* Expose the main ViewModel class
|
||||
* and add extend method
|
||||
|
|
@ -57,9 +74,6 @@ api.ViewModel = ViewModel
|
|||
ViewModel.extend = function (options) {
|
||||
var ExtendedVM = function (opts) {
|
||||
opts = opts || {}
|
||||
if (options.template) {
|
||||
opts.template = utils.getTemplate(options.template)
|
||||
}
|
||||
if (options.init) {
|
||||
opts.init = options.init
|
||||
}
|
||||
|
|
@ -78,4 +92,7 @@ ViewModel.extend = function (options) {
|
|||
return ExtendedVM
|
||||
}
|
||||
|
||||
// collect templates on load
|
||||
utils.collectTemplates()
|
||||
|
||||
module.exports = api
|
||||
|
|
@ -120,7 +120,7 @@ module.exports = {
|
|||
},
|
||||
|
||||
unobserve: function (obj, path, observer) {
|
||||
if (!obj.__observer__) return
|
||||
if (!obj || !obj.__observer__) return
|
||||
path = path + '.'
|
||||
var proxies = observer.proxies[path]
|
||||
obj.__observer__
|
||||
|
|
|
|||
25
src/utils.js
25
src/utils.js
|
|
@ -14,14 +14,25 @@ module.exports = {
|
|||
|
||||
typeOf: typeOf,
|
||||
|
||||
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)
|
||||
collectTemplates: function () {
|
||||
var selector = 'script[type="text/' + config.prefix + '-template"]',
|
||||
templates = document.querySelectorAll(selector),
|
||||
i = templates.length
|
||||
while (i--) {
|
||||
this.storeTemplate(templates[i])
|
||||
}
|
||||
return el
|
||||
},
|
||||
|
||||
storeTemplate: function (template) {
|
||||
var id = template.getAttribute(config.prefix + '-template-id')
|
||||
if (id) {
|
||||
templates[id] = template.innerHTML.trim()
|
||||
}
|
||||
template.parentNode.removeChild(template)
|
||||
},
|
||||
|
||||
getTemplate: function (id) {
|
||||
return templates[id]
|
||||
},
|
||||
|
||||
registerVM: function (id, VM) {
|
||||
|
|
|
|||
|
|
@ -6,19 +6,7 @@ var Compiler = require('./compiler')
|
|||
* and a few reserved methods
|
||||
*/
|
||||
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. options are passed directly to compiler
|
||||
// just compile. options are passed directly to compiler
|
||||
new Compiler(this, options)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue