/* @flow */ import { emptyNode } from 'core/vdom/patch' import { resolveAsset, handleError } from 'core/util/index' import { mergeVNodeHook } from 'core/vdom/helpers/index' export default { create: updateDirectives, update: updateDirectives, destroy: function unbindDirectives (vnode: VNodeWithData) { updateDirectives(vnode, emptyNode) } } function updateDirectives (oldVnode: VNodeWithData, vnode: VNodeWithData) { if (oldVnode.data.directives || vnode.data.directives) { _update(oldVnode, vnode) } } function _update (oldVnode, vnode) { const isCreate = oldVnode === emptyNode const isDestroy = vnode === emptyNode const oldDirs = normalizeDirectives(oldVnode.data.directives, oldVnode.context) const newDirs = normalizeDirectives(vnode.data.directives, vnode.context) const dirsWithInsert = [] const dirsWithPostpatch = [] let key, oldDir, dir for (key in newDirs) { oldDir = oldDirs[key] dir = newDirs[key] if (!oldDir) { // new directive, bind callHook(dir, 'bind', vnode, oldVnode) if (dir.def && dir.def.inserted) { dirsWithInsert.push(dir) } } else { // existing directive, update dir.oldValue = oldDir.value dir.oldArg = oldDir.arg callHook(dir, 'update', vnode, oldVnode) if (dir.def && dir.def.componentUpdated) { dirsWithPostpatch.push(dir) } } } if (dirsWithInsert.length) { const callInsert = () => { for (let i = 0; i < dirsWithInsert.length; i++) { callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode) } } if (isCreate) { mergeVNodeHook(vnode, 'insert', callInsert) } else { callInsert() } } if (dirsWithPostpatch.length) { mergeVNodeHook(vnode, 'postpatch', () => { for (let i = 0; i < dirsWithPostpatch.length; i++) { callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode) } }) } if (!isCreate) { for (key in oldDirs) { if (!newDirs[key]) { // no longer present, unbind callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy) } } } } const emptyModifiers = Object.create(null) function normalizeDirectives ( dirs: ?Array, vm: Component ): { [key: string]: VNodeDirective } { const res = Object.create(null) if (!dirs) { // $flow-disable-line return res } let i, dir for (i = 0; i < dirs.length; i++) { dir = dirs[i] if (!dir.modifiers) { // $flow-disable-line dir.modifiers = emptyModifiers } res[getRawDirName(dir)] = dir dir.def = resolveAsset(vm.$options, 'directives', dir.name, true) } // $flow-disable-line return res } function getRawDirName (dir: VNodeDirective): string { return dir.rawName || `${dir.name}.${Object.keys(dir.modifiers || {}).join('.')}` } function callHook (dir, hook, vnode, oldVnode, isDestroy) { const fn = dir.def && dir.def[hook] if (fn) { try { fn(vnode.elm, dir, vnode, oldVnode, isDestroy) } catch (e) { handleError(e, vnode.context, `directive ${dir.name} ${hook} hook`) } } }