mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] playground: update Owl to 0.9.0
This commit is contained in:
+233
-187
@@ -965,23 +965,6 @@
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
/**
|
||||
* Attach a child widget to a given html element
|
||||
*
|
||||
* This is most of the time not necessary, since widgets should primarily be
|
||||
* created/managed with the t-widget directive in a qweb template. However,
|
||||
* for the cases where we need more control, this method will do what is
|
||||
* necessary to make sure all the proper hooks are called (for example,
|
||||
* mounted/willUnmount)
|
||||
*
|
||||
* Note that this method makes a few assumptions:
|
||||
* - the child widget is indeed a child of the current widget
|
||||
* - the target is inside the dom of the current widget (typically a ref)
|
||||
*/
|
||||
attachChild(child, target) {
|
||||
target.appendChild(child.el);
|
||||
child.__mount();
|
||||
}
|
||||
async mount(target) {
|
||||
const vnode = await this._prepare();
|
||||
if (this.__owl__.isDestroyed) {
|
||||
@@ -991,63 +974,93 @@
|
||||
this._patch(vnode);
|
||||
target.appendChild(this.el);
|
||||
if (document.body.contains(target)) {
|
||||
this._visitSubTree(w => {
|
||||
if (!w.__owl__.isMounted && this.el.contains(w.el)) {
|
||||
w.__owl__.isMounted = true;
|
||||
w.mounted();
|
||||
return true;
|
||||
this._callMounted();
|
||||
}
|
||||
}
|
||||
_callMounted() {
|
||||
const children = this.__owl__.children;
|
||||
for (let id in children) {
|
||||
const comp = children[id];
|
||||
if (!comp.__owl__.isMounted && this.el.contains(comp.el)) {
|
||||
comp._callMounted();
|
||||
}
|
||||
}
|
||||
this.__owl__.isMounted = true;
|
||||
this.mounted();
|
||||
}
|
||||
_callWillUnmount() {
|
||||
this.willUnmount();
|
||||
this.__owl__.isMounted = false;
|
||||
const children = this.__owl__.children;
|
||||
for (let id in children) {
|
||||
const comp = children[id];
|
||||
if (comp.__owl__.isMounted) {
|
||||
comp._callWillUnmount();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
unmount() {
|
||||
if (this.el) {
|
||||
this._visitSubTree(w => {
|
||||
if (w.__owl__.isMounted) {
|
||||
w.willUnmount();
|
||||
w.__owl__.isMounted = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (this.__owl__.isMounted) {
|
||||
this._callWillUnmount();
|
||||
this.el.remove();
|
||||
}
|
||||
}
|
||||
async render(force = false) {
|
||||
async render(force = false, patchQueue) {
|
||||
if (this.__owl__.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
const renderVDom = this._render(force);
|
||||
const shouldCallPatchHooks = !patchQueue;
|
||||
if (shouldCallPatchHooks) {
|
||||
patchQueue = [];
|
||||
}
|
||||
const renderVDom = this._render(force, patchQueue);
|
||||
const renderId = this.__owl__.renderId;
|
||||
const vnode = await renderVDom;
|
||||
if (renderId === this.__owl__.renderId) {
|
||||
// we only update the vnode and the actual DOM if no other rendering
|
||||
// occurred between now and when the render method was initially called.
|
||||
if (shouldCallPatchHooks) {
|
||||
for (let i = 0; i < patchQueue.length; i++) {
|
||||
const c = patchQueue[i];
|
||||
c.__owl__.willPatchVal = c.willPatch();
|
||||
}
|
||||
}
|
||||
this._patch(vnode);
|
||||
if (shouldCallPatchHooks) {
|
||||
for (let i = patchQueue.length - 1; i >= 0; i--) {
|
||||
const c = patchQueue[i];
|
||||
c.patched(c.__owl__.willPatchVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
destroy() {
|
||||
if (!this.__owl__.isDestroyed) {
|
||||
for (let id in this.__owl__.children) {
|
||||
this.__owl__.children[id].destroy();
|
||||
const el = this.el;
|
||||
this._destroy(this.__owl__.parent);
|
||||
if (el) {
|
||||
el.remove();
|
||||
}
|
||||
if (this.__owl__.isMounted) {
|
||||
}
|
||||
}
|
||||
_destroy(parent) {
|
||||
const isMounted = this.__owl__.isMounted;
|
||||
if (isMounted) {
|
||||
this.willUnmount();
|
||||
}
|
||||
if (this.el) {
|
||||
this.el.remove();
|
||||
this.__owl__.isMounted = false;
|
||||
delete this.__owl__.vnode;
|
||||
}
|
||||
if (this.__owl__.parent) {
|
||||
const children = Object.values(this.__owl__.children);
|
||||
for (let child of children) {
|
||||
child._destroy(this);
|
||||
}
|
||||
if (parent) {
|
||||
let id = this.__owl__.id;
|
||||
delete this.__owl__.parent.__owl__.children[id];
|
||||
delete parent.__owl__.children[id];
|
||||
this.__owl__.parent = null;
|
||||
}
|
||||
this.clear();
|
||||
this.__owl__.isDestroyed = true;
|
||||
}
|
||||
delete this.__owl__.vnode;
|
||||
}
|
||||
shouldUpdate(nextProps) {
|
||||
return true;
|
||||
@@ -1070,38 +1083,36 @@
|
||||
await this.render(true);
|
||||
}
|
||||
}
|
||||
async updateProps(nextProps, forceUpdate = false) {
|
||||
const shouldUpdate = forceUpdate || this.shouldUpdate(nextProps);
|
||||
return shouldUpdate ? this._updateProps(nextProps) : Promise.resolve();
|
||||
}
|
||||
set(target, key, value) {
|
||||
this.__owl__.observer.set(target, key, value);
|
||||
}
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
async _updateProps(nextProps) {
|
||||
async _updateProps(nextProps, forceUpdate = false, patchQueue) {
|
||||
const shouldUpdate = forceUpdate || this.shouldUpdate(nextProps);
|
||||
if (shouldUpdate) {
|
||||
await this.willUpdateProps(nextProps);
|
||||
this.props = nextProps;
|
||||
await this.render();
|
||||
await this.render(false, patchQueue);
|
||||
}
|
||||
}
|
||||
_patch(vnode) {
|
||||
this.__owl__.renderPromise = null;
|
||||
if (this.__owl__.vnode) {
|
||||
const isMounted = this.__owl__.isMounted;
|
||||
const snapshot = isMounted && this.willPatch();
|
||||
this.__owl__.vnode = patch(this.__owl__.vnode, vnode);
|
||||
if (isMounted) {
|
||||
this.patched(snapshot);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.__owl__.vnode = patch(document.createElement(vnode.sel), vnode);
|
||||
}
|
||||
}
|
||||
async _prepare() {
|
||||
_prepare() {
|
||||
this.__owl__.renderProps = this.props;
|
||||
this.__owl__.renderPromise = this.willStart().then(() => {
|
||||
this.__owl__.renderPromise = this._prepareAndRender();
|
||||
return this.__owl__.renderPromise;
|
||||
}
|
||||
async _prepareAndRender() {
|
||||
await this.willStart();
|
||||
if (this.__owl__.isDestroyed) {
|
||||
return Promise.resolve(h("div"));
|
||||
}
|
||||
@@ -1109,22 +1120,24 @@
|
||||
if (this.inlineTemplate) {
|
||||
this.env.qweb.addTemplate(this.inlineTemplate, this.inlineTemplate, true);
|
||||
}
|
||||
this.__owl__.render = this.env.qweb.render.bind(this.env.qweb, this.inlineTemplate || this.template);
|
||||
this._observeState();
|
||||
return this._render();
|
||||
});
|
||||
return this.__owl__.renderPromise;
|
||||
}
|
||||
async _render(force = false) {
|
||||
async _render(force = false, patchQueue = []) {
|
||||
if (this.__owl__.isMounted) {
|
||||
patchQueue.push(this);
|
||||
}
|
||||
this.__owl__.renderId++;
|
||||
const promises = [];
|
||||
const template = this.inlineTemplate || this.template;
|
||||
if (this.__owl__.observer) {
|
||||
this.__owl__.observer.allowMutations = false;
|
||||
}
|
||||
let vnode = this.env.qweb.render(template, this, {
|
||||
let vnode = this.__owl__.render(this, {
|
||||
promises,
|
||||
handlers: this.__owl__.boundHandlers,
|
||||
forceUpdate: force
|
||||
forceUpdate: force,
|
||||
patchQueue
|
||||
});
|
||||
if (this.__owl__.observer) {
|
||||
this.__owl__.observer.allowMutations = true;
|
||||
@@ -1144,32 +1157,17 @@
|
||||
*/
|
||||
_mount(vnode, elm) {
|
||||
this.__owl__.vnode = patch(elm, vnode);
|
||||
this.__mount();
|
||||
if (this.__owl__.parent &&
|
||||
this.__owl__.parent.__owl__.isMounted &&
|
||||
!this.__owl__.isMounted) {
|
||||
this._callMounted();
|
||||
}
|
||||
return this.__owl__.vnode;
|
||||
}
|
||||
__mount() {
|
||||
if (this.__owl__.isMounted) {
|
||||
return;
|
||||
}
|
||||
this._observeState();
|
||||
if (this.__owl__.parent) {
|
||||
if (this.__owl__.parent.__owl__.isMounted) {
|
||||
if (!this.__owl__.isMounted) {
|
||||
this.__owl__.isMounted = true;
|
||||
this.mounted();
|
||||
const children = this.__owl__.children;
|
||||
for (let id in children) {
|
||||
children[id].__mount();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_visitSubTree(callback) {
|
||||
const shouldVisitChildren = callback(this);
|
||||
if (shouldVisitChildren) {
|
||||
const children = this.__owl__.children;
|
||||
for (let id in children) {
|
||||
children[id]._visitSubTree(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
_observeState() {
|
||||
@@ -1200,6 +1198,62 @@
|
||||
];
|
||||
const lineBreakRE = /[\r\n]/;
|
||||
const whitespaceRE = /\s+/g;
|
||||
function parseXML(xml) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(xml, "text/xml");
|
||||
if (doc.getElementsByTagName("parsererror").length) {
|
||||
throw new Error("Invalid XML in template");
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
const UTILS = {
|
||||
h: h,
|
||||
getFragment(str) {
|
||||
const temp = document.createElement("template");
|
||||
temp.innerHTML = str;
|
||||
return temp.content;
|
||||
},
|
||||
objectToAttrString(obj) {
|
||||
let classes = [];
|
||||
for (let k in obj) {
|
||||
if (obj[k]) {
|
||||
classes.push(k);
|
||||
}
|
||||
}
|
||||
return classes.join(" ");
|
||||
},
|
||||
nextFrame(cb) {
|
||||
requestAnimationFrame(() => requestAnimationFrame(cb));
|
||||
},
|
||||
transitionCreate(elm, name) {
|
||||
elm.classList.add(name + "-enter");
|
||||
elm.classList.add(name + "-enter-active");
|
||||
},
|
||||
transitionInsert(elm, name) {
|
||||
const finalize = () => {
|
||||
elm.classList.remove(name + "-enter-active");
|
||||
elm.classList.remove(name + "-enter-to");
|
||||
};
|
||||
elm.addEventListener("transitionend", finalize);
|
||||
this.nextFrame(() => {
|
||||
elm.classList.remove(name + "-enter");
|
||||
elm.classList.add(name + "-enter-to");
|
||||
});
|
||||
},
|
||||
transitionRemove(elm, name, rm) {
|
||||
elm.classList.add(name + "-leave");
|
||||
elm.classList.add(name + "-leave-active");
|
||||
elm.addEventListener("transitionend", () => {
|
||||
elm.classList.remove(name + "-leave-active");
|
||||
elm.classList.remove(name + "-enter-to");
|
||||
rm();
|
||||
});
|
||||
this.nextFrame(() => {
|
||||
elm.classList.remove(name + "-leave");
|
||||
elm.classList.add(name + "-leave-to");
|
||||
});
|
||||
}
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
// Compilation Context
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -1335,26 +1389,9 @@
|
||||
//------------------------------------------------------------------------------
|
||||
class QWeb {
|
||||
constructor(data) {
|
||||
this.processedTemplates = {};
|
||||
this.templates = {};
|
||||
this.directives = [];
|
||||
this.utils = {
|
||||
h: h,
|
||||
getFragment(str) {
|
||||
const temp = document.createElement("template");
|
||||
temp.innerHTML = str;
|
||||
return temp.content;
|
||||
},
|
||||
objectToAttrString(obj) {
|
||||
let classes = [];
|
||||
for (let k in obj) {
|
||||
if (obj[k]) {
|
||||
classes.push(k);
|
||||
}
|
||||
}
|
||||
return classes.join(" ");
|
||||
}
|
||||
};
|
||||
this.utils = UTILS;
|
||||
this.directiveNames = {
|
||||
as: 1,
|
||||
name: 1,
|
||||
@@ -1364,7 +1401,8 @@
|
||||
props: 1,
|
||||
key: 1,
|
||||
keepalive: 1,
|
||||
debug: 1
|
||||
debug: 1,
|
||||
log: 1
|
||||
};
|
||||
[
|
||||
forEachDirective,
|
||||
@@ -1377,6 +1415,9 @@
|
||||
callDirective,
|
||||
onDirective,
|
||||
refDirective,
|
||||
transitionDirective,
|
||||
debugDirective,
|
||||
logDirective,
|
||||
widgetDirective
|
||||
].forEach(d => this.addDirective(d));
|
||||
if (data) {
|
||||
@@ -1392,26 +1433,30 @@
|
||||
* Add a template to the internal template map. Note that it is not
|
||||
* immediately compiled.
|
||||
*/
|
||||
addTemplate(name, template, allowDuplicates = false) {
|
||||
if (name in this.processedTemplates) {
|
||||
if (allowDuplicates) {
|
||||
addTemplate(name, xmlString, allowDuplicates = false) {
|
||||
if (name in this.templates && allowDuplicates) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
throw new Error(`Template ${name} already defined`);
|
||||
}
|
||||
}
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(template, "text/xml");
|
||||
const doc = parseXML(xmlString);
|
||||
if (!doc.firstChild) {
|
||||
throw new Error("Invalid template (should not be empty)");
|
||||
}
|
||||
if (doc.getElementsByTagName("parsererror").length) {
|
||||
throw new Error("Invalid XML in template");
|
||||
this._addTemplate(name, doc.firstChild);
|
||||
}
|
||||
_addTemplate(name, elem) {
|
||||
if (name in this.templates) {
|
||||
throw new Error(`Template ${name} already defined`);
|
||||
}
|
||||
let elem = doc.firstChild;
|
||||
this._processTemplate(elem);
|
||||
this.processedTemplates[name] = elem;
|
||||
const template = {
|
||||
elem,
|
||||
fn: (context, extra) => {
|
||||
const compiledFunction = this._compile(name, elem);
|
||||
template.fn = compiledFunction;
|
||||
return compiledFunction.call(this, context, extra);
|
||||
}
|
||||
};
|
||||
this.templates[name] = template;
|
||||
}
|
||||
_processTemplate(elem) {
|
||||
let tbranch = elem.querySelectorAll("[t-elif], [t-else]");
|
||||
@@ -1453,19 +1498,14 @@
|
||||
* the name given by the t-name attribute.
|
||||
*/
|
||||
loadTemplates(xmlstr) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(xmlstr, "text/xml");
|
||||
if (doc.getElementsByTagName("parsererror").length) {
|
||||
throw new Error("Invalid XML in template");
|
||||
}
|
||||
const doc = parseXML(xmlstr);
|
||||
const templates = doc.getElementsByTagName("templates")[0];
|
||||
if (!templates) {
|
||||
return;
|
||||
}
|
||||
for (let elem of templates.children) {
|
||||
const name = elem.getAttribute("t-name");
|
||||
this._processTemplate(elem);
|
||||
this.processedTemplates[name] = elem;
|
||||
this._addTemplate(name, elem);
|
||||
}
|
||||
}
|
||||
/**
|
||||
@@ -1474,20 +1514,16 @@
|
||||
* @param {string} name the template should already have been added
|
||||
*/
|
||||
render(name, context = {}, extra = null) {
|
||||
if (!(name in this.processedTemplates)) {
|
||||
const template = this.templates[name];
|
||||
if (!template) {
|
||||
throw new Error(`Template ${name} does not exist`);
|
||||
}
|
||||
const template = this.templates[name] || this._compile(name);
|
||||
return template.call(this, context, extra);
|
||||
return template.fn.call(this, context, extra);
|
||||
}
|
||||
_compile(name) {
|
||||
if (name in this.templates) {
|
||||
return this.templates[name];
|
||||
}
|
||||
const mainNode = this.processedTemplates[name];
|
||||
const isDebug = mainNode.attributes.hasOwnProperty("t-debug");
|
||||
_compile(name, elem) {
|
||||
const isDebug = elem.attributes.hasOwnProperty("t-debug");
|
||||
const ctx = new Context(name);
|
||||
this._compileNode(mainNode, ctx);
|
||||
this._compileNode(elem, ctx);
|
||||
if (ctx.shouldProtectContext) {
|
||||
ctx.code.unshift(" context = Object.create(context);");
|
||||
}
|
||||
@@ -1500,9 +1536,6 @@
|
||||
throw new Error("A template should have one root node");
|
||||
}
|
||||
ctx.addLine(`return vn${ctx.rootNode};`);
|
||||
if (isDebug) {
|
||||
ctx.code.unshift(" debugger");
|
||||
}
|
||||
let template;
|
||||
try {
|
||||
template = new Function("context", "extra", ctx.code.join("\n"));
|
||||
@@ -1511,9 +1544,9 @@
|
||||
throw new Error(`Invalid generated code while compiling template '${ctx.templateName.replace(/`/g, "'")}': ${e.message}`);
|
||||
}
|
||||
if (isDebug) {
|
||||
console.log(`Template: ${this.processedTemplates[name].outerHTML}\nCompiled code:\n` + template.toString());
|
||||
console.log(`Template: ${this.templates[name].elem.outerHTML}\nCompiled code:\n` +
|
||||
template.toString());
|
||||
}
|
||||
this.templates[name] = template;
|
||||
return template;
|
||||
}
|
||||
/**
|
||||
@@ -1890,7 +1923,7 @@
|
||||
throw new Error("Invalid tag for t-call directive (should be 't')");
|
||||
}
|
||||
const subTemplate = node.getAttribute("t-call");
|
||||
const nodeTemplate = qweb.processedTemplates[subTemplate];
|
||||
const nodeTemplate = qweb.templates[subTemplate];
|
||||
if (!nodeTemplate) {
|
||||
throw new Error(`Cannot find template "${subTemplate}" (t-call)`);
|
||||
}
|
||||
@@ -1918,7 +1951,7 @@
|
||||
.subContext("caller", nodeCopy)
|
||||
.subContext("variables", Object.create(vars))
|
||||
.subContext("definedVariables", Object.create(definedVariables));
|
||||
qweb._compileNode(nodeTemplate, subCtx);
|
||||
qweb._compileNode(nodeTemplate.elem, subCtx);
|
||||
// close new scope
|
||||
if (hasNewVariables) {
|
||||
ctx.dedent();
|
||||
@@ -1952,7 +1985,19 @@
|
||||
ctx.addLine(`context.${name} = _${keysID}[i];`);
|
||||
ctx.addLine(`context.${name}_value = _${valuesID}[i];`);
|
||||
const nodeCopy = node.cloneNode(true);
|
||||
if (nodeCopy.tagName !== "t" && !nodeCopy.hasAttribute("t-key")) {
|
||||
let shouldWarn = nodeCopy.tagName !== "t" && !nodeCopy.hasAttribute("t-key");
|
||||
if (!shouldWarn && node.tagName === "t") {
|
||||
if (node.hasAttribute("t-widget") && !node.hasAttribute("t-key")) {
|
||||
shouldWarn = true;
|
||||
}
|
||||
if (!shouldWarn &&
|
||||
node.children.length === 1 &&
|
||||
node.children[0].tagName !== 't' &&
|
||||
!node.children[0].hasAttribute("t-key")) {
|
||||
shouldWarn = true;
|
||||
}
|
||||
}
|
||||
if (shouldWarn) {
|
||||
console.warn(`Directive t-foreach should always be used with a t-key! (in template: '${ctx.templateName}')`);
|
||||
}
|
||||
nodeCopy.removeAttribute("t-foreach");
|
||||
@@ -1989,13 +2034,47 @@
|
||||
const refDirective = {
|
||||
name: "ref",
|
||||
priority: 95,
|
||||
atNodeCreation({ ctx, node }) {
|
||||
let ref = node.getAttribute("t-ref");
|
||||
ctx.addLine(`p${ctx.parentNode}.hook = {
|
||||
create: (_, n) => context.refs[${ctx.formatExpression(ref)}] = n.elm,
|
||||
atNodeCreation({ ctx, nodeID, value }) {
|
||||
const refKey = `ref${ctx.generateID()}`;
|
||||
ctx.addLine(`const ${refKey} = ${ctx.formatExpression(value)}`);
|
||||
ctx.addLine(`p${nodeID}.hook = {
|
||||
create: (_, n) => context.refs[${refKey}] = n.elm,
|
||||
};`);
|
||||
}
|
||||
};
|
||||
const transitionDirective = {
|
||||
name: "transition",
|
||||
priority: 96,
|
||||
atNodeCreation({ ctx, value }) {
|
||||
let name = value;
|
||||
ctx.addLine(`p${ctx.parentNode}.hook = {
|
||||
create: (_, n) => {
|
||||
this.utils.transitionCreate(n.elm, '${name}');
|
||||
},
|
||||
insert: vn => {
|
||||
this.utils.transitionInsert(vn.elm, '${name}');
|
||||
},
|
||||
remove: (vn, rm) => {
|
||||
this.utils.transitionRemove(vn.elm, '${name}', rm);
|
||||
}
|
||||
};`);
|
||||
}
|
||||
};
|
||||
const debugDirective = {
|
||||
name: "debug",
|
||||
priority: 99,
|
||||
atNodeEncounter({ ctx }) {
|
||||
ctx.addLine("debugger;");
|
||||
}
|
||||
};
|
||||
const logDirective = {
|
||||
name: "log",
|
||||
priority: 99,
|
||||
atNodeEncounter({ ctx, value }) {
|
||||
const expr = ctx.formatExpression(value);
|
||||
ctx.addLine(`console.log(${expr})`);
|
||||
}
|
||||
};
|
||||
const widgetDirective = {
|
||||
name: "widget",
|
||||
priority: 100,
|
||||
@@ -2043,7 +2122,7 @@
|
||||
// check if we can reuse current rendering promise
|
||||
ctx.addIf(`w${widgetID} && w${widgetID}.__owl__.renderPromise`);
|
||||
ctx.addIf(`w${widgetID}.__owl__.isStarted`);
|
||||
ctx.addLine(`def${defID} = w${widgetID}.updateProps(props${widgetID}, extra.forceUpdate);`);
|
||||
ctx.addLine(`def${defID} = w${widgetID}._updateProps(props${widgetID}, extra.forceUpdate, extra.patchQueue);`);
|
||||
ctx.addElse();
|
||||
ctx.addLine(`isNew${widgetID} = true`);
|
||||
ctx.addIf(`props${widgetID} === w${widgetID}.__owl__.renderProps`);
|
||||
@@ -2056,7 +2135,7 @@
|
||||
ctx.closeIf();
|
||||
ctx.addIf(`!def${defID}`);
|
||||
ctx.addIf(`w${widgetID}`);
|
||||
ctx.addLine(`def${defID} = w${widgetID}.updateProps(props${widgetID}, extra.forceUpdate);`);
|
||||
ctx.addLine(`def${defID} = w${widgetID}._updateProps(props${widgetID}, extra.forceUpdate, extra.patchQueue);`);
|
||||
ctx.addElse();
|
||||
ctx.addLine(`w${widgetID} = new context.widgets['${value}'](owner, props${widgetID});`);
|
||||
ctx.addLine(`context.__owl__.cmap[${templateID}] = w${widgetID}.__owl__.id;`);
|
||||
@@ -2228,7 +2307,7 @@
|
||||
}
|
||||
if (didChange) {
|
||||
this.__owl__.currentStoreProps = storeProps;
|
||||
this.updateProps(ownProps, false);
|
||||
this._updateProps(ownProps, false);
|
||||
}
|
||||
});
|
||||
super.mounted();
|
||||
@@ -2237,13 +2316,13 @@
|
||||
this.env.store.off("update", this);
|
||||
super.willUnmount();
|
||||
}
|
||||
updateProps(nextProps, forceUpdate) {
|
||||
_updateProps(nextProps, forceUpdate) {
|
||||
if (this.__owl__.ownProps !== nextProps) {
|
||||
this.__owl__.currentStoreProps = mapStateToProps(this.env.store.state, nextProps);
|
||||
}
|
||||
this.__owl__.ownProps = nextProps;
|
||||
const mergedProps = Object.assign({}, nextProps, this.__owl__.currentStoreProps);
|
||||
return super.updateProps(mergedProps, forceUpdate);
|
||||
return super._updateProps(mergedProps, forceUpdate);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -2263,19 +2342,6 @@
|
||||
.replace(/"/g, "'")
|
||||
.replace(/`/g, "`");
|
||||
}
|
||||
/**
|
||||
* Remove trailing and leading spaces
|
||||
*/
|
||||
function htmlTrim(s) {
|
||||
let result = s.replace(/(^\s+|\s+$)/g, "");
|
||||
if (s[0] === " ") {
|
||||
result = " " + result;
|
||||
}
|
||||
if (result !== " " && s[s.length - 1] === " ") {
|
||||
result = result + " ";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function memoize(f, hash) {
|
||||
if (!hash) {
|
||||
hash = args => args.map(a => String(a)).join(",");
|
||||
@@ -2317,24 +2383,6 @@
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Find a node in a tree.
|
||||
*
|
||||
* This will traverse the tree (depth first) and return the first child that
|
||||
* matches the predicate, if any
|
||||
*/
|
||||
function findInTree(tree, predicate) {
|
||||
if (predicate(tree)) {
|
||||
return tree;
|
||||
}
|
||||
for (let child of tree.children) {
|
||||
let match = findInTree(child, predicate);
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function patch$1(C, patchName, patch) {
|
||||
const proto = C.prototype;
|
||||
if (!proto.__patches) {
|
||||
@@ -2422,10 +2470,8 @@
|
||||
|
||||
var _utils = /*#__PURE__*/Object.freeze({
|
||||
escape: escape,
|
||||
htmlTrim: htmlTrim,
|
||||
memoize: memoize,
|
||||
debounce: debounce,
|
||||
findInTree: findInTree,
|
||||
patch: patch$1,
|
||||
unpatch: unpatch,
|
||||
loadTemplates: loadTemplates,
|
||||
@@ -2443,9 +2489,9 @@
|
||||
exports.connect = connect;
|
||||
exports.Store = Store;
|
||||
|
||||
exports._version = '0.8.0';
|
||||
exports._date = '2019-04-26T13:32:23.079Z';
|
||||
exports._hash = 'b44f274';
|
||||
exports._version = '0.9.0';
|
||||
exports._date = '2019-05-03T10:06:05.040Z';
|
||||
exports._hash = '5b9abb6';
|
||||
exports._url = 'https://github.com/odoo/owl';
|
||||
|
||||
}(this.owl = this.owl || {}));
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "owl-playground",
|
||||
"description": "Odoo Web Library",
|
||||
"scripts": {
|
||||
"start": "python server.py"
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -6,7 +6,7 @@ async function owlSourceCode() {
|
||||
if (owlJS) {
|
||||
return owlJS;
|
||||
}
|
||||
const result = await fetch("/libs/owl.js");
|
||||
const result = await fetch("../owl.js");
|
||||
owlJS = await result.text();
|
||||
return owlJS;
|
||||
}
|
||||
@@ -196,7 +196,7 @@ class App extends owl.Component {
|
||||
// inject js
|
||||
const owlScript = doc.createElement("script");
|
||||
owlScript.type = "text/javascript";
|
||||
owlScript.src = "../libs/owl.js";
|
||||
owlScript.src = "../owl.js";
|
||||
owlScript.addEventListener("load", () => {
|
||||
const script = doc.createElement("script");
|
||||
script.type = "text/javascript";
|
||||
@@ -278,8 +278,8 @@ class App extends owl.Component {
|
||||
}
|
||||
|
||||
async downloadCode() {
|
||||
await owl.utils.loadJS("../libs/FileSaver.min.js");
|
||||
await owl.utils.loadJS("../libs/jszip.min.js");
|
||||
await owl.utils.loadJS("libs/FileSaver.min.js");
|
||||
await owl.utils.loadJS("libs/jszip.min.js");
|
||||
|
||||
const zip = new JSZip();
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<title>OWL Playground</title>
|
||||
<link rel="icon" href="data:,">
|
||||
|
||||
<script src="../libs/ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="../libs/owl.js"></script>
|
||||
<script src="libs/ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="../owl.js"></script>
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/solid.css" integrity="sha384-QokYePQSOwpBDuhlHOsX0ymF6R/vLk/UQVz3WHa6wygxI5oGTmDTv8wahFOSspdm" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/fontawesome.css" integrity="sha384-vd1e11sR28tEK9YANUtpIOdjGW14pS87bUBuOIoBILVWLFnS+MCX9T6MMf0VdPGq" crossorigin="anonymous">
|
||||
|
||||
|
||||
+106
-37
@@ -2,7 +2,7 @@ const CLICK_COUNTER = `class ClickCounter extends owl.Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.template = "clickcounter";
|
||||
this.state = {value: 0};
|
||||
this.state = { value: 0 };
|
||||
}
|
||||
|
||||
increment() {
|
||||
@@ -11,7 +11,7 @@ const CLICK_COUNTER = `class ClickCounter extends owl.Component {
|
||||
}
|
||||
|
||||
const qweb = new owl.QWeb(TEMPLATES);
|
||||
const counter = new ClickCounter({qweb});
|
||||
const counter = new ClickCounter({ qweb });
|
||||
counter.mount(document.body);
|
||||
`;
|
||||
|
||||
@@ -30,7 +30,7 @@ const CLICK_COUNTER_CSS = `button {
|
||||
const CLICK_COUNTER_ESNEXT = `// This example will not work if your browser does not support ESNext class fields
|
||||
class ClickCounter extends owl.Component {
|
||||
template = "clickcounter";
|
||||
state = {value: 0};
|
||||
state = { value: 0 };
|
||||
|
||||
increment() {
|
||||
this.state.value++;
|
||||
@@ -38,7 +38,7 @@ class ClickCounter extends owl.Component {
|
||||
}
|
||||
|
||||
const qweb = new owl.QWeb(TEMPLATES);
|
||||
const counter = new ClickCounter({qweb});
|
||||
const counter = new ClickCounter({ qweb });
|
||||
counter.mount(document.body);
|
||||
`;
|
||||
|
||||
@@ -46,7 +46,7 @@ const WIDGET_COMPOSITION = `class ClickCounter extends owl.Component {
|
||||
constructor(parent, props) {
|
||||
super(parent, props);
|
||||
this.template = "clickcounter";
|
||||
this.state = {value: props.initialState || 0};
|
||||
this.state = { value: props.initialState || 0 };
|
||||
}
|
||||
|
||||
increment() {
|
||||
@@ -54,14 +54,13 @@ const WIDGET_COMPOSITION = `class ClickCounter extends owl.Component {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let nextId = 1;
|
||||
|
||||
class App extends owl.Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.template = "app";
|
||||
this.state = {counters: []}
|
||||
this.state = { counters: [] }
|
||||
this.widgets = { ClickCounter };
|
||||
}
|
||||
|
||||
@@ -71,8 +70,9 @@ class App extends owl.Component {
|
||||
}
|
||||
|
||||
const qweb = new owl.QWeb(TEMPLATES);
|
||||
const app = new App({qweb});
|
||||
app.mount(document.body);`;
|
||||
const app = new App({ qweb });
|
||||
app.mount(document.body);
|
||||
`;
|
||||
|
||||
const WIDGET_COMPOSITION_XML = `<templates>
|
||||
<button t-name="clickcounter" t-on-click="increment">
|
||||
@@ -95,9 +95,61 @@ const WIDGET_COMPOSITION_CSS = `button {
|
||||
width: 220px;
|
||||
}`;
|
||||
|
||||
const LIFECYCLE_DEMO = `const { Component, QWeb } = owl;
|
||||
const ANIMATION = `// This example will not work if your browser does not support ESNext class fields
|
||||
class App extends owl.Component {
|
||||
template = "app";
|
||||
state = {flag: 0};
|
||||
|
||||
class HookWidget extends Component {
|
||||
toggle() {
|
||||
this.state.flag = !this.state.flag;
|
||||
}
|
||||
}
|
||||
|
||||
const qweb = new owl.QWeb(TEMPLATES);
|
||||
const app = new App({qweb});
|
||||
app.mount(document.body);
|
||||
`;
|
||||
|
||||
const ANIMATION_XML = `<templates>
|
||||
<div t-name="app">
|
||||
<button t-on-click="toggle">
|
||||
Click Me!
|
||||
</button>
|
||||
<div>
|
||||
<div t-if="state.flag" class="square" t-transition="fade">Hello</div>
|
||||
</div>
|
||||
</div>
|
||||
</templates>
|
||||
`;
|
||||
|
||||
const ANIMATION_CSS = `button {
|
||||
width: 100px;
|
||||
height: 30px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.square {
|
||||
background-color: red;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
color: white;
|
||||
margin: 20px;
|
||||
font-size: 24px;
|
||||
line-height: 100px;
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
}
|
||||
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity .5s;
|
||||
}
|
||||
.fade-enter, .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
const LIFECYCLE_DEMO = `class HookWidget extends owl.Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.template = "demo.hookwidget";
|
||||
@@ -127,7 +179,7 @@ class HookWidget extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
class ParentWidget extends Component {
|
||||
class ParentWidget extends owl.Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.widgets = { HookWidget };
|
||||
@@ -142,7 +194,7 @@ class ParentWidget extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
const qweb = new QWeb(TEMPLATES);
|
||||
const qweb = new owl.QWeb(TEMPLATES);
|
||||
const widget = new ParentWidget({ qweb });
|
||||
widget.mount(document.body);
|
||||
`;
|
||||
@@ -190,9 +242,7 @@ class Counter extends owl.Component {
|
||||
constructor(parent, props) {
|
||||
super(parent, props);
|
||||
this.template = "counter";
|
||||
this.state = {
|
||||
counter: props.initialState || 0
|
||||
};
|
||||
this.state = { counter: props.initialState || 0 };
|
||||
}
|
||||
|
||||
increment(delta) {
|
||||
@@ -225,13 +275,11 @@ class App extends owl.Component {
|
||||
super(...arguments);
|
||||
this.template = "root";
|
||||
this.widgets = { Message };
|
||||
this.state = {
|
||||
messages: messages.slice(0, 10)
|
||||
};
|
||||
this.state = { messages: messages.slice(0, 10) };
|
||||
}
|
||||
|
||||
setMessageCount(n) {
|
||||
this.state.messages = messages.slice(0,n);
|
||||
this.state.messages = messages.slice(0, n);
|
||||
}
|
||||
|
||||
removeMessage(data) {
|
||||
@@ -368,7 +416,10 @@ const actions = {
|
||||
},
|
||||
toggleAll({ state, commit }, completed) {
|
||||
state.todos.forEach(todo => {
|
||||
commit("editTodo", { id: todo.id, completed });
|
||||
commit("editTodo", {
|
||||
id: todo.id,
|
||||
completed
|
||||
});
|
||||
});
|
||||
},
|
||||
editTodo({ commit }, { id, title }) {
|
||||
@@ -379,7 +430,11 @@ const actions = {
|
||||
const mutations = {
|
||||
addTodo({ state }, title) {
|
||||
const id = state.nextId++;
|
||||
const todo = { id, title, completed: false };
|
||||
const todo = {
|
||||
id,
|
||||
title,
|
||||
completed: false
|
||||
};
|
||||
state.todos.push(todo);
|
||||
},
|
||||
removeTodo({ state }, id) {
|
||||
@@ -402,8 +457,15 @@ function makeStore() {
|
||||
window.localStorage.getItem(LOCALSTORAGE_KEY) || "[]"
|
||||
);
|
||||
const nextId = Math.max(0, ...todos.map(t => t.id || 0)) + 1;
|
||||
const state = { todos, nextId };
|
||||
const store = new owl.Store({ state, actions, mutations });
|
||||
const state = {
|
||||
todos,
|
||||
nextId
|
||||
};
|
||||
const store = new owl.Store({
|
||||
state,
|
||||
actions,
|
||||
mutations
|
||||
});
|
||||
store.on("update", null, () => {
|
||||
const state = JSON.stringify(store.state.todos);
|
||||
window.localStorage.setItem(LOCALSTORAGE_KEY, state);
|
||||
@@ -467,7 +529,9 @@ class TodoItem extends owl.Component {
|
||||
// TodoApp
|
||||
//------------------------------------------------------------------------------
|
||||
function mapStateToProps(state) {
|
||||
return { todos: state.todos };
|
||||
return {
|
||||
todos: state.todos
|
||||
};
|
||||
}
|
||||
|
||||
class TodoApp extends owl.Component {
|
||||
@@ -970,16 +1034,14 @@ html .clear-completed:active {
|
||||
}
|
||||
`;
|
||||
|
||||
const RESPONSIVE = `const { Component, QWeb, utils } = owl;
|
||||
|
||||
class SubWidget extends Component {
|
||||
const RESPONSIVE = `class SubWidget extends owl.Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.template = "subwidget";
|
||||
}
|
||||
}
|
||||
|
||||
class ResponsiveWidget extends Component {
|
||||
class ResponsiveWidget extends owl.Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.template = "responsivewidget";
|
||||
@@ -992,7 +1054,7 @@ function isMobile() {
|
||||
}
|
||||
|
||||
const env = {
|
||||
qweb: new QWeb(TEMPLATES),
|
||||
qweb: new owl.QWeb(TEMPLATES),
|
||||
isMobile: isMobile()
|
||||
};
|
||||
|
||||
@@ -1001,10 +1063,12 @@ widget.mount(document.body);
|
||||
|
||||
window.addEventListener(
|
||||
"resize",
|
||||
utils.debounce(function() {
|
||||
owl.utils.debounce(function() {
|
||||
const _isMobile = isMobile();
|
||||
if (_isMobile !== env.isMobile) {
|
||||
widget.updateEnv({ isMobile: _isMobile });
|
||||
widget.updateEnv({
|
||||
isMobile: _isMobile
|
||||
});
|
||||
}
|
||||
}, 20)
|
||||
);
|
||||
@@ -1039,13 +1103,12 @@ const RESPONSIVE_CSS = `.info {
|
||||
margin: 30px;
|
||||
}`;
|
||||
|
||||
const EMPTY = `const {Component, QWeb} = owl;
|
||||
class Widget extends Component {
|
||||
const EMPTY = `class App extends owl.Component {
|
||||
}
|
||||
|
||||
const qweb = new QWeb(TEMPLATES);
|
||||
const widget = new Widget({qweb});
|
||||
widget.mount(document.body);
|
||||
const qweb = new owl.QWeb(TEMPLATES);
|
||||
const app = new App({qweb});
|
||||
app.mount(document.body);
|
||||
`;
|
||||
|
||||
export const SAMPLES = [
|
||||
@@ -1067,6 +1130,12 @@ export const SAMPLES = [
|
||||
xml: WIDGET_COMPOSITION_XML,
|
||||
css: WIDGET_COMPOSITION_CSS
|
||||
},
|
||||
{
|
||||
description: "Animations",
|
||||
code: ANIMATION,
|
||||
xml: ANIMATION_XML,
|
||||
css: ANIMATION_CSS,
|
||||
},
|
||||
{
|
||||
description: "Lifecycle demo",
|
||||
code: LIFECYCLE_DEMO,
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import sys
|
||||
import thread
|
||||
import webbrowser
|
||||
import time
|
||||
|
||||
import BaseHTTPServer, SimpleHTTPServer
|
||||
|
||||
def start_server():
|
||||
httpd = BaseHTTPServer.HTTPServer(('127.0.0.1', 3600), SimpleHTTPServer.SimpleHTTPRequestHandler)
|
||||
httpd.serve_forever()
|
||||
|
||||
thread.start_new_thread(start_server,())
|
||||
url = 'http://127.0.0.1:3600'
|
||||
webbrowser.open_new(url)
|
||||
|
||||
while True:
|
||||
try:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(0)
|
||||
Reference in New Issue
Block a user