silk-gui
Version:
GUI for developers and Node OS
224 lines (208 loc) • 5.27 kB
JavaScript
var _ = require('./util')
var config = require('./config')
var Watcher = require('./watcher')
var textParser = require('./parsers/text')
var expParser = require('./parsers/expression')
/**
* A directive links a DOM element with a piece of data,
* which is the result of evaluating an expression.
* It registers a watcher with the expression and calls
* the DOM update function when a change is triggered.
*
* @param {String} name
* @param {Node} el
* @param {Vue} vm
* @param {Object} descriptor
* - {String} expression
* - {String} [arg]
* - {Array<Object>} [filters]
* @param {Object} def - directive definition object
* @param {Vue|undefined} host - transclusion host target
* @constructor
*/
function Directive (name, el, vm, descriptor, def, host) {
// public
this.name = name
this.el = el
this.vm = vm
// copy descriptor props
this.raw = descriptor.raw
this.expression = descriptor.expression
this.arg = descriptor.arg
this.filters = _.resolveFilters(vm, descriptor.filters)
// private
this._host = host
this._locked = false
this._bound = false
// init
this._bind(def)
}
var p = Directive.prototype
/**
* Initialize the directive, mixin definition properties,
* setup the watcher, call definition bind() and update()
* if present.
*
* @param {Object} def
*/
p._bind = function (def) {
if (this.name !== 'cloak' && this.el && this.el.removeAttribute) {
this.el.removeAttribute(config.prefix + this.name)
}
if (typeof def === 'function') {
this.update = def
} else {
_.extend(this, def)
}
this._watcherExp = this.expression
this._checkDynamicLiteral()
if (this.bind) {
this.bind()
}
if (this._watcherExp &&
(this.update || this.twoWay) &&
(!this.isLiteral || this._isDynamicLiteral) &&
!this._checkStatement()) {
// wrapped updater for context
var dir = this
var update = this._update = this.update
? function (val, oldVal) {
if (!dir._locked) {
dir.update(val, oldVal)
}
}
: function () {} // noop if no update is provided
// use raw expression as identifier because filters
// make them different watchers
var watcher = this.vm._watchers[this.raw]
// v-repeat always creates a new watcher because it has
// a special filter that's bound to its directive
// instance.
if (!watcher || this.name === 'repeat') {
watcher = this.vm._watchers[this.raw] = new Watcher(
this.vm,
this._watcherExp,
update, // callback
{
filters: this.filters,
twoWay: this.twoWay,
deep: this.deep
}
)
} else {
watcher.addCb(update)
}
this._watcher = watcher
if (this._initValue != null) {
watcher.set(this._initValue)
} else if (this.update) {
this.update(watcher.value)
}
}
this._bound = true
}
/**
* check if this is a dynamic literal binding.
*
* e.g. v-component="{{currentView}}"
*/
p._checkDynamicLiteral = function () {
var expression = this.expression
if (expression && this.isLiteral) {
var tokens = textParser.parse(expression)
if (tokens) {
var exp = textParser.tokensToExp(tokens)
this.expression = this.vm.$get(exp)
this._watcherExp = exp
this._isDynamicLiteral = true
}
}
}
/**
* Check if the directive is a function caller
* and if the expression is a callable one. If both true,
* we wrap up the expression and use it as the event
* handler.
*
* e.g. v-on="click: a++"
*
* @return {Boolean}
*/
p._checkStatement = function () {
var expression = this.expression
if (
expression && this.acceptStatement &&
!expParser.pathTestRE.test(expression)
) {
var fn = expParser.parse(expression).get
var vm = this.vm
var handler = function () {
fn.call(vm, vm)
}
if (this.filters) {
handler = _.applyFilters(
handler,
this.filters.read,
vm
)
}
this.update(handler)
return true
}
}
/**
* Check for an attribute directive param, e.g. lazy
*
* @param {String} name
* @return {String}
*/
p._checkParam = function (name) {
var param = this.el.getAttribute(name)
if (param !== null) {
this.el.removeAttribute(name)
}
return param
}
/**
* Teardown the watcher and call unbind.
*/
p._teardown = function () {
if (this._bound) {
if (this.unbind) {
this.unbind()
}
var watcher = this._watcher
if (watcher && watcher.active) {
watcher.removeCb(this._update)
if (!watcher.active) {
this.vm._watchers[this.raw] = null
}
}
this._bound = false
this.vm = this.el = this._watcher = null
}
}
/**
* Set the corresponding value with the setter.
* This should only be used in two-way directives
* e.g. v-model.
*
* @param {*} value
* @param {Boolean} lock - prevent wrtie triggering update.
* @public
*/
p.set = function (value, lock) {
if (this.twoWay) {
if (lock) {
this._locked = true
}
this._watcher.set(value)
if (lock) {
var self = this
_.nextTick(function () {
self._locked = false
})
}
}
}
module.exports = Directive