github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/public/libs/vue-1.0.24/src/directive.js (about) 1 import { 2 extend, 3 bind, 4 on, 5 off, 6 getAttr, 7 getBindAttr, 8 camelize, 9 hyphenate, 10 nextTick, 11 warn 12 } from './util/index' 13 import Watcher from './watcher' 14 import { parseExpression, isSimplePath } from './parsers/expression' 15 16 function noop () {} 17 18 /** 19 * A directive links a DOM element with a piece of data, 20 * which is the result of evaluating an expression. 21 * It registers a watcher with the expression and calls 22 * the DOM update function when a change is triggered. 23 * 24 * @param {Object} descriptor 25 * - {String} name 26 * - {Object} def 27 * - {String} expression 28 * - {Array<Object>} [filters] 29 * - {Object} [modifiers] 30 * - {Boolean} literal 31 * - {String} attr 32 * - {String} arg 33 * - {String} raw 34 * - {String} [ref] 35 * - {Array<Object>} [interp] 36 * - {Boolean} [hasOneTime] 37 * @param {Vue} vm 38 * @param {Node} el 39 * @param {Vue} [host] - transclusion host component 40 * @param {Object} [scope] - v-for scope 41 * @param {Fragment} [frag] - owner fragment 42 * @constructor 43 */ 44 45 export default function Directive (descriptor, vm, el, host, scope, frag) { 46 this.vm = vm 47 this.el = el 48 // copy descriptor properties 49 this.descriptor = descriptor 50 this.name = descriptor.name 51 this.expression = descriptor.expression 52 this.arg = descriptor.arg 53 this.modifiers = descriptor.modifiers 54 this.filters = descriptor.filters 55 this.literal = this.modifiers && this.modifiers.literal 56 // private 57 this._locked = false 58 this._bound = false 59 this._listeners = null 60 // link context 61 this._host = host 62 this._scope = scope 63 this._frag = frag 64 // store directives on node in dev mode 65 if (process.env.NODE_ENV !== 'production' && this.el) { 66 this.el._vue_directives = this.el._vue_directives || [] 67 this.el._vue_directives.push(this) 68 } 69 } 70 71 /** 72 * Initialize the directive, mixin definition properties, 73 * setup the watcher, call definition bind() and update() 74 * if present. 75 */ 76 77 Directive.prototype._bind = function () { 78 var name = this.name 79 var descriptor = this.descriptor 80 81 // remove attribute 82 if ( 83 (name !== 'cloak' || this.vm._isCompiled) && 84 this.el && this.el.removeAttribute 85 ) { 86 var attr = descriptor.attr || ('v-' + name) 87 this.el.removeAttribute(attr) 88 } 89 90 // copy def properties 91 var def = descriptor.def 92 if (typeof def === 'function') { 93 this.update = def 94 } else { 95 extend(this, def) 96 } 97 98 // setup directive params 99 this._setupParams() 100 101 // initial bind 102 if (this.bind) { 103 this.bind() 104 } 105 this._bound = true 106 107 if (this.literal) { 108 this.update && this.update(descriptor.raw) 109 } else if ( 110 (this.expression || this.modifiers) && 111 (this.update || this.twoWay) && 112 !this._checkStatement() 113 ) { 114 // wrapped updater for context 115 var dir = this 116 if (this.update) { 117 this._update = function (val, oldVal) { 118 if (!dir._locked) { 119 dir.update(val, oldVal) 120 } 121 } 122 } else { 123 this._update = noop 124 } 125 var preProcess = this._preProcess 126 ? bind(this._preProcess, this) 127 : null 128 var postProcess = this._postProcess 129 ? bind(this._postProcess, this) 130 : null 131 var watcher = this._watcher = new Watcher( 132 this.vm, 133 this.expression, 134 this._update, // callback 135 { 136 filters: this.filters, 137 twoWay: this.twoWay, 138 deep: this.deep, 139 preProcess: preProcess, 140 postProcess: postProcess, 141 scope: this._scope 142 } 143 ) 144 // v-model with inital inline value need to sync back to 145 // model instead of update to DOM on init. They would 146 // set the afterBind hook to indicate that. 147 if (this.afterBind) { 148 this.afterBind() 149 } else if (this.update) { 150 this.update(watcher.value) 151 } 152 } 153 } 154 155 /** 156 * Setup all param attributes, e.g. track-by, 157 * transition-mode, etc... 158 */ 159 160 Directive.prototype._setupParams = function () { 161 if (!this.params) { 162 return 163 } 164 var params = this.params 165 // swap the params array with a fresh object. 166 this.params = Object.create(null) 167 var i = params.length 168 var key, val, mappedKey 169 while (i--) { 170 key = hyphenate(params[i]) 171 mappedKey = camelize(key) 172 val = getBindAttr(this.el, key) 173 if (val != null) { 174 // dynamic 175 this._setupParamWatcher(mappedKey, val) 176 } else { 177 // static 178 val = getAttr(this.el, key) 179 if (val != null) { 180 this.params[mappedKey] = val === '' ? true : val 181 } 182 } 183 } 184 } 185 186 /** 187 * Setup a watcher for a dynamic param. 188 * 189 * @param {String} key 190 * @param {String} expression 191 */ 192 193 Directive.prototype._setupParamWatcher = function (key, expression) { 194 var self = this 195 var called = false 196 var unwatch = (this._scope || this.vm).$watch(expression, function (val, oldVal) { 197 self.params[key] = val 198 // since we are in immediate mode, 199 // only call the param change callbacks if this is not the first update. 200 if (called) { 201 var cb = self.paramWatchers && self.paramWatchers[key] 202 if (cb) { 203 cb.call(self, val, oldVal) 204 } 205 } else { 206 called = true 207 } 208 }, { 209 immediate: true, 210 user: false 211 }) 212 ;(this._paramUnwatchFns || (this._paramUnwatchFns = [])).push(unwatch) 213 } 214 215 /** 216 * Check if the directive is a function caller 217 * and if the expression is a callable one. If both true, 218 * we wrap up the expression and use it as the event 219 * handler. 220 * 221 * e.g. on-click="a++" 222 * 223 * @return {Boolean} 224 */ 225 226 Directive.prototype._checkStatement = function () { 227 var expression = this.expression 228 if ( 229 expression && this.acceptStatement && 230 !isSimplePath(expression) 231 ) { 232 var fn = parseExpression(expression).get 233 var scope = this._scope || this.vm 234 var handler = function (e) { 235 scope.$event = e 236 fn.call(scope, scope) 237 scope.$event = null 238 } 239 if (this.filters) { 240 handler = scope._applyFilters(handler, null, this.filters) 241 } 242 this.update(handler) 243 return true 244 } 245 } 246 247 /** 248 * Set the corresponding value with the setter. 249 * This should only be used in two-way directives 250 * e.g. v-model. 251 * 252 * @param {*} value 253 * @public 254 */ 255 256 Directive.prototype.set = function (value) { 257 /* istanbul ignore else */ 258 if (this.twoWay) { 259 this._withLock(function () { 260 this._watcher.set(value) 261 }) 262 } else if (process.env.NODE_ENV !== 'production') { 263 warn( 264 'Directive.set() can only be used inside twoWay' + 265 'directives.' 266 ) 267 } 268 } 269 270 /** 271 * Execute a function while preventing that function from 272 * triggering updates on this directive instance. 273 * 274 * @param {Function} fn 275 */ 276 277 Directive.prototype._withLock = function (fn) { 278 var self = this 279 self._locked = true 280 fn.call(self) 281 nextTick(function () { 282 self._locked = false 283 }) 284 } 285 286 /** 287 * Convenience method that attaches a DOM event listener 288 * to the directive element and autometically tears it down 289 * during unbind. 290 * 291 * @param {String} event 292 * @param {Function} handler 293 * @param {Boolean} [useCapture] 294 */ 295 296 Directive.prototype.on = function (event, handler, useCapture) { 297 on(this.el, event, handler, useCapture) 298 ;(this._listeners || (this._listeners = [])) 299 .push([event, handler]) 300 } 301 302 /** 303 * Teardown the watcher and call unbind. 304 */ 305 306 Directive.prototype._teardown = function () { 307 if (this._bound) { 308 this._bound = false 309 if (this.unbind) { 310 this.unbind() 311 } 312 if (this._watcher) { 313 this._watcher.teardown() 314 } 315 var listeners = this._listeners 316 var i 317 if (listeners) { 318 i = listeners.length 319 while (i--) { 320 off(this.el, listeners[i][0], listeners[i][1]) 321 } 322 } 323 var unwatchFns = this._paramUnwatchFns 324 if (unwatchFns) { 325 i = unwatchFns.length 326 while (i--) { 327 unwatchFns[i]() 328 } 329 } 330 if (process.env.NODE_ENV !== 'production' && this.el) { 331 this.el._vue_directives.$remove(this) 332 } 333 this.vm = this.el = this._watcher = this._listeners = null 334 } 335 }