UNPKG

framework7

Version:

Full featured mobile HTML framework for building iOS & Android apps

279 lines (261 loc) 8.52 kB
/* eslint no-use-before-define: "off" */ /* eslint import/no-named-as-default: "off" */ import { window, document } from 'ssr-window'; import h from './snabbdom/h'; const selfClosing = 'area base br col command embed hr img input keygen link menuitem meta param source track wbr'.split(' '); const propsAttrs = 'hidden checked disabled readonly selected autocomplete autofocus autoplay required multiple value indeterminate'.split(' '); const booleanProps = 'hidden checked disabled readonly selected autocomplete autofocus autoplay required multiple readOnly indeterminate'.split(' '); const tempDom = document.createElement('div'); function getHooks(data, app, initial, isRoot) { const hooks = {}; if (!data || !data.attrs || !data.attrs.class) return hooks; const classNames = data.attrs.class; const insert = []; const destroy = []; const update = []; const postpatch = []; classNames.split(' ').forEach((className) => { if (!initial) { insert.push(...app.getVnodeHooks('insert', className)); } destroy.push(...app.getVnodeHooks('destroy', className)); update.push(...app.getVnodeHooks('update', className)); postpatch.push(...app.getVnodeHooks('postpatch', className)); }); if (isRoot && !initial) { postpatch.push((oldVnode, vnode) => { const vn = vnode || oldVnode; if (!vn) return; if (vn.data && vn.data.context && vn.data.context.$options.updated) { vn.data.context.$options.updated(); } }); } if (insert.length === 0 && destroy.length === 0 && update.length === 0 && postpatch.length === 0) { return hooks; } if (insert.length) { hooks.insert = (vnode) => { insert.forEach(f => f(vnode)); }; } if (destroy.length) { hooks.destroy = (vnode) => { destroy.forEach(f => f(vnode)); }; } if (update.length) { hooks.update = (oldVnode, vnode) => { update.forEach(f => f(oldVnode, vnode)); }; } if (postpatch.length) { hooks.postpatch = (oldVnode, vnode) => { postpatch.forEach(f => f(oldVnode, vnode)); }; } return hooks; } function getEventHandler(handlerString, context, { stop, prevent, once } = {}) { let fired = false; let methodName; let method; let customArgs = []; let needMethodBind = true; if (handlerString.indexOf('(') < 0) { methodName = handlerString; } else { methodName = handlerString.split('(')[0]; } if (methodName.indexOf('.') >= 0) { methodName.split('.').forEach((path, pathIndex) => { if (pathIndex === 0 && path === 'this') return; if (pathIndex === 0 && path === 'window') { // eslint-disable-next-line method = window; needMethodBind = false; return; } if (!method) method = context; if (method[path]) method = method[path]; else { throw new Error(`Framework7: Component doesn't have method "${methodName.split('.').slice(0, pathIndex + 1).join('.')}"`); } }); } else { if (!context[methodName]) { throw new Error(`Framework7: Component doesn't have method "${methodName}"`); } method = context[methodName]; } if (needMethodBind) { method = method.bind(context); } function handler(...args) { const e = args[0]; if (once && fired) return; if (stop) e.stopPropagation(); if (prevent) e.preventDefault(); fired = true; if (handlerString.indexOf('(') < 0) { customArgs = args; } else { const handlerArguments = handlerString .split('(')[1] .split(')')[0] .replace(/'[^']*'|"[^"]*"/g, a => a.replace(/,/g, '<_comma_>')) .split(',') .map(a => a.replace(/<_comma_>/g, ',')); handlerArguments.forEach((argument) => { let arg = argument.trim(); // eslint-disable-next-line if (!isNaN(arg)) arg = parseFloat(arg); else if (arg === 'true') arg = true; else if (arg === 'false') arg = false; else if (arg === 'null') arg = null; else if (arg === 'undefined') arg = undefined; else if (arg[0] === '"') arg = arg.replace(/"/g, ''); else if (arg[0] === '\'') arg = arg.replace(/'/g, ''); else if (arg.indexOf('.') > 0) { let deepArg; arg.split('.').forEach((path) => { if (!deepArg) deepArg = context; deepArg = deepArg[path]; }); arg = deepArg; } else { arg = context[arg]; } customArgs.push(arg); }); } method(...customArgs); } return handler; } function getData(el, context, app, initial, isRoot) { const data = { context, }; const attributes = el.attributes; Array.prototype.forEach.call(attributes, (attr) => { let attrName = attr.name; const attrValue = attr.value; if (propsAttrs.indexOf(attrName) >= 0) { // Props if (!data.props) data.props = {}; if (attrName === 'readonly') { attrName = 'readOnly'; } if (booleanProps.indexOf(attrName) >= 0) { // eslint-disable-next-line data.props[attrName] = attrValue === false ? false : true; } else { data.props[attrName] = attrValue; } } else if (attrName === 'key') { // Key data.key = attrValue; } else if (attrName.indexOf('@') === 0) { // Events if (!data.on) data.on = {}; let eventName = attrName.substr(1); let stop = false; let prevent = false; let once = false; if (eventName.indexOf('.') >= 0) { eventName.split('.').forEach((eventNamePart, eventNameIndex) => { if (eventNameIndex === 0) eventName = eventNamePart; else { if (eventNamePart === 'stop') stop = true; if (eventNamePart === 'prevent') prevent = true; if (eventNamePart === 'once') once = true; } }); } data.on[eventName] = getEventHandler(attrValue, context, { stop, prevent, once }); } else if (attrName === 'style') { // Style if (attrValue.indexOf('{') >= 0 && attrValue.indexOf('}') >= 0) { try { data.style = JSON.parse(attrValue); } catch (e) { if (!data.attrs) data.attrs = {}; data.attrs.style = attrValue; } } else { if (!data.attrs) data.attrs = {}; data.attrs.style = attrValue; } } else { // Rest of attribures if (!data.attrs) data.attrs = {}; data.attrs[attrName] = attrValue; // ID -> Key if (attrName === 'id' && !data.key && !isRoot) { data.key = attrValue; } } }); const hooks = getHooks(data, app, initial, isRoot); hooks.prepatch = (oldVnode, vnode) => { if (!oldVnode || !vnode) return; if (oldVnode && oldVnode.data && oldVnode.data.props) { Object.keys(oldVnode.data.props).forEach((key) => { if (booleanProps.indexOf(key) < 0) return; if (!vnode.data) vnode.data = {}; if (!vnode.data.props) vnode.data.props = {}; if (oldVnode.data.props[key] === true && !(key in vnode.data.props)) { vnode.data.props[key] = false; } }); } }; if (hooks) { data.hook = hooks; } return data; } function getChildren(el, context, app, initial) { const children = []; const nodes = el.childNodes; for (let i = 0; i < nodes.length; i += 1) { const childNode = nodes[i]; const child = elementToVNode(childNode, context, app, initial); if (child) { children.push(child); } } return children; } function elementToVNode(el, context, app, initial, isRoot) { if (el.nodeType === 1) { // element (statement adds inline SVG compatibility) const tagName = (el instanceof window.SVGElement) ? el.nodeName : el.nodeName.toLowerCase(); return h( tagName, getData(el, context, app, initial, isRoot), selfClosing.indexOf(tagName) >= 0 ? [] : getChildren(el, context, app, initial) ); } if (el.nodeType === 3) { // text return el.textContent; } return null; } export default function (html = '', context, app, initial) { // Save to temp dom tempDom.innerHTML = html.trim(); // Parse DOM let rootEl; for (let i = 0; i < tempDom.childNodes.length; i += 1) { if (!rootEl && tempDom.childNodes[i].nodeType === 1) { rootEl = tempDom.childNodes[i]; } } const result = elementToVNode(rootEl, context, app, initial, true); // Clean tempDom.innerHTML = ''; return result; }