mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] owl: update to v0.23.0
This commit is contained in:
+84
-24
@@ -1,5 +1,5 @@
|
|||||||
import { SAMPLES } from "./samples.js";
|
import { SAMPLES } from "./samples.js";
|
||||||
const {useState, useRef} = owl.hooks;
|
const { useState, useRef, onMounted, onWillUnmount } = owl.hooks;
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Constants, helpers, utils
|
// Constants, helpers, utils
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -150,6 +150,51 @@ Promise.all([loadTemplates(), owl.utils.whenReady()]).then(start);
|
|||||||
return zip.generateAsync({ type: "blob" });
|
return zip.generateAsync({ type: "blob" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// SAMPLES
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
function loadSamples() {
|
||||||
|
let result = SAMPLES.slice();
|
||||||
|
const localSample = localStorage.getItem("owl-playground-local-sample");
|
||||||
|
if (localSample) {
|
||||||
|
const { js, css, xml } = JSON.parse(localSample);
|
||||||
|
result.unshift({
|
||||||
|
description: "Local Storage Code",
|
||||||
|
code: js,
|
||||||
|
xml,
|
||||||
|
css
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveLocalSample(js, css, xml) {
|
||||||
|
const str = JSON.stringify({ js, css, xml });
|
||||||
|
localStorage.setItem("owl-playground-local-sample", str);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteLocalSample() {
|
||||||
|
localStorage.removeItem("owl-playground-local-sample");
|
||||||
|
}
|
||||||
|
|
||||||
|
function useSamples() {
|
||||||
|
const samples = loadSamples();
|
||||||
|
const component = owl.Component.current;
|
||||||
|
let interval;
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const state = component.state;
|
||||||
|
interval = setInterval(() => {
|
||||||
|
if (component.isDirty) {
|
||||||
|
saveLocalSample(state.js, state.css, state.xml);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
onWillUnmount(() => {
|
||||||
|
clearInterval(interval);
|
||||||
|
});
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Tabbed editor
|
// Tabbed editor
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -157,13 +202,14 @@ class TabbedEditor extends owl.Component {
|
|||||||
constructor(parent, props) {
|
constructor(parent, props) {
|
||||||
super(parent, props);
|
super(parent, props);
|
||||||
this.state = useState({
|
this.state = useState({
|
||||||
currentTab: props.js ? "js" : props.xml ? "xml" : "css"
|
currentTab: props.js !== false ? "js" : props.xml ? "xml" : "css"
|
||||||
});
|
});
|
||||||
this.setTab = owl.utils.debounce(this.setTab, 250, true);
|
this.setTab = owl.utils.debounce(this.setTab, 250, true);
|
||||||
|
|
||||||
this.sessions = {};
|
this.sessions = {};
|
||||||
this._setupSessions(props);
|
this._setupSessions(props);
|
||||||
this.editorNode = useRef("editor");
|
this.editorNode = useRef("editor");
|
||||||
|
this._updateCode = this._updateCode.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -175,18 +221,14 @@ class TabbedEditor extends owl.Component {
|
|||||||
this.editor.setSession(this.sessions[this.state.currentTab]);
|
this.editor.setSession(this.sessions[this.state.currentTab]);
|
||||||
const tabSize = this.state.currentTab === "xml" ? 2 : 4;
|
const tabSize = this.state.currentTab === "xml" ? 2 : 4;
|
||||||
this.editor.session.setOption("tabSize", tabSize);
|
this.editor.session.setOption("tabSize", tabSize);
|
||||||
this.editor.on("blur", () => {
|
this.editor.on("blur", this._updateCode);
|
||||||
const editorValue = this.editor.getValue();
|
this.interval = setInterval(this._updateCode, 3000);
|
||||||
const propsValue = this.props[this.state.currentTab];
|
|
||||||
if (editorValue !== propsValue) {
|
|
||||||
this.trigger("updateCode", {
|
|
||||||
type: this.state.currentTab,
|
|
||||||
value: editorValue
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
willUnmount() {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
this.editor.off("blur", this._updateCode);
|
||||||
|
}
|
||||||
willUpdateProps(nextProps) {
|
willUpdateProps(nextProps) {
|
||||||
this._setupSessions(nextProps);
|
this._setupSessions(nextProps);
|
||||||
}
|
}
|
||||||
@@ -195,13 +237,15 @@ class TabbedEditor extends owl.Component {
|
|||||||
const session = this.sessions[this.state.currentTab];
|
const session = this.sessions[this.state.currentTab];
|
||||||
let content = this.props[this.state.currentTab];
|
let content = this.props[this.state.currentTab];
|
||||||
if (content === false) {
|
if (content === false) {
|
||||||
const tab = this.props.js ? "js" : this.props.xml ? "xml" : "css";
|
const tab = this.props.js !== false ? "js" : this.props.xml ? "xml" : "css";
|
||||||
content = this.props[tab];
|
content = this.props[tab];
|
||||||
this.state.currentTab = tab;
|
this.state.currentTab = tab;
|
||||||
}
|
}
|
||||||
session.setValue(content, -1);
|
if (this.editor.getValue() !== content) {
|
||||||
this.editor.setSession(session);
|
session.setValue(content, -1);
|
||||||
this.editor.resize();
|
this.editor.setSession(session);
|
||||||
|
this.editor.resize();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTab(tab) {
|
setTab(tab) {
|
||||||
@@ -230,7 +274,7 @@ class TabbedEditor extends owl.Component {
|
|||||||
|
|
||||||
_setupSessions(props) {
|
_setupSessions(props) {
|
||||||
for (let tab of ["js", "xml", "css"]) {
|
for (let tab of ["js", "xml", "css"]) {
|
||||||
if (props[tab] && !this.sessions[tab]) {
|
if (props[tab] !== false && !this.sessions[tab]) {
|
||||||
this.sessions[tab] = new ace.EditSession(props[tab], MODES[tab]);
|
this.sessions[tab] = new ace.EditSession(props[tab], MODES[tab]);
|
||||||
this.sessions[tab].setOption("useWorker", false);
|
this.sessions[tab].setOption("useWorker", false);
|
||||||
const tabSize = tab === "xml" ? 2 : 4;
|
const tabSize = tab === "xml" ? 2 : 4;
|
||||||
@@ -239,6 +283,17 @@ class TabbedEditor extends owl.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_updateCode() {
|
||||||
|
const editorValue = this.editor.getValue();
|
||||||
|
const propsValue = this.props[this.state.currentTab];
|
||||||
|
if (editorValue !== propsValue) {
|
||||||
|
this.trigger("updateCode", {
|
||||||
|
type: this.state.currentTab,
|
||||||
|
value: editorValue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -248,12 +303,13 @@ class App extends owl.Component {
|
|||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
super(...args);
|
super(...args);
|
||||||
this.version = owl.__info__.version;
|
this.version = owl.__info__.version;
|
||||||
this.SAMPLES = SAMPLES;
|
this.SAMPLES = useSamples();
|
||||||
|
this.isDirty = false;
|
||||||
|
|
||||||
this.state = useState({
|
this.state = useState({
|
||||||
js: SAMPLES[0].code,
|
js: this.SAMPLES[0].code,
|
||||||
css: SAMPLES[0].css || "",
|
css: this.SAMPLES[0].css || "",
|
||||||
xml: SAMPLES[0].xml || DEFAULT_XML,
|
xml: this.SAMPLES[0].xml || DEFAULT_XML,
|
||||||
error: false,
|
error: false,
|
||||||
displayWelcome: true,
|
displayWelcome: true,
|
||||||
splitLayout: true,
|
splitLayout: true,
|
||||||
@@ -302,10 +358,12 @@ class App extends owl.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setSample(ev) {
|
setSample(ev) {
|
||||||
const sample = SAMPLES.find(s => s.description === ev.target.value);
|
const sample = this.SAMPLES.find(s => s.description === ev.target.value);
|
||||||
this.state.js = sample.code;
|
this.state.js = sample.code;
|
||||||
this.state.css = sample.css || "";
|
this.state.css = sample.css || "";
|
||||||
this.state.xml = sample.xml || DEFAULT_XML;
|
this.state.xml = sample.xml || DEFAULT_XML;
|
||||||
|
deleteLocalSample();
|
||||||
|
this.isDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
get leftPaneStyle() {
|
get leftPaneStyle() {
|
||||||
@@ -338,7 +396,10 @@ class App extends owl.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
updateCode(ev) {
|
updateCode(ev) {
|
||||||
this.state[ev.detail.type] = ev.detail.value;
|
if (this.state[ev.detail.type] !== ev.detail.value) {
|
||||||
|
this.state[ev.detail.type] = ev.detail.value;
|
||||||
|
this.isDirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
toggleLayout() {
|
toggleLayout() {
|
||||||
this.state.splitLayout = !this.state.splitLayout;
|
this.state.splitLayout = !this.state.splitLayout;
|
||||||
@@ -363,7 +424,6 @@ class App extends owl.Component {
|
|||||||
}
|
}
|
||||||
App.components = { TabbedEditor };
|
App.components = { TabbedEditor };
|
||||||
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Application initialization
|
// Application initialization
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|||||||
+151
-92
@@ -318,6 +318,66 @@ const HOOKS_CSS = `button {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
const CONTEXT_JS = `// In this example, we show how components can use the Context and 'useContext'
|
||||||
|
// hook to share information between them.
|
||||||
|
const { Component, Context } = owl;
|
||||||
|
const { useContext } = owl.hooks;
|
||||||
|
|
||||||
|
class ToolbarButton extends Component {
|
||||||
|
theme = useContext(this.env.themeContext);
|
||||||
|
|
||||||
|
get style () {
|
||||||
|
const theme = this.theme;
|
||||||
|
return \`background-color: \${theme.background}; color: \${theme.foreground}\`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Toolbar extends Component {
|
||||||
|
static components = { ToolbarButton };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main root component
|
||||||
|
class App extends Component {
|
||||||
|
static components = { Toolbar };
|
||||||
|
|
||||||
|
toggleTheme() {
|
||||||
|
const { background, foreground } = this.env.themeContext.state;
|
||||||
|
this.env.themeContext.state.background = foreground;
|
||||||
|
this.env.themeContext.state.foreground = background;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Application setup
|
||||||
|
const themeContext = new Context({
|
||||||
|
background: '#000',
|
||||||
|
foreground: '#fff',
|
||||||
|
});
|
||||||
|
const env = {
|
||||||
|
qweb: new owl.QWeb(TEMPLATES),
|
||||||
|
themeContext: themeContext,
|
||||||
|
};
|
||||||
|
const app = new App(env);
|
||||||
|
app.mount(document.body);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CONTEXT_XML = `<templates>
|
||||||
|
<button t-name="ToolbarButton" t-att-style="style">
|
||||||
|
<t t-esc="props.name"/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div t-name="Toolbar">
|
||||||
|
<ToolbarButton name="'A'"/>
|
||||||
|
<ToolbarButton name="'B'"/>
|
||||||
|
<ToolbarButton name="'C'"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="App">
|
||||||
|
<button t-on-click="toggleTheme">Toggle Mode</button>
|
||||||
|
<Toolbar/>
|
||||||
|
</div>
|
||||||
|
</templates>
|
||||||
|
`;
|
||||||
|
|
||||||
const TODO_APP_STORE = `// This example is an implementation of the TodoList application, from the
|
const TODO_APP_STORE = `// This example is an implementation of the TodoList application, from the
|
||||||
// www.todomvc.com project. This is a non trivial application with some
|
// www.todomvc.com project. This is a non trivial application with some
|
||||||
// interesting user interactions. It uses the local storage for persistence.
|
// interesting user interactions. It uses the local storage for persistence.
|
||||||
@@ -325,99 +385,94 @@ const TODO_APP_STORE = `// This example is an implementation of the TodoList app
|
|||||||
// In this implementation, we use the owl Store class to manage the state. It
|
// In this implementation, we use the owl Store class to manage the state. It
|
||||||
// is very similar to the VueX store.
|
// is very similar to the VueX store.
|
||||||
const { Component, useState } = owl;
|
const { Component, useState } = owl;
|
||||||
const { useRef } = owl.hooks;
|
const { useRef, useStore, useDispatch, onPatched, onMounted } = owl.hooks;
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Constants, helpers
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
const ENTER_KEY = 13;
|
const ENTER_KEY = 13;
|
||||||
const ESC_KEY = 27;
|
const ESC_KEY = 27;
|
||||||
const LOCALSTORAGE_KEY = "todomvc";
|
const LOCALSTORAGE_KEY = "todomvc";
|
||||||
|
|
||||||
|
function useAutofocus(name) {
|
||||||
|
let ref = useRef(name);
|
||||||
|
let isInDom = false;
|
||||||
|
function updateFocus() {
|
||||||
|
if (!isInDom && ref.el) {
|
||||||
|
isInDom = true;
|
||||||
|
const current = ref.el.value;
|
||||||
|
ref.el.value = "";
|
||||||
|
ref.el.focus();
|
||||||
|
ref.el.value = current;
|
||||||
|
} else if (isInDom && !ref.el) {
|
||||||
|
isInDom = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onPatched(updateFocus);
|
||||||
|
onMounted(updateFocus);
|
||||||
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Store Definition
|
// Store
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
const initialState = { todos: [], nextId: 1};
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
addTodo({ state }, title) {
|
addTodo({ state }, title) {
|
||||||
state.todos.push({
|
const todo = {
|
||||||
id: state.nextId++,
|
id: state.nextId++,
|
||||||
title,
|
title,
|
||||||
completed: false
|
completed: false
|
||||||
});
|
}
|
||||||
|
state.todos.push(todo);
|
||||||
},
|
},
|
||||||
removeTodo({ state }, id) {
|
removeTodo({ state }, id) {
|
||||||
const index = state.todos.findIndex(t => t.id === id);
|
const index = state.todos.findIndex(t => t.id === id);
|
||||||
state.todos.splice(index, 1);
|
state.todos.splice(index, 1);
|
||||||
},
|
},
|
||||||
toggleTodo({ state, dispatch }, id) {
|
updateTodo({state, dispatch}, {id, title}) {
|
||||||
|
const value = title.trim();
|
||||||
|
if (!value) {
|
||||||
|
dispatch('removeTodo', id);
|
||||||
|
} else {
|
||||||
|
const todo = state.todos.find(t => t.id === id);
|
||||||
|
todo.title = value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggleTodo({ state }, id) {
|
||||||
const todo = state.todos.find(t => t.id === id);
|
const todo = state.todos.find(t => t.id === id);
|
||||||
dispatch("editTodo", { id, completed: !todo.completed });
|
todo.completed = !todo.completed;
|
||||||
},
|
},
|
||||||
clearCompleted({ state, dispatch }) {
|
clearCompleted({ state, dispatch }) {
|
||||||
state.todos
|
for (let todo of state.todos) {
|
||||||
.filter(todo => todo.completed)
|
if (todo.completed) {
|
||||||
.forEach(todo => {
|
|
||||||
dispatch("removeTodo", todo.id);
|
dispatch("removeTodo", todo.id);
|
||||||
});
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
toggleAll({ state, dispatch }, completed) {
|
toggleAll({ state, dispatch }, completed) {
|
||||||
state.todos.forEach(todo => {
|
for (let todo of state.todos) {
|
||||||
dispatch("editTodo", {
|
|
||||||
id: todo.id,
|
|
||||||
completed
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
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;
|
todo.completed = completed;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function saveState(state) {
|
|
||||||
const str = JSON.stringify(state);
|
|
||||||
window.localStorage.setItem(LOCALSTORAGE_KEY, str);
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadState() {
|
|
||||||
const localState = window.localStorage.getItem(LOCALSTORAGE_KEY);
|
|
||||||
return localState ? JSON.parse(localState) : { todos: [], nextId: 1};
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeStore() {
|
|
||||||
const state = loadState();
|
|
||||||
const store = new owl.store.Store({ state, actions });
|
|
||||||
store.on("update", null, () => saveState(store.state));
|
|
||||||
return store;
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// TodoItem
|
// TodoItem
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
class TodoItem extends Component {
|
class TodoItem extends Component {
|
||||||
state = useState({ isEditing: false });
|
state = useState({ isEditing: false });
|
||||||
inputRef = useRef("input");
|
dispatch = useDispatch();
|
||||||
|
|
||||||
removeTodo() {
|
constructor(...args) {
|
||||||
this.env.store.dispatch("removeTodo", this.props.id);
|
super(...args);
|
||||||
|
useAutofocus("input");
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleTodo() {
|
editTodo() {
|
||||||
this.env.store.dispatch("toggleTodo", this.props.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
async editTodo() {
|
|
||||||
this.state.isEditing = true;
|
this.state.isEditing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
focusInput() {
|
|
||||||
this.inputRef.el.value = "";
|
|
||||||
this.inputRef.el.focus();
|
|
||||||
this.inputRef.el.value = this.props.title;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleKeyup(ev) {
|
handleKeyup(ev) {
|
||||||
if (ev.keyCode === ENTER_KEY) {
|
if (ev.keyCode === ENTER_KEY) {
|
||||||
this.updateTitle(ev.target.value);
|
this.updateTitle(ev.target.value);
|
||||||
@@ -433,48 +488,34 @@ class TodoItem extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateTitle(title) {
|
updateTitle(title) {
|
||||||
const value = title.trim();
|
this.dispatch("updateTodo", {title, id: this.props.id});
|
||||||
if (!value) {
|
this.state.isEditing = false;
|
||||||
this.removeTodo(this.props.id);
|
|
||||||
} else {
|
|
||||||
this.env.store.dispatch("editTodo", {
|
|
||||||
id: this.props.id,
|
|
||||||
title: value
|
|
||||||
});
|
|
||||||
this.state.isEditing = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// TodoApp
|
// TodoApp
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
class TodoApp extends owl.store.ConnectedComponent {
|
class TodoApp extends Component {
|
||||||
static components = { TodoItem };
|
static components = { TodoItem };
|
||||||
state = useState({ filter: "all" });
|
state = useState({ filter: "all" });
|
||||||
|
todos = useStore(state => state.todos);
|
||||||
|
dispatch = useDispatch();
|
||||||
|
|
||||||
static mapStoreToProps(state) {
|
|
||||||
return {
|
|
||||||
todos: state.todos
|
|
||||||
};
|
|
||||||
}
|
|
||||||
get visibleTodos() {
|
get visibleTodos() {
|
||||||
let todos = this.storeProps.todos;
|
switch (this.state.filter) {
|
||||||
if (this.state.filter === "active") {
|
case "active": return this.todos.filter(t => !t.completed);
|
||||||
todos = todos.filter(t => !t.completed);
|
case "completed": return this.todos.filter(t => t.completed);
|
||||||
|
case "all": return this.todos;
|
||||||
}
|
}
|
||||||
if (this.state.filter === "completed") {
|
|
||||||
todos = todos.filter(t => t.completed);
|
|
||||||
}
|
|
||||||
return todos;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get allChecked() {
|
get allChecked() {
|
||||||
return this.storeProps.todos.every(todo => todo.completed);
|
return this.todos.every(todo => todo.completed);
|
||||||
}
|
}
|
||||||
|
|
||||||
get remaining() {
|
get remaining() {
|
||||||
return this.storeProps.todos.filter(todo => !todo.completed).length;
|
return this.todos.filter(todo => !todo.completed).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
get remainingText() {
|
get remainingText() {
|
||||||
@@ -500,12 +541,25 @@ class TodoApp extends owl.store.ConnectedComponent {
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// App Initialization
|
// App Initialization
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
const store = makeStore();
|
function saveState(state) {
|
||||||
const qweb = new owl.QWeb(TEMPLATES);
|
const str = JSON.stringify(state);
|
||||||
const env = {
|
window.localStorage.setItem(LOCALSTORAGE_KEY, str);
|
||||||
qweb,
|
}
|
||||||
store,
|
|
||||||
};
|
function loadState() {
|
||||||
|
const localState = window.localStorage.getItem(LOCALSTORAGE_KEY);
|
||||||
|
return localState ? JSON.parse(localState) : initialState;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeEnv() {
|
||||||
|
const state = loadState();
|
||||||
|
const store = new owl.Store({ state, actions });
|
||||||
|
store.on("update", null, () => saveState(store.state));
|
||||||
|
const qweb = new owl.QWeb(TEMPLATES);
|
||||||
|
return { qweb, store };
|
||||||
|
}
|
||||||
|
|
||||||
|
const env = makeEnv();
|
||||||
const app = new TodoApp(env);
|
const app = new TodoApp(env);
|
||||||
app.mount(document.body);
|
app.mount(document.body);
|
||||||
`;
|
`;
|
||||||
@@ -516,7 +570,7 @@ const TODO_APP_STORE_XML = `<templates>
|
|||||||
<h1>todos</h1>
|
<h1>todos</h1>
|
||||||
<input class="new-todo" autofocus="true" autocomplete="off" placeholder="What needs to be done?" t-on-keyup="addTodo"/>
|
<input class="new-todo" autofocus="true" autocomplete="off" placeholder="What needs to be done?" t-on-keyup="addTodo"/>
|
||||||
</header>
|
</header>
|
||||||
<section class="main" t-if="storeProps.todos.length">
|
<section class="main" t-if="todos.length">
|
||||||
<input class="toggle-all" id="toggle-all" type="checkbox" t-att-checked="allChecked" t-on-click="dispatch('toggleAll', !allChecked)"/>
|
<input class="toggle-all" id="toggle-all" type="checkbox" t-att-checked="allChecked" t-on-click="dispatch('toggleAll', !allChecked)"/>
|
||||||
<label for="toggle-all"></label>
|
<label for="toggle-all"></label>
|
||||||
<ul class="todo-list">
|
<ul class="todo-list">
|
||||||
@@ -525,7 +579,7 @@ const TODO_APP_STORE_XML = `<templates>
|
|||||||
</t>
|
</t>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
<footer class="footer" t-if="storeProps.todos.length">
|
<footer class="footer" t-if="todos.length">
|
||||||
<span class="todo-count">
|
<span class="todo-count">
|
||||||
<strong>
|
<strong>
|
||||||
<t t-esc="remaining"/>
|
<t t-esc="remaining"/>
|
||||||
@@ -543,7 +597,7 @@ const TODO_APP_STORE_XML = `<templates>
|
|||||||
<a t-on-click="setFilter('completed')" t-att-class="{selected: state.filter === 'completed'}">Completed</a>
|
<a t-on-click="setFilter('completed')" t-att-class="{selected: state.filter === 'completed'}">Completed</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<button class="clear-completed" t-if="storeProps.todos.length gt remaining" t-on-click="dispatch('clearCompleted')">
|
<button class="clear-completed" t-if="todos.length gt remaining" t-on-click="dispatch('clearCompleted')">
|
||||||
Clear completed
|
Clear completed
|
||||||
</button>
|
</button>
|
||||||
</footer>
|
</footer>
|
||||||
@@ -551,13 +605,13 @@ const TODO_APP_STORE_XML = `<templates>
|
|||||||
|
|
||||||
<li t-name="TodoItem" class="todo" t-att-class="{completed: props.completed, editing: state.isEditing}">
|
<li t-name="TodoItem" class="todo" t-att-class="{completed: props.completed, editing: state.isEditing}">
|
||||||
<div class="view">
|
<div class="view">
|
||||||
<input class="toggle" type="checkbox" t-on-change="toggleTodo" t-att-checked="props.completed"/>
|
<input class="toggle" type="checkbox" t-on-change="dispatch('toggleTodo', props.id)" t-att-checked="props.completed"/>
|
||||||
<label t-on-dblclick="editTodo">
|
<label t-on-dblclick="editTodo">
|
||||||
<t t-esc="props.title"/>
|
<t t-esc="props.title"/>
|
||||||
</label>
|
</label>
|
||||||
<button class="destroy" t-on-click="removeTodo"></button>
|
<button class="destroy" t-on-click="dispatch('removeTodo', props.id)"></button>
|
||||||
</div>
|
</div>
|
||||||
<input class="edit" t-ref="input" t-if="state.isEditing" t-att-value="props.title" t-on-keyup="handleKeyup" t-mounted="focusInput" t-on-blur="handleBlur"/>
|
<input class="edit" t-ref="input" t-if="state.isEditing" t-att-value="props.title" t-on-keyup="handleKeyup" t-on-blur="handleBlur"/>
|
||||||
</li>
|
</li>
|
||||||
</templates>`;
|
</templates>`;
|
||||||
|
|
||||||
@@ -1655,6 +1709,11 @@ export const SAMPLES = [
|
|||||||
xml: HOOKS_DEMO_XML,
|
xml: HOOKS_DEMO_XML,
|
||||||
css: HOOKS_CSS
|
css: HOOKS_CSS
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Context",
|
||||||
|
code: CONTEXT_JS,
|
||||||
|
xml: CONTEXT_XML,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Todo List App (with store)",
|
description: "Todo List App (with store)",
|
||||||
code: TODO_APP_STORE,
|
code: TODO_APP_STORE,
|
||||||
|
|||||||
Reference in New Issue
Block a user