diff --git a/tools/playground/samples.js b/tools/playground/samples.js index cb39cf83..e361521c 100644 --- a/tools/playground/samples.js +++ b/tools/playground/samples.js @@ -1,33 +1,6 @@ -const CLICK_COUNTER = `class ClickCounter extends owl.Component { - constructor() { - super(...arguments); - this.state = { value: 0 }; - } +const COMPONENTS = `// In this example, we show how components can be defined and created. - increment() { - this.state.value++; - } -} - -const qweb = new owl.QWeb(TEMPLATES); -const counter = new ClickCounter({ qweb }); -counter.mount(document.body); -`; - -const CLICK_COUNTER_XML = ` - -`; - -const CLICK_COUNTER_CSS = `button { - color: darkred; - font-size: 30px; - width: 220px; -}`; - -const CLICK_COUNTER_ESNEXT = `// This example will not work if your browser does not support ESNext class fields -class ClickCounter extends owl.Component { +class Counter extends owl.Component { state = { value: 0 }; increment() { @@ -35,68 +8,39 @@ class ClickCounter extends owl.Component { } } -const qweb = new owl.QWeb(TEMPLATES); -const counter = new ClickCounter({ qweb }); -counter.mount(document.body); -`; - -const WIDGET_COMPOSITION = `// This example will not work if your browser does not support ESNext class fields - -class ClickCounter extends owl.Component { - state = { value: 0 }; - - increment() { - this.state.value++; - } -} - -class InputWidget extends owl.Component { - state = {text: ""}; - - updateVal(event) { - let value = event.target.value; - if (this.props.reverse) { - value = value.split("").reverse().join(""); - } - this.state.text = value - } -} - // Main root widget class App extends owl.Component { - widgets = {ClickCounter, InputWidget}; + widgets = { Counter }; } // Application setup +// Note that the xml templates are injected into the global TEMPLATES variable. const qweb = new owl.QWeb(TEMPLATES); const app = new App({ qweb }); app.mount(document.body); `; -const WIDGET_COMPOSITION_XML = ` - - -
- - -
+const COMPONENTS_XML = ` +
- - + +
`; -const WIDGET_COMPOSITION_CSS = `button, input { +const COMPONENTS_CSS = `button { font-size: 20px; width: 220px; margin: 5px; }`; -const ANIMATION = `// This example will not work if your browser does not support ESNext class fields -class ClickCounter extends owl.Component { +const ANIMATION = `// The goal of this component is to see how the t-transition directive can be +// used to generate simple transition effects. + +class Counter extends owl.Component { state = { value: 0 }; increment() { @@ -105,15 +49,11 @@ class ClickCounter extends owl.Component { } class App extends owl.Component { - state = {flag: false, widgetFlag: false, numbers: []}; - widgets = {ClickCounter}; + state = { flag: false, widgetFlag: false, numbers: [] }; + widgets = { Counter }; - toggle() { - this.state.flag = !this.state.flag; - } - - toggleWidget() { - this.state.widgetFlag = !this.state.widgetFlag; + toggle(key) { + this.state[key] = !this.state[key]; } addNumber() { @@ -129,46 +69,44 @@ app.mount(document.body); `; const ANIMATION_XML = ` -
+

Transition on DOM element

-

Transition on DOM element

- -
- -
-
Hello
+
+ +
+
Hello
+
-
-

Transition on sub widgets

+

Transition on sub widgets

-
- -
- +
+ +
+ +
-
-

Transition on lists

-

Transitions can also be applied on lists

-
- -
- - - +

Transition on lists

+

Transitions can also be applied on lists

+
+ +
+ + + +
-
-

Simple CSS animation

-

Remember, normal CSS still apply: for example, a simple flash animation with pure css

- +

Simple CSS animation

+

Remember, normal CSS still apply: for example, a simple flash animation with pure css

+
`; @@ -177,6 +115,7 @@ const ANIMATION_CSS = `button { font-size: 18px; height: 35px; } + .btn { cursor: pointer; padding: 5px; @@ -184,7 +123,6 @@ const ANIMATION_CSS = `button { background-color: #dddddd; } - .flash { background-position: center; transition: background .6s; @@ -195,7 +133,6 @@ const ANIMATION_CSS = `button { transition: background 0s; } - .square { background-color: red; width: 100px; @@ -210,6 +147,7 @@ const ANIMATION_CSS = `button { .fade-enter-active, .fade-leave-active { transition: opacity .6s; } + .fade-enter, .fade-leave-to { opacity: 0; } @@ -233,7 +171,14 @@ const ANIMATION_CSS = `button { } `; -const LIFECYCLE_DEMO = `class HookWidget extends owl.Component { +const LIFECYCLE_DEMO = `// This example shows all the possible lifecycle hooks +// +// The root widget controls a sub widget (DemoWidget). 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. + +class DemoWidget extends owl.Component { constructor() { super(...arguments); this.state = { n: 0 }; @@ -262,37 +207,60 @@ const LIFECYCLE_DEMO = `class HookWidget extends owl.Component { } } -class ParentWidget extends owl.Component { - constructor() { - super(...arguments); - this.widgets = { HookWidget }; - this.state = { n: 0, flag: true }; - } +class App extends owl.Component { + widgets = { DemoWidget }; + state = { n: 0, flag: true }; + increment() { this.state.n++; } + toggleSubWidget() { this.state.flag = !this.state.flag; } } const qweb = new owl.QWeb(TEMPLATES); -const widget = new ParentWidget({ qweb }); -widget.mount(document.body); +const app = new App({ qweb }); +app.mount(document.body); `; const LIFECYCLE_DEMO_XML = ` -
- - -
- -
+
+
Demo Sub Widget
+
(click on me to update me)
+
Props: , State: .
+
+ +
+ + +
+
-
Demo Sub Widget. Props: . State: . (click on me to update me)
+
`; -const TODO_APP_STORE = `const ENTER_KEY = 13; +const LIFECYCLE_CSS = `button { + font-size: 18px; + margin: 5px; +} + +.demo { + margin: 10px; + padding: 10px; + background-color: #dddddd; + width: 250px; +}`; + +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 +// 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 ENTER_KEY = 13; const ESC_KEY = 27; const LOCALSTORAGE_KEY = "todos-odoo"; @@ -937,7 +905,20 @@ html .clear-completed:active { } `; -const RESPONSIVE = `class Navbar extends owl.Component {} +const RESPONSIVE = `// In this example, we show how we can modify keys in the global environment to +// make a responsive application. +// +// The main idea is to have a "isMobile" key in the environment, then listen +// to resize events and update the env if needed. Then, the whole interface +// will be updated, creating and destroying widgets as needed. +// +// To see this in action, try resizing the window. The application will switch +// to mobile mode whenever it has less than 768px. + +//------------------------------------------------------------------------------ +// Components +//------------------------------------------------------------------------------ +class Navbar extends owl.Component {} class ControlPanel extends owl.Component { widgets = { MobileSearchView }; @@ -958,6 +939,9 @@ class App extends owl.Component { widgets = { Navbar, ControlPanel, FormView, Chatter }; } +//------------------------------------------------------------------------------ +// Application Startup +//------------------------------------------------------------------------------ function isMobile() { return window.innerWidth <= 768; } @@ -967,6 +951,7 @@ const env = { isMobile: isMobile() }; + const app = new App(env); app.mount(document.body); @@ -983,44 +968,44 @@ window.addEventListener("resize", owl.utils.debounce(updateEnv, 20)); `; const RESPONSIVE_XML = ` - + -
-

controlpanel

- -
- -
-

formview

- -
- -
-

Chatter

-
Message
-
- -
MOBILE searchview
- -
- - -
-
- - -
-
- - - - - -
-
- This widget is only created in desktop mode. - +
+

Control Panel

+ +
+ +
+

Form View

+ +
+ +
+

Chatter

+
Message
+
+ +
Mobile searchview
+ +
+ This widget is only created in desktop mode. + +
+ +
+ + +
+
+ + +
+ + + + +
`; @@ -1036,6 +1021,7 @@ const RESPONSIVE_CSS = `body { .app.desktop { display: flex; } + .navbar { flex: 0 0 30px; height: 30px; @@ -1081,17 +1067,19 @@ 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. +const SLOTS = `// We show here how slots can be used to create generic components. +// In this example, the Card component is basically only a container. It is not +// aware of its content. It just knows where it should be (with t-slot). +// The parent widget define the content with t-set. +// +// Note that the t-on-click event, defined in the App template, is executed in +// the context of the App component, even though it is inside the Card component class Card extends owl.Component { - state = { fullDisplay: true }; + state = { showContent: true }; toggleDisplay() { - this.state.fullDisplay = !this.state.fullDisplay; + this.state.showContent = !this.state.showContent; } } @@ -1119,11 +1107,11 @@ const app = new App({ qweb }); app.mount(document.body);`; const SLOTS_XML = ` -
+
- +
@@ -1198,34 +1186,18 @@ const SLOTS_CSS = `.main { const ASYNC_COMPONENTS = `// This example will not work if your browser does not support ESNext class fields -// In this example, we have 2 sub widgets, one of them being async. However, we -// don't want renderings of the sync sub widget to be delayed because of the -// async one. We use the 't-asyncroot' directive for this purpose. +// In this example, we have 2 sub widgets, one of them being async (slow). +// However, we don't want renderings of the other sub widget to be delayed +// because of the slow widget. We use the 't-asyncroot' directive for this +// purpose. Try removing it to see the difference. class App extends owl.Component { - widgets = {AsyncChild, NotificationManager}; - state = { value: 0 }; + widgets = {SlowWidget, NotificationList}; + state = { value: 0, notifs: [] }; increment() { this.state.value++; - this.refs.notificationManager.notify("Value will be set to " + this.state.value); - } -} - -class AsyncChild extends owl.Component { - willUpdateProps() { - // simulate a widget that needs to perform async stuff (e.g. an RPC) - // with the updated props before re-rendering itself - return new Promise(function (resolve) { - setTimeout(resolve, 1000); - }); - } -} - -class NotificationManager extends owl.Component { - state = { notifs: [] }; - - notify(notif) { + const notif = "Value will be set to " + this.state.value; this.state.notifs.push(notif); setTimeout(() => { var index = this.state.notifs.indexOf(notif); @@ -1234,23 +1206,34 @@ class NotificationManager extends owl.Component { } } +class SlowWidget extends owl.Component { + willUpdateProps() { + // simulate a widget that needs to perform async stuff (e.g. an RPC) + // with the updated props before re-rendering itself + return new Promise(resolve => setTimeout(resolve, 1500)); + } +} + +class NotificationList extends owl.Component {} + const qweb = new owl.QWeb(TEMPLATES); const app = new App({ qweb }); -app.mount(document.body);`; +app.mount(document.body); +`; const ASYNC_COMPONENTS_XML = `
- - + +
-
+
Current value:
-
- +
+
@@ -1271,11 +1254,12 @@ button { padding: 20px; } -.notification_manager { +.notification-list { position: absolute; top: 0; right: 0; } + .notification { width: 150px; margin: 4px 8px; @@ -1284,15 +1268,12 @@ button { background-color: lightgray; }`; -const EMPTY = `class App extends owl.Component { -} +const FORM = `// This example illustrate how the t-model directive can be used to synchronize +// data between html inputs (and select/textareas) and the state of a component. +// Note that there are two controls with t-model="color": they are totally +// synchronized. -const qweb = new owl.QWeb(TEMPLATES); -const app = new App({qweb}); -app.mount(document.body); -`; - -const FORM = `class Form extends owl.Component { +class Form extends owl.Component { state = { text: "", othertext: "", @@ -1302,73 +1283,62 @@ const FORM = `class Form extends owl.Component { }; } +// Application setup const qweb = new owl.QWeb(TEMPLATES); const form = new Form({ qweb }); form.mount(document.body); `; const FORM_XML = ` -
-

Form

-
- Text (immediate): -
-
- Other text (lazy): -
-
- Number: -
-
- Boolean: -
-
- Color, with a select: -
-
- Color, with radio buttons: - - -
- -
-

State

-
Text:
-
Other Text:
-
Number:
-
Boolean: TrueFalse
-
Color:
+
+

Form

+
+ Text (immediate):
+
+ Other text (lazy): +
+
+ Number: +
+
+ Boolean: +
+
+ Color, with a select: + +
+
+ Color, with radio buttons: + + +
+
+

State

+
Text:
+
Other Text:
+
Number:
+
Boolean: TrueFalse
+
Color:
+
`; export const SAMPLES = [ { - description: "Click Counter", - code: CLICK_COUNTER, - xml: CLICK_COUNTER_XML, - css: CLICK_COUNTER_CSS + description: "Components", + code: COMPONENTS, + xml: COMPONENTS_XML, + css: COMPONENTS_CSS }, { - description: "Click Counter (ESNext)", - code: CLICK_COUNTER_ESNEXT, - xml: CLICK_COUNTER_XML, - css: CLICK_COUNTER_CSS - }, - { - description: "Form input bindings", + description: "Form Input Bindings", code: FORM, - xml: FORM_XML, - }, - { - description: "Widget Composition", - code: WIDGET_COMPOSITION, - xml: WIDGET_COMPOSITION_XML, - css: WIDGET_COMPOSITION_CSS + xml: FORM_XML }, { description: "Animations", @@ -1379,7 +1349,8 @@ export const SAMPLES = [ { description: "Lifecycle demo", code: LIFECYCLE_DEMO, - xml: LIFECYCLE_DEMO_XML + xml: LIFECYCLE_DEMO_XML, + css: LIFECYCLE_CSS, }, { description: "Todo List App (with store)", @@ -1394,7 +1365,7 @@ export const SAMPLES = [ xml: RESPONSIVE_XML }, { - description: "Slots", + description: "Slots And Generic Components", code: SLOTS, xml: SLOTS_XML, css: SLOTS_CSS @@ -1404,9 +1375,5 @@ export const SAMPLES = [ code: ASYNC_COMPONENTS, xml: ASYNC_COMPONENTS_XML, css: ASYNC_COMPONENTS_CSS - }, - { - description: "Empty", - code: EMPTY } ];