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  }