github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/public/libs/vue-1.0.24/src/directives/internal/component.js (about) 1 import { cloneNode } from '../../parsers/template' 2 import { COMPONENT } from '../priorities' 3 import { 4 extractContent, 5 createAnchor, 6 replace, 7 hyphenate, 8 warn, 9 cancellable, 10 extend 11 } from '../../util/index' 12 13 export default { 14 15 priority: COMPONENT, 16 17 params: [ 18 'keep-alive', 19 'transition-mode', 20 'inline-template' 21 ], 22 23 /** 24 * Setup. Two possible usages: 25 * 26 * - static: 27 * <comp> or <div v-component="comp"> 28 * 29 * - dynamic: 30 * <component :is="view"> 31 */ 32 33 bind () { 34 if (!this.el.__vue__) { 35 // keep-alive cache 36 this.keepAlive = this.params.keepAlive 37 if (this.keepAlive) { 38 this.cache = {} 39 } 40 // check inline-template 41 if (this.params.inlineTemplate) { 42 // extract inline template as a DocumentFragment 43 this.inlineTemplate = extractContent(this.el, true) 44 } 45 // component resolution related state 46 this.pendingComponentCb = 47 this.Component = null 48 // transition related state 49 this.pendingRemovals = 0 50 this.pendingRemovalCb = null 51 // create a ref anchor 52 this.anchor = createAnchor('v-component') 53 replace(this.el, this.anchor) 54 // remove is attribute. 55 // this is removed during compilation, but because compilation is 56 // cached, when the component is used elsewhere this attribute 57 // will remain at link time. 58 this.el.removeAttribute('is') 59 this.el.removeAttribute(':is') 60 // remove ref, same as above 61 if (this.descriptor.ref) { 62 this.el.removeAttribute('v-ref:' + hyphenate(this.descriptor.ref)) 63 } 64 // if static, build right now. 65 if (this.literal) { 66 this.setComponent(this.expression) 67 } 68 } else { 69 process.env.NODE_ENV !== 'production' && warn( 70 'cannot mount component "' + this.expression + '" ' + 71 'on already mounted element: ' + this.el 72 ) 73 } 74 }, 75 76 /** 77 * Public update, called by the watcher in the dynamic 78 * literal scenario, e.g. <component :is="view"> 79 */ 80 81 update (value) { 82 if (!this.literal) { 83 this.setComponent(value) 84 } 85 }, 86 87 /** 88 * Switch dynamic components. May resolve the component 89 * asynchronously, and perform transition based on 90 * specified transition mode. Accepts a few additional 91 * arguments specifically for vue-router. 92 * 93 * The callback is called when the full transition is 94 * finished. 95 * 96 * @param {String} value 97 * @param {Function} [cb] 98 */ 99 100 setComponent (value, cb) { 101 this.invalidatePending() 102 if (!value) { 103 // just remove current 104 this.unbuild(true) 105 this.remove(this.childVM, cb) 106 this.childVM = null 107 } else { 108 var self = this 109 this.resolveComponent(value, function () { 110 self.mountComponent(cb) 111 }) 112 } 113 }, 114 115 /** 116 * Resolve the component constructor to use when creating 117 * the child vm. 118 * 119 * @param {String|Function} value 120 * @param {Function} cb 121 */ 122 123 resolveComponent (value, cb) { 124 var self = this 125 this.pendingComponentCb = cancellable(function (Component) { 126 self.ComponentName = 127 Component.options.name || 128 (typeof value === 'string' ? value : null) 129 self.Component = Component 130 cb() 131 }) 132 this.vm._resolveComponent(value, this.pendingComponentCb) 133 }, 134 135 /** 136 * Create a new instance using the current constructor and 137 * replace the existing instance. This method doesn't care 138 * whether the new component and the old one are actually 139 * the same. 140 * 141 * @param {Function} [cb] 142 */ 143 144 mountComponent (cb) { 145 // actual mount 146 this.unbuild(true) 147 var self = this 148 var activateHooks = this.Component.options.activate 149 var cached = this.getCached() 150 var newComponent = this.build() 151 if (activateHooks && !cached) { 152 this.waitingFor = newComponent 153 callActivateHooks(activateHooks, newComponent, function () { 154 if (self.waitingFor !== newComponent) { 155 return 156 } 157 self.waitingFor = null 158 self.transition(newComponent, cb) 159 }) 160 } else { 161 // update ref for kept-alive component 162 if (cached) { 163 newComponent._updateRef() 164 } 165 this.transition(newComponent, cb) 166 } 167 }, 168 169 /** 170 * When the component changes or unbinds before an async 171 * constructor is resolved, we need to invalidate its 172 * pending callback. 173 */ 174 175 invalidatePending () { 176 if (this.pendingComponentCb) { 177 this.pendingComponentCb.cancel() 178 this.pendingComponentCb = null 179 } 180 }, 181 182 /** 183 * Instantiate/insert a new child vm. 184 * If keep alive and has cached instance, insert that 185 * instance; otherwise build a new one and cache it. 186 * 187 * @param {Object} [extraOptions] 188 * @return {Vue} - the created instance 189 */ 190 191 build (extraOptions) { 192 var cached = this.getCached() 193 if (cached) { 194 return cached 195 } 196 if (this.Component) { 197 // default options 198 var options = { 199 name: this.ComponentName, 200 el: cloneNode(this.el), 201 template: this.inlineTemplate, 202 // make sure to add the child with correct parent 203 // if this is a transcluded component, its parent 204 // should be the transclusion host. 205 parent: this._host || this.vm, 206 // if no inline-template, then the compiled 207 // linker can be cached for better performance. 208 _linkerCachable: !this.inlineTemplate, 209 _ref: this.descriptor.ref, 210 _asComponent: true, 211 _isRouterView: this._isRouterView, 212 // if this is a transcluded component, context 213 // will be the common parent vm of this instance 214 // and its host. 215 _context: this.vm, 216 // if this is inside an inline v-for, the scope 217 // will be the intermediate scope created for this 218 // repeat fragment. this is used for linking props 219 // and container directives. 220 _scope: this._scope, 221 // pass in the owner fragment of this component. 222 // this is necessary so that the fragment can keep 223 // track of its contained components in order to 224 // call attach/detach hooks for them. 225 _frag: this._frag 226 } 227 // extra options 228 // in 1.0.0 this is used by vue-router only 229 /* istanbul ignore if */ 230 if (extraOptions) { 231 extend(options, extraOptions) 232 } 233 var child = new this.Component(options) 234 if (this.keepAlive) { 235 this.cache[this.Component.cid] = child 236 } 237 /* istanbul ignore if */ 238 if (process.env.NODE_ENV !== 'production' && 239 this.el.hasAttribute('transition') && 240 child._isFragment) { 241 warn( 242 'Transitions will not work on a fragment instance. ' + 243 'Template: ' + child.$options.template, 244 child 245 ) 246 } 247 return child 248 } 249 }, 250 251 /** 252 * Try to get a cached instance of the current component. 253 * 254 * @return {Vue|undefined} 255 */ 256 257 getCached () { 258 return this.keepAlive && this.cache[this.Component.cid] 259 }, 260 261 /** 262 * Teardown the current child, but defers cleanup so 263 * that we can separate the destroy and removal steps. 264 * 265 * @param {Boolean} defer 266 */ 267 268 unbuild (defer) { 269 if (this.waitingFor) { 270 if (!this.keepAlive) { 271 this.waitingFor.$destroy() 272 } 273 this.waitingFor = null 274 } 275 var child = this.childVM 276 if (!child || this.keepAlive) { 277 if (child) { 278 // remove ref 279 child._inactive = true 280 child._updateRef(true) 281 } 282 return 283 } 284 // the sole purpose of `deferCleanup` is so that we can 285 // "deactivate" the vm right now and perform DOM removal 286 // later. 287 child.$destroy(false, defer) 288 }, 289 290 /** 291 * Remove current destroyed child and manually do 292 * the cleanup after removal. 293 * 294 * @param {Function} cb 295 */ 296 297 remove (child, cb) { 298 var keepAlive = this.keepAlive 299 if (child) { 300 // we may have a component switch when a previous 301 // component is still being transitioned out. 302 // we want to trigger only one lastest insertion cb 303 // when the existing transition finishes. (#1119) 304 this.pendingRemovals++ 305 this.pendingRemovalCb = cb 306 var self = this 307 child.$remove(function () { 308 self.pendingRemovals-- 309 if (!keepAlive) child._cleanup() 310 if (!self.pendingRemovals && self.pendingRemovalCb) { 311 self.pendingRemovalCb() 312 self.pendingRemovalCb = null 313 } 314 }) 315 } else if (cb) { 316 cb() 317 } 318 }, 319 320 /** 321 * Actually swap the components, depending on the 322 * transition mode. Defaults to simultaneous. 323 * 324 * @param {Vue} target 325 * @param {Function} [cb] 326 */ 327 328 transition (target, cb) { 329 var self = this 330 var current = this.childVM 331 // for devtool inspection 332 if (current) current._inactive = true 333 target._inactive = false 334 this.childVM = target 335 switch (self.params.transitionMode) { 336 case 'in-out': 337 target.$before(self.anchor, function () { 338 self.remove(current, cb) 339 }) 340 break 341 case 'out-in': 342 self.remove(current, function () { 343 target.$before(self.anchor, cb) 344 }) 345 break 346 default: 347 self.remove(current) 348 target.$before(self.anchor, cb) 349 } 350 }, 351 352 /** 353 * Unbind. 354 */ 355 356 unbind () { 357 this.invalidatePending() 358 // Do not defer cleanup when unbinding 359 this.unbuild() 360 // destroy all keep-alive cached instances 361 if (this.cache) { 362 for (var key in this.cache) { 363 this.cache[key].$destroy() 364 } 365 this.cache = null 366 } 367 } 368 } 369 370 /** 371 * Call activate hooks in order (asynchronous) 372 * 373 * @param {Array} hooks 374 * @param {Vue} vm 375 * @param {Function} cb 376 */ 377 378 function callActivateHooks (hooks, vm, cb) { 379 var total = hooks.length 380 var called = 0 381 hooks[0].call(vm, next) 382 function next () { 383 if (++called >= total) { 384 cb() 385 } else { 386 hooks[called].call(vm, next) 387 } 388 } 389 }