[IMP] owl: update to v0.14.0

This commit is contained in:
Géry Debongnie
2019-06-13 12:20:25 +02:00
parent 84f810e6ae
commit 44e2de9709
3 changed files with 299 additions and 129 deletions
+151 -102
View File
@@ -210,14 +210,7 @@
*/
function vnode(sel, data, children, text, elm) {
let key = data === undefined ? undefined : data.key;
return {
sel: sel,
data: data,
children: children,
text: text,
elm: elm,
key: key
};
return { sel, data, children, text, elm, key };
}
//------------------------------------------------------------------------------
// snabbdom.ts
@@ -885,7 +878,7 @@
"(": "LEFT_PAREN",
")": "RIGHT_PAREN"
};
const OPERATORS = [".", "===", "==", "+", "!", "||", "&&", ">", "?", "-", "*"];
const OPERATORS = ".,===,==,+,!,||,&&,>=,>,<=,<,?,-,*,/,%".split(',');
let tokenizeString = function (expr) {
let s = expr[0];
let start = s;
@@ -997,7 +990,7 @@
}
}
if (expr.length) {
throw new Error("Tokenizer error...");
throw new Error(`Tokenizer error: could not tokenize "${expr}"`);
}
return result;
}
@@ -1125,6 +1118,10 @@
// use case is that component's templates are qweb dependant, and need to be
// able to map a qweb instance to a template name.
this.id = nextID++;
// slots contains sub templates defined with t-set inside t-widget nodes, and
// are meant to be used by the t-slot directive.
this.slots = {};
this.nextSlotId = 1;
if (data) {
this.addTemplates(data);
}
@@ -1288,13 +1285,16 @@
if (ctx.parentNode) {
ctx.addLine(`c${ctx.parentNode}.push({text: \`${text}\`});`);
}
else if (ctx.parentTextNode) {
ctx.addLine(`vn${ctx.parentTextNode}.text += \`${text}\`;`);
}
else {
// this is an unusual situation: this text node is the result of the
// template rendering.
let nodeID = ctx.generateID();
ctx.addLine(`var vn${nodeID} = {text: \`${text}\`};`);
ctx.rootContext.rootNode = nodeID;
ctx.rootContext.parentNode = nodeID;
ctx.rootContext.parentTextNode = nodeID;
}
return;
}
@@ -1543,6 +1543,7 @@
this.variables = {};
this.escaping = false;
this.parentNode = null;
this.parentTextNode = null;
this.rootNode = null;
this.indentLevel = 0;
this.shouldDefineOwner = false;
@@ -1560,7 +1561,7 @@
return id;
}
withParent(node) {
if (this === this.rootContext && this.parentNode) {
if (this === this.rootContext && (this.parentNode || this.parentTextNode)) {
throw new Error("A template should not have more than one root node");
}
if (!this.rootContext.rootNode) {
@@ -2268,11 +2269,19 @@
exprID = value.id;
}
ctx.addIf(`${exprID} || ${exprID} === 0`);
if (!ctx.parentNode) {
throw new Error("Should not have a text node without a parent");
}
if (ctx.escaping) {
ctx.addLine(`c${ctx.parentNode}.push({text: ${exprID}});`);
if (ctx.parentTextNode) {
ctx.addLine(`vn${ctx.parentTextNode}.text += ${exprID};`);
}
else if (ctx.parentNode) {
ctx.addLine(`c${ctx.parentNode}.push({text: ${exprID}});`);
}
else {
let nodeID = ctx.generateID();
ctx.rootContext.rootNode = nodeID;
ctx.rootContext.parentTextNode = nodeID;
ctx.addLine(`var vn${nodeID} = {text: ${exprID}};`);
}
}
else {
let fragID = ctx.generateID();
@@ -2524,6 +2533,7 @@
* - t-transition
* - t-widget/t-keepalive
* - t-mounted
* - t-slot
*/
//------------------------------------------------------------------------------
// t-on
@@ -2595,7 +2605,15 @@
UTILS.nextFrame = function (cb) {
requestAnimationFrame(() => requestAnimationFrame(cb));
};
UTILS.transitionInsert = function (elm, name) {
UTILS.transitionInsert = function (vn, name) {
const elm = vn.elm;
// remove potential duplicated vnode that is currently being removed, to
// prevent from having twice the same node in the DOM during an animation
const dup = elm.parentElement &&
elm.parentElement.querySelector(`*[data-owl-key='${vn.key}']`);
if (dup) {
dup.remove();
}
elm.classList.add(name + "-enter");
elm.classList.add(name + "-enter-active");
const finalize = () => {
@@ -2608,7 +2626,9 @@
whenTransitionEnd(elm, finalize);
});
};
UTILS.transitionRemove = function (elm, name, rm) {
UTILS.transitionRemove = function (vn, name, rm) {
const elm = vn.elm;
elm.setAttribute("data-owl-key", vn.key);
elm.classList.add(name + "-leave");
elm.classList.add(name + "-leave-active");
const finalize = () => {
@@ -2656,8 +2676,8 @@
atNodeCreation({ value, addNodeHook }) {
let name = value;
const hooks = {
insert: `this.utils.transitionInsert(vn.elm, '${name}');`,
remove: `this.utils.transitionRemove(vn.elm, '${name}', rm);`
insert: `this.utils.transitionInsert(vn, '${name}');`,
remove: `this.utils.transitionRemove(vn, '${name}', rm);`
};
for (let hookName in hooks) {
addNodeHook(hookName, hooks[hookName]);
@@ -2780,7 +2800,7 @@
* let nvn = w4._mount(vnode, vn.elm);
* pvnode.elm = nvn.elm;
* // what follows is only present if there are animations on the widget
* utils.transitionInsert(vn.elm, "fade");
* utils.transitionInsert(vn, "fade");
* },
* remove() {
* // override with empty function to prevent from removing the node
@@ -2793,7 +2813,7 @@
* let finalize = () => {
* w4.destroy();
* };
* utils.transitionRemove(vn.elm, "fade", finalize);
* utils.transitionRemove(vn, "fade", finalize);
* }
* };
* // the pvnode is inserted at the correct position in the div's children
@@ -2834,7 +2854,7 @@
name: "widget",
extraNames: ["props", "keepalive"],
priority: 100,
atNodeEncounter({ ctx, value, node }) {
atNodeEncounter({ ctx, value, node, qweb }) {
ctx.addLine("//WIDGET");
ctx.rootContext.shouldDefineOwner = true;
ctx.rootContext.shouldDefineQWeb = true;
@@ -2902,7 +2922,7 @@
}
let transitionsInsertCode = "";
if (transition) {
transitionsInsertCode = `utils.transitionInsert(vn.elm, '${transition}');`;
transitionsInsertCode = `utils.transitionInsert(vn, '${transition}');`;
}
let finalizeWidgetCode = `w${widgetID}.${keepAlive ? "unmount" : "destroy"}();`;
if (ref && !keepAlive) {
@@ -2912,7 +2932,7 @@
finalizeWidgetCode = `let finalize = () => {
${finalizeWidgetCode}
};
utils.transitionRemove(vn.elm, '${transition}', finalize);`;
utils.transitionRemove(vn, '${transition}', finalize);`;
}
let createHook = "";
let classAttr = node.getAttribute("class");
@@ -2987,6 +3007,19 @@
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;`);
// SLOTS
const slotNodes = node.querySelectorAll("[t-set]");
if (slotNodes.length) {
const slotId = qweb.nextSlotId++;
for (let i = 0, length = slotNodes.length; i < length; i++) {
const slotNode = slotNodes[i];
const key = slotNode.getAttribute("t-set");
slotNode.removeAttribute("t-set");
const slotFn = qweb._compile(`slot_${key}_template`, slotNode);
qweb.slots[`${slotId}_${key}`] = slotFn.bind(qweb);
}
ctx.addLine(`w${widgetID}.__owl__.slotId = ${slotId};`);
}
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
@@ -3036,6 +3069,19 @@
addNodeHook("insert", `if (context.__owl__.isMounted) { extra.mountedHandlers[${nodeID}](); }`);
}
});
//------------------------------------------------------------------------------
// t-slot
//------------------------------------------------------------------------------
QWeb.addDirective({
name: "slot",
priority: 80,
atNodeEncounter({ ctx, value }) {
const slotKey = ctx.generateID();
ctx.addLine(`const slot${slotKey} = this.slots[context.__owl__.slotId + '_' + '${value}'];`);
ctx.addLine(`c${ctx.parentNode}.push(slot${slotKey}(context.__owl__.parent, extra));`);
return true;
}
});
class Store extends EventBus {
constructor(config, options = {}) {
@@ -3122,8 +3168,9 @@
return 0;
}
let nextID$1 = 1;
function connect(mapStateToProps, options = {}) {
function connect(Comp, mapStoreToProps, options = {}) {
let hashFunction = options.hashFunction || null;
const getStore = options.getStore || (env => env.store);
if (!hashFunction) {
let deep = "deep" in options ? options.deep : true;
let defaultRevFunction = deep ? deepRevNumber : revNumber;
@@ -3148,77 +3195,77 @@
return hash;
};
}
return function (Comp) {
const Result = class extends Comp {
constructor(parent, props) {
const env = parent instanceof Component ? parent.env : parent;
const ownProps = Object.assign({}, props || {});
const storeProps = mapStateToProps(env.store.state, ownProps, env.store.getters);
const mergedProps = Object.assign({}, props || {}, storeProps);
super(parent, mergedProps);
this.__owl__.ownProps = ownProps;
const Result = class extends Comp {
constructor(parent, props) {
const env = parent instanceof Component ? parent.env : parent;
const store = getStore(env);
const ownProps = Object.assign({}, props || {});
const storeProps = mapStoreToProps(store.state, ownProps, store.getters);
const mergedProps = Object.assign({}, props || {}, storeProps);
super(parent, mergedProps);
this.__owl__.ownProps = ownProps;
this.__owl__.currentStoreProps = storeProps;
this.__owl__.store = store;
this.__owl__.storeHash = hashFunction({
state: store.state,
storeProps: storeProps,
revNumber,
deepRevNumber
}, {
currentStoreProps: storeProps
});
}
/**
* We do not use the mounted hook here for a subtle reason: we want the
* updates to be called for the parents before the children. However,
* if we use the mounted hook, this will be done in the reverse order.
*/
_callMounted() {
this.__owl__.store.on("update", this, this._checkUpdate);
super._callMounted();
}
willUnmount() {
this.__owl__.store.off("update", this);
super.willUnmount();
}
_checkUpdate() {
const ownProps = this.__owl__.ownProps;
const storeProps = mapStoreToProps(this.__owl__.store.state, ownProps, this.__owl__.store.getters);
const options = {
currentStoreProps: this.__owl__.currentStoreProps
};
const storeHash = hashFunction({
state: this.__owl__.store.state,
storeProps: storeProps,
revNumber,
deepRevNumber
}, options);
let didChange = options.didChange;
if (storeHash !== this.__owl__.storeHash) {
didChange = true;
this.__owl__.storeHash = storeHash;
}
if (didChange) {
this.__owl__.currentStoreProps = storeProps;
this.__owl__.storeHash = hashFunction({
state: env.store.state,
storeProps: storeProps,
revNumber,
deepRevNumber
}, {
currentStoreProps: storeProps
});
this._updateProps(ownProps, false);
}
/**
* We do not use the mounted hook here for a subtle reason: we want the
* updates to be called for the parents before the children. However,
* if we use the mounted hook, this will be done in the reverse order.
*/
_callMounted() {
this.env.store.on("update", this, this._checkUpdate);
super._callMounted();
}
_updateProps(nextProps, forceUpdate, patchQueue) {
if (this.__owl__.ownProps !== nextProps) {
this.__owl__.currentStoreProps = mapStoreToProps(this.__owl__.store.state, nextProps, this.__owl__.store.getters);
}
willUnmount() {
this.env.store.off("update", this);
super.willUnmount();
}
_checkUpdate() {
const ownProps = this.__owl__.ownProps;
const storeProps = mapStateToProps(this.env.store.state, ownProps, this.env.store.getters);
const options = {
currentStoreProps: this.__owl__.currentStoreProps
};
const storeHash = hashFunction({
state: this.env.store.state,
storeProps: storeProps,
revNumber,
deepRevNumber
}, options);
let didChange = options.didChange;
if (storeHash !== this.__owl__.storeHash) {
didChange = true;
this.__owl__.storeHash = storeHash;
}
if (didChange) {
this.__owl__.currentStoreProps = storeProps;
this._updateProps(ownProps, false);
}
}
_updateProps(nextProps, forceUpdate, patchQueue) {
if (this.__owl__.ownProps !== nextProps) {
this.__owl__.currentStoreProps = mapStateToProps(this.env.store.state, nextProps, this.env.store.getters);
}
this.__owl__.ownProps = nextProps;
const mergedProps = Object.assign({}, nextProps, this.__owl__.currentStoreProps);
return super._updateProps(mergedProps, forceUpdate, patchQueue);
}
};
// we assign here a unique name to the resulting anonymous class.
// this is necessary for Owl to be able to properly deduce templates.
// Otherwise, all connected components would have the same name, and then
// each component after the first will necessarily have the same template.
let name = `ConnectedComponent${nextID$1++}`;
Object.defineProperty(Result, "name", { value: name });
return Result;
this.__owl__.ownProps = nextProps;
const mergedProps = Object.assign({}, nextProps, this.__owl__.currentStoreProps);
return super._updateProps(mergedProps, forceUpdate, patchQueue);
}
};
// we assign here a unique name to the resulting anonymous class.
// this is necessary for Owl to be able to properly deduce templates.
// Otherwise, all connected components would have the same name, and then
// each component after the first will necessarily have the same template.
let name = `ConnectedComponent${nextID$1++}`;
Object.defineProperty(Result, "name", { value: name });
return Result;
}
/**
@@ -3233,12 +3280,14 @@
* - debounce
*/
function whenReady(fn) {
if (document.readyState !== "loading") {
fn();
}
else {
document.addEventListener("DOMContentLoaded", fn, false);
}
return new Promise(function (resolve) {
if (document.readyState !== "loading") {
resolve();
}
else {
document.addEventListener("DOMContentLoaded", resolve, false);
}
}).then(fn || function () { });
}
const loadedScripts = {};
function loadJS(url) {
@@ -3353,9 +3402,9 @@
exports.connect = connect;
exports.utils = utils;
exports.__info__.version = '0.13.0';
exports.__info__.date = '2019-06-08T14:30:19.424Z';
exports.__info__.hash = '2a19aeb';
exports.__info__.version = '0.14.0';
exports.__info__.date = '2019-06-13T10:11:15.338Z';
exports.__info__.hash = '2701a7d';
exports.__info__.url = 'https://github.com/odoo/owl';
}(this.owl = this.owl || {}));
+14 -14
View File
@@ -127,22 +127,22 @@ async function makeApp(js, css, xml) {
.map(l => (l === "" ? "" : " " + l))
.join("\n");
const JS = `async function startApp() {
// Loading templates
let TEMPLATES;
const JS = `
async function loadTemplates() {
try {
TEMPLATES = await owl.utils.loadTemplates('app.xml');
return owl.utils.loadTemplates('app.xml');
} catch(e) {
document.write(\`This app requires a static server. If you have python installed, try 'python app.py'\`);
return;
console.error(\`This app requires a static server. If you have python installed, try 'python app.py'\`);
}
}
function start([TEMPLATES]) {
// Application code
${processedJS}
}
// wait for DOM ready before starting
owl.utils.whenReady(startApp);`;
Promise.all([loadTemplates(), owl.utils.whenReady()]).then(start);
`;
zip.file("app.js", JS);
zip.file("app.css", css);
@@ -354,13 +354,13 @@ class TabbedEditor extends owl.Component {
//------------------------------------------------------------------------------
async function start() {
document.title = `${document.title} (v${owl.__info__.version})`;
const templates = await owl.utils.loadTemplates("templates.xml");
const [templates] = await Promise.all([
owl.utils.loadTemplates("templates.xml"),
owl.utils.whenReady()
]);
const qweb = new owl.QWeb(templates);
const env = { qweb };
owl.utils.whenReady(() => {
const app = new App(env);
app.mount(document.body);
});
const app = new App({ qweb });
app.mount(document.body);
}
start();
+134 -13
View File
@@ -187,7 +187,7 @@ const ANIMATION_CSS = `button {
.flash {
background-position: center;
transition: background 0.5s;
transition: background .6s;
}
.flash:active {
@@ -208,7 +208,7 @@ const ANIMATION_CSS = `button {
}
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
transition: opacity .6s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
@@ -233,7 +233,6 @@ const ANIMATION_CSS = `button {
}
`;
const LIFECYCLE_DEMO = `class HookWidget extends owl.Component {
constructor() {
super(...arguments);
@@ -384,11 +383,11 @@ class TodoItem extends owl.Component {
state = { isEditing: false };
removeTodo() {
this.env.store.dispatch("removeTodo", this.props.id);
this.env.dispatch("removeTodo", this.props.id);
}
toggleTodo() {
this.env.store.dispatch("toggleTodo", this.props.id);
this.env.dispatch("toggleTodo", this.props.id);
}
async editTodo() {
@@ -420,7 +419,7 @@ class TodoItem extends owl.Component {
if (!value) {
this.removeTodo(this.props.id);
} else {
this.env.store.dispatch("editTodo", {
this.env.dispatch("editTodo", {
id: this.props.id,
title: value
});
@@ -432,7 +431,7 @@ class TodoItem extends owl.Component {
//------------------------------------------------------------------------------
// TodoApp
//------------------------------------------------------------------------------
function mapStateToProps(state) {
function mapStoreToProps(state) {
return {
todos: state.todos
};
@@ -470,18 +469,18 @@ class TodoApp extends owl.Component {
if (ev.keyCode === ENTER_KEY) {
const title = ev.target.value;
if (title.trim()) {
this.env.store.dispatch("addTodo", title);
this.env.dispatch("addTodo", title);
}
ev.target.value = "";
}
}
clearCompleted() {
this.env.store.dispatch("clearCompleted");
this.env.dispatch("clearCompleted");
}
toggleAll() {
this.env.store.dispatch("toggleAll", !this.allChecked);
this.env.dispatch("toggleAll", !this.allChecked);
}
setFilter(filter) {
@@ -489,7 +488,7 @@ class TodoApp extends owl.Component {
}
}
const ConnectedTodoApp = owl.connect(mapStateToProps)(TodoApp);
const ConnectedTodoApp = owl.connect(TodoApp, mapStoreToProps);
//------------------------------------------------------------------------------
// App Initialization
@@ -498,7 +497,8 @@ const store = makeStore();
const qweb = new owl.QWeb(TEMPLATES);
const env = {
qweb,
store
store,
dispatch: store.dispatch.bind(store),
};
const app = new ConnectedTodoApp(env);
app.mount(document.body);
@@ -1081,6 +1081,121 @@ const RESPONSIVE_CSS = `body {
}
`;
const SLOTS = `// This example will not work if your browser does not support ESNext class fields
// We show here how slots can be used to create generic components. In this
// example, the Card component is basically only a container, and is created
// by giving it slots, inside the t-widget directive.
class Card extends owl.Component {
state = { fullDisplay: true };
toggleDisplay() {
this.state.fullDisplay = !this.state.fullDisplay;
}
}
class Counter extends owl.Component {
state = {val: 1};
inc() {
this.state.val++;
}
}
// Main root widget
class App extends owl.Component {
widgets = {Card, Counter};
state = {a: 1, b: 3};
inc(key, delta) {
this.state[key] += delta;
}
}
// Application setup
const qweb = new owl.QWeb(TEMPLATES);
const app = new App({ qweb });
app.mount(document.body);`;
const SLOTS_XML = `<templates>
<div t-name="Card" class="card" t-att-class="state.fullDisplay ? 'full' : 'small'">
<div class="card-title">
<t t-esc="props.title"/><button t-on-click="toggleDisplay">Toggle</button>
</div>
<t t-if="state.fullDisplay">
<div class="card-content" >
<t t-slot="content"/>
</div>
<div class="card-footer">
<t t-slot="footer"/>
</div>
</t>
</div>
<div t-name="Counter">
<t t-esc="state.val"/><button t-on-click="inc">Inc</button>
</div>
<div t-name="App" class="main">
<t t-widget="Card" title="'Title card A'">
<t t-set="content">Content of card 1... [<t t-esc="state.a"/>]</t>
<t t-set="footer"><button t-on-click="inc('a', 1)">Increment A</button></t>
</t>
<t t-widget="Card" title="'Title card B'">
<div t-set="content">
<div>Card 2... [<t t-esc="state.b"/>]</div>
<t t-widget="Counter"/>
</div>
<t t-set="footer"><button t-on-click="inc('b', -1)">Decrement B</button></t>
</t>
</div>
</templates>`;
const SLOTS_CSS = `.main {
display: flex;
}
.card {
display: flex;
flex-direction: column;
background-color: #eeeeee;
width: 200px;
height: 100px;
margin: 10px;
border: 1px solid gray;
}
.card.full {
height: 100px;
}
.card.small {
height: 25px;
}
.card-title {
flex: 0 0 25px;
font-weight: bold;
background-color: darkcyan;
color: white;
padding: 2px;
}
.card-title button {
float: right;
}
.card-content {
flex: 1 1 auto;
padding: 5px;
border-top: 1px solid white;
}
.card-footer {
border-top: 1px solid white;
}`;
const EMPTY = `class App extends owl.Component {
}
@@ -1112,7 +1227,7 @@ export const SAMPLES = [
description: "Animations",
code: ANIMATION,
xml: ANIMATION_XML,
css: ANIMATION_CSS,
css: ANIMATION_CSS
},
{
description: "Lifecycle demo",
@@ -1131,6 +1246,12 @@ export const SAMPLES = [
css: RESPONSIVE_CSS,
xml: RESPONSIVE_XML
},
{
description: "Slots",
code: SLOTS,
xml: SLOTS_XML,
css: SLOTS_CSS
},
{
description: "Empty",
code: EMPTY