Change to use vendor/assets/*/bower/* for js

This commit is contained in:
uu59 2014-06-02 11:37:27 +09:00
parent 3c37a6201b
commit 8eaa1a12fc
35 changed files with 4468 additions and 2 deletions

View File

@ -15,6 +15,6 @@
//= require sb-admin-v2/bootstrap
//= require sb-admin-v2/plugins/dataTables/jquery.dataTables
//= require sb-admin-v2/plugins/dataTables/dataTables.bootstrap
//= require vue-0.10.4
//= require promise-1.0.0
//= require bower/vue/dist/vue
//= require bower/es6-promise/promise
//= require_tree .

View File

@ -0,0 +1,15 @@
{
"name": "es6-promise",
"version": "1.0.0",
"main": "./promise.js",
"homepage": "https://github.com/components/es6-promise",
"_release": "1.0.0",
"_resolution": {
"type": "version",
"tag": "v1.0.0",
"commit": "c95149ffaa2e8162601c57d4282362eac84f929b"
},
"_source": "git://github.com/components/es6-promise.git",
"_target": "~1.0.0",
"_originalSource": "es6-promise"
}

View File

@ -0,0 +1,5 @@
{
"name": "es6-promise",
"version": "1.0.0",
"main": "./promise.js"
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,29 @@
{
"name": "vue",
"version": "0.10.4",
"main": "dist/vue.js",
"description": "Simple, Fast & Composable MVVM for building interative interfaces",
"authors": [
"Evan You <yyx990803@gmail.com>"
],
"license": "MIT",
"ignore": [
".*",
"examples",
"test",
"tasks",
"Gruntfile.js",
"*.json",
"*.md"
],
"homepage": "https://github.com/yyx990803/vue",
"_release": "0.10.4",
"_resolution": {
"type": "version",
"tag": "v0.10.4",
"commit": "d3758fc7e17a0e115092c5fc89264ec7fff7ea39"
},
"_source": "git://github.com/yyx990803/vue.git",
"_target": "~0.10.0",
"_originalSource": "vue"
}

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013 Yuxi Evan You
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,45 @@
var utils = require('./utils')
function Batcher () {
this.reset()
}
var BatcherProto = Batcher.prototype
BatcherProto.push = function (job) {
if (!job.id || !this.has[job.id]) {
this.queue.push(job)
this.has[job.id] = job
if (!this.waiting) {
this.waiting = true
utils.nextTick(utils.bind(this.flush, this))
}
} else if (job.override) {
var oldJob = this.has[job.id]
oldJob.cancelled = true
this.queue.push(job)
this.has[job.id] = job
}
}
BatcherProto.flush = function () {
// before flush hook
if (this._preFlush) this._preFlush()
// do not cache length because more jobs might be pushed
// as we execute existing jobs
for (var i = 0; i < this.queue.length; i++) {
var job = this.queue[i]
if (!job.cancelled) {
job.execute()
}
}
this.reset()
}
BatcherProto.reset = function () {
this.has = utils.hash()
this.queue = []
this.waiting = false
}
module.exports = Batcher

View File

@ -0,0 +1,103 @@
var Batcher = require('./batcher'),
bindingBatcher = new Batcher(),
bindingId = 1
/**
* Binding class.
*
* each property on the viewmodel has one corresponding Binding object
* which has multiple directive instances on the DOM
* and multiple computed property dependents
*/
function Binding (compiler, key, isExp, isFn) {
this.id = bindingId++
this.value = undefined
this.isExp = !!isExp
this.isFn = isFn
this.root = !this.isExp && key.indexOf('.') === -1
this.compiler = compiler
this.key = key
this.dirs = []
this.subs = []
this.deps = []
this.unbound = false
}
var BindingProto = Binding.prototype
/**
* Update value and queue instance updates.
*/
BindingProto.update = function (value) {
if (!this.isComputed || this.isFn) {
this.value = value
}
if (this.dirs.length || this.subs.length) {
var self = this
bindingBatcher.push({
id: this.id,
execute: function () {
if (!self.unbound) {
self._update()
}
}
})
}
}
/**
* Actually update the directives.
*/
BindingProto._update = function () {
var i = this.dirs.length,
value = this.val()
while (i--) {
this.dirs[i].$update(value)
}
this.pub()
}
/**
* Return the valuated value regardless
* of whether it is computed or not
*/
BindingProto.val = function () {
return this.isComputed && !this.isFn
? this.value.$get()
: this.value
}
/**
* Notify computed properties that depend on this binding
* to update themselves
*/
BindingProto.pub = function () {
var i = this.subs.length
while (i--) {
this.subs[i].update()
}
}
/**
* Unbind the binding, remove itself from all of its dependencies
*/
BindingProto.unbind = function () {
// Indicate this has been unbound.
// It's possible this binding will be in
// the batcher's flush queue when its owner
// compiler has already been destroyed.
this.unbound = true
var i = this.dirs.length
while (i--) {
this.dirs[i].$unbind()
}
i = this.deps.length
var subs
while (i--) {
subs = this.deps[i].subs
var j = subs.indexOf(this)
if (j > -1) subs.splice(j, 1)
}
}
module.exports = Binding

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
var TextParser = require('./text-parser')
module.exports = {
prefix : 'v',
debug : false,
silent : false,
enterClass : 'v-enter',
leaveClass : 'v-leave',
interpolate : true
}
Object.defineProperty(module.exports, 'delimiters', {
get: function () {
return TextParser.delimiters
},
set: function (delimiters) {
TextParser.setDelimiters(delimiters)
}
})

View File

@ -0,0 +1,65 @@
var Emitter = require('./emitter'),
utils = require('./utils'),
Observer = require('./observer'),
catcher = new Emitter()
/**
* Auto-extract the dependencies of a computed property
* by recording the getters triggered when evaluating it.
*/
function catchDeps (binding) {
if (binding.isFn) return
utils.log('\n- ' + binding.key)
var got = utils.hash()
binding.deps = []
catcher.on('get', function (dep) {
var has = got[dep.key]
if (
// avoid duplicate bindings
(has && has.compiler === dep.compiler) ||
// avoid repeated items as dependency
// only when the binding is from self or the parent chain
(dep.compiler.repeat && !isParentOf(dep.compiler, binding.compiler))
) {
return
}
got[dep.key] = dep
utils.log(' - ' + dep.key)
binding.deps.push(dep)
dep.subs.push(binding)
})
binding.value.$get()
catcher.off('get')
}
/**
* Test if A is a parent of or equals B
*/
function isParentOf (a, b) {
while (b) {
if (a === b) {
return true
}
b = b.parent
}
}
module.exports = {
/**
* the observer that catches events triggered by getters
*/
catcher: catcher,
/**
* parse a list of computed property bindings
*/
parse: function (bindings) {
utils.log('\nparsing dependencies...')
Observer.shouldGet = true
bindings.forEach(catchDeps)
Observer.shouldGet = false
utils.log('\ndone.')
}
}

View File

@ -0,0 +1,256 @@
var dirId = 1,
ARG_RE = /^[\w\$-]+$/,
FILTER_TOKEN_RE = /[^\s'"]+|'[^']+'|"[^"]+"/g,
NESTING_RE = /^\$(parent|root)\./,
SINGLE_VAR_RE = /^[\w\.$]+$/,
QUOTE_RE = /"/g
/**
* Directive class
* represents a single directive instance in the DOM
*/
function Directive (name, ast, definition, compiler, el) {
this.id = dirId++
this.name = name
this.compiler = compiler
this.vm = compiler.vm
this.el = el
this.computeFilters = false
this.key = ast.key
this.arg = ast.arg
this.expression = ast.expression
var isEmpty = this.expression === ''
// mix in properties from the directive definition
if (typeof definition === 'function') {
this[isEmpty ? 'bind' : 'update'] = definition
} else {
for (var prop in definition) {
this[prop] = definition[prop]
}
}
// empty expression, we're done.
if (isEmpty || this.isEmpty) {
this.isEmpty = true
return
}
this.expression = (
this.isLiteral
? compiler.eval(this.expression)
: this.expression
).trim()
var filters = ast.filters,
filter, fn, i, l, computed
if (filters) {
this.filters = []
for (i = 0, l = filters.length; i < l; i++) {
filter = filters[i]
fn = this.compiler.getOption('filters', filter.name)
if (fn) {
filter.apply = fn
this.filters.push(filter)
if (fn.computed) {
computed = true
}
}
}
}
if (!this.filters || !this.filters.length) {
this.filters = null
}
if (computed) {
this.computedKey = Directive.inlineFilters(this.key, this.filters)
this.filters = null
}
this.isExp =
computed ||
!SINGLE_VAR_RE.test(this.key) ||
NESTING_RE.test(this.key)
}
var DirProto = Directive.prototype
/**
* called when a new value is set
* for computed properties, this will only be called once
* during initialization.
*/
DirProto.$update = function (value, init) {
if (this.$lock) return
if (init || value !== this.value || (value && typeof value === 'object')) {
this.value = value
if (this.update) {
this.update(
this.filters && !this.computeFilters
? this.$applyFilters(value)
: value,
init
)
}
}
}
/**
* pipe the value through filters
*/
DirProto.$applyFilters = function (value) {
var filtered = value, filter
for (var i = 0, l = this.filters.length; i < l; i++) {
filter = this.filters[i]
filtered = filter.apply.apply(this.vm, [filtered].concat(filter.args))
}
return filtered
}
/**
* Unbind diretive
*/
DirProto.$unbind = function () {
// this can be called before the el is even assigned...
if (!this.el || !this.vm) return
if (this.unbind) this.unbind()
this.vm = this.el = this.binding = this.compiler = null
}
// Exposed static methods -----------------------------------------------------
/**
* Parse a directive string into an Array of
* AST-like objects representing directives
*/
Directive.parse = function (str) {
var inSingle = false,
inDouble = false,
curly = 0,
square = 0,
paren = 0,
begin = 0,
argIndex = 0,
dirs = [],
dir = {},
lastFilterIndex = 0,
arg
for (var c, i = 0, l = str.length; i < l; i++) {
c = str.charAt(i)
if (inSingle) {
// check single quote
if (c === "'") inSingle = !inSingle
} else if (inDouble) {
// check double quote
if (c === '"') inDouble = !inDouble
} else if (c === ',' && !paren && !curly && !square) {
// reached the end of a directive
pushDir()
// reset & skip the comma
dir = {}
begin = argIndex = lastFilterIndex = i + 1
} else if (c === ':' && !dir.key && !dir.arg) {
// argument
arg = str.slice(begin, i).trim()
if (ARG_RE.test(arg)) {
argIndex = i + 1
dir.arg = str.slice(begin, i).trim()
}
} else if (c === '|' && str.charAt(i + 1) !== '|' && str.charAt(i - 1) !== '|') {
if (dir.key === undefined) {
// first filter, end of key
lastFilterIndex = i + 1
dir.key = str.slice(argIndex, i).trim()
} else {
// already has filter
pushFilter()
}
} else if (c === '"') {
inDouble = true
} else if (c === "'") {
inSingle = true
} else if (c === '(') {
paren++
} else if (c === ')') {
paren--
} else if (c === '[') {
square++
} else if (c === ']') {
square--
} else if (c === '{') {
curly++
} else if (c === '}') {
curly--
}
}
if (i === 0 || begin !== i) {
pushDir()
}
function pushDir () {
dir.expression = str.slice(begin, i).trim()
if (dir.key === undefined) {
dir.key = str.slice(argIndex, i).trim()
} else if (lastFilterIndex !== begin) {
pushFilter()
}
if (i === 0 || dir.key) {
dirs.push(dir)
}
}
function pushFilter () {
var exp = str.slice(lastFilterIndex, i).trim(),
filter
if (exp) {
filter = {}
var tokens = exp.match(FILTER_TOKEN_RE)
filter.name = tokens[0]
filter.args = tokens.length > 1 ? tokens.slice(1) : null
}
if (filter) {
(dir.filters = dir.filters || []).push(filter)
}
lastFilterIndex = i + 1
}
return dirs
}
/**
* Inline computed filters so they become part
* of the expression
*/
Directive.inlineFilters = function (key, filters) {
var args, filter
for (var i = 0, l = filters.length; i < l; i++) {
filter = filters[i]
args = filter.args
? ',"' + filter.args.map(escapeQuote).join('","') + '"'
: ''
key = 'this.$compiler.getOption("filters", "' +
filter.name +
'").call(this,' +
key + args +
')'
}
return key
}
/**
* Convert double quotes to single quotes
* so they don't mess up the generated function body
*/
function escapeQuote (v) {
return v.indexOf('"') > -1
? v.replace(QUOTE_RE, '\'')
: v
}
module.exports = Directive

View File

@ -0,0 +1,41 @@
var utils = require('../utils'),
slice = [].slice
/**
* Binding for innerHTML
*/
module.exports = {
bind: function () {
// a comment node means this is a binding for
// {{{ inline unescaped html }}}
if (this.el.nodeType === 8) {
// hold nodes
this.nodes = []
}
},
update: function (value) {
value = utils.guard(value)
if (this.nodes) {
this.swap(value)
} else {
this.el.innerHTML = value
}
},
swap: function (value) {
var parent = this.el.parentNode,
nodes = this.nodes,
i = nodes.length
// remove old nodes
while (i--) {
parent.removeChild(nodes[i])
}
// convert new value to a fragment
var frag = utils.toFragment(value)
// save a reference to these nodes so we can remove later
this.nodes = slice.call(frag.childNodes)
parent.insertBefore(frag, this.el)
}
}

View File

@ -0,0 +1,56 @@
var utils = require('../utils')
/**
* Manages a conditional child VM
*/
module.exports = {
bind: function () {
this.parent = this.el.parentNode
this.ref = document.createComment('vue-if')
this.Ctor = this.compiler.resolveComponent(this.el)
// insert ref
this.parent.insertBefore(this.ref, this.el)
this.parent.removeChild(this.el)
if (utils.attr(this.el, 'view')) {
utils.warn(
'Conflict: v-if cannot be used together with v-view. ' +
'Just set v-view\'s binding value to empty string to empty it.'
)
}
if (utils.attr(this.el, 'repeat')) {
utils.warn(
'Conflict: v-if cannot be used together with v-repeat. ' +
'Use `v-show` or the `filterBy` filter instead.'
)
}
},
update: function (value) {
if (!value) {
this.unbind()
} else if (!this.childVM) {
this.childVM = new this.Ctor({
el: this.el.cloneNode(true),
parent: this.vm
})
if (this.compiler.init) {
this.parent.insertBefore(this.childVM.$el, this.ref)
} else {
this.childVM.$before(this.ref)
}
}
},
unbind: function () {
if (this.childVM) {
this.childVM.$destroy()
this.childVM = null
}
}
}

View File

@ -0,0 +1,129 @@
var utils = require('../utils'),
config = require('../config'),
transition = require('../transition'),
directives = module.exports = utils.hash()
/**
* Nest and manage a Child VM
*/
directives.component = {
isLiteral: true,
bind: function () {
if (!this.el.vue_vm) {
this.childVM = new this.Ctor({
el: this.el,
parent: this.vm
})
}
},
unbind: function () {
if (this.childVM) {
this.childVM.$destroy()
}
}
}
/**
* Binding HTML attributes
*/
directives.attr = {
bind: function () {
var params = this.vm.$options.paramAttributes
this.isParam = params && params.indexOf(this.arg) > -1
},
update: function (value) {
if (value || value === 0) {
this.el.setAttribute(this.arg, value)
} else {
this.el.removeAttribute(this.arg)
}
if (this.isParam) {
this.vm[this.arg] = utils.checkNumber(value)
}
}
}
/**
* Binding textContent
*/
directives.text = {
bind: function () {
this.attr = this.el.nodeType === 3
? 'nodeValue'
: 'textContent'
},
update: function (value) {
this.el[this.attr] = utils.guard(value)
}
}
/**
* Binding CSS display property
*/
directives.show = function (value) {
var el = this.el,
target = value ? '' : 'none',
change = function () {
el.style.display = target
}
transition(el, value ? 1 : -1, change, this.compiler)
}
/**
* Binding CSS classes
*/
directives['class'] = function (value) {
if (this.arg) {
utils[value ? 'addClass' : 'removeClass'](this.el, this.arg)
} else {
if (this.lastVal) {
utils.removeClass(this.el, this.lastVal)
}
if (value) {
utils.addClass(this.el, value)
this.lastVal = value
}
}
}
/**
* Only removed after the owner VM is ready
*/
directives.cloak = {
isEmpty: true,
bind: function () {
var el = this.el
this.compiler.observer.once('hook:ready', function () {
el.removeAttribute(config.prefix + '-cloak')
})
}
}
/**
* Store a reference to self in parent VM's $
*/
directives.ref = {
isLiteral: true,
bind: function () {
var id = this.expression
if (id) {
this.vm.$parent.$[id] = this.vm
}
},
unbind: function () {
var id = this.expression
if (id) {
delete this.vm.$parent.$[id]
}
}
}
directives.on = require('./on')
directives.repeat = require('./repeat')
directives.model = require('./model')
directives['if'] = require('./if')
directives['with'] = require('./with')
directives.html = require('./html')
directives.style = require('./style')
directives.partial = require('./partial')
directives.view = require('./view')

View File

@ -0,0 +1,174 @@
var utils = require('../utils'),
isIE9 = navigator.userAgent.indexOf('MSIE 9.0') > 0,
filter = [].filter
/**
* Returns an array of values from a multiple select
*/
function getMultipleSelectOptions (select) {
return filter
.call(select.options, function (option) {
return option.selected
})
.map(function (option) {
return option.value || option.text
})
}
/**
* Two-way binding for form input elements
*/
module.exports = {
bind: function () {
var self = this,
el = self.el,
type = el.type,
tag = el.tagName
self.lock = false
self.ownerVM = self.binding.compiler.vm
// determine what event to listen to
self.event =
(self.compiler.options.lazy ||
tag === 'SELECT' ||
type === 'checkbox' || type === 'radio')
? 'change'
: 'input'
// determine the attribute to change when updating
self.attr = type === 'checkbox'
? 'checked'
: (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA')
? 'value'
: 'innerHTML'
// select[multiple] support
if(tag === 'SELECT' && el.hasAttribute('multiple')) {
this.multi = true
}
var compositionLock = false
self.cLock = function () {
compositionLock = true
}
self.cUnlock = function () {
compositionLock = false
}
el.addEventListener('compositionstart', this.cLock)
el.addEventListener('compositionend', this.cUnlock)
// attach listener
self.set = self.filters
? function () {
if (compositionLock) return
// if this directive has filters
// we need to let the vm.$set trigger
// update() so filters are applied.
// therefore we have to record cursor position
// so that after vm.$set changes the input
// value we can put the cursor back at where it is
var cursorPos
try { cursorPos = el.selectionStart } catch (e) {}
self._set()
// since updates are async
// we need to reset cursor position async too
utils.nextTick(function () {
if (cursorPos !== undefined) {
el.setSelectionRange(cursorPos, cursorPos)
}
})
}
: function () {
if (compositionLock) return
// no filters, don't let it trigger update()
self.lock = true
self._set()
utils.nextTick(function () {
self.lock = false
})
}
el.addEventListener(self.event, self.set)
// fix shit for IE9
// since it doesn't fire input on backspace / del / cut
if (isIE9) {
self.onCut = function () {
// cut event fires before the value actually changes
utils.nextTick(function () {
self.set()
})
}
self.onDel = function (e) {
if (e.keyCode === 46 || e.keyCode === 8) {
self.set()
}
}
el.addEventListener('cut', self.onCut)
el.addEventListener('keyup', self.onDel)
}
},
_set: function () {
this.ownerVM.$set(
this.key, this.multi
? getMultipleSelectOptions(this.el)
: this.el[this.attr]
)
},
update: function (value, init) {
/* jshint eqeqeq: false */
// sync back inline value if initial data is undefined
if (init && value === undefined) {
return this._set()
}
if (this.lock) return
var el = this.el
if (el.tagName === 'SELECT') { // select dropdown
el.selectedIndex = -1
if(this.multi && Array.isArray(value)) {
value.forEach(this.updateSelect, this)
} else {
this.updateSelect(value)
}
} else if (el.type === 'radio') { // radio button
el.checked = value == el.value
} else if (el.type === 'checkbox') { // checkbox
el.checked = !!value
} else {
el[this.attr] = utils.guard(value)
}
},
updateSelect: function (value) {
/* jshint eqeqeq: false */
// setting <select>'s value in IE9 doesn't work
// we have to manually loop through the options
var options = this.el.options,
i = options.length
while (i--) {
if (options[i].value == value) {
options[i].selected = true
break
}
}
},
unbind: function () {
var el = this.el
el.removeEventListener(this.event, this.set)
el.removeEventListener('compositionstart', this.cLock)
el.removeEventListener('compositionend', this.cUnlock)
if (isIE9) {
el.removeEventListener('cut', this.onCut)
el.removeEventListener('keyup', this.onDel)
}
}
}

View File

@ -0,0 +1,37 @@
var utils = require('../utils')
/**
* Binding for event listeners
*/
module.exports = {
isFn: true,
bind: function () {
this.context = this.binding.isExp
? this.vm
: this.binding.compiler.vm
},
update: function (handler) {
if (typeof handler !== 'function') {
utils.warn('Directive "v-on:' + this.expression + '" expects a method.')
return
}
this.unbind()
var vm = this.vm,
context = this.context
this.handler = function (e) {
e.targetVM = vm
context.$event = e
var res = handler.call(context, e)
context.$event = null
return res
}
this.el.addEventListener(this.arg, this.handler)
},
unbind: function () {
this.el.removeEventListener(this.arg, this.handler)
}
}

View File

@ -0,0 +1,50 @@
var utils = require('../utils')
/**
* Binding for partials
*/
module.exports = {
isLiteral: true,
bind: function () {
var id = this.expression
if (!id) return
var el = this.el,
compiler = this.compiler,
partial = compiler.getOption('partials', id)
if (!partial) {
if (id === 'yield') {
utils.warn('{{>yield}} syntax has been deprecated. Use <content> tag instead.')
}
return
}
partial = partial.cloneNode(true)
// comment ref node means inline partial
if (el.nodeType === 8) {
// keep a ref for the partial's content nodes
var nodes = [].slice.call(partial.childNodes),
parent = el.parentNode
parent.insertBefore(partial, el)
parent.removeChild(el)
// compile partial after appending, because its children's parentNode
// will change from the fragment to the correct parentNode.
// This could affect directives that need access to its element's parentNode.
nodes.forEach(compiler.compile, compiler)
} else {
// just set innerHTML...
el.innerHTML = ''
el.appendChild(partial.cloneNode(true))
}
}
}

View File

@ -0,0 +1,246 @@
var utils = require('../utils'),
config = require('../config')
/**
* Binding that manages VMs based on an Array
*/
module.exports = {
bind: function () {
this.identifier = '$r' + this.id
// a hash to cache the same expressions on repeated instances
// so they don't have to be compiled for every single instance
this.expCache = utils.hash()
var el = this.el,
ctn = this.container = el.parentNode
// extract child Id, if any
this.childId = this.compiler.eval(utils.attr(el, 'ref'))
// create a comment node as a reference node for DOM insertions
this.ref = document.createComment(config.prefix + '-repeat-' + this.key)
ctn.insertBefore(this.ref, el)
ctn.removeChild(el)
this.collection = null
this.vms = null
},
update: function (collection) {
if (!Array.isArray(collection)) {
if (utils.isObject(collection)) {
collection = utils.objectToArray(collection)
} else {
utils.warn('v-repeat only accepts Array or Object values.')
}
}
// keep reference of old data and VMs
// so we can reuse them if possible
this.oldVMs = this.vms
this.oldCollection = this.collection
collection = this.collection = collection || []
var isObject = collection[0] && utils.isObject(collection[0])
this.vms = this.oldCollection
? this.diff(collection, isObject)
: this.init(collection, isObject)
if (this.childId) {
this.vm.$[this.childId] = this.vms
}
},
init: function (collection, isObject) {
var vm, vms = []
for (var i = 0, l = collection.length; i < l; i++) {
vm = this.build(collection[i], i, isObject)
vms.push(vm)
if (this.compiler.init) {
this.container.insertBefore(vm.$el, this.ref)
} else {
vm.$before(this.ref)
}
}
return vms
},
/**
* Diff the new array with the old
* and determine the minimum amount of DOM manipulations.
*/
diff: function (newCollection, isObject) {
var i, l, item, vm,
oldIndex,
targetNext,
currentNext,
nextEl,
ctn = this.container,
oldVMs = this.oldVMs,
vms = []
vms.length = newCollection.length
// first pass, collect new reused and new created
for (i = 0, l = newCollection.length; i < l; i++) {
item = newCollection[i]
if (isObject) {
item.$index = i
if (item.__emitter__ && item.__emitter__[this.identifier]) {
// this piece of data is being reused.
// record its final position in reused vms
item.$reused = true
} else {
vms[i] = this.build(item, i, isObject)
}
} else {
// we can't attach an identifier to primitive values
// so have to do an indexOf...
oldIndex = indexOf(oldVMs, item)
if (oldIndex > -1) {
// record the position on the existing vm
oldVMs[oldIndex].$reused = true
oldVMs[oldIndex].$data.$index = i
} else {
vms[i] = this.build(item, i, isObject)
}
}
}
// second pass, collect old reused and destroy unused
for (i = 0, l = oldVMs.length; i < l; i++) {
vm = oldVMs[i]
item = this.arg
? vm.$data[this.arg]
: vm.$data
if (item.$reused) {
vm.$reused = true
delete item.$reused
}
if (vm.$reused) {
// update the index to latest
vm.$index = item.$index
// the item could have had a new key
if (item.$key && item.$key !== vm.$key) {
vm.$key = item.$key
}
vms[vm.$index] = vm
} else {
// this one can be destroyed.
if (item.__emitter__) {
delete item.__emitter__[this.identifier]
}
vm.$destroy()
}
}
// final pass, move/insert DOM elements
i = vms.length
while (i--) {
vm = vms[i]
item = vm.$data
targetNext = vms[i + 1]
if (vm.$reused) {
nextEl = vm.$el.nextSibling
// destroyed VMs' element might still be in the DOM
// due to transitions
while (!nextEl.vue_vm && nextEl !== this.ref) {
nextEl = nextEl.nextSibling
}
currentNext = nextEl.vue_vm
if (currentNext !== targetNext) {
if (!targetNext) {
ctn.insertBefore(vm.$el, this.ref)
} else {
nextEl = targetNext.$el
// new VMs' element might not be in the DOM yet
// due to transitions
while (!nextEl.parentNode) {
targetNext = vms[nextEl.vue_vm.$index + 1]
nextEl = targetNext
? targetNext.$el
: this.ref
}
ctn.insertBefore(vm.$el, nextEl)
}
}
delete vm.$reused
delete item.$index
delete item.$key
} else { // a new vm
vm.$before(targetNext ? targetNext.$el : this.ref)
}
}
return vms
},
build: function (data, index, isObject) {
// wrap non-object values
var raw, alias,
wrap = !isObject || this.arg
if (wrap) {
raw = data
alias = this.arg || '$value'
data = {}
data[alias] = raw
}
data.$index = index
var el = this.el.cloneNode(true),
Ctor = this.compiler.resolveComponent(el, data),
vm = new Ctor({
el: el,
data: data,
parent: this.vm,
compilerOptions: {
repeat: true,
expCache: this.expCache
}
})
if (isObject) {
// attach an ienumerable identifier to the raw data
(raw || data).__emitter__[this.identifier] = true
}
return vm
},
unbind: function () {
if (this.childId) {
delete this.vm.$[this.childId]
}
if (this.vms) {
var i = this.vms.length
while (i--) {
this.vms[i].$destroy()
}
}
}
}
// Helpers --------------------------------------------------------------------
/**
* Find an object or a wrapped data object
* from an Array
*/
function indexOf (vms, obj) {
for (var vm, i = 0, l = vms.length; i < l; i++) {
vm = vms[i]
if (!vm.$reused && vm.$value === obj) {
return i
}
}
return -1
}

View File

@ -0,0 +1,44 @@
var camelRE = /-([a-z])/g,
prefixes = ['webkit', 'moz', 'ms']
function camelReplacer (m) {
return m[1].toUpperCase()
}
/**
* Binding for CSS styles
*/
module.exports = {
bind: function () {
var prop = this.arg
if (!prop) return
var first = prop.charAt(0)
if (first === '$') {
// properties that start with $ will be auto-prefixed
prop = prop.slice(1)
this.prefixed = true
} else if (first === '-') {
// normal starting hyphens should not be converted
prop = prop.slice(1)
}
this.prop = prop.replace(camelRE, camelReplacer)
},
update: function (value) {
var prop = this.prop
if (prop) {
this.el.style[prop] = value
if (this.prefixed) {
prop = prop.charAt(0).toUpperCase() + prop.slice(1)
var i = prefixes.length
while (i--) {
this.el.style[prefixes[i] + prop] = value
}
}
} else {
this.el.style.cssText = value
}
}
}

View File

@ -0,0 +1,56 @@
/**
* Manages a conditional child VM using the
* binding's value as the component ID.
*/
module.exports = {
bind: function () {
// track position in DOM with a ref node
var el = this.raw = this.el,
parent = el.parentNode,
ref = this.ref = document.createComment('v-view')
parent.insertBefore(ref, el)
parent.removeChild(el)
// cache original content
/* jshint boss: true */
var node,
frag = this.inner = document.createElement('div')
while (node = el.firstChild) {
frag.appendChild(node)
}
},
update: function(value) {
this.unbind()
var Ctor = this.compiler.getOption('components', value)
if (!Ctor) return
this.childVM = new Ctor({
el: this.raw.cloneNode(true),
parent: this.vm,
compilerOptions: {
rawContent: this.inner.cloneNode(true)
}
})
this.el = this.childVM.$el
if (this.compiler.init) {
this.ref.parentNode.insertBefore(this.el, this.ref)
} else {
this.childVM.$before(this.ref)
}
},
unbind: function() {
if (this.childVM) {
this.childVM.$destroy()
}
}
}

View File

@ -0,0 +1,50 @@
var utils = require('../utils')
/**
* Binding for inheriting data from parent VMs.
*/
module.exports = {
bind: function () {
var self = this,
childKey = self.arg,
parentKey = self.key,
compiler = self.compiler,
owner = self.binding.compiler
if (compiler === owner) {
this.alone = true
return
}
if (childKey) {
if (!compiler.bindings[childKey]) {
compiler.createBinding(childKey)
}
// sync changes on child back to parent
compiler.observer.on('change:' + childKey, function (val) {
if (compiler.init) return
if (!self.lock) {
self.lock = true
utils.nextTick(function () {
self.lock = false
})
}
owner.vm.$set(parentKey, val)
})
}
},
update: function (value) {
// sync from parent
if (!this.alone && !this.lock) {
if (this.arg) {
this.vm.$set(this.arg, value)
} else {
this.vm.$data = value
}
}
}
}

View File

@ -0,0 +1,97 @@
var slice = [].slice
function Emitter (ctx) {
this._ctx = ctx || this
}
var EmitterProto = Emitter.prototype
EmitterProto.on = function (event, fn) {
this._cbs = this._cbs || {}
;(this._cbs[event] = this._cbs[event] || [])
.push(fn)
return this
}
EmitterProto.once = function (event, fn) {
var self = this
this._cbs = this._cbs || {}
function on () {
self.off(event, on)
fn.apply(this, arguments)
}
on.fn = fn
this.on(event, on)
return this
}
EmitterProto.off = function (event, fn) {
this._cbs = this._cbs || {}
// all
if (!arguments.length) {
this._cbs = {}
return this
}
// specific event
var callbacks = this._cbs[event]
if (!callbacks) return this
// remove all handlers
if (arguments.length === 1) {
delete this._cbs[event]
return this
}
// remove specific handler
var cb
for (var i = 0; i < callbacks.length; i++) {
cb = callbacks[i]
if (cb === fn || cb.fn === fn) {
callbacks.splice(i, 1)
break
}
}
return this
}
/**
* The internal, faster emit with fixed amount of arguments
* using Function.call
*/
EmitterProto.emit = function (event, a, b, c) {
this._cbs = this._cbs || {}
var callbacks = this._cbs[event]
if (callbacks) {
callbacks = callbacks.slice(0)
for (var i = 0, len = callbacks.length; i < len; i++) {
callbacks[i].call(this._ctx, a, b, c)
}
}
return this
}
/**
* The external emit using Function.apply
*/
EmitterProto.applyEmit = function (event) {
this._cbs = this._cbs || {}
var callbacks = this._cbs[event], args
if (callbacks) {
callbacks = callbacks.slice(0)
args = slice.call(arguments, 1)
for (var i = 0, len = callbacks.length; i < len; i++) {
callbacks[i].apply(this._ctx, args)
}
}
return this
}
module.exports = Emitter

View File

@ -0,0 +1,190 @@
var utils = require('./utils'),
STR_SAVE_RE = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g,
STR_RESTORE_RE = /"(\d+)"/g,
NEWLINE_RE = /\n/g,
CTOR_RE = new RegExp('constructor'.split('').join('[\'"+, ]*')),
UNICODE_RE = /\\u\d\d\d\d/
// Variable extraction scooped from https://github.com/RubyLouvre/avalon
var KEYWORDS =
// keywords
'break,case,catch,continue,debugger,default,delete,do,else,false' +
',finally,for,function,if,in,instanceof,new,null,return,switch,this' +
',throw,true,try,typeof,var,void,while,with,undefined' +
// reserved
',abstract,boolean,byte,char,class,const,double,enum,export,extends' +
',final,float,goto,implements,import,int,interface,long,native' +
',package,private,protected,public,short,static,super,synchronized' +
',throws,transient,volatile' +
// ECMA 5 - use strict
',arguments,let,yield' +
// allow using Math in expressions
',Math',
KEYWORDS_RE = new RegExp(["\\b" + KEYWORDS.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g'),
REMOVE_RE = /\/\*(?:.|\n)*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|'[^']*'|"[^"]*"|[\s\t\n]*\.[\s\t\n]*[$\w\.]+|[\{,]\s*[\w\$_]+\s*:/g,
SPLIT_RE = /[^\w$]+/g,
NUMBER_RE = /\b\d[^,]*/g,
BOUNDARY_RE = /^,+|,+$/g
/**
* Strip top level variable names from a snippet of JS expression
*/
function getVariables (code) {
code = code
.replace(REMOVE_RE, '')
.replace(SPLIT_RE, ',')
.replace(KEYWORDS_RE, '')
.replace(NUMBER_RE, '')
.replace(BOUNDARY_RE, '')
return code
? code.split(/,+/)
: []
}
/**
* A given path could potentially exist not on the
* current compiler, but up in the parent chain somewhere.
* This function generates an access relationship string
* that can be used in the getter function by walking up
* the parent chain to check for key existence.
*
* It stops at top parent if no vm in the chain has the
* key. It then creates any missing bindings on the
* final resolved vm.
*/
function traceScope (path, compiler, data) {
var rel = '',
dist = 0,
self = compiler
if (data && utils.get(data, path) !== undefined) {
// hack: temporarily attached data
return '$temp.'
}
while (compiler) {
if (compiler.hasKey(path)) {
break
} else {
compiler = compiler.parent
dist++
}
}
if (compiler) {
while (dist--) {
rel += '$parent.'
}
if (!compiler.bindings[path] && path.charAt(0) !== '$') {
compiler.createBinding(path)
}
} else {
self.createBinding(path)
}
return rel
}
/**
* Create a function from a string...
* this looks like evil magic but since all variables are limited
* to the VM's data it's actually properly sandboxed
*/
function makeGetter (exp, raw) {
var fn
try {
fn = new Function(exp)
} catch (e) {
utils.warn('Error parsing expression: ' + raw)
}
return fn
}
/**
* Escape a leading dollar sign for regex construction
*/
function escapeDollar (v) {
return v.charAt(0) === '$'
? '\\' + v
: v
}
/**
* Parse and return an anonymous computed property getter function
* from an arbitrary expression, together with a list of paths to be
* created as bindings.
*/
exports.parse = function (exp, compiler, data) {
// unicode and 'constructor' are not allowed for XSS security.
if (UNICODE_RE.test(exp) || CTOR_RE.test(exp)) {
utils.warn('Unsafe expression: ' + exp)
return
}
// extract variable names
var vars = getVariables(exp)
if (!vars.length) {
return makeGetter('return ' + exp, exp)
}
vars = utils.unique(vars)
var accessors = '',
has = utils.hash(),
strings = [],
// construct a regex to extract all valid variable paths
// ones that begin with "$" are particularly tricky
// because we can't use \b for them
pathRE = new RegExp(
"[^$\\w\\.](" +
vars.map(escapeDollar).join('|') +
")[$\\w\\.]*\\b", 'g'
),
body = (' ' + exp)
.replace(STR_SAVE_RE, saveStrings)
.replace(pathRE, replacePath)
.replace(STR_RESTORE_RE, restoreStrings)
body = accessors + 'return ' + body
function saveStrings (str) {
var i = strings.length
// escape newlines in strings so the expression
// can be correctly evaluated
strings[i] = str.replace(NEWLINE_RE, '\\n')
return '"' + i + '"'
}
function replacePath (path) {
// keep track of the first char
var c = path.charAt(0)
path = path.slice(1)
var val = 'this.' + traceScope(path, compiler, data) + path
if (!has[path]) {
accessors += val + ';'
has[path] = 1
}
// don't forget to put that first char back
return c + val
}
function restoreStrings (str, i) {
return strings[i]
}
return makeGetter(body, exp)
}
/**
* Evaluate an expression in the context of a compiler.
* Accepts additional data.
*/
exports.eval = function (exp, compiler, data) {
var getter = exports.parse(exp, compiler, data), res
if (getter) {
// hack: temporarily attach the additional data so
// it can be accessed in the getter
compiler.vm.$temp = data
res = getter.call(compiler.vm)
delete compiler.vm.$temp
}
return res
}

View File

@ -0,0 +1,190 @@
var utils = require('./utils'),
get = utils.get,
slice = [].slice,
QUOTE_RE = /^'.*'$/,
filters = module.exports = utils.hash()
/**
* 'abc' => 'Abc'
*/
filters.capitalize = function (value) {
if (!value && value !== 0) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
/**
* 'abc' => 'ABC'
*/
filters.uppercase = function (value) {
return (value || value === 0)
? value.toString().toUpperCase()
: ''
}
/**
* 'AbC' => 'abc'
*/
filters.lowercase = function (value) {
return (value || value === 0)
? value.toString().toLowerCase()
: ''
}
/**
* 12345 => $12,345.00
*/
filters.currency = function (value, sign) {
if (!value && value !== 0) return ''
sign = sign || '$'
var s = Math.floor(value).toString(),
i = s.length % 3,
h = i > 0 ? (s.slice(0, i) + (s.length > 3 ? ',' : '')) : '',
f = '.' + value.toFixed(2).slice(-2)
return sign + h + s.slice(i).replace(/(\d{3})(?=\d)/g, '$1,') + f
}
/**
* args: an array of strings corresponding to
* the single, double, triple ... forms of the word to
* be pluralized. When the number to be pluralized
* exceeds the length of the args, it will use the last
* entry in the array.
*
* e.g. ['single', 'double', 'triple', 'multiple']
*/
filters.pluralize = function (value) {
var args = slice.call(arguments, 1)
return args.length > 1
? (args[value - 1] || args[args.length - 1])
: (args[value - 1] || args[0] + 's')
}
/**
* A special filter that takes a handler function,
* wraps it so it only gets triggered on specific keypresses.
*
* v-on only
*/
var keyCodes = {
enter : 13,
tab : 9,
'delete' : 46,
up : 38,
left : 37,
right : 39,
down : 40,
esc : 27
}
filters.key = function (handler, key) {
if (!handler) return
var code = keyCodes[key]
if (!code) {
code = parseInt(key, 10)
}
return function (e) {
if (e.keyCode === code) {
return handler.call(this, e)
}
}
}
/**
* Filter filter for v-repeat
*/
filters.filterBy = function (arr, searchKey, delimiter, dataKey) {
// allow optional `in` delimiter
// because why not
if (delimiter && delimiter !== 'in') {
dataKey = delimiter
}
// get the search string
var search = stripQuotes(searchKey) || this.$get(searchKey)
if (!search) return arr
search = search.toLowerCase()
// get the optional dataKey
dataKey = dataKey && (stripQuotes(dataKey) || this.$get(dataKey))
// convert object to array
if (!Array.isArray(arr)) {
arr = utils.objectToArray(arr)
}
return arr.filter(function (item) {
return dataKey
? contains(get(item, dataKey), search)
: contains(item, search)
})
}
filters.filterBy.computed = true
/**
* Sort fitler for v-repeat
*/
filters.orderBy = function (arr, sortKey, reverseKey) {
var key = stripQuotes(sortKey) || this.$get(sortKey)
if (!key) return arr
// convert object to array
if (!Array.isArray(arr)) {
arr = utils.objectToArray(arr)
}
var order = 1
if (reverseKey) {
if (reverseKey === '-1') {
order = -1
} else if (reverseKey.charAt(0) === '!') {
reverseKey = reverseKey.slice(1)
order = this.$get(reverseKey) ? 1 : -1
} else {
order = this.$get(reverseKey) ? -1 : 1
}
}
// sort on a copy to avoid mutating original array
return arr.slice().sort(function (a, b) {
a = get(a, key)
b = get(b, key)
return a === b ? 0 : a > b ? order : -order
})
}
filters.orderBy.computed = true
// Array filter helpers -------------------------------------------------------
/**
* String contain helper
*/
function contains (val, search) {
/* jshint eqeqeq: false */
if (utils.isObject(val)) {
for (var key in val) {
if (contains(val[key], search)) {
return true
}
}
} else if (val != null) {
return val.toString().toLowerCase().indexOf(search) > -1
}
}
/**
* Test whether a string is in quotes,
* if yes return stripped string
*/
function stripQuotes (str) {
if (QUOTE_RE.test(str)) {
return str.slice(1, -1)
}
}

View File

@ -0,0 +1,84 @@
// string -> DOM conversion
// wrappers originally from jQuery, scooped from component/domify
var map = {
legend : [1, '<fieldset>', '</fieldset>'],
tr : [2, '<table><tbody>', '</tbody></table>'],
col : [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
_default : [0, '', '']
}
map.td =
map.th = [3, '<table><tbody><tr>', '</tr></tbody></table>']
map.option =
map.optgroup = [1, '<select multiple="multiple">', '</select>']
map.thead =
map.tbody =
map.colgroup =
map.caption =
map.tfoot = [1, '<table>', '</table>']
map.text =
map.circle =
map.ellipse =
map.line =
map.path =
map.polygon =
map.polyline =
map.rect = [1, '<svg xmlns="http://www.w3.org/2000/svg" version="1.1">','</svg>']
var TAG_RE = /<([\w:]+)/
module.exports = function (template) {
if (typeof template !== 'string') {
return template
}
// template by ID
if (template.charAt(0) === '#') {
var templateNode = document.getElementById(template.slice(1))
if (!templateNode) return
// if its a template tag and the browser supports it,
// its content is already a document fragment!
if (templateNode.tagName === 'TEMPLATE' && templateNode.content) {
return templateNode.content
}
template = templateNode.innerHTML
}
var frag = document.createDocumentFragment(),
m = TAG_RE.exec(template)
// text only
if (!m) {
frag.appendChild(document.createTextNode(template))
return frag
}
var tag = m[1],
wrap = map[tag] || map._default,
depth = wrap[0],
prefix = wrap[1],
suffix = wrap[2],
node = document.createElement('div')
node.innerHTML = prefix + template.trim() + suffix
while (depth--) node = node.lastChild
// one element
if (node.firstChild === node.lastChild) {
frag.appendChild(node.firstChild)
return frag
}
// multiple nodes, return a fragment
var child
/* jshint boss: true */
while (child = node.firstChild) {
if (node.nodeType === 1) {
frag.appendChild(child)
}
}
return frag
}

View File

@ -0,0 +1,186 @@
var config = require('./config'),
ViewModel = require('./viewmodel'),
utils = require('./utils'),
makeHash = utils.hash,
assetTypes = ['directive', 'filter', 'partial', 'effect', 'component']
// require these so Browserify can catch them
// so they can be used in Vue.require
require('./observer')
require('./transition')
ViewModel.options = config.globalAssets = {
directives : require('./directives'),
filters : require('./filters'),
partials : makeHash(),
effects : makeHash(),
components : makeHash()
}
/**
* Expose asset registration methods
*/
assetTypes.forEach(function (type) {
ViewModel[type] = function (id, value) {
var hash = this.options[type + 's']
if (!hash) {
hash = this.options[type + 's'] = makeHash()
}
if (!value) return hash[id]
if (type === 'partial') {
value = utils.toFragment(value)
} else if (type === 'component') {
value = utils.toConstructor(value)
} else if (type === 'filter') {
utils.checkFilter(value)
}
hash[id] = value
return this
}
})
/**
* Set config options
*/
ViewModel.config = function (opts, val) {
if (typeof opts === 'string') {
if (val === undefined) {
return config[opts]
} else {
config[opts] = val
}
} else {
utils.extend(config, opts)
}
return this
}
/**
* Expose an interface for plugins
*/
ViewModel.use = function (plugin) {
if (typeof plugin === 'string') {
try {
plugin = require(plugin)
} catch (e) {
utils.warn('Cannot find plugin: ' + plugin)
return
}
}
// additional parameters
var args = [].slice.call(arguments, 1)
args.unshift(this)
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else {
plugin.apply(null, args)
}
return this
}
/**
* Expose internal modules for plugins
*/
ViewModel.require = function (path) {
return require('./' + path)
}
ViewModel.extend = extend
ViewModel.nextTick = utils.nextTick
/**
* Expose the main ViewModel class
* and add extend method
*/
function extend (options) {
var ParentVM = this
// extend data options need to be copied
// on instantiation
if (options.data) {
options.defaultData = options.data
delete options.data
}
// inherit options
// but only when the super class is not the native Vue.
if (ParentVM !== ViewModel) {
options = inheritOptions(options, ParentVM.options, true)
}
utils.processOptions(options)
var ExtendedVM = function (opts, asParent) {
if (!asParent) {
opts = inheritOptions(opts, options, true)
}
ParentVM.call(this, opts, true)
}
// inherit prototype props
var proto = ExtendedVM.prototype = Object.create(ParentVM.prototype)
utils.defProtected(proto, 'constructor', ExtendedVM)
// allow extended VM to be further extended
ExtendedVM.extend = extend
ExtendedVM.super = ParentVM
ExtendedVM.options = options
// allow extended VM to add its own assets
assetTypes.forEach(function (type) {
ExtendedVM[type] = ViewModel[type]
})
// allow extended VM to use plugins
ExtendedVM.use = ViewModel.use
ExtendedVM.require = ViewModel.require
return ExtendedVM
}
/**
* Inherit options
*
* For options such as `data`, `vms`, `directives`, 'partials',
* they should be further extended. However extending should only
* be done at top level.
*
* `proto` is an exception because it's handled directly on the
* prototype.
*
* `el` is an exception because it's not allowed as an
* extension option, but only as an instance option.
*/
function inheritOptions (child, parent, topLevel) {
child = child || {}
if (!parent) return child
for (var key in parent) {
if (key === 'el') continue
var val = child[key],
parentVal = parent[key]
if (topLevel && typeof val === 'function' && parentVal) {
// merge hook functions into an array
child[key] = [val]
if (Array.isArray(parentVal)) {
child[key] = child[key].concat(parentVal)
} else {
child[key].push(parentVal)
}
} else if (
topLevel &&
(utils.isTrueObject(val) || utils.isTrueObject(parentVal))
&& !(parentVal instanceof ViewModel)
) {
// merge toplevel object options
child[key] = inheritOptions(val, parentVal)
} else if (val === undefined) {
// inherit if child doesn't override
child[key] = parentVal
}
}
return child
}
module.exports = ViewModel

View File

@ -0,0 +1,446 @@
/* jshint proto:true */
var Emitter = require('./emitter'),
utils = require('./utils'),
// cache methods
def = utils.defProtected,
isObject = utils.isObject,
isArray = Array.isArray,
hasOwn = ({}).hasOwnProperty,
oDef = Object.defineProperty,
slice = [].slice,
// fix for IE + __proto__ problem
// define methods as inenumerable if __proto__ is present,
// otherwise enumerable so we can loop through and manually
// attach to array instances
hasProto = ({}).__proto__
// Array Mutation Handlers & Augmentations ------------------------------------
// The proxy prototype to replace the __proto__ of
// an observed array
var ArrayProxy = Object.create(Array.prototype)
// intercept mutation methods
;[
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
].forEach(watchMutation)
// Augment the ArrayProxy with convenience methods
def(ArrayProxy, '$set', function (index, data) {
return this.splice(index, 1, data)[0]
}, !hasProto)
def(ArrayProxy, '$remove', function (index) {
if (typeof index !== 'number') {
index = this.indexOf(index)
}
if (index > -1) {
return this.splice(index, 1)[0]
}
}, !hasProto)
/**
* Intercep a mutation event so we can emit the mutation info.
* we also analyze what elements are added/removed and link/unlink
* them with the parent Array.
*/
function watchMutation (method) {
def(ArrayProxy, method, function () {
var args = slice.call(arguments),
result = Array.prototype[method].apply(this, args),
inserted, removed
// determine new / removed elements
if (method === 'push' || method === 'unshift') {
inserted = args
} else if (method === 'pop' || method === 'shift') {
removed = [result]
} else if (method === 'splice') {
inserted = args.slice(2)
removed = result
}
// link & unlink
linkArrayElements(this, inserted)
unlinkArrayElements(this, removed)
// emit the mutation event
this.__emitter__.emit('mutate', '', this, {
method : method,
args : args,
result : result,
inserted : inserted,
removed : removed
})
return result
}, !hasProto)
}
/**
* Link new elements to an Array, so when they change
* and emit events, the owner Array can be notified.
*/
function linkArrayElements (arr, items) {
if (items) {
var i = items.length, item, owners
while (i--) {
item = items[i]
if (isWatchable(item)) {
// if object is not converted for observing
// convert it...
if (!item.__emitter__) {
convert(item)
watch(item)
}
owners = item.__emitter__.owners
if (owners.indexOf(arr) < 0) {
owners.push(arr)
}
}
}
}
}
/**
* Unlink removed elements from the ex-owner Array.
*/
function unlinkArrayElements (arr, items) {
if (items) {
var i = items.length, item
while (i--) {
item = items[i]
if (item && item.__emitter__) {
var owners = item.__emitter__.owners
if (owners) owners.splice(owners.indexOf(arr))
}
}
}
}
// Object add/delete key augmentation -----------------------------------------
var ObjProxy = Object.create(Object.prototype)
def(ObjProxy, '$add', function (key, val) {
if (hasOwn.call(this, key)) return
this[key] = val
convertKey(this, key, true)
}, !hasProto)
def(ObjProxy, '$delete', function (key) {
if (!(hasOwn.call(this, key))) return
// trigger set events
this[key] = undefined
delete this[key]
this.__emitter__.emit('delete', key)
}, !hasProto)
// Watch Helpers --------------------------------------------------------------
/**
* Check if a value is watchable
*/
function isWatchable (obj) {
return typeof obj === 'object' && obj && !obj.$compiler
}
/**
* Convert an Object/Array to give it a change emitter.
*/
function convert (obj) {
if (obj.__emitter__) return true
var emitter = new Emitter()
def(obj, '__emitter__', emitter)
emitter
.on('set', function (key, val, propagate) {
if (propagate) propagateChange(obj)
})
.on('mutate', function () {
propagateChange(obj)
})
emitter.values = utils.hash()
emitter.owners = []
return false
}
/**
* Propagate an array element's change to its owner arrays
*/
function propagateChange (obj) {
var owners = obj.__emitter__.owners,
i = owners.length
while (i--) {
owners[i].__emitter__.emit('set', '', '', true)
}
}
/**
* Watch target based on its type
*/
function watch (obj) {
if (isArray(obj)) {
watchArray(obj)
} else {
watchObject(obj)
}
}
/**
* Augment target objects with modified
* methods
*/
function augment (target, src) {
if (hasProto) {
target.__proto__ = src
} else {
for (var key in src) {
def(target, key, src[key])
}
}
}
/**
* Watch an Object, recursive.
*/
function watchObject (obj) {
augment(obj, ObjProxy)
for (var key in obj) {
convertKey(obj, key)
}
}
/**
* Watch an Array, overload mutation methods
* and add augmentations by intercepting the prototype chain
*/
function watchArray (arr) {
augment(arr, ArrayProxy)
linkArrayElements(arr, arr)
}
/**
* Define accessors for a property on an Object
* so it emits get/set events.
* Then watch the value itself.
*/
function convertKey (obj, key, propagate) {
var keyPrefix = key.charAt(0)
if (keyPrefix === '$' || keyPrefix === '_') {
return
}
// emit set on bind
// this means when an object is observed it will emit
// a first batch of set events.
var emitter = obj.__emitter__,
values = emitter.values
init(obj[key], propagate)
oDef(obj, key, {
enumerable: true,
configurable: true,
get: function () {
var value = values[key]
// only emit get on tip values
if (pub.shouldGet) {
emitter.emit('get', key)
}
return value
},
set: function (newVal) {
var oldVal = values[key]
unobserve(oldVal, key, emitter)
copyPaths(newVal, oldVal)
// an immediate property should notify its parent
// to emit set for itself too
init(newVal, true)
}
})
function init (val, propagate) {
values[key] = val
emitter.emit('set', key, val, propagate)
if (isArray(val)) {
emitter.emit('set', key + '.length', val.length, propagate)
}
observe(val, key, emitter)
}
}
/**
* When a value that is already converted is
* observed again by another observer, we can skip
* the watch conversion and simply emit set event for
* all of its properties.
*/
function emitSet (obj) {
var emitter = obj && obj.__emitter__
if (!emitter) return
if (isArray(obj)) {
emitter.emit('set', 'length', obj.length)
} else {
var key, val
for (key in obj) {
val = obj[key]
emitter.emit('set', key, val)
emitSet(val)
}
}
}
/**
* Make sure all the paths in an old object exists
* in a new object.
* So when an object changes, all missing keys will
* emit a set event with undefined value.
*/
function copyPaths (newObj, oldObj) {
if (!isObject(newObj) || !isObject(oldObj)) {
return
}
var path, oldVal, newVal
for (path in oldObj) {
if (!(hasOwn.call(newObj, path))) {
oldVal = oldObj[path]
if (isArray(oldVal)) {
newObj[path] = []
} else if (isObject(oldVal)) {
newVal = newObj[path] = {}
copyPaths(newVal, oldVal)
} else {
newObj[path] = undefined
}
}
}
}
/**
* walk along a path and make sure it can be accessed
* and enumerated in that object
*/
function ensurePath (obj, key) {
var path = key.split('.'), sec
for (var i = 0, d = path.length - 1; i < d; i++) {
sec = path[i]
if (!obj[sec]) {
obj[sec] = {}
if (obj.__emitter__) convertKey(obj, sec)
}
obj = obj[sec]
}
if (isObject(obj)) {
sec = path[i]
if (!(hasOwn.call(obj, sec))) {
obj[sec] = undefined
if (obj.__emitter__) convertKey(obj, sec)
}
}
}
// Main API Methods -----------------------------------------------------------
/**
* Observe an object with a given path,
* and proxy get/set/mutate events to the provided observer.
*/
function observe (obj, rawPath, observer) {
if (!isWatchable(obj)) return
var path = rawPath ? rawPath + '.' : '',
alreadyConverted = convert(obj),
emitter = obj.__emitter__
// setup proxy listeners on the parent observer.
// we need to keep reference to them so that they
// can be removed when the object is un-observed.
observer.proxies = observer.proxies || {}
var proxies = observer.proxies[path] = {
get: function (key) {
observer.emit('get', path + key)
},
set: function (key, val, propagate) {
if (key) observer.emit('set', path + key, val)
// also notify observer that the object itself changed
// but only do so when it's a immediate property. this
// avoids duplicate event firing.
if (rawPath && propagate) {
observer.emit('set', rawPath, obj, true)
}
},
mutate: function (key, val, mutation) {
// if the Array is a root value
// the key will be null
var fixedPath = key ? path + key : rawPath
observer.emit('mutate', fixedPath, val, mutation)
// also emit set for Array's length when it mutates
var m = mutation.method
if (m !== 'sort' && m !== 'reverse') {
observer.emit('set', fixedPath + '.length', val.length)
}
}
}
// attach the listeners to the child observer.
// now all the events will propagate upwards.
emitter
.on('get', proxies.get)
.on('set', proxies.set)
.on('mutate', proxies.mutate)
if (alreadyConverted) {
// for objects that have already been converted,
// emit set events for everything inside
emitSet(obj)
} else {
watch(obj)
}
}
/**
* Cancel observation, turn off the listeners.
*/
function unobserve (obj, path, observer) {
if (!obj || !obj.__emitter__) return
path = path ? path + '.' : ''
var proxies = observer.proxies[path]
if (!proxies) return
// turn off listeners
obj.__emitter__
.off('get', proxies.get)
.off('set', proxies.set)
.off('mutate', proxies.mutate)
// remove reference
observer.proxies[path] = null
}
// Expose API -----------------------------------------------------------------
var pub = module.exports = {
// whether to emit get events
// only enabled during dependency parsing
shouldGet : false,
observe : observe,
unobserve : unobserve,
ensurePath : ensurePath,
copyPaths : copyPaths,
watch : watch,
convert : convert,
convertKey : convertKey
}

View File

@ -0,0 +1,95 @@
var openChar = '{',
endChar = '}',
ESCAPE_RE = /[-.*+?^${}()|[\]\/\\]/g,
BINDING_RE = buildInterpolationRegex(),
// lazy require
Directive
function buildInterpolationRegex () {
var open = escapeRegex(openChar),
end = escapeRegex(endChar)
return new RegExp(open + open + open + '?(.+?)' + end + '?' + end + end)
}
function escapeRegex (str) {
return str.replace(ESCAPE_RE, '\\$&')
}
function setDelimiters (delimiters) {
exports.delimiters = delimiters
openChar = delimiters[0]
endChar = delimiters[1]
BINDING_RE = buildInterpolationRegex()
}
/**
* Parse a piece of text, return an array of tokens
* token types:
* 1. plain string
* 2. object with key = binding key
* 3. object with key & html = true
*/
function parse (text) {
if (!BINDING_RE.test(text)) return null
var m, i, token, match, tokens = []
/* jshint boss: true */
while (m = text.match(BINDING_RE)) {
i = m.index
if (i > 0) tokens.push(text.slice(0, i))
token = { key: m[1].trim() }
match = m[0]
token.html =
match.charAt(2) === openChar &&
match.charAt(match.length - 3) === endChar
tokens.push(token)
text = text.slice(i + m[0].length)
}
if (text.length) tokens.push(text)
return tokens
}
/**
* Parse an attribute value with possible interpolation tags
* return a Directive-friendly expression
*
* e.g. a {{b}} c => "a " + b + " c"
*/
function parseAttr (attr) {
Directive = Directive || require('./directive')
var tokens = parse(attr)
if (!tokens) return null
if (tokens.length === 1) return tokens[0].key
var res = [], token
for (var i = 0, l = tokens.length; i < l; i++) {
token = tokens[i]
res.push(
token.key
? inlineFilters(token.key)
: ('"' + token + '"')
)
}
return res.join('+')
}
/**
* Inlines any possible filters in a binding
* so that we can combine everything into a huge expression
*/
function inlineFilters (key) {
if (key.indexOf('|') > -1) {
var dirs = Directive.parse(key),
dir = dirs && dirs[0]
if (dir && dir.filters) {
key = Directive.inlineFilters(
dir.key,
dir.filters
)
}
}
return '(' + key + ')'
}
exports.parse = parse
exports.parseAttr = parseAttr
exports.setDelimiters = setDelimiters
exports.delimiters = [openChar, endChar]

View File

@ -0,0 +1,226 @@
var endEvents = sniffEndEvents(),
config = require('./config'),
// batch enter animations so we only force the layout once
Batcher = require('./batcher'),
batcher = new Batcher(),
// cache timer functions
setTO = window.setTimeout,
clearTO = window.clearTimeout,
// exit codes for testing
codes = {
CSS_E : 1,
CSS_L : 2,
JS_E : 3,
JS_L : 4,
CSS_SKIP : -1,
JS_SKIP : -2,
JS_SKIP_E : -3,
JS_SKIP_L : -4,
INIT : -5,
SKIP : -6
}
// force layout before triggering transitions/animations
batcher._preFlush = function () {
/* jshint unused: false */
var f = document.body.offsetHeight
}
/**
* stage:
* 1 = enter
* 2 = leave
*/
var transition = module.exports = function (el, stage, cb, compiler) {
var changeState = function () {
cb()
compiler.execHook(stage > 0 ? 'attached' : 'detached')
}
if (compiler.init) {
changeState()
return codes.INIT
}
var hasTransition = el.vue_trans === '',
hasAnimation = el.vue_anim === '',
effectId = el.vue_effect
if (effectId) {
return applyTransitionFunctions(
el,
stage,
changeState,
effectId,
compiler
)
} else if (hasTransition || hasAnimation) {
return applyTransitionClass(
el,
stage,
changeState,
hasAnimation
)
} else {
changeState()
return codes.SKIP
}
}
transition.codes = codes
/**
* Togggle a CSS class to trigger transition
*/
function applyTransitionClass (el, stage, changeState, hasAnimation) {
if (!endEvents.trans) {
changeState()
return codes.CSS_SKIP
}
// if the browser supports transition,
// it must have classList...
var onEnd,
classList = el.classList,
existingCallback = el.vue_trans_cb,
enterClass = config.enterClass,
leaveClass = config.leaveClass,
endEvent = hasAnimation ? endEvents.anim : endEvents.trans
// cancel unfinished callbacks and jobs
if (existingCallback) {
el.removeEventListener(endEvent, existingCallback)
classList.remove(enterClass)
classList.remove(leaveClass)
el.vue_trans_cb = null
}
if (stage > 0) { // enter
// set to enter state before appending
classList.add(enterClass)
// append
changeState()
// trigger transition
if (!hasAnimation) {
batcher.push({
execute: function () {
classList.remove(enterClass)
}
})
} else {
onEnd = function (e) {
if (e.target === el) {
el.removeEventListener(endEvent, onEnd)
el.vue_trans_cb = null
classList.remove(enterClass)
}
}
el.addEventListener(endEvent, onEnd)
el.vue_trans_cb = onEnd
}
return codes.CSS_E
} else { // leave
if (el.offsetWidth || el.offsetHeight) {
// trigger hide transition
classList.add(leaveClass)
onEnd = function (e) {
if (e.target === el) {
el.removeEventListener(endEvent, onEnd)
el.vue_trans_cb = null
// actually remove node here
changeState()
classList.remove(leaveClass)
}
}
// attach transition end listener
el.addEventListener(endEvent, onEnd)
el.vue_trans_cb = onEnd
} else {
// directly remove invisible elements
changeState()
}
return codes.CSS_L
}
}
function applyTransitionFunctions (el, stage, changeState, effectId, compiler) {
var funcs = compiler.getOption('effects', effectId)
if (!funcs) {
changeState()
return codes.JS_SKIP
}
var enter = funcs.enter,
leave = funcs.leave,
timeouts = el.vue_timeouts
// clear previous timeouts
if (timeouts) {
var i = timeouts.length
while (i--) {
clearTO(timeouts[i])
}
}
timeouts = el.vue_timeouts = []
function timeout (cb, delay) {
var id = setTO(function () {
cb()
timeouts.splice(timeouts.indexOf(id), 1)
if (!timeouts.length) {
el.vue_timeouts = null
}
}, delay)
timeouts.push(id)
}
if (stage > 0) { // enter
if (typeof enter !== 'function') {
changeState()
return codes.JS_SKIP_E
}
enter(el, changeState, timeout)
return codes.JS_E
} else { // leave
if (typeof leave !== 'function') {
changeState()
return codes.JS_SKIP_L
}
leave(el, changeState, timeout)
return codes.JS_L
}
}
/**
* Sniff proper transition end event name
*/
function sniffEndEvents () {
var el = document.createElement('vue'),
defaultEvent = 'transitionend',
events = {
'transition' : defaultEvent,
'mozTransition' : defaultEvent,
'webkitTransition' : 'webkitTransitionEnd'
},
ret = {}
for (var name in events) {
if (el.style[name] !== undefined) {
ret.trans = events[name]
break
}
}
ret.anim = el.style.animation === ''
? 'animationend'
: 'webkitAnimationEnd'
return ret
}

View File

@ -0,0 +1,307 @@
var config = require('./config'),
toString = ({}).toString,
win = window,
console = win.console,
def = Object.defineProperty,
OBJECT = 'object',
THIS_RE = /[^\w]this[^\w]/,
hasClassList = 'classList' in document.documentElement,
ViewModel // late def
var defer =
win.requestAnimationFrame ||
win.webkitRequestAnimationFrame ||
win.setTimeout
var utils = module.exports = {
/**
* Convert a string template to a dom fragment
*/
toFragment: require('./fragment'),
/**
* get a value from an object keypath
*/
get: function (obj, key) {
/* jshint eqeqeq: false */
if (key.indexOf('.') < 0) {
return obj[key]
}
var path = key.split('.'),
d = -1, l = path.length
while (++d < l && obj != null) {
obj = obj[path[d]]
}
return obj
},
/**
* set a value to an object keypath
*/
set: function (obj, key, val) {
/* jshint eqeqeq: false */
if (key.indexOf('.') < 0) {
obj[key] = val
return
}
var path = key.split('.'),
d = -1, l = path.length - 1
while (++d < l) {
if (obj[path[d]] == null) {
obj[path[d]] = {}
}
obj = obj[path[d]]
}
obj[path[d]] = val
},
/**
* return the base segment of a keypath
*/
baseKey: function (key) {
return key.indexOf('.') > 0
? key.split('.')[0]
: key
},
/**
* Create a prototype-less object
* which is a better hash/map
*/
hash: function () {
return Object.create(null)
},
/**
* get an attribute and remove it.
*/
attr: function (el, type) {
var attr = config.prefix + '-' + type,
val = el.getAttribute(attr)
if (val !== null) {
el.removeAttribute(attr)
}
return val
},
/**
* Define an ienumerable property
* This avoids it being included in JSON.stringify
* or for...in loops.
*/
defProtected: function (obj, key, val, enumerable, writable) {
def(obj, key, {
value : val,
enumerable : enumerable,
writable : writable,
configurable : true
})
},
/**
* A less bullet-proof but more efficient type check
* than Object.prototype.toString
*/
isObject: function (obj) {
return typeof obj === OBJECT && obj && !Array.isArray(obj)
},
/**
* A more accurate but less efficient type check
*/
isTrueObject: function (obj) {
return toString.call(obj) === '[object Object]'
},
/**
* Most simple bind
* enough for the usecase and fast than native bind()
*/
bind: function (fn, ctx) {
return function (arg) {
return fn.call(ctx, arg)
}
},
/**
* Make sure null and undefined output empty string
*/
guard: function (value) {
/* jshint eqeqeq: false, eqnull: true */
return value == null
? ''
: (typeof value == 'object')
? JSON.stringify(value)
: value
},
/**
* When setting value on the VM, parse possible numbers
*/
checkNumber: function (value) {
return (isNaN(value) || value === null || typeof value === 'boolean')
? value
: Number(value)
},
/**
* simple extend
*/
extend: function (obj, ext) {
for (var key in ext) {
if (obj[key] !== ext[key]) {
obj[key] = ext[key]
}
}
return obj
},
/**
* filter an array with duplicates into uniques
*/
unique: function (arr) {
var hash = utils.hash(),
i = arr.length,
key, res = []
while (i--) {
key = arr[i]
if (hash[key]) continue
hash[key] = 1
res.push(key)
}
return res
},
/**
* Convert the object to a ViewModel constructor
* if it is not already one
*/
toConstructor: function (obj) {
ViewModel = ViewModel || require('./viewmodel')
return utils.isObject(obj)
? ViewModel.extend(obj)
: typeof obj === 'function'
? obj
: null
},
/**
* Check if a filter function contains references to `this`
* If yes, mark it as a computed filter.
*/
checkFilter: function (filter) {
if (THIS_RE.test(filter.toString())) {
filter.computed = true
}
},
/**
* convert certain option values to the desired format.
*/
processOptions: function (options) {
var components = options.components,
partials = options.partials,
template = options.template,
filters = options.filters,
key
if (components) {
for (key in components) {
components[key] = utils.toConstructor(components[key])
}
}
if (partials) {
for (key in partials) {
partials[key] = utils.toFragment(partials[key])
}
}
if (filters) {
for (key in filters) {
utils.checkFilter(filters[key])
}
}
if (template) {
options.template = utils.toFragment(template)
}
},
/**
* used to defer batch updates
*/
nextTick: function (cb) {
defer(cb, 0)
},
/**
* add class for IE9
* uses classList if available
*/
addClass: function (el, cls) {
if (hasClassList) {
el.classList.add(cls)
} else {
var cur = ' ' + el.className + ' '
if (cur.indexOf(' ' + cls + ' ') < 0) {
el.className = (cur + cls).trim()
}
}
},
/**
* remove class for IE9
*/
removeClass: function (el, cls) {
if (hasClassList) {
el.classList.remove(cls)
} else {
var cur = ' ' + el.className + ' ',
tar = ' ' + cls + ' '
while (cur.indexOf(tar) >= 0) {
cur = cur.replace(tar, ' ')
}
el.className = cur.trim()
}
},
/**
* Convert an object to Array
* used in v-repeat and array filters
*/
objectToArray: function (obj) {
var res = [], val, data
for (var key in obj) {
val = obj[key]
data = utils.isObject(val)
? val
: { $value: val }
data.$key = key
res.push(data)
}
return res
}
}
enableDebug()
function enableDebug () {
/**
* log for debugging
*/
utils.log = function (msg) {
if (config.debug && console) {
console.log(msg)
}
}
/**
* warnings, traces by default
* can be suppressed by `silent` option.
*/
utils.warn = function (msg) {
if (!config.silent && console) {
console.warn(msg)
if (config.debug && console.trace) {
console.trace()
}
}
}
}

View File

@ -0,0 +1,180 @@
var Compiler = require('./compiler'),
utils = require('./utils'),
transition = require('./transition'),
Batcher = require('./batcher'),
slice = [].slice,
def = utils.defProtected,
nextTick = utils.nextTick,
// batch $watch callbacks
watcherBatcher = new Batcher(),
watcherId = 1
/**
* ViewModel exposed to the user that holds data,
* computed properties, event handlers
* and a few reserved methods
*/
function ViewModel (options) {
// just compile. options are passed directly to compiler
new Compiler(this, options)
}
// All VM prototype methods are inenumerable
// so it can be stringified/looped through as raw data
var VMProto = ViewModel.prototype
/**
* Convenience function to get a value from
* a keypath
*/
def(VMProto, '$get', function (key) {
var val = utils.get(this, key)
return val === undefined && this.$parent
? this.$parent.$get(key)
: val
})
/**
* Convenience function to set an actual nested value
* from a flat key string. Used in directives.
*/
def(VMProto, '$set', function (key, value) {
utils.set(this, key, value)
})
/**
* watch a key on the viewmodel for changes
* fire callback with new value
*/
def(VMProto, '$watch', function (key, callback) {
// save a unique id for each watcher
var id = watcherId++,
self = this
function on () {
var args = slice.call(arguments)
watcherBatcher.push({
id: id,
override: true,
execute: function () {
callback.apply(self, args)
}
})
}
callback._fn = on
self.$compiler.observer.on('change:' + key, on)
})
/**
* unwatch a key
*/
def(VMProto, '$unwatch', function (key, callback) {
// workaround here
// since the emitter module checks callback existence
// by checking the length of arguments
var args = ['change:' + key],
ob = this.$compiler.observer
if (callback) args.push(callback._fn)
ob.off.apply(ob, args)
})
/**
* unbind everything, remove everything
*/
def(VMProto, '$destroy', function () {
this.$compiler.destroy()
})
/**
* broadcast an event to all child VMs recursively.
*/
def(VMProto, '$broadcast', function () {
var children = this.$compiler.children,
i = children.length,
child
while (i--) {
child = children[i]
child.emitter.applyEmit.apply(child.emitter, arguments)
child.vm.$broadcast.apply(child.vm, arguments)
}
})
/**
* emit an event that propagates all the way up to parent VMs.
*/
def(VMProto, '$dispatch', function () {
var compiler = this.$compiler,
emitter = compiler.emitter,
parent = compiler.parent
emitter.applyEmit.apply(emitter, arguments)
if (parent) {
parent.vm.$dispatch.apply(parent.vm, arguments)
}
})
/**
* delegate on/off/once to the compiler's emitter
*/
;['emit', 'on', 'off', 'once'].forEach(function (method) {
// internal emit has fixed number of arguments.
// exposed emit uses the external version
// with fn.apply.
var realMethod = method === 'emit'
? 'applyEmit'
: method
def(VMProto, '$' + method, function () {
var emitter = this.$compiler.emitter
emitter[realMethod].apply(emitter, arguments)
})
})
// DOM convenience methods
def(VMProto, '$appendTo', function (target, cb) {
target = query(target)
var el = this.$el
transition(el, 1, function () {
target.appendChild(el)
if (cb) nextTick(cb)
}, this.$compiler)
})
def(VMProto, '$remove', function (cb) {
var el = this.$el
transition(el, -1, function () {
if (el.parentNode) {
el.parentNode.removeChild(el)
}
if (cb) nextTick(cb)
}, this.$compiler)
})
def(VMProto, '$before', function (target, cb) {
target = query(target)
var el = this.$el
transition(el, 1, function () {
target.parentNode.insertBefore(el, target)
if (cb) nextTick(cb)
}, this.$compiler)
})
def(VMProto, '$after', function (target, cb) {
target = query(target)
var el = this.$el
transition(el, 1, function () {
if (target.nextSibling) {
target.parentNode.insertBefore(el, target.nextSibling)
} else {
target.parentNode.appendChild(el)
}
if (cb) nextTick(cb)
}, this.$compiler)
})
function query (el) {
return typeof el === 'string'
? document.querySelector(el)
: el
}
module.exports = ViewModel