[IMP] owl: update to v0.13.0

This commit is contained in:
Géry Debongnie
2019-06-05 10:58:52 +02:00
parent c394b955ed
commit 449c9beaab
5 changed files with 324 additions and 207 deletions
+274 -164
View File
@@ -1,71 +1,6 @@
(function (exports) { (function (exports) {
'use strict'; 'use strict';
/**
* We define here a simple event bus: it can
* - emit events
* - add/remove listeners.
*
* This is a useful pattern of communication in many cases. For OWL, each
* components and stores are event buses.
*/
//------------------------------------------------------------------------------
// EventBus
//------------------------------------------------------------------------------
class EventBus {
constructor() {
this.subscriptions = {};
}
/**
* Add a listener for the 'eventType' events.
*
* Note that the 'owner' of this event can be anything, but will more likely
* be a widget or a class. The idea is that the callback will be called with
* the proper owner bound.
*
* Also, the owner should be kind of unique. This will be used to remove the
* listener.
*/
on(eventType, owner, callback) {
if (!callback) {
throw new Error("Missing callback");
}
if (!this.subscriptions[eventType]) {
this.subscriptions[eventType] = [];
}
this.subscriptions[eventType].push({
owner,
callback
});
}
/**
* Remove a listener
*/
off(eventType, owner) {
const subs = this.subscriptions[eventType];
if (subs) {
this.subscriptions[eventType] = subs.filter(s => s.owner !== owner);
}
}
/**
* Emit an event of type 'eventType'. Any extra arguments will be passed to
* the listeners callback.
*/
trigger(eventType, ...args) {
const subs = this.subscriptions[eventType] || [];
for (let i = 0, iLen = subs.length; i < iLen; i++) {
const sub = subs[i];
sub.callback.call(sub.owner, ...args);
}
}
/**
* Remove all subscriptions.
*/
clear() {
this.subscriptions = {};
}
}
/** /**
* Owl Observer * Owl Observer
* *
@@ -171,9 +106,15 @@
} }
} }
set(target, key, value) { set(target, key, value) {
this.rev++; let alreadyDefined = key in target &&
Object.getOwnPropertyDescriptor(target, key).configurable === false;
if (alreadyDefined) {
target[key] = value;
}
else {
this._addProp(target, key, value); this._addProp(target, key, value);
target.__owl__.rev++; this._updateRevNumber(target);
}
this.notifyChange(); this.notifyChange();
} }
_observeObj(obj, parent) { _observeObj(obj, parent) {
@@ -201,23 +142,26 @@
}, },
set(newVal) { set(newVal) {
if (newVal !== value) { if (newVal !== value) {
self.rev++;
if (!self.allowMutations) { if (!self.allowMutations) {
throw new Error(`Observed state cannot be changed here! (key: "${key}", val: "${newVal}")`); throw new Error(`Observed state cannot be changed here! (key: "${key}", val: "${newVal}")`);
} }
self._updateRevNumber(obj);
value = newVal; value = newVal;
self.observe(newVal, obj); self.observe(newVal, obj);
obj.__owl__.rev++;
let parent = obj;
do {
parent.__owl__.deepRev++;
} while ((parent = parent.__owl__.parent) && parent !== obj);
self.notifyChange(); self.notifyChange();
} }
} }
}); });
this.observe(value, obj); this.observe(value, obj);
} }
_updateRevNumber(target) {
this.rev++;
target.__owl__.rev++;
let parent = target;
do {
parent.__owl__.deepRev++;
} while ((parent = parent.__owl__.parent) && parent !== target);
}
} }
/** /**
@@ -913,6 +857,14 @@
} }
} }
return classes.join(" "); return classes.join(" ");
},
shallowEqual(p1, p2) {
for (let k in p1) {
if (p1[k] !== p2[k]) {
return false;
}
}
return true;
} }
}; };
function parseXML(xml) { function parseXML(xml) {
@@ -1069,7 +1021,11 @@
template = new Function("context", "extra", ctx.code.join("\n")); template = new Function("context", "extra", ctx.code.join("\n"));
} }
catch (e) { catch (e) {
throw new Error(`Invalid generated code while compiling template '${ctx.templateName.replace(/`/g, "'")}': ${e.message}`); const templateName = ctx.templateName.replace(/`/g, "'");
console.groupCollapsed(`Invalid Code generated by ${templateName}`);
console.warn(ctx.code.join("\n"));
console.groupEnd();
throw new Error(`Invalid generated code while compiling template '${templateName}': ${e.message}`);
} }
if (isDebug) { if (isDebug) {
console.log(`Template: ${this.templates[name].elem.outerHTML}\nCompiled code:\n` + console.log(`Template: ${this.templates[name].elem.outerHTML}\nCompiled code:\n` +
@@ -1471,19 +1427,20 @@
} }
/** /**
* Perform string interpolation on the given string. Note that if the whole * Perform string interpolation on the given string. Note that if the whole
* string is an expression, it simply returns it (formatted). * string is an expression, it simply returns it (formatted and enclosed in
* parentheses).
* For instance: * For instance:
* 'Hello {{x}}!' -> `Hello ${x}` * 'Hello {{x}}!' -> `Hello ${x}`
* '{{x}}' -> x * '{{x ? 'a': 'b'}}' -> (x ? 'a' : 'b')
*/ */
interpolate(s) { interpolate(s) {
let matches = s.match(/\{\{.*?\}\}/g); let matches = s.match(/\{\{.*?\}\}/g);
if (matches && matches[0].length === s.length) { if (matches && matches[0].length === s.length) {
return this.formatExpression(s.slice(2, -2)); return `(${this.formatExpression(s.slice(2, -2))})`;
} }
matches = s.match(/\#\{.*?\}/g); matches = s.match(/\#\{.*?\}/g);
if (matches && matches[0].length === s.length) { if (matches && matches[0].length === s.length) {
return this.formatExpression(s.slice(2, -1)); return `(${this.formatExpression(s.slice(2, -1))})`;
} }
let formatter = expr => "${" + this.formatExpression(expr) + "}"; let formatter = expr => "${" + this.formatExpression(expr) + "}";
let r = s let r = s
@@ -1502,7 +1459,7 @@
// Widget // Widget
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
let nextId = 1; let nextId = 1;
class Component extends EventBus { class Component {
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Lifecycle // Lifecycle
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@@ -1526,7 +1483,6 @@
* the t-widget directive in a template) * the t-widget directive in a template)
*/ */
constructor(parent, props) { constructor(parent, props) {
super();
this.refs = {}; this.refs = {};
const defaultProps = this.constructor.defaultProps; const defaultProps = this.constructor.defaultProps;
if (defaultProps) { if (defaultProps) {
@@ -1565,6 +1521,10 @@
mountedHandlers: {} mountedHandlers: {}
}; };
} }
/**
* The `el` is the root element of the widget. Note that it could be null:
* this is the case if the widget is not mounted yet, or is destroyed.
*/
get el() { get el() {
return this.__owl__.vnode ? this.__owl__.vnode.elm : null; return this.__owl__.vnode ? this.__owl__.vnode.elm : null;
} }
@@ -1641,10 +1601,16 @@
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Public // Public
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
/**
* Mount the component to a target element.
*
* This should only be done if the component was created manually. Components
* created declaratively in templates are managed by the Owl system.
*/
async mount(target) { async mount(target) {
const vnode = await this._prepare(); const vnode = await this._prepare();
if (this.__owl__.isDestroyed) { if (this.__owl__.isDestroyed) {
// widget was destroyed before we get here... // component was destroyed before we get here...
return; return;
} }
this._patch(vnode); this._patch(vnode);
@@ -1653,34 +1619,6 @@
this._callMounted(); this._callMounted();
} }
} }
_callMounted() {
const __owl__ = this.__owl__;
const children = __owl__.children;
for (let id in children) {
const comp = children[id];
if (!comp.__owl__.isMounted && this.el.contains(comp.el)) {
comp._callMounted();
}
}
__owl__.isMounted = true;
const handlers = __owl__.mountedHandlers;
for (let key in handlers) {
handlers[key]();
}
this.mounted();
}
_callWillUnmount() {
this.willUnmount();
const __owl__ = this.__owl__;
__owl__.isMounted = false;
const children = __owl__.children;
for (let id in children) {
const comp = children[id];
if (comp.__owl__.isMounted) {
comp._callWillUnmount();
}
}
}
unmount() { unmount() {
if (this.__owl__.isMounted) { if (this.__owl__.isMounted) {
this._callWillUnmount(); this._callWillUnmount();
@@ -1717,6 +1655,15 @@
} }
} }
} }
/**
* Destroy the component. This operation is quite complex:
* - it recursively destroy all children
* - call the willUnmount hooks if necessary
* - remove the dom node from the dom
*
* This should only be called manually if you created the widget. Most widgets
* will be automatically destroyed.
*/
destroy() { destroy() {
const __owl__ = this.__owl__; const __owl__ = this.__owl__;
if (!__owl__.isDestroyed) { if (!__owl__.isDestroyed) {
@@ -1727,26 +1674,11 @@
} }
} }
} }
_destroy(parent) { /**
const __owl__ = this.__owl__; * This method is called by the component system whenever its props are
const isMounted = __owl__.isMounted; * updated. If it returns true, then the component will be rendered.
if (isMounted) { * Otherwise, it will skip the rendering (also, its props will not be updated)
this.willUnmount(); */
__owl__.isMounted = false;
}
const children = __owl__.children;
for (let key in children) {
children[key]._destroy(this);
}
if (parent) {
let id = __owl__.id;
delete parent.__owl__.children[id];
__owl__.parent = null;
}
this.clear();
__owl__.isDestroyed = true;
delete __owl__.vnode;
}
shouldUpdate(nextProps) { shouldUpdate(nextProps) {
return true; return true;
} }
@@ -1769,12 +1701,79 @@
await this.render(true); await this.render(true);
} }
} }
/**
* Sets a key (from the state) to a specific value. This is mostly useful to
* work around the limitation in observed value with new keys.
*/
set(target, key, value) { set(target, key, value) {
this.__owl__.observer.set(target, key, value); this.__owl__.observer.set(target, key, value);
} }
/**
* Emit a custom event of type 'eventType' with the given 'payload' on the
* component's el, if it exists. However, note that the event will only bubble
* up to the parent DOM nodes. Thus, it must be called between mounted() and
* willUnmount().
*/
trigger(eventType, payload) {
if (this.el) {
const ev = new CustomEvent(eventType, {
bubbles: true,
cancelable: true,
detail: payload
});
this.el.dispatchEvent(ev);
}
}
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Private // Private
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
_destroy(parent) {
const __owl__ = this.__owl__;
const isMounted = __owl__.isMounted;
if (isMounted) {
this.willUnmount();
__owl__.isMounted = false;
}
const children = __owl__.children;
for (let key in children) {
children[key]._destroy(this);
}
if (parent) {
let id = __owl__.id;
delete parent.__owl__.children[id];
__owl__.parent = null;
}
__owl__.isDestroyed = true;
delete __owl__.vnode;
}
_callMounted() {
const __owl__ = this.__owl__;
const children = __owl__.children;
for (let id in children) {
const comp = children[id];
if (!comp.__owl__.isMounted && this.el.contains(comp.el)) {
comp._callMounted();
}
}
__owl__.isMounted = true;
const handlers = __owl__.mountedHandlers;
for (let key in handlers) {
handlers[key]();
}
this.mounted();
}
_callWillUnmount() {
this.willUnmount();
const __owl__ = this.__owl__;
__owl__.isMounted = false;
const children = __owl__.children;
for (let id in children) {
const comp = children[id];
if (comp.__owl__.isMounted) {
comp._callWillUnmount();
}
}
}
async _updateProps(nextProps, forceUpdate = false, patchQueue) { async _updateProps(nextProps, forceUpdate = false, patchQueue) {
const shouldUpdate = forceUpdate || this.shouldUpdate(nextProps); const shouldUpdate = forceUpdate || this.shouldUpdate(nextProps);
if (shouldUpdate) { if (shouldUpdate) {
@@ -1994,6 +1993,71 @@
return result; return result;
} }
/**
* We define here a simple event bus: it can
* - emit events
* - add/remove listeners.
*
* This is a useful pattern of communication in many cases. For OWL, each
* components and stores are event buses.
*/
//------------------------------------------------------------------------------
// EventBus
//------------------------------------------------------------------------------
class EventBus {
constructor() {
this.subscriptions = {};
}
/**
* Add a listener for the 'eventType' events.
*
* Note that the 'owner' of this event can be anything, but will more likely
* be a widget or a class. The idea is that the callback will be called with
* the proper owner bound.
*
* Also, the owner should be kind of unique. This will be used to remove the
* listener.
*/
on(eventType, owner, callback) {
if (!callback) {
throw new Error("Missing callback");
}
if (!this.subscriptions[eventType]) {
this.subscriptions[eventType] = [];
}
this.subscriptions[eventType].push({
owner,
callback
});
}
/**
* Remove a listener
*/
off(eventType, owner) {
const subs = this.subscriptions[eventType];
if (subs) {
this.subscriptions[eventType] = subs.filter(s => s.owner !== owner);
}
}
/**
* Emit an event of type 'eventType'. Any extra arguments will be passed to
* the listeners callback.
*/
trigger(eventType, ...args) {
const subs = this.subscriptions[eventType] || [];
for (let i = 0, iLen = subs.length; i < iLen; i++) {
const sub = subs[i];
sub.callback.call(sub.owner, ...args);
}
}
/**
* Remove all subscriptions.
*/
clear() {
this.subscriptions = {};
}
}
/** /**
* Owl QWeb Directives * Owl QWeb Directives
* *
@@ -2281,7 +2345,7 @@
* - t-on * - t-on
* - t-ref * - t-ref
* - t-transition * - t-transition
* - t-widget/t-props/t-keepalive * - t-widget/t-keepalive
* - t-mounted * - t-mounted
*/ */
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -2426,6 +2490,9 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// t-widget // t-widget
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
const T_WIDGET_MODS_CODE = Object.assign({}, MODS_CODE, {
self: "if (e.target !== vn.elm) {return}"
});
/** /**
* The t-widget directive is certainly a complicated and hard to maintain piece * The t-widget directive is certainly a complicated and hard to maintain piece
* of code. To help you, fellow developer, if you have to maintain it, I offer * of code. To help you, fellow developer, if you have to maintain it, I offer
@@ -2438,11 +2505,15 @@
* ```xml * ```xml
* <t t-widget="child" * <t t-widget="child"
* t-key="'somestring'" * t-key="'somestring'"
* t-props="{flag:state.flag}" * flag="state.flag"
* t-transition="fade"/> * t-transition="fade"/>
* ``` * ```
* *
* ```js * ```js
* // we assign utils on top of the function because it will be useful for
* // each widgets
* let utils = this.utils;
*
* // this is the virtual node representing the parent div * // this is the virtual node representing the parent div
* let c1 = [], p1 = { key: 1 }; * let c1 = [], p1 = { key: 1 };
* var vn1 = h("div", p1, c1); * var vn1 = h("div", p1, c1);
@@ -2457,7 +2528,7 @@
* let _2_index = c1.length; * let _2_index = c1.length;
* c1.push(null); * c1.push(null);
* *
* // def3 is the deferred that will contain later either the new widget * // def3 is the promise that will contain later either the new widget
* // creation, or the props update... * // creation, or the props update...
* let def3; * let def3;
* *
@@ -2476,31 +2547,32 @@
* // computation, so it is certainly better to do it only once * // computation, so it is certainly better to do it only once
* let props4 = { flag: context["state"].flag }; * let props4 = { flag: context["state"].flag };
* *
* // If we have a widget, currently rendering, but not ready yet, and which was * // If we have a widget, currently rendering, but not ready yet, we do not want
* // rendered with different props, we do not want to wait for it to be ready, * // to wait for it to be ready if we can avoid it
* // then update it. We simply destroy it, and start anew. * if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
* if ( * // we check if the props are the same. In that case, we can simply reuse
* w4 && * // the previous rendering and skip all useless work
* w4.__owl__.renderPromise && * if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
* !w4.__owl__.isStarted && * def3 = w4.__owl__.renderPromise;
* props4 !== w4.__owl__.renderProps * } else {
* ) { * // if the props are not the same, we destroy the widget and starts anew.
* // this will be faster than waiting for its rendering, then updating it
* w4.destroy(); * w4.destroy();
* w4 = false; * w4 = false;
* } * }
* }
* *
* if (!w4) { * if (!w4) {
* // in this situation, we need to create a new widget. First step is * // in this situation, we need to create a new widget. First step is
* // to get a reference to the class, then create an instance with * // to get a reference to the class, then create an instance with
* // current context as parent, and the props. * // current context as parent, and the props.
* let W4 = context.widgets["child"]; * let W4 = context.widgets && context.widgets[widgetKey4] || QWeb.widgets[widgetKey4];
* if (!W4) { * if (!W4) {
* throw new Error("Cannot find the definition of widget 'child'"); * throw new Error("Cannot find the definition of widget 'child'");
* } * }
* w4 = new W4(owner, props4); * w4 = new W4(owner, props4);
* *
* let utils = this.utils;
*
* // Whenever we rerender the parent widget, we need to be sure that we * // Whenever we rerender the parent widget, we need to be sure that we
* // are able to find the widget instance. To do that, we register it to * // are able to find the widget instance. To do that, we register it to
* // the parent cmap (children map). Note that the 'template' key is * // the parent cmap (children map). Note that the 'template' key is
@@ -2557,7 +2629,9 @@
* } else { * } else {
* // this is the 'update' path of the directive. * // this is the 'update' path of the directive.
* // the call to _updateProps is the actual widget update * // the call to _updateProps is the actual widget update
* def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue); * // Note that we only update the props if we cannot reuse the previous
* // rendering work (in the case it was rendered with the same props)
* def3 = def3 || w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
* def3 = def3.then(() => { * def3 = def3.then(() => {
* // if widget was destroyed in the meantime, we do nothing (so, this * // if widget was destroyed in the meantime, we do nothing (so, this
* // means that the parent's element children list will have a null in * // means that the parent's element children list will have a null in
@@ -2588,28 +2662,42 @@
ctx.rootContext.shouldDefineOwner = true; ctx.rootContext.shouldDefineOwner = true;
ctx.rootContext.shouldDefineQWeb = true; ctx.rootContext.shouldDefineQWeb = true;
ctx.rootContext.shouldDefineUtils = true; ctx.rootContext.shouldDefineUtils = true;
let props = node.getAttribute("t-props");
let keepAlive = node.getAttribute("t-keepalive") ? true : false; let keepAlive = node.getAttribute("t-keepalive") ? true : false;
// t-on- events and t-transition // t-on- events and t-transition
const events = []; const events = [];
let transition = ""; let transition = "";
const attributes = node.attributes; const attributes = node.attributes;
const props = {};
for (let i = 0; i < attributes.length; i++) { for (let i = 0; i < attributes.length; i++) {
const name = attributes[i].name; const name = attributes[i].name;
const value = attributes[i].textContent;
if (name.startsWith("t-on-")) { if (name.startsWith("t-on-")) {
events.push([name.slice(5), attributes[i].textContent]); const [eventName, ...mods] = name.slice(5).split(".");
let extraArgs;
let handlerName = value.replace(/\(.*\)/, function (args) {
extraArgs = args.slice(1, -1);
return "";
});
events.push([eventName, mods, handlerName, extraArgs]);
} }
else if (name === "t-transition") { else if (name === "t-transition") {
transition = attributes[i].textContent; transition = value;
}
else if (!name.startsWith("t-")) {
if (name !== "class" && name !== "style") {
// this is a prop!
props[name] = ctx.formatExpression(value);
}
} }
} }
let key = node.getAttribute("t-key"); let key = node.getAttribute("t-key");
if (key) { if (key) {
key = ctx.formatExpression(key); key = ctx.formatExpression(key);
} }
if (props) { // computing the props string representing the props object
props = ctx.formatExpression(props); let propStr = Object.keys(props)
} .map(k => k + ":" + props[k])
.join(",");
let dummyID = ctx.generateID(); let dummyID = ctx.generateID();
let defID = ctx.generateID(); let defID = ctx.generateID();
let widgetID = ctx.generateID(); let widgetID = ctx.generateID();
@@ -2660,7 +2748,7 @@
tattStyle = attVar; tattStyle = attVar;
} }
let updateClassCode = ""; let updateClassCode = "";
if (classAttr || tattClass || styleAttr || tattStyle) { if (classAttr || tattClass || styleAttr || tattStyle || events.length) {
let classCode = ""; let classCode = "";
if (classAttr) { if (classAttr) {
classCode = classCode =
@@ -2679,15 +2767,40 @@
}`; }`;
updateClassCode = `let cl=w${widgetID}.el.classList;for (let k in ${attVar}) {if (${attVar}[k]) {cl.add(k)} else {cl.remove(k)}}`; updateClassCode = `let cl=w${widgetID}.el.classList;for (let k in ${attVar}) {if (${attVar}[k]) {cl.add(k)} else {cl.remove(k)}}`;
} }
let eventsCode = events
.map(function ([eventName, mods, handlerName, extraArgs]) {
let params = extraArgs
? `owner, ${ctx.formatExpression(extraArgs)}`
: "owner";
let handler;
if (mods.length > 0) {
handler = `function (e) {`;
handler += mods
.map(function (mod) {
return T_WIDGET_MODS_CODE[mod];
})
.join("");
handler += `owner['${handlerName}'].call(${params}, e);}`;
}
else {
handler = `owner['${handlerName}'].bind(${params})`;
}
return `vn.elm.addEventListener('${eventName}', ${handler});`;
})
.join("");
const styleExpr = tattStyle || (styleAttr ? `'${styleAttr}'` : false); const styleExpr = tattStyle || (styleAttr ? `'${styleAttr}'` : false);
const styleCode = styleExpr ? `vn.elm.style = ${styleExpr}` : ""; const styleCode = styleExpr ? `vn.elm.style = ${styleExpr};` : "";
createHook = `vnode.data.hook = {create(_, vn){${classCode}${styleCode}}};`; createHook = `vnode.data.hook = {create(_, vn){${classCode}${styleCode}${eventsCode}}};`;
} }
ctx.addLine(`let w${widgetID} = ${templateID} in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[${templateID}]] : false;`); ctx.addLine(`let w${widgetID} = ${templateID} in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[${templateID}]] : false;`);
ctx.addLine(`let props${widgetID} = ${props || "{}"};`); ctx.addLine(`let props${widgetID} = {${propStr}};`);
ctx.addIf(`w${widgetID} && w${widgetID}.__owl__.renderPromise && !w${widgetID}.__owl__.vnode && props${widgetID} !== w${widgetID}.__owl__.renderProps`); ctx.addIf(`w${widgetID} && w${widgetID}.__owl__.renderPromise && !w${widgetID}.__owl__.vnode`);
ctx.addIf(`utils.shallowEqual(props${widgetID}, w${widgetID}.__owl__.renderProps)`);
ctx.addLine(`def${defID} = w${widgetID}.__owl__.renderPromise;`);
ctx.addElse();
ctx.addLine(`w${widgetID}.destroy();`); ctx.addLine(`w${widgetID}.destroy();`);
ctx.addLine(`w${widgetID} = false`); ctx.addLine(`w${widgetID} = false;`);
ctx.closeIf();
ctx.closeIf(); ctx.closeIf();
ctx.addIf(`!w${widgetID}`); ctx.addIf(`!w${widgetID}`);
// new widget // new widget
@@ -2697,16 +2810,13 @@
ctx.addLine(`if (!W${widgetID}) {throw new Error('Cannot find the definition of widget "' + widgetKey${widgetID} + '"')}`); ctx.addLine(`if (!W${widgetID}) {throw new Error('Cannot find the definition of widget "' + widgetKey${widgetID} + '"')}`);
ctx.addLine(`w${widgetID} = new W${widgetID}(owner, props${widgetID});`); ctx.addLine(`w${widgetID} = new W${widgetID}(owner, props${widgetID});`);
ctx.addLine(`context.__owl__.cmap[${templateID}] = w${widgetID}.__owl__.id;`); ctx.addLine(`context.__owl__.cmap[${templateID}] = w${widgetID}.__owl__.id;`);
for (let [event, method] of events) {
ctx.addLine(`w${widgetID}.on('${event}', owner, owner['${method}'])`);
}
ctx.addLine(`def${defID} = w${widgetID}._prepare();`); ctx.addLine(`def${defID} = w${widgetID}._prepare();`);
// hack: specify empty remove hook to prevent the node from being removed from the DOM // hack: specify empty remove hook to prevent the node from being removed from the DOM
// FIXME: click to re-add widget during remove transition -> leak // FIXME: click to re-add widget during remove transition -> leak
ctx.addLine(`def${defID} = def${defID}.then(vnode=>{${createHook}let pvnode=h(vnode.sel, {key: ${templateID}, hook: {insert(vn) {let nvn=w${widgetID}._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;${refExpr}${transitionsInsertCode}},remove() {},destroy(vn) {${finalizeWidgetCode}}}});c${ctx.parentNode}[_${dummyID}_index]=pvnode;w${widgetID}.__owl__.pvnode = pvnode;});`); ctx.addLine(`def${defID} = def${defID}.then(vnode=>{${createHook}let pvnode=h(vnode.sel, {key: ${templateID}, hook: {insert(vn) {let nvn=w${widgetID}._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;${refExpr}${transitionsInsertCode}},remove() {},destroy(vn) {${finalizeWidgetCode}}}});c${ctx.parentNode}[_${dummyID}_index]=pvnode;w${widgetID}.__owl__.pvnode = pvnode;});`);
ctx.addElse(); ctx.addElse();
// need to update widget // need to update widget
ctx.addLine(`def${defID} = w${widgetID}._updateProps(props${widgetID}, extra.forceUpdate, extra.patchQueue);`); ctx.addLine(`def${defID} = def${defID} || w${widgetID}._updateProps(props${widgetID}, extra.forceUpdate, extra.patchQueue);`);
let keepAliveCode = ""; let keepAliveCode = "";
if (keepAlive) { if (keepAlive) {
keepAliveCode = `pvnode.data.hook.insert = vn => {vn.elm.parentNode.replaceChild(w${widgetID}.el,vn.elm);vn.elm=w${widgetID}.el;w${widgetID}._remount();};`; keepAliveCode = `pvnode.data.hook.insert = vn => {vn.elm.parentNode.replaceChild(w${widgetID}.el,vn.elm);vn.elm=w${widgetID}.el;w${widgetID}._remount();};`;
@@ -3068,9 +3178,9 @@
exports.connect = connect; exports.connect = connect;
exports.utils = utils; exports.utils = utils;
exports.__info__.version = '0.12.0'; exports.__info__.version = '0.13.0';
exports.__info__.date = '2019-05-29T09:26:54.764Z'; exports.__info__.date = '2019-06-05T08:48:29.138Z';
exports.__info__.hash = 'f60904b'; exports.__info__.hash = '7d3c374';
exports.__info__.url = 'https://github.com/odoo/owl'; exports.__info__.url = 'https://github.com/odoo/owl';
}(this.owl = this.owl || {})); }(this.owl = this.owl || {}));
+20 -17
View File
@@ -39,26 +39,32 @@ const DEFAULT_HTML = `<!DOCTYPE html>
</html> </html>
`; `;
const APP_PY = `import sys const APP_PY = `#!/usr/bin/env python3
import thread
import webbrowser import threading
import time import time
import BaseHTTPServer, SimpleHTTPServer from http.server import SimpleHTTPRequestHandler, HTTPServer
def start_server(): def start_server():
httpd = BaseHTTPServer.HTTPServer(('127.0.0.1', 3600), SimpleHTTPServer.SimpleHTTPRequestHandler) SimpleHTTPRequestHandler.extensions_map['.js'] = 'application/javascript'
httpd = HTTPServer(('0.0.0.0', 3600), SimpleHTTPRequestHandler)
httpd.serve_forever() httpd.serve_forever()
thread.start_new_thread(start_server,())
url = 'http://127.0.0.1:3600' url = 'http://127.0.0.1:3600'
webbrowser.open_new(url)
if __name__ == "__main__":
print("Owl Application")
print("---------------")
print("Server running on: {}".format(url))
threading.Thread(target=start_server, daemon=True).start()
while True: while True:
try: try:
time.sleep(1) time.sleep(1)
except KeyboardInterrupt: except KeyboardInterrupt:
sys.exit(0) httpd.server_close()
quit(0)
`; `;
/** /**
@@ -242,20 +248,20 @@ class App extends owl.Component {
}); });
} }
updateCode(ev) { updateCode(ev) {
this.state[ev.type] = ev.value; this.state[ev.detail.type] = ev.detail.value;
} }
toggleLayout() { toggleLayout() {
this.state.splitLayout = !this.state.splitLayout; this.state.splitLayout = !this.state.splitLayout;
} }
updatePanelHeight(ev) { updatePanelHeight(ev) {
if (!ev.delta) { if (!ev.detail.delta) {
return; return;
} }
let height = this.state.topPanelHeight; let height = this.state.topPanelHeight;
if (!height) { if (!height) {
height = document.getElementsByClassName("tabbed-editor")[0].clientHeight; height = document.getElementsByClassName("tabbed-editor")[0].clientHeight;
} }
this.state.topPanelHeight = height + ev.delta; this.state.topPanelHeight = height + ev.detail.delta;
} }
async downloadCode() { async downloadCode() {
@@ -287,10 +293,11 @@ class TabbedEditor extends owl.Component {
this.sessions[tab].setUndoManager(new ace.UndoManager()); this.sessions[tab].setUndoManager(new ace.UndoManager());
} }
} }
this.editor = null;
} }
mounted() { mounted() {
this.editor = ace.edit(this.refs.editor); this.editor = this.editor || ace.edit(this.refs.editor);
this.editor.setValue(this.props[this.state.currentTab], -1); this.editor.setValue(this.props[this.state.currentTab], -1);
this.editor.setFontSize("12px"); this.editor.setFontSize("12px");
@@ -314,11 +321,7 @@ class TabbedEditor extends owl.Component {
const session = this.sessions[this.state.currentTab]; const session = this.sessions[this.state.currentTab];
session.setValue(this.props[this.state.currentTab], -1); session.setValue(this.props[this.state.currentTab], -1);
this.editor.setSession(session); this.editor.setSession(session);
} this.editor.resize();
willUnmount() {
this.editor.destroy();
delete this.editor;
} }
setTab(tab) { setTab(tab) {
+2
View File
@@ -185,4 +185,6 @@ body {
font-size: 30px; font-size: 30px;
color: darkred; color: darkred;
text-align: center; text-align: center;
padding-left: 30px;
padding-right: 30px;
} }
+3 -3
View File
@@ -85,7 +85,7 @@ const WIDGET_COMPOSITION_XML = `<templates>
<div t-name="App"> <div t-name="App">
<t t-widget="ClickCounter"/> <t t-widget="ClickCounter"/>
<t t-widget="InputWidget" t-props="{reverse: true}"/> <t t-widget="InputWidget" reverse="true"/>
</div> </div>
</templates>`; </templates>`;
@@ -287,7 +287,7 @@ const LIFECYCLE_DEMO_XML = `<templates>
<button t-on-click="increment">Increment</button> <button t-on-click="increment">Increment</button>
<button t-on-click="toggleSubWidget">ToggleSubWidget</button> <button t-on-click="toggleSubWidget">ToggleSubWidget</button>
<div t-if="state.flag"> <div t-if="state.flag">
<t t-widget="HookWidget" t-props="{n:state.n}"/> <t t-widget="HookWidget" n="state.n"/>
</div> </div>
</div> </div>
<div t-name="HookWidget" t-on-click="increment">Demo Sub Widget. Props: <t t-esc="props.n"/>. State: <t t-esc="state.n"/>. (click on me to update me)</div> <div t-name="HookWidget" t-on-click="increment">Demo Sub Widget. Props: <t t-esc="props.n"/>. State: <t t-esc="state.n"/>. (click on me to update me)</div>
@@ -515,7 +515,7 @@ const TODO_APP_STORE_XML = `<templates>
<label for="toggle-all"></label> <label for="toggle-all"></label>
<ul class="todo-list"> <ul class="todo-list">
<t t-foreach="visibleTodos" t-as="todo"> <t t-foreach="visibleTodos" t-as="todo">
<t t-widget="TodoItem" t-key="todo.id" t-props="todo"/> <t t-widget="TodoItem" t-key="todo.id" id="todo.id" completed="todo.completed" title="todo.title"/>
</t> </t>
</ul> </ul>
</section> </section>
+11 -9
View File
@@ -11,7 +11,9 @@
</div> </div>
<div t-name="App" class="playground"> <div t-name="App" class="playground">
<div class="left-bar" t-att-style="leftPaneStyle" t-att-class="{split: state.splitLayout}"> <div class="left-bar" t-att-class="{split: state.splitLayout}"
t-att-style="leftPaneStyle"
t-on-updateCode="updateCode">
<div class="menubar"> <div class="menubar">
<a class="btn run-code flash" t-on-click="runCode" title="Execute this Code">▶ Run</a> <a class="btn run-code flash" t-on-click="runCode" title="Execute this Code">▶ Run</a>
<select t-on-change="setSample"> <select t-on-change="setSample">
@@ -22,20 +24,20 @@
<a class="btn flash" t-on-click="downloadCode" title="Download a Zip with this Code"><i class="fas fa-download"></i></a> <a class="btn flash" t-on-click="downloadCode" title="Download a Zip with this Code"><i class="fas fa-download"></i></a>
<a class="layout-selector flash" t-on-click="toggleLayout" title="Toggle Layout"><i class="fas" t-att-class="state.splitLayout ? 'fa-toggle-on' : 'fa-toggle-off'"></i></a> <a class="layout-selector flash" t-on-click="toggleLayout" title="Toggle Layout"><i class="fas" t-att-class="state.splitLayout ? 'fa-toggle-on' : 'fa-toggle-off'"></i></a>
</div> </div>
<t t-if="state.splitLayout">
<t t-widget="TabbedEditor" <t t-widget="TabbedEditor"
t-props="{js:state.js, css:false, xml: false}" js="state.js"
t-on-updateCode="updateCode" css="!state.splitLayout and state.css"
xml="!state.splitLayout and state.js"
t-att-style="topEditorStyle"/> t-att-style="topEditorStyle"/>
<t t-if="state.splitLayout">
<div class="separator horizontal"/> <div class="separator horizontal"/>
<t t-widget="TabbedEditor" t-keepalive="1" <t t-widget="TabbedEditor" t-keepalive="1"
t-props="{js:false, css:state.css, xml: state.xml, resizeable: true}" js="false"
t-on-updateCode="updateCode" css="state.css"
xml="state.xml"
resizeable="true"
t-on-updatePanelHeight="updatePanelHeight"/> t-on-updatePanelHeight="updatePanelHeight"/>
</t> </t>
<t t-else="1">
<t t-widget="TabbedEditor" t-props="{js:state.js, css:state.css, xml: state.xml, display: 'js|xml|css'}" t-on-updateCode="updateCode"/>
</t>
</div> </div>
<div class="separator vertical" t-on-mousedown="onMouseDown"/> <div class="separator vertical" t-on-mousedown="onMouseDown"/>
<div class="right-pane" t-att-style="rightPaneStyle"> <div class="right-pane" t-att-style="rightPaneStyle">