[DOC] reorganize doc, unskip test, fix some links

This commit is contained in:
Géry Debongnie
2021-12-13 14:53:46 +01:00
committed by Aaron Bohy
parent 63fbcf99fd
commit 702fb3b253
7 changed files with 226 additions and 271 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# ChangeLog # Changelog
This document contains an overview of all changes between Owl 1.x and This document contains an overview of all changes between Owl 1.x and
Owl 2.x, with some pointers on how to update the code. Owl 2.x, with some pointers on how to update the code.
+56 -55
View File
@@ -1,4 +1,4 @@
<h1 align="center">🦉 <a href="https://odoo.github.io/owl/">OWL Framework</a> 🦉</h1> <h1 align="center">🦉 <a href="https://odoo.github.io/owl/">Owl Framework</a> 🦉</h1>
[![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
[![npm version](https://badge.fury.io/js/@odoo%2Fowl.svg)](https://badge.fury.io/js/@odoo%2Fowl) [![npm version](https://badge.fury.io/js/@odoo%2Fowl.svg)](https://badge.fury.io/js/@odoo%2Fowl)
@@ -6,10 +6,12 @@
_Class based components with hooks, reactive state and concurrent mode_ _Class based components with hooks, reactive state and concurrent mode_
**Try it online!** you can experiment with the Owl framework in an online [playground](https://odoo.github.io/owl/playground).
## Project Overview ## Project Overview
The Odoo Web Library (OWL) is a smallish (~<20kb gzipped) UI framework intended to The Odoo Web Library (Owl) is a smallish (~<20kb gzipped) UI framework built by
be the basis for the [Odoo](https://www.odoo.com/) Web Client. Owl is a modern [Odoo](https://www.odoo.com/) for its products. Owl is a modern
framework, written in Typescript, taking the best ideas from React and Vue in a framework, written in Typescript, taking the best ideas from React and Vue in a
simple and consistent way. Owl's main features are: simple and consistent way. Owl's main features are:
@@ -17,39 +19,26 @@ simple and consistent way. Owl's main features are:
- a reactivity system based on hooks, - a reactivity system based on hooks,
- concurrent mode by default, - concurrent mode by default,
Owl components are defined with ES6 classes, they use QWeb templates, an Owl components are defined with ES6 classes and xml templates, uses an
underlying virtual DOM, integrates beautifully with hooks, and the rendering is underlying virtual DOM, integrates beautifully with hooks, and the rendering is
asynchronous. asynchronous.
**Try it online!** An online playground is available at Quick links:
[https://odoo.github.io/owl/playground](https://odoo.github.io/owl/playground)
to let you experiment with the Owl framework. There are some code examples to
showcase some interesting features.
Owl is currently stable. Possible future changes are explained in the - [documentation](#documentation),
[roadmap](roadmap.md). - [changelog](CHANGELOG.md) (from Owl 1.x to 2.x),
- [playground](https://odoo.github.io/owl/playground)
## Why Owl?
Why did Odoo decide to make Yet Another Framework? This is really a question
that deserves [a long answer](doc/miscellaneous/why_owl.md). But in short, we believe that
while the current state of the art frameworks are excellent, they are not
optimized for our use case, and there is still room for something else.
If you are interested in a comparison with React or Vue, you will
find some more additional information [here](doc/miscellaneous/comparison.md).
## Example ## Example
Here is a short example to illustrate interactive components: Here is a short example to illustrate interactive components:
```javascript ```javascript
const { Component, useState, mount } = owl; const { Component, useState, mount, xml } = owl;
const { xml } = owl.tags;
class Counter extends Component { class Counter extends Component {
static template = xml` static template = xml`
<button t-on-click="state.value++"> <button t-on-click="() => state.value++">
Click Me! [<t t-esc="state.value"/>] Click Me! [<t t-esc="state.value"/>]
</button>`; </button>`;
@@ -66,7 +55,7 @@ class App extends Component {
static components = { Counter }; static components = { Counter };
} }
mount(App, { target: document.body }); mount(App, document.body);
``` ```
Note that the counter component is made reactive with the [`useState` hook](doc/reference/hooks.md#usestate). Note that the counter component is made reactive with the [`useState` hook](doc/reference/hooks.md#usestate).
@@ -76,41 +65,56 @@ But this is not mandatory, many applications will load templates separately.
More interesting examples can be found on the More interesting examples can be found on the
[playground](https://odoo.github.io/owl/playground) application. [playground](https://odoo.github.io/owl/playground) application.
## Design Principles
OWL is designed to be used in highly dynamic applications where changing
requirements are common and code needs to be maintained by large teams.
- **XML based**: templates are based on the XML format, which allows interesting
applications. For example, they could be stored in a database and modified
dynamically with `xpaths`.
- **templates compilation in the browser**: this may not be a good fit for all
applications, but if you need to generate dynamically user interfaces in the
browser, this is very powerful. For example, a generic form view component
could generate a specific form user interface for each various models, from a XML view.
- **no toolchain required**: this is extremely useful for some applications, if,
for various reasons (security/deployment/dynamic modules/specific assets tools),
it is not ok to use standard web tools based on `npm`.
Owl is not designed to be fast nor small (even though it is quite good on those
two topics). It is a no nonsense framework to build applications. There is only
one way to define components (with classes). There is no black magic. It just
works.
## Documentation ## Documentation
A complete documentation for Owl can be found here: ### Learning Owl
- [Main documentation page](doc/readme.md). Are you new to Owl? This is the place to start!
Some of the most important pages are: - [Tutorial: create a TodoList application](doc/learning/tutorial_todoapp.md)
- [Quick Overview](doc/learning/overview.md)
- [Tutorial: TodoList application](doc/learning/tutorial_todoapp.md)
- [How to start an Owl project](doc/learning/quick_start.md) - [How to start an Owl project](doc/learning/quick_start.md)
- [QWeb templating language](doc/reference/qweb_templating_language.md) - [How to test Components](doc/learning/how_to_test.md)
- [How to write Single File Components](doc/learning/how_to_write_sfc.md)
### Reference
You will find here a complete reference of every feature, class or object
provided by Owl.
- [Animations](doc/reference/animations.md)
- [Browser](doc/reference/browser.md)
- [Component](doc/reference/component.md) - [Component](doc/reference/component.md)
- [Content](doc/reference/content.md)
- [Concurrency Model](doc/reference/concurrency_model.md)
- [Configuration](doc/reference/config.md)
- [Context](doc/reference/context.md)
- [Environment](doc/reference/environment.md)
- [Event Bus](doc/reference/event_bus.md)
- [Event Handling](doc/reference/event_handling.md)
- [Error Handling](doc/reference/error_handling.md)
- [Hooks](doc/reference/hooks.md) - [Hooks](doc/reference/hooks.md)
- [Mounting a component](doc/reference/mounting.md)
- [Miscellaneous Components](doc/reference/misc.md)
- [Observer](doc/reference/observer.md)
- [Props](doc/reference/props.md)
- [Props Validation](doc/reference/props_validation.md)
- [QWeb Templating Language](doc/reference/qweb_templating_language.md)
- [QWeb Engine](doc/reference/qweb_engine.md)
- [Slots](doc/reference/slots.md)
- [Tags](doc/reference/tags.md)
- [Utils](doc/reference/utils.md)
### Other Topics
This section provides miscellaneous document that explains some topics
which cannot be considered either a tutorial, or reference documentation.
- [Owl architecture: the Virtual DOM](doc/miscellaneous/vdom.md)
- [Owl architecture: the rendering pipeline](doc/miscellaneous/rendering.md)
- [Comparison with React/Vue](doc/miscellaneous/comparison.md)
- [Why did Odoo built Owl?](doc/miscellaneous/why_owl.md)
## Installing Owl ## Installing Owl
@@ -125,6 +129,3 @@ If you want to use a simple `<script>` tag, the last release can be downloaded h
- [owl-1.4.10](https://github.com/odoo/owl/releases/tag/v1.4.10) - [owl-1.4.10](https://github.com/odoo/owl/releases/tag/v1.4.10)
## License
OWL is [LGPL licensed](./LICENSE).
+1 -2
View File
@@ -539,8 +539,7 @@ deleteTask(ev) {
Looking at the code, it is apparent that we now have code to handle tasks Looking at the code, it is apparent that we now have code to handle tasks
scattered in more than one place. Also, it mixes UI code and business logic scattered in more than one place. Also, it mixes UI code and business logic
code. Owl has a way to manage state separately from the user interface: a code. Owl has a way to manage state separately from the user interface: a store.
[`Store`](../reference/store.md).
Let us use it in our application. This is a pretty large refactoring (for our Let us use it in our application. This is a pretty large refactoring (for our
application), since it involves extracting all task related code out of the application), since it involves extracting all task related code out of the
+2 -3
View File
@@ -14,8 +14,7 @@ discussed, feel free to open an issue/submit a PR to correct this text.
- [Tooling/Build Step](#toolingbuild-step) - [Tooling/Build Step](#toolingbuild-step)
- [Templating](#templating) - [Templating](#templating)
- [Asynchronous rendering](#asynchronous-rendering) - [Asynchronous rendering](#asynchronous-rendering)
- [Reactiveness](#reactiveness) - [Reactivity](#reactivity)
- [State Management](#state-management)
- [Hooks](#hooks) - [Hooks](#hooks)
## Size ## Size
@@ -173,7 +172,7 @@ more convoluted. For example, in Vue, you need to use a dynamic import keyword
that needs to be transpiled at build time in order for the component to be loaded that needs to be transpiled at build time in order for the component to be loaded
asynchronously (see [the documentation](https://vuejs.org/v2/guide/components-dynamic-async.html#Async-Components)). asynchronously (see [the documentation](https://vuejs.org/v2/guide/components-dynamic-async.html#Async-Components)).
## Reactiveness ## Reactivity
React has a simple model: whenever the state changes, it is React has a simple model: whenever the state changes, it is
replaced with a new state (via the `setState` method). Then, the DOM is patched. replaced with a new state (via the `setState` method). Then, the DOM is patched.
-54
View File
@@ -1,54 +0,0 @@
# 🦉 OWL Documentation 🦉
## Learning Owl
Are you new to Owl? This is the place to start!
- [Tutorial: create a TodoList application](learning/tutorial_todoapp.md)
- [Quick Overview](learning/overview.md)
- [How to start an Owl project](learning/quick_start.md)
- [How to test Components](learning/how_to_test.md)
- [How to write Single File Components](learning/how_to_write_sfc.md)
## Reference
You will find here a complete reference of every feature, class or object
provided by Owl.
- [Animations](reference/animations.md)
- [Browser](reference/browser.md)
- [Component](reference/component.md)
- [Content](reference/content.md)
- [Concurrency Model](reference/concurrency_model.md)
- [Configuration](reference/config.md)
- [Context](reference/context.md)
- [Environment](reference/environment.md)
- [Event Bus](reference/event_bus.md)
- [Event Handling](reference/event_handling.md)
- [Error Handling](reference/error_handling.md)
- [Hooks](reference/hooks.md)
- [Mounting a component](reference/mounting.md)
- [Miscellaneous Components](reference/misc.md)
- [Observer](reference/observer.md)
- [Props](reference/props.md)
- [Props Validation](reference/props_validation.md)
- [QWeb Templating Language](reference/qweb_templating_language.md)
- [QWeb Engine](reference/qweb_engine.md)
- [Slots](reference/slots.md)
- [Tags](reference/tags.md)
- [Utils](reference/utils.md)
## Other Topics
This section provides miscellaneous document that explains some topics
which cannot be considered either a tutorial, or reference documentation.
- [Owl architecture: the Virtual DOM](miscellaneous/vdom.md)
- [Owl architecture: the rendering pipeline](miscellaneous/rendering.md)
- [Comparison with React/Vue](miscellaneous/comparison.md)
- [Why did Odoo built Owl?](miscellaneous/why_owl.md)
---
Found an issue in the documentation? A broken link? Some outdated information?
Please open an issue or submit a PR!
+10
View File
@@ -372,6 +372,16 @@ to be closed:
useExternalListener(window, "click", this.closeMenu); useExternalListener(window, "click", this.closeMenu);
``` ```
### `useComponent`
The `useComponent` hook is useful as a building block for some customized hooks,
that may need a reference to the component calling them.
### `useEnv`
The `useEnv` hook is useful as a building block for some customized hooks,
that may need a reference to the env of the component calling them.
### Making customized hooks ### Making customized hooks
Hooks are a wonderful way to organize the code of a complex component by feature Hooks are a wonderful way to organize the code of a complex component by feature
+156 -156
View File
@@ -4,180 +4,180 @@
* We define here a test to make sure that there are no dead link in the Owl * We define here a test to make sure that there are no dead link in the Owl
* documentation. * documentation.
*/ */
// import * as fs from "fs"; import * as fs from "fs";
// //-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// // Helpers // Helpers
// //-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// interface MarkDownLink { interface MarkDownLink {
// name: string; name: string;
// link: string; link: string;
// } }
// interface MarkDownSection { interface MarkDownSection {
// name: string; name: string;
// slug: string; slug: string;
// } }
// interface FileData { interface FileData {
// name: string; name: string;
// path: string[]; path: string[];
// fullName: string; fullName: string;
// links: MarkDownLink[]; links: MarkDownLink[];
// sections: MarkDownSection[]; sections: MarkDownSection[];
// } }
// const LINK_REGEXP = /\[([^\[]+)\]\(([^\)]+)\)/g; const LINK_REGEXP = /\[([^\[]+)\]\(([^\)]+)\)/g;
// const HEADING_REGEXP = /\n(#+\s*)(.*)/g; const HEADING_REGEXP = /\n(#+\s*)(.*)/g;
// export function addMardownData(fileData): void { export function addMarkdownData(fileData: FileData): void {
// const sep = fileData.path.length > 0 ? "/" : ""; const sep = fileData.path.length > 0 ? "/" : "";
// const fullName = fileData.path.join("/") + sep + fileData.name; const fullName = fileData.path.join("/") + sep + fileData.name;
// const content = fs.readFileSync(fullName, { encoding: "utf8" }); const content = fs.readFileSync(fullName, { encoding: "utf8" });
// let m; let m;
// // get links info // get links info
// do { do {
// m = LINK_REGEXP.exec(content); m = LINK_REGEXP.exec(content);
// if (m) { if (m) {
// fileData.links.push({ name: m[0], link: m[2] }); fileData.links.push({ name: m[0], link: m[2] });
// } }
// } while (m); } while (m);
// // get sections info // get sections info
// do { do {
// m = HEADING_REGEXP.exec(content); m = HEADING_REGEXP.exec(content);
// if (m) { if (m) {
// fileData.sections.push({ name: m[0], slug: slugify(m[2]) }); fileData.sections.push({ name: m[0], slug: slugify(m[2]) });
// } }
// } while (m); } while (m);
// } }
// /** /**
// * Returns a list of FileData corresponding to all files that need to be * Returns a list of FileData corresponding to all files that need to be
// * validated. * validated.
// */ */
// function getFiles(path: string[] = []): FileData[] { function getFiles(path: string[] = []): FileData[] {
// if (path.length === 0) { if (path.length === 0) {
// const baseFiles: FileData[] = [ const baseFiles: FileData[] = [
// { name: "README.md", path: [], links: [], sections: [], fullName: "README.md" }, { name: "README.md", path: [], links: [], sections: [], fullName: "README.md" },
// { name: "roadmap.md", path: [], links: [], sections: [], fullName: "roadmap.md" }, { name: "roadmap.md", path: [], links: [], sections: [], fullName: "roadmap.md" },
// ]; ];
// const rest = getFiles(["doc"]); const rest = getFiles(["doc"]);
// const result = baseFiles.concat(rest); const result = baseFiles.concat(rest);
// result.forEach(addMardownData); result.forEach(addMarkdownData);
// return result; return result;
// } }
// const files = fs.readdirSync(path.join("/"), { withFileTypes: true }).map((f) => { const files = fs.readdirSync(path.join("/"), { withFileTypes: true }).map((f) => {
// if (f.isDirectory()) { if (f.isDirectory()) {
// return getFiles(path.concat(f.name)); return getFiles(path.concat(f.name));
// } }
// const fullName = path.join("/") + (path.length > 0 ? "/" : "") + f.name; const fullName = path.join("/") + (path.length > 0 ? "/" : "") + f.name;
// return [ return [
// { {
// name: f.name, name: f.name,
// path, path,
// links: [], links: [],
// sections: [], sections: [],
// fullName, fullName,
// }, },
// ]; ];
// }); });
// return Array.prototype.concat(...files); return Array.prototype.concat(...files);
// } }
// const LOCAL_FILES = ["LICENSE", "tools/debug.js"]; const LOCAL_FILES = ["LICENSE", "CHANGELOG.md"];
// export function isLinkValid(link: MarkDownLink, current: FileData, files: FileData[]): boolean { export function isLinkValid(link: MarkDownLink, current: FileData, files: FileData[]): boolean {
// if (link.link.startsWith("http")) { if (link.link.startsWith("http")) {
// // no check on external links // no check on external links
// return true; return true;
// } }
// // Step 1: extract path, name, hash // Step 1: extract path, name, hash
// // path = ['doc', 'architecture] // path = ['doc', 'architecture]
// // name = 'rendering.md' // name = 'rendering.md'
// // hash = 'blabla' (or '' if no hash) // hash = 'blabla' (or '' if no hash)
// const parts = link.link.split("#"); const parts = link.link.split("#");
// const hash = parts[1] || ""; const hash = parts[1] || "";
// let name; let name;
// let path; let path;
// if (parts[0]) { if (parts[0]) {
// let temp = parts[0].split("/"); let temp = parts[0].split("/");
// name = temp[temp.length - 1]; name = temp[temp.length - 1];
// temp.splice(-1); temp.splice(-1);
// path = current.path.slice(); path = current.path.slice();
// for (let elem of temp) { for (let elem of temp) {
// if (elem === "..") { if (elem === "..") {
// path.splice(-1); path.splice(-1);
// } else if (elem !== ".") { } else if (elem !== ".") {
// path.push(elem); path.push(elem);
// } }
// } }
// } else { } else {
// // there are no file name, so this is a relative link to the current file // there are no file name, so this is a relative link to the current file
// name = current.name; name = current.name;
// path = current.path; path = current.path;
// } }
// // Step 2: build normalized link file name // Step 2: build normalized link file name
// const linkFullName = path.join("/") + (path.length > 0 ? "/" : "") + name; const linkFullName = path.join("/") + (path.length > 0 ? "/" : "") + name;
// // Step 3: check link name against white list of local files // Step 3: check link name against white list of local files
// if (LOCAL_FILES.includes(linkFullName)) { if (LOCAL_FILES.includes(linkFullName)) {
// return true; return true;
// } }
// // Step 4: check if there is a matching file // Step 4: check if there is a matching file
// let target: FileData | undefined = files.find((f) => f.fullName === linkFullName); let target: FileData | undefined = files.find((f) => f.fullName === linkFullName);
// if (!target) { if (!target) {
// return false; return false;
// } }
// // Step 5: if necessary, check if there is a corresponding link inside the target // Step 5: if necessary, check if there is a corresponding link inside the target
// // link name // link name
// if (hash) { if (hash) {
// if (!target.sections.find((s) => s.slug === hash)) { if (!target.sections.find((s) => s.slug === hash)) {
// return false; return false;
// } }
// } }
// return true; return true;
// } }
// // adapted from https://medium.com/@mhagemann/the-ultimate-way-to-slugify-a-url-string-in-javascript-b8e4a0d849e1 // adapted from https://medium.com/@mhagemann/the-ultimate-way-to-slugify-a-url-string-in-javascript-b8e4a0d849e1
// function slugify(str) { function slugify(str: string): string {
// const a = "àáäâãåăæçèéëêǵḧìíïîḿńǹñòóöôœøṕŕßśșțùúüûǘẃẍÿź·_,:;"; const a = "àáäâãåăæçèéëêǵḧìíïîḿńǹñòóöôœøṕŕßśșțùúüûǘẃẍÿź·_,:;";
// const b = "aaaaaaaaceeeeghiiiimnnnooooooprssstuuuuuwxyz-----"; const b = "aaaaaaaaceeeeghiiiimnnnooooooprssstuuuuuwxyz-----";
// const p = new RegExp(a.split("").join("|"), "g"); const p = new RegExp(a.split("").join("|"), "g");
// return str return str
// .toString() .toString()
// .toLowerCase() .toLowerCase()
// .replace(/\//g, "") // remove / .replace(/\//g, "") // remove /
// .replace(/\s+/g, "-") // Replace spaces with - .replace(/\s+/g, "-") // Replace spaces with -
// .replace(p, (c) => b.charAt(a.indexOf(c))) // Replace special characters .replace(p, (c) => b.charAt(a.indexOf(c))) // Replace special characters
// .replace(/&/g, "-and-") // Replace & with and .replace(/&/g, "-and-") // Replace & with and
// .replace(/[^\w\-]+/g, "") // Remove all non-word characters .replace(/[^\w\-]+/g, "") // Remove all non-word characters
// .replace(/\-\-+/g, "-") // Replace multiple - with single - .replace(/\-\-+/g, "-") // Replace multiple - with single -
// .replace(/^-+/, "") // Trim - from start of text .replace(/^-+/, "") // Trim - from start of text
// .replace(/-+$/, ""); // Trim - from end of text .replace(/-+$/, ""); // Trim - from end of text
// } }
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Test // Test
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
test.skip("All markdown links work", () => { test("All markdown links work", () => {
// let linkNumber = 0; let linkNumber = 0;
// let invalidLinkNumber = 0; let invalidLinkNumber = 0;
// const data = getFiles(); const data = getFiles();
// for (let file of data) { for (let file of data) {
// for (let link of file.links) { for (let link of file.links) {
// linkNumber++; linkNumber++;
// if (!isLinkValid(link, file, data)) { if (!isLinkValid(link, file, data)) {
// console.warn(`Invalid Link: "${link.name}" in "${file.name}"`); console.warn(`Invalid Link: "${link.name}" in "${file.name}"`);
// invalidLinkNumber++; invalidLinkNumber++;
// } }
// } }
// } }
// expect(invalidLinkNumber).toBe(0); expect(invalidLinkNumber).toBe(0);
// expect(linkNumber).toBeGreaterThan(10); expect(linkNumber).toBeGreaterThan(10);
}); });