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:
+254
-208
@@ -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,64 +974,94 @@
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
this.willUnmount();
|
||||
}
|
||||
if (this.el) {
|
||||
this.el.remove();
|
||||
this.__owl__.isMounted = false;
|
||||
delete this.__owl__.vnode;
|
||||
}
|
||||
if (this.__owl__.parent) {
|
||||
let id = this.__owl__.id;
|
||||
delete this.__owl__.parent.__owl__.children[id];
|
||||
this.__owl__.parent = null;
|
||||
}
|
||||
this.clear();
|
||||
this.__owl__.isDestroyed = true;
|
||||
}
|
||||
}
|
||||
_destroy(parent) {
|
||||
const isMounted = this.__owl__.isMounted;
|
||||
if (isMounted) {
|
||||
this.willUnmount();
|
||||
this.__owl__.isMounted = false;
|
||||
}
|
||||
const children = Object.values(this.__owl__.children);
|
||||
for (let child of children) {
|
||||
child._destroy(this);
|
||||
}
|
||||
if (parent) {
|
||||
let id = this.__owl__.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,61 +1083,61 @@
|
||||
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) {
|
||||
await this.willUpdateProps(nextProps);
|
||||
this.props = nextProps;
|
||||
await this.render();
|
||||
async _updateProps(nextProps, forceUpdate = false, patchQueue) {
|
||||
const shouldUpdate = forceUpdate || this.shouldUpdate(nextProps);
|
||||
if (shouldUpdate) {
|
||||
await this.willUpdateProps(nextProps);
|
||||
this.props = nextProps;
|
||||
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(() => {
|
||||
if (this.__owl__.isDestroyed) {
|
||||
return Promise.resolve(h("div"));
|
||||
}
|
||||
this.__owl__.isStarted = true;
|
||||
if (this.inlineTemplate) {
|
||||
this.env.qweb.addTemplate(this.inlineTemplate, this.inlineTemplate, true);
|
||||
}
|
||||
this._observeState();
|
||||
return this._render();
|
||||
});
|
||||
this.__owl__.renderPromise = this._prepareAndRender();
|
||||
return this.__owl__.renderPromise;
|
||||
}
|
||||
async _render(force = false) {
|
||||
async _prepareAndRender() {
|
||||
await this.willStart();
|
||||
if (this.__owl__.isDestroyed) {
|
||||
return Promise.resolve(h("div"));
|
||||
}
|
||||
this.__owl__.isStarted = true;
|
||||
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();
|
||||
}
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
if (!this.__owl__.isMounted) {
|
||||
this.__owl__.isMounted = true;
|
||||
this.mounted();
|
||||
}
|
||||
}
|
||||
_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) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
throw new Error(`Template ${name} already defined`);
|
||||
}
|
||||
addTemplate(name, xmlString, allowDuplicates = false) {
|
||||
if (name in this.templates && allowDuplicates) {
|
||||
return;
|
||||
}
|
||||
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">
|
||||
|
||||
|
||||
+365
-296
@@ -1,17 +1,17 @@
|
||||
const CLICK_COUNTER = `class ClickCounter extends owl.Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.template = "clickcounter";
|
||||
this.state = {value: 0};
|
||||
}
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.template = "clickcounter";
|
||||
this.state = { value: 0 };
|
||||
}
|
||||
|
||||
increment() {
|
||||
this.state.value++;
|
||||
}
|
||||
increment() {
|
||||
this.state.value++;
|
||||
}
|
||||
}
|
||||
|
||||
const qweb = new owl.QWeb(TEMPLATES);
|
||||
const counter = new ClickCounter({qweb});
|
||||
const counter = new ClickCounter({ qweb });
|
||||
counter.mount(document.body);
|
||||
`;
|
||||
|
||||
@@ -29,50 +29,50 @@ 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};
|
||||
template = "clickcounter";
|
||||
state = { value: 0 };
|
||||
|
||||
increment() {
|
||||
this.state.value++;
|
||||
}
|
||||
increment() {
|
||||
this.state.value++;
|
||||
}
|
||||
}
|
||||
|
||||
const qweb = new owl.QWeb(TEMPLATES);
|
||||
const counter = new ClickCounter({qweb});
|
||||
const counter = new ClickCounter({ qweb });
|
||||
counter.mount(document.body);
|
||||
`;
|
||||
|
||||
const WIDGET_COMPOSITION = `class ClickCounter extends owl.Component {
|
||||
constructor(parent, props) {
|
||||
super(parent, props);
|
||||
this.template = "clickcounter";
|
||||
this.state = {value: props.initialState || 0};
|
||||
}
|
||||
constructor(parent, props) {
|
||||
super(parent, props);
|
||||
this.template = "clickcounter";
|
||||
this.state = { value: props.initialState || 0 };
|
||||
}
|
||||
|
||||
increment() {
|
||||
this.state.value++;
|
||||
}
|
||||
increment() {
|
||||
this.state.value++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let nextId = 1;
|
||||
|
||||
class App extends owl.Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.template = "app";
|
||||
this.state = {counters: []}
|
||||
this.widgets = { ClickCounter };
|
||||
}
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.template = "app";
|
||||
this.state = { counters: [] }
|
||||
this.widgets = { ClickCounter };
|
||||
}
|
||||
|
||||
addCounter() {
|
||||
this.state.counters.push(nextId++);
|
||||
}
|
||||
addCounter() {
|
||||
this.state.counters.push(nextId++);
|
||||
}
|
||||
}
|
||||
|
||||
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,54 +95,106 @@ 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 {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.template = "demo.hookwidget";
|
||||
this.state = { n: 0 };
|
||||
console.log("constructor");
|
||||
}
|
||||
async willStart() {
|
||||
console.log("willstart");
|
||||
}
|
||||
mounted() {
|
||||
console.log("mounted");
|
||||
}
|
||||
async willUpdateProps(nextProps) {
|
||||
console.log("willUpdateProps", nextProps);
|
||||
}
|
||||
willPatch() {
|
||||
console.log("willPatch");
|
||||
}
|
||||
patched() {
|
||||
console.log("patched");
|
||||
}
|
||||
willUnmount() {
|
||||
console.log("willUnmount");
|
||||
}
|
||||
increment() {
|
||||
this.state.n++;
|
||||
}
|
||||
toggle() {
|
||||
this.state.flag = !this.state.flag;
|
||||
}
|
||||
}
|
||||
|
||||
class ParentWidget extends Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.widgets = { HookWidget };
|
||||
this.template = "demo.parentwidget";
|
||||
this.state = { n: 0, flag: true };
|
||||
}
|
||||
increment() {
|
||||
this.state.n++;
|
||||
}
|
||||
toggleSubWidget() {
|
||||
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;
|
||||
}
|
||||
|
||||
const qweb = new QWeb(TEMPLATES);
|
||||
.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";
|
||||
this.state = { n: 0 };
|
||||
console.log("constructor");
|
||||
}
|
||||
async willStart() {
|
||||
console.log("willstart");
|
||||
}
|
||||
mounted() {
|
||||
console.log("mounted");
|
||||
}
|
||||
async willUpdateProps(nextProps) {
|
||||
console.log("willUpdateProps", nextProps);
|
||||
}
|
||||
willPatch() {
|
||||
console.log("willPatch");
|
||||
}
|
||||
patched() {
|
||||
console.log("patched");
|
||||
}
|
||||
willUnmount() {
|
||||
console.log("willUnmount");
|
||||
}
|
||||
increment() {
|
||||
this.state.n++;
|
||||
}
|
||||
}
|
||||
|
||||
class ParentWidget extends owl.Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.widgets = { HookWidget };
|
||||
this.template = "demo.parentwidget";
|
||||
this.state = { n: 0, flag: true };
|
||||
}
|
||||
increment() {
|
||||
this.state.n++;
|
||||
}
|
||||
toggleSubWidget() {
|
||||
this.state.flag = !this.state.flag;
|
||||
}
|
||||
}
|
||||
|
||||
const qweb = new owl.QWeb(TEMPLATES);
|
||||
const widget = new ParentWidget({ qweb });
|
||||
widget.mount(document.body);
|
||||
`;
|
||||
@@ -164,92 +216,88 @@ const BENCHMARK_APP = `//-------------------------------------------------------
|
||||
const messages = [];
|
||||
const authors = ["Aaron", "David", "Vincent"];
|
||||
const content = [
|
||||
"Lorem ipsum dolor sit amet",
|
||||
"Sed ut perspiciatis unde omnis iste natus error sit voluptatem",
|
||||
"Excepteur sint occaecat cupidatat non proident"
|
||||
"Lorem ipsum dolor sit amet",
|
||||
"Sed ut perspiciatis unde omnis iste natus error sit voluptatem",
|
||||
"Excepteur sint occaecat cupidatat non proident"
|
||||
];
|
||||
|
||||
function chooseRandomly(array) {
|
||||
const index = Math.floor(Math.random() * array.length);
|
||||
return array[index];
|
||||
const index = Math.floor(Math.random() * array.length);
|
||||
return array[index];
|
||||
}
|
||||
|
||||
for (let i = 1; i < 16000; i++) {
|
||||
messages.push({
|
||||
id: i,
|
||||
author: chooseRandomly(authors),
|
||||
msg: \`\${i}: \${chooseRandomly(content)}\`,
|
||||
likes: 0
|
||||
});
|
||||
messages.push({
|
||||
id: i,
|
||||
author: chooseRandomly(authors),
|
||||
msg: \`\${i}: \${chooseRandomly(content)}\`,
|
||||
likes: 0
|
||||
});
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Counter Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class Counter extends owl.Component {
|
||||
constructor(parent, props) {
|
||||
super(parent, props);
|
||||
this.template = "counter";
|
||||
this.state = {
|
||||
counter: props.initialState || 0
|
||||
};
|
||||
}
|
||||
constructor(parent, props) {
|
||||
super(parent, props);
|
||||
this.template = "counter";
|
||||
this.state = { counter: props.initialState || 0 };
|
||||
}
|
||||
|
||||
increment(delta) {
|
||||
this.state.counter += delta;
|
||||
}
|
||||
increment(delta) {
|
||||
this.state.counter += delta;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Message Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class Message extends owl.Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.template = "message";
|
||||
this.widgets = { Counter };
|
||||
}
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.template = "message";
|
||||
this.widgets = { Counter };
|
||||
}
|
||||
|
||||
removeMessage() {
|
||||
this.trigger("remove_message", {
|
||||
id: this.props.id
|
||||
});
|
||||
}
|
||||
removeMessage() {
|
||||
this.trigger("remove_message", {
|
||||
id: this.props.id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Root Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class App extends owl.Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.template = "root";
|
||||
this.widgets = { Message };
|
||||
this.state = {
|
||||
messages: messages.slice(0, 10)
|
||||
};
|
||||
}
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.template = "root";
|
||||
this.widgets = { Message };
|
||||
this.state = { messages: messages.slice(0, 10) };
|
||||
}
|
||||
|
||||
setMessageCount(n) {
|
||||
this.state.messages = messages.slice(0,n);
|
||||
}
|
||||
setMessageCount(n) {
|
||||
this.state.messages = messages.slice(0, n);
|
||||
}
|
||||
|
||||
removeMessage(data) {
|
||||
const index = messages.findIndex(m => m.id === data.id);
|
||||
this.state.messages.splice(index, 1);
|
||||
}
|
||||
removeMessage(data) {
|
||||
const index = messages.findIndex(m => m.id === data.id);
|
||||
this.state.messages.splice(index, 1);
|
||||
}
|
||||
|
||||
increment(delta) {
|
||||
const n = this.state.messages.length + delta;
|
||||
this.setMessageCount(n);
|
||||
}
|
||||
increment(delta) {
|
||||
const n = this.state.messages.length + delta;
|
||||
this.setMessageCount(n);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Application initialization
|
||||
//------------------------------------------------------------------------------
|
||||
const env = {
|
||||
qweb: new owl.QWeb(TEMPLATES)
|
||||
qweb: new owl.QWeb(TEMPLATES)
|
||||
};
|
||||
|
||||
const app = new App(env);
|
||||
@@ -349,177 +397,193 @@ const LOCALSTORAGE_KEY = "todos-odoo";
|
||||
// Store Definition
|
||||
//------------------------------------------------------------------------------
|
||||
const actions = {
|
||||
addTodo({ commit }, title) {
|
||||
commit("addTodo", title);
|
||||
},
|
||||
removeTodo({ commit }, id) {
|
||||
commit("removeTodo", id);
|
||||
},
|
||||
toggleTodo({ state, commit }, id) {
|
||||
const todo = state.todos.find(t => t.id === id);
|
||||
commit("editTodo", { id, completed: !todo.completed });
|
||||
},
|
||||
clearCompleted({ state, commit }) {
|
||||
state.todos
|
||||
.filter(todo => todo.completed)
|
||||
.forEach(todo => {
|
||||
commit("removeTodo", todo.id);
|
||||
});
|
||||
},
|
||||
toggleAll({ state, commit }, completed) {
|
||||
state.todos.forEach(todo => {
|
||||
commit("editTodo", { id: todo.id, completed });
|
||||
});
|
||||
},
|
||||
editTodo({ commit }, { id, title }) {
|
||||
commit("editTodo", { id, title });
|
||||
}
|
||||
addTodo({ commit }, title) {
|
||||
commit("addTodo", title);
|
||||
},
|
||||
removeTodo({ commit }, id) {
|
||||
commit("removeTodo", id);
|
||||
},
|
||||
toggleTodo({ state, commit }, id) {
|
||||
const todo = state.todos.find(t => t.id === id);
|
||||
commit("editTodo", { id, completed: !todo.completed });
|
||||
},
|
||||
clearCompleted({ state, commit }) {
|
||||
state.todos
|
||||
.filter(todo => todo.completed)
|
||||
.forEach(todo => {
|
||||
commit("removeTodo", todo.id);
|
||||
});
|
||||
},
|
||||
toggleAll({ state, commit }, completed) {
|
||||
state.todos.forEach(todo => {
|
||||
commit("editTodo", {
|
||||
id: todo.id,
|
||||
completed
|
||||
});
|
||||
});
|
||||
},
|
||||
editTodo({ commit }, { id, title }) {
|
||||
commit("editTodo", { id, title });
|
||||
}
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
addTodo({ state }, title) {
|
||||
const id = state.nextId++;
|
||||
const todo = { id, title, completed: false };
|
||||
state.todos.push(todo);
|
||||
},
|
||||
removeTodo({ state }, id) {
|
||||
const index = state.todos.findIndex(t => t.id === id);
|
||||
state.todos.splice(index, 1);
|
||||
},
|
||||
editTodo({ state }, { id, title, completed }) {
|
||||
const todo = state.todos.find(t => t.id === id);
|
||||
if (title !== undefined) {
|
||||
todo.title = title;
|
||||
addTodo({ state }, title) {
|
||||
const id = state.nextId++;
|
||||
const todo = {
|
||||
id,
|
||||
title,
|
||||
completed: false
|
||||
};
|
||||
state.todos.push(todo);
|
||||
},
|
||||
removeTodo({ state }, id) {
|
||||
const index = state.todos.findIndex(t => t.id === id);
|
||||
state.todos.splice(index, 1);
|
||||
},
|
||||
editTodo({ state }, { id, title, completed }) {
|
||||
const todo = state.todos.find(t => t.id === id);
|
||||
if (title !== undefined) {
|
||||
todo.title = title;
|
||||
}
|
||||
if (completed !== undefined) {
|
||||
todo.completed = completed;
|
||||
}
|
||||
}
|
||||
if (completed !== undefined) {
|
||||
todo.completed = completed;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function makeStore() {
|
||||
const todos = JSON.parse(
|
||||
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 });
|
||||
store.on("update", null, () => {
|
||||
const state = JSON.stringify(store.state.todos);
|
||||
window.localStorage.setItem(LOCALSTORAGE_KEY, state);
|
||||
});
|
||||
return store;
|
||||
const todos = JSON.parse(
|
||||
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
|
||||
});
|
||||
store.on("update", null, () => {
|
||||
const state = JSON.stringify(store.state.todos);
|
||||
window.localStorage.setItem(LOCALSTORAGE_KEY, state);
|
||||
});
|
||||
return store;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// TodoItem
|
||||
//------------------------------------------------------------------------------
|
||||
class TodoItem extends owl.Component {
|
||||
template = "todoitem";
|
||||
state = { isEditing: false };
|
||||
template = "todoitem";
|
||||
state = { isEditing: false };
|
||||
|
||||
removeTodo() {
|
||||
this.env.store.dispatch("removeTodo", this.props.id);
|
||||
}
|
||||
|
||||
toggleTodo() {
|
||||
this.env.store.dispatch("toggleTodo", this.props.id);
|
||||
}
|
||||
|
||||
async editTodo() {
|
||||
this.state.isEditing = true;
|
||||
setTimeout(() => {
|
||||
this.refs.input.value = "";
|
||||
this.refs.input.focus();
|
||||
this.refs.input.value = this.props.title;
|
||||
});
|
||||
}
|
||||
|
||||
handleKeyup(ev) {
|
||||
if (ev.keyCode === ENTER_KEY) {
|
||||
this.updateTitle(ev.target.value);
|
||||
removeTodo() {
|
||||
this.env.store.dispatch("removeTodo", this.props.id);
|
||||
}
|
||||
if (ev.keyCode === ESC_KEY) {
|
||||
ev.target.value = this.props.title;
|
||||
this.state.isEditing = false;
|
||||
}
|
||||
}
|
||||
|
||||
handleBlur(ev) {
|
||||
this.updateTitle(ev.target.value);
|
||||
}
|
||||
|
||||
updateTitle(title) {
|
||||
const value = title.trim();
|
||||
if (!value) {
|
||||
this.removeTodo(this.props.id);
|
||||
} else {
|
||||
this.env.store.dispatch("editTodo", {
|
||||
id: this.props.id,
|
||||
title: value
|
||||
});
|
||||
this.state.isEditing = false;
|
||||
toggleTodo() {
|
||||
this.env.store.dispatch("toggleTodo", this.props.id);
|
||||
}
|
||||
|
||||
async editTodo() {
|
||||
this.state.isEditing = true;
|
||||
setTimeout(() => {
|
||||
this.refs.input.value = "";
|
||||
this.refs.input.focus();
|
||||
this.refs.input.value = this.props.title;
|
||||
});
|
||||
}
|
||||
|
||||
handleKeyup(ev) {
|
||||
if (ev.keyCode === ENTER_KEY) {
|
||||
this.updateTitle(ev.target.value);
|
||||
}
|
||||
if (ev.keyCode === ESC_KEY) {
|
||||
ev.target.value = this.props.title;
|
||||
this.state.isEditing = false;
|
||||
}
|
||||
}
|
||||
|
||||
handleBlur(ev) {
|
||||
this.updateTitle(ev.target.value);
|
||||
}
|
||||
|
||||
updateTitle(title) {
|
||||
const value = title.trim();
|
||||
if (!value) {
|
||||
this.removeTodo(this.props.id);
|
||||
} else {
|
||||
this.env.store.dispatch("editTodo", {
|
||||
id: this.props.id,
|
||||
title: value
|
||||
});
|
||||
this.state.isEditing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// TodoApp
|
||||
//------------------------------------------------------------------------------
|
||||
function mapStateToProps(state) {
|
||||
return { todos: state.todos };
|
||||
return {
|
||||
todos: state.todos
|
||||
};
|
||||
}
|
||||
|
||||
class TodoApp extends owl.Component {
|
||||
template = "todoapp";
|
||||
widgets = { TodoItem };
|
||||
state = { filter: "all" };
|
||||
template = "todoapp";
|
||||
widgets = { TodoItem };
|
||||
state = { filter: "all" };
|
||||
|
||||
get visibleTodos() {
|
||||
let todos = this.props.todos;
|
||||
if (this.state.filter === "active") {
|
||||
todos = todos.filter(t => !t.completed);
|
||||
get visibleTodos() {
|
||||
let todos = this.props.todos;
|
||||
if (this.state.filter === "active") {
|
||||
todos = todos.filter(t => !t.completed);
|
||||
}
|
||||
if (this.state.filter === "completed") {
|
||||
todos = todos.filter(t => t.completed);
|
||||
}
|
||||
return todos;
|
||||
}
|
||||
if (this.state.filter === "completed") {
|
||||
todos = todos.filter(t => t.completed);
|
||||
|
||||
get allChecked() {
|
||||
return this.props.todos.every(todo => todo.completed);
|
||||
}
|
||||
return todos;
|
||||
}
|
||||
|
||||
get allChecked() {
|
||||
return this.props.todos.every(todo => todo.completed);
|
||||
}
|
||||
|
||||
get remaining() {
|
||||
return this.props.todos.filter(todo => !todo.completed).length;
|
||||
}
|
||||
|
||||
get remainingText() {
|
||||
const items = this.remaining < 2 ? "item" : "items";
|
||||
return \` \${items} left\`;
|
||||
}
|
||||
|
||||
addTodo(ev) {
|
||||
if (ev.keyCode === ENTER_KEY) {
|
||||
const title = ev.target.value;
|
||||
if (title.trim()) {
|
||||
this.env.store.dispatch("addTodo", title);
|
||||
}
|
||||
ev.target.value = "";
|
||||
get remaining() {
|
||||
return this.props.todos.filter(todo => !todo.completed).length;
|
||||
}
|
||||
}
|
||||
|
||||
clearCompleted() {
|
||||
this.env.store.dispatch("clearCompleted");
|
||||
}
|
||||
get remainingText() {
|
||||
const items = this.remaining < 2 ? "item" : "items";
|
||||
return \` \${items} left\`;
|
||||
}
|
||||
|
||||
toggleAll() {
|
||||
this.env.store.dispatch("toggleAll", !this.allChecked);
|
||||
}
|
||||
addTodo(ev) {
|
||||
if (ev.keyCode === ENTER_KEY) {
|
||||
const title = ev.target.value;
|
||||
if (title.trim()) {
|
||||
this.env.store.dispatch("addTodo", title);
|
||||
}
|
||||
ev.target.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
setFilter(filter) {
|
||||
this.state.filter = filter;
|
||||
}
|
||||
clearCompleted() {
|
||||
this.env.store.dispatch("clearCompleted");
|
||||
}
|
||||
|
||||
toggleAll() {
|
||||
this.env.store.dispatch("toggleAll", !this.allChecked);
|
||||
}
|
||||
|
||||
setFilter(filter) {
|
||||
this.state.filter = filter;
|
||||
}
|
||||
}
|
||||
|
||||
const ConnectedTodoApp = owl.connect(mapStateToProps)(TodoApp);
|
||||
@@ -530,8 +594,8 @@ const ConnectedTodoApp = owl.connect(mapStateToProps)(TodoApp);
|
||||
const store = makeStore();
|
||||
const qweb = new owl.QWeb(TEMPLATES);
|
||||
const env = {
|
||||
qweb,
|
||||
store
|
||||
qweb,
|
||||
store
|
||||
};
|
||||
const app = new ConnectedTodoApp(env);
|
||||
app.mount(document.body);
|
||||
@@ -970,43 +1034,43 @@ html .clear-completed:active {
|
||||
}
|
||||
`;
|
||||
|
||||
const RESPONSIVE = `const { Component, QWeb, utils } = owl;
|
||||
|
||||
class SubWidget extends Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.template = "subwidget";
|
||||
}
|
||||
const RESPONSIVE = `class SubWidget extends owl.Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.template = "subwidget";
|
||||
}
|
||||
}
|
||||
|
||||
class ResponsiveWidget extends Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.template = "responsivewidget";
|
||||
this.widgets = { SubWidget };
|
||||
}
|
||||
class ResponsiveWidget extends owl.Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.template = "responsivewidget";
|
||||
this.widgets = { SubWidget };
|
||||
}
|
||||
}
|
||||
|
||||
function isMobile() {
|
||||
return window.innerWidth <= 768;
|
||||
return window.innerWidth <= 768;
|
||||
}
|
||||
|
||||
const env = {
|
||||
qweb: new QWeb(TEMPLATES),
|
||||
isMobile: isMobile()
|
||||
qweb: new owl.QWeb(TEMPLATES),
|
||||
isMobile: isMobile()
|
||||
};
|
||||
|
||||
const widget = new ResponsiveWidget(env);
|
||||
widget.mount(document.body);
|
||||
|
||||
window.addEventListener(
|
||||
"resize",
|
||||
utils.debounce(function() {
|
||||
const _isMobile = isMobile();
|
||||
if (_isMobile !== env.isMobile) {
|
||||
widget.updateEnv({ isMobile: _isMobile });
|
||||
}
|
||||
}, 20)
|
||||
"resize",
|
||||
owl.utils.debounce(function() {
|
||||
const _isMobile = isMobile();
|
||||
if (_isMobile !== env.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