-
-`;
-
-const ANIMATION_CSS = `button {
- font-size: 18px;
- height: 35px;
-}
-
-.btn {
- cursor: pointer;
- padding: 5px;
- margin: 5px;
- background-color: #dddddd;
-}
-
-.flash {
- background-position: center;
- transition: background .6s;
-}
-
-.flash:active {
- background-color: gray;
- transition: background 0s;
-}
-
-.square {
- background-color: red;
- width: 100px;
- height: 70px;
- color: white;
- margin: 0 20px;
- font-size: 24px;
- line-height: 70px;
- text-align: center;
-}
-
-.fade-enter-active, .fade-leave-active {
- transition: opacity .6s;
-}
-
-.fade-enter, .fade-leave-to {
- opacity: 0;
-}
-
-.demo {
- display: flex;
- height: 80px;
-}
-
-.clickcounter {
- margin-left: 20px;
- height: 50px;
- background-color: blue;
- color: white;
-}
-
-.numberspan {
- border: 1px solid green;
- margin: 5px;
- padding: 5px;
-}
-`;
-
-const LIFECYCLE_DEMO = `// This example shows all the possible lifecycle hooks
+const LIFECYCLE_DEMO = /*js*/`
+// This example shows all the possible lifecycle hooks
//
// The root component controls a sub component (DemoComponent). It logs all its lifecycle
// methods in the console. Try modifying its state by clicking on it, or by
// clicking on the two main buttons, and look into the console to see what
// happens.
-const { Component, useState, mount } = owl;
+const { Component, useState, mount, onWillStart, onMounted, onWillUnmount, onWillUpdateProps, onPatched, onWillPatch } = owl;
class DemoComponent extends Component {
setup() {
this.state = useState({ n: 0 });
console.log("setup");
- }
- async willStart() {
- console.log("willstart");
- }
- mounted() {
- console.log("mounted");
- }
- async willUpdateProps(nextProps) {
- console.log("willUpdateProps", nextProps);
- }
- willPatch() {
- console.log("willPatch");
- }
- patched() {
- console.log("patched");
- }
- willUnmount() {
- console.log("willUnmount");
+
+ onWillStart(() => console.log("willstart"));
+ onMounted(() => console.log("mounted"));
+ onWillPatch(() => console.log("willPatch"));
+ onWillUpdateProps((nextProps) => console.log("willUpdateProps", nextProps));
+ onPatched(() => console.log("patched"));
+ onWillUnmount(() => console.log("willUnmount"));
}
increment() {
this.state.n++;
}
}
+DemoComponent.template = "DemoComponent";
class App extends Component {
setup() {
@@ -231,11 +91,13 @@ class App extends Component {
}
}
App.components = { DemoComponent };
+App.template = "App";
-mount(App, { target: document.body });
+mount(App, document.body);
`;
-const LIFECYCLE_DEMO_XML = `
+const LIFECYCLE_DEMO_XML = /*xml*/ `
+
Demo Sub Component
(click on me to update me)
@@ -251,7 +113,8 @@ const LIFECYCLE_DEMO_XML = `
`;
-const LIFECYCLE_CSS = `button {
+const LIFECYCLE_CSS = /*css*/`
+button {
font-size: 18px;
margin: 5px;
}
@@ -263,9 +126,9 @@ const LIFECYCLE_CSS = `button {
width: 250px;
}`;
-const HOOKS_DEMO = `// In this example, we show how hooks can be used or defined.
-const { hooks, mount } = owl;
-const {useState, onMounted, onWillUnmount} = hooks;
+const HOOKS_DEMO = /*js*/ `
+// In this example, we show how hooks can be used or defined.
+const { mount, useState, onMounted, onWillUnmount } = owl;
// We define here a custom behaviour: this hook tracks the state of the mouse
// position
@@ -301,12 +164,14 @@ class App extends owl.Component {
this.counter.value++;
}
}
+App.template = "App";
// Application setup
-mount(App, { target: document.body });
+mount(App, document.body);
`;
-const HOOKS_DEMO_XML = `
+const HOOKS_DEMO_XML = /*xml*/ `
+
Mouse: ,
@@ -314,77 +179,19 @@ const HOOKS_DEMO_XML = `
`;
-const HOOKS_CSS = `button {
+const HOOKS_CSS = /*css*/ `button {
width: 120px;
height: 35px;
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, mount } = owl;
-const { useContext } = owl.hooks;
-
-class ToolbarButton extends Component {
- setup() {
- this.theme = useContext(this.env.themeContext);
- }
-
- get style () {
- const theme = this.theme;
- return \`background-color: \${theme.background}; color: \${theme.foreground}\`;
- }
-}
-
-class Toolbar extends Component {}
-Toolbar.components = { ToolbarButton };
-
-// Main root component
-class App extends Component {
- toggleTheme() {
- const { background, foreground } = this.env.themeContext.state;
- this.env.themeContext.state.background = foreground;
- this.env.themeContext.state.foreground = background;
- }
-}
-App.components = { Toolbar };
-
-// Application setup
-const themeContext = new Context({
- background: '#000',
- foreground: '#fff',
-});
-// Add the themeContext the environment to make it available to all components
-App.env.themeContext = themeContext;
-mount(App, { target: document.body });
-`;
-
-const CONTEXT_XML = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-const TODO_APP_STORE = `// This example is an implementation of the TodoList application, from the
+const TODO_APP_REACTIVITY = /*js*/ `
+// This example is an implementation of the TodoList application, from the
// www.todomvc.com project. This is a non trivial application with some
// interesting user interactions. It uses the local storage for persistence.
//
-// In this implementation, we use the owl Store class to manage the state. It
-// is very similar to the VueX store.
-const { Component, useState, mount } = owl;
-const { useRef, useStore, useDispatch, onPatched, onMounted } = owl.hooks;
+// In this implementation, we use the owl reactivity mechanism.
+const { Component, useState, mount, useRef, onPatched, onMounted, reactive } = owl;
//------------------------------------------------------------------------------
// Constants, helpers
@@ -411,59 +218,15 @@ function useAutofocus(name) {
onMounted(updateFocus);
}
-//------------------------------------------------------------------------------
-// Store
-//------------------------------------------------------------------------------
-const initialState = { todos: [], nextId: 1};
-
-const actions = {
- addTodo({ state }, title) {
- const todo = {
- id: state.nextId++,
- title,
- completed: false
- }
- state.todos.push(todo);
- },
- removeTodo({ state }, id) {
- const index = state.todos.findIndex(t => t.id === id);
- state.todos.splice(index, 1);
- },
- 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);
- todo.completed = !todo.completed;
- },
- clearCompleted({ state, dispatch }) {
- for (let todo of state.todos.slice()) {
- if (todo.completed) {
- dispatch("removeTodo", todo.id);
- }
- }
- },
- toggleAll({ state, dispatch }, completed) {
- for (let todo of state.todos) {
- todo.completed = completed;
- }
- },
-};
-
//------------------------------------------------------------------------------
// TodoItem
//------------------------------------------------------------------------------
class TodoItem extends Component {
setup() {
useAutofocus("input");
- this.state = useState({ isEditing: false });
- this.dispatch = useDispatch();
+ this.todo = this.env.todo;
+ this.todoState = useState(this.todo.state);
+ this.state = useState({});
}
handleKeyup(ev) {
@@ -481,19 +244,26 @@ class TodoItem extends Component {
}
updateTitle(title) {
- this.dispatch("updateTodo", {title, id: this.props.id});
+ this.todo.updateTodo({title, id: this.props.id});
this.state.isEditing = false;
}
}
+TodoItem.template = "TodoItem";
//------------------------------------------------------------------------------
// TodoApp
//------------------------------------------------------------------------------
class TodoApp extends Component {
setup() {
+ this.todo = this.env.todo;
+ this.todoState = useState(this.todo.state);
+
this.state = useState({ filter: "all" });
- this.todos = useStore(state => state.todos);
- this.dispatch = useDispatch();
+ this.setFilter = this.setFilter.bind(this);
+ }
+
+ get todos() {
+ return this.todoState.todos;
}
get visibleTodos() {
@@ -521,7 +291,7 @@ class TodoApp extends Component {
if (ev.keyCode === ENTER_KEY) {
const title = ev.target.value;
if (title.trim()) {
- this.dispatch("addTodo", title);
+ this.todo.addTodo(title);
}
ev.target.value = "";
}
@@ -532,43 +302,103 @@ class TodoApp extends Component {
}
}
TodoApp.components = { TodoItem };
+TodoApp.template = "TodoApp";
//------------------------------------------------------------------------------
// App Initialization
//------------------------------------------------------------------------------
-function makeStore() {
+function makeGlobalState(initialState, key) {
+ const state = Object.assign(initialState, loadState());
+ const reactiveState = reactive(state, () => saveState(reactiveState));
+ // read everything to be notified afterwards
+ saveState(reactiveState);
+
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) : initialState;
+ const str = JSON.stringify(state);
+ try {
+ localStorage.setItem(key, str);
+ } catch (e) {};
}
- const state = loadState();
- const store = new owl.Store({ state, actions });
- store.on("update", null, () => saveState(store.state));
- return store;
+ function loadState() {
+ const localState = localStorage.getItem(key);
+ return localState ? JSON.parse(localState) : {};
+ }
+
+ return { state: reactiveState };
}
-TodoApp.env.store = makeStore();
-mount(TodoApp, { target: document.body });
+function toDoService() {
+ const { state } = makeGlobalState({ todos: [], nextId: 1}, LOCALSTORAGE_KEY);
+
+ function addTodo(title) {
+ const todo = {
+ id: state.nextId++,
+ title,
+ completed: false
+ }
+ state.todos.push(todo);
+ }
+
+ function removeTodo(id) {
+ const index = state.todos.findIndex(t => t.id === id);
+ state.todos.splice(index, 1);
+ }
+
+ function updateTodo({id, title}) {
+ const value = title.trim();
+ if (!value) {
+ removeTodo(id);
+ } else {
+ const todo = state.todos.find(t => t.id === id);
+ todo.title = value;
+ }
+ }
+
+ function toggleTodo(id) {
+ const todo = state.todos.find(t => t.id === id);
+ todo.completed = !todo.completed;
+ }
+
+ function clearCompleted() {
+ for (let todo of state.todos.slice()) {
+ if (todo.completed) {
+ removeTodo(todo.id);
+ }
+ }
+ }
+
+ function toggleAll(completed) {
+ for (let todo of state.todos) {
+ todo.completed = completed;
+ }
+ }
+
+ return {
+ state, addTodo, removeTodo, updateTodo, toggleTodo, clearCompleted, toggleAll
+ }
+}
+
+const env = {
+ todo: toDoService(),
+};
+mount(TodoApp, document.body, { env });
`;
-const TODO_APP_STORE_XML = `
+const TODO_APP_REACTIVITY_XML = /*xml*/ `
+