[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
+277 -167
View File
@@ -1,71 +1,6 @@
(function (exports) {
'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
*
@@ -171,9 +106,15 @@
}
}
set(target, key, value) {
this.rev++;
this._addProp(target, key, value);
target.__owl__.rev++;
let alreadyDefined = key in target &&
Object.getOwnPropertyDescriptor(target, key).configurable === false;
if (alreadyDefined) {
target[key] = value;
}
else {
this._addProp(target, key, value);
this._updateRevNumber(target);
}
this.notifyChange();
}
_observeObj(obj, parent) {
@@ -201,23 +142,26 @@
},
set(newVal) {
if (newVal !== value) {
self.rev++;
if (!self.allowMutations) {
throw new Error(`Observed state cannot be changed here! (key: "${key}", val: "${newVal}")`);
}
self._updateRevNumber(obj);
value = newVal;
self.observe(newVal, obj);
obj.__owl__.rev++;
let parent = obj;
do {
parent.__owl__.deepRev++;
} while ((parent = parent.__owl__.parent) && parent !== obj);
self.notifyChange();
}
}
});
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(" ");
},
shallowEqual(p1, p2) {
for (let k in p1) {
if (p1[k] !== p2[k]) {
return false;
}
}
return true;
}
};
function parseXML(xml) {
@@ -1069,7 +1021,11 @@
template = new Function("context", "extra", ctx.code.join("\n"));
}
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) {
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
* string is an expression, it simply returns it (formatted).
* string is an expression, it simply returns it (formatted and enclosed in
* parentheses).
* For instance:
* 'Hello {{x}}!' -> `Hello ${x}`
* '{{x}}' -> x
* '{{x ? 'a': 'b'}}' -> (x ? 'a' : 'b')
*/
interpolate(s) {
let matches = s.match(/\{\{.*?\}\}/g);
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);
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 r = s
@@ -1502,7 +1459,7 @@
// Widget
//------------------------------------------------------------------------------
let nextId = 1;
class Component extends EventBus {
class Component {
//--------------------------------------------------------------------------
// Lifecycle
//--------------------------------------------------------------------------
@@ -1526,7 +1483,6 @@
* the t-widget directive in a template)
*/
constructor(parent, props) {
super();
this.refs = {};
const defaultProps = this.constructor.defaultProps;
if (defaultProps) {
@@ -1565,6 +1521,10 @@
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() {
return this.__owl__.vnode ? this.__owl__.vnode.elm : null;
}
@@ -1641,10 +1601,16 @@
//--------------------------------------------------------------------------
// 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) {
const vnode = await this._prepare();
if (this.__owl__.isDestroyed) {
// widget was destroyed before we get here...
// component was destroyed before we get here...
return;
}
this._patch(vnode);
@@ -1653,34 +1619,6 @@
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() {
if (this.__owl__.isMounted) {
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() {
const __owl__ = this.__owl__;
if (!__owl__.isDestroyed) {
@@ -1727,26 +1674,11 @@
}
}
}
_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;
}
this.clear();
__owl__.isDestroyed = true;
delete __owl__.vnode;
}
/**
* This method is called by the component system whenever its props are
* updated. If it returns true, then the component will be rendered.
* Otherwise, it will skip the rendering (also, its props will not be updated)
*/
shouldUpdate(nextProps) {
return true;
}
@@ -1769,12 +1701,79 @@
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) {
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
//--------------------------------------------------------------------------
_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) {
const shouldUpdate = forceUpdate || this.shouldUpdate(nextProps);
if (shouldUpdate) {
@@ -1994,6 +1993,71 @@
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
*
@@ -2281,7 +2345,7 @@
* - t-on
* - t-ref
* - t-transition
* - t-widget/t-props/t-keepalive
* - t-widget/t-keepalive
* - t-mounted
*/
//------------------------------------------------------------------------------
@@ -2426,6 +2490,9 @@
//------------------------------------------------------------------------------
// 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
* of code. To help you, fellow developer, if you have to maintain it, I offer
@@ -2438,11 +2505,15 @@
* ```xml
* <t t-widget="child"
* t-key="'somestring'"
* t-props="{flag:state.flag}"
* flag="state.flag"
* t-transition="fade"/>
* ```
*
* ```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
* let c1 = [], p1 = { key: 1 };
* var vn1 = h("div", p1, c1);
@@ -2457,7 +2528,7 @@
* let _2_index = c1.length;
* 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...
* let def3;
*
@@ -2476,31 +2547,32 @@
* // computation, so it is certainly better to do it only once
* let props4 = { flag: context["state"].flag };
*
* // If we have a widget, currently rendering, but not ready yet, and which was
* // rendered with different props, we do not want to wait for it to be ready,
* // then update it. We simply destroy it, and start anew.
* if (
* w4 &&
* w4.__owl__.renderPromise &&
* !w4.__owl__.isStarted &&
* props4 !== w4.__owl__.renderProps
* ) {
* w4.destroy();
* w4 = false;
* // If we have a widget, currently rendering, but not ready yet, we do not want
* // to wait for it to be ready if we can avoid it
* if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
* // we check if the props are the same. In that case, we can simply reuse
* // the previous rendering and skip all useless work
* if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
* def3 = w4.__owl__.renderPromise;
* } 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 = false;
* }
* }
*
* if (!w4) {
* // 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
* // current context as parent, and the props.
* let W4 = context.widgets["child"];
* let W4 = context.widgets && context.widgets[widgetKey4] || QWeb.widgets[widgetKey4];
* if (!W4) {
* throw new Error("Cannot find the definition of widget 'child'");
* }
* w4 = new W4(owner, props4);
*
* let utils = this.utils;
*
* // 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
* // the parent cmap (children map). Note that the 'template' key is
@@ -2557,7 +2629,9 @@
* } else {
* // this is the 'update' path of the directive.
* // 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(() => {
* // 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
@@ -2588,28 +2662,42 @@
ctx.rootContext.shouldDefineOwner = true;
ctx.rootContext.shouldDefineQWeb = true;
ctx.rootContext.shouldDefineUtils = true;
let props = node.getAttribute("t-props");
let keepAlive = node.getAttribute("t-keepalive") ? true : false;
// t-on- events and t-transition
const events = [];
let transition = "";
const attributes = node.attributes;
const props = {};
for (let i = 0; i < attributes.length; i++) {
const name = attributes[i].name;
const value = attributes[i].textContent;
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") {
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");
if (key) {
key = ctx.formatExpression(key);
}
if (props) {
props = ctx.formatExpression(props);
}
// computing the props string representing the props object
let propStr = Object.keys(props)
.map(k => k + ":" + props[k])
.join(",");
let dummyID = ctx.generateID();
let defID = ctx.generateID();
let widgetID = ctx.generateID();
@@ -2660,7 +2748,7 @@
tattStyle = attVar;
}
let updateClassCode = "";
if (classAttr || tattClass || styleAttr || tattStyle) {
if (classAttr || tattClass || styleAttr || tattStyle || events.length) {
let classCode = "";
if (classAttr) {
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)}}`;
}
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 styleCode = styleExpr ? `vn.elm.style = ${styleExpr}` : "";
createHook = `vnode.data.hook = {create(_, vn){${classCode}${styleCode}}};`;
const styleCode = styleExpr ? `vn.elm.style = ${styleExpr};` : "";
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 props${widgetID} = ${props || "{}"};`);
ctx.addIf(`w${widgetID} && w${widgetID}.__owl__.renderPromise && !w${widgetID}.__owl__.vnode && props${widgetID} !== w${widgetID}.__owl__.renderProps`);
ctx.addLine(`let props${widgetID} = {${propStr}};`);
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} = false`);
ctx.addLine(`w${widgetID} = false;`);
ctx.closeIf();
ctx.closeIf();
ctx.addIf(`!w${widgetID}`);
// new widget
@@ -2697,16 +2810,13 @@
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(`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();`);
// 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
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();
// 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 = "";
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();};`;
@@ -3068,9 +3178,9 @@
exports.connect = connect;
exports.utils = utils;
exports.__info__.version = '0.12.0';
exports.__info__.date = '2019-05-29T09:26:54.764Z';
exports.__info__.hash = 'f60904b';
exports.__info__.version = '0.13.0';
exports.__info__.date = '2019-06-05T08:48:29.138Z';
exports.__info__.hash = '7d3c374';
exports.__info__.url = 'https://github.com/odoo/owl';
}(this.owl = this.owl || {}));
+30 -27
View File
@@ -39,26 +39,32 @@ const DEFAULT_HTML = `<!DOCTYPE html>
</html>
`;
const APP_PY = `import sys
import thread
import webbrowser
const APP_PY = `#!/usr/bin/env python3
import threading
import time
import BaseHTTPServer, SimpleHTTPServer
from http.server import SimpleHTTPRequestHandler, HTTPServer
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()
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)
if __name__ == "__main__":
print("Owl Application")
print("---------------")
print("Server running on: {}".format(url))
threading.Thread(target=start_server, daemon=True).start()
while True:
try:
time.sleep(1)
except KeyboardInterrupt:
httpd.server_close()
quit(0)
`;
/**
@@ -242,20 +248,20 @@ class App extends owl.Component {
});
}
updateCode(ev) {
this.state[ev.type] = ev.value;
this.state[ev.detail.type] = ev.detail.value;
}
toggleLayout() {
this.state.splitLayout = !this.state.splitLayout;
}
updatePanelHeight(ev) {
if (!ev.delta) {
if (!ev.detail.delta) {
return;
}
let height = this.state.topPanelHeight;
if (!height) {
height = document.getElementsByClassName("tabbed-editor")[0].clientHeight;
}
this.state.topPanelHeight = height + ev.delta;
this.state.topPanelHeight = height + ev.detail.delta;
}
async downloadCode() {
@@ -287,10 +293,11 @@ class TabbedEditor extends owl.Component {
this.sessions[tab].setUndoManager(new ace.UndoManager());
}
}
this.editor = null;
}
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.setFontSize("12px");
@@ -314,20 +321,16 @@ class TabbedEditor extends owl.Component {
const session = this.sessions[this.state.currentTab];
session.setValue(this.props[this.state.currentTab], -1);
this.editor.setSession(session);
}
willUnmount() {
this.editor.destroy();
delete this.editor;
this.editor.resize();
}
setTab(tab) {
if (this.state.currentTab !== tab) {
this.state.currentTab = tab;
const session = this.sessions[this.state.currentTab];
session.doc.setValue(this.props[tab], -1);
this.editor.setSession(session);
}
if (this.state.currentTab !== tab) {
this.state.currentTab = tab;
const session = this.sessions[this.state.currentTab];
session.doc.setValue(this.props[tab], -1);
this.editor.setSession(session);
}
}
onMouseDown(ev) {
+2
View File
@@ -185,4 +185,6 @@ body {
font-size: 30px;
color: darkred;
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">
<t t-widget="ClickCounter"/>
<t t-widget="InputWidget" t-props="{reverse: true}"/>
<t t-widget="InputWidget" reverse="true"/>
</div>
</templates>`;
@@ -287,7 +287,7 @@ const LIFECYCLE_DEMO_XML = `<templates>
<button t-on-click="increment">Increment</button>
<button t-on-click="toggleSubWidget">ToggleSubWidget</button>
<div t-if="state.flag">
<t t-widget="HookWidget" t-props="{n:state.n}"/>
<t t-widget="HookWidget" n="state.n"/>
</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>
@@ -515,7 +515,7 @@ const TODO_APP_STORE_XML = `<templates>
<label for="toggle-all"></label>
<ul class="todo-list">
<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>
</ul>
</section>
+12 -10
View File
@@ -11,7 +11,9 @@
</div>
<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">
<a class="btn run-code flash" t-on-click="runCode" title="Execute this Code">▶ Run</a>
<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="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>
<t t-widget="TabbedEditor"
js="state.js"
css="!state.splitLayout and state.css"
xml="!state.splitLayout and state.js"
t-att-style="topEditorStyle"/>
<t t-if="state.splitLayout">
<t t-widget="TabbedEditor"
t-props="{js:state.js, css:false, xml: false}"
t-on-updateCode="updateCode"
t-att-style="topEditorStyle"/>
<div class="separator horizontal"/>
<t t-widget="TabbedEditor" t-keepalive="1"
t-props="{js:false, css:state.css, xml: state.xml, resizeable: true}"
t-on-updateCode="updateCode"
js="false"
css="state.css"
xml="state.xml"
resizeable="true"
t-on-updatePanelHeight="updatePanelHeight"/>
</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 class="separator vertical" t-on-mousedown="onMouseDown"/>
<div class="right-pane" t-att-style="rightPaneStyle">