The website builder is a client action named website_preview. It allows users
to create and edit website pages.
Most of the code involved is located in three addons:
html_editoris the low level html wysiwyg editor (mostly about editing text). The core class is namedEditor. It is organized with plugins that cooperate. Everything inside this is maintained by the html editor team.html_builderis a generic application that provides an interfact to edit some html page by drag and dropping snippets, selecting sections and various elements, or applying various options. Note that it is intended to be shared between mass_mailing and website. The content of this addon is maintained by the website team.website: contains thewebsite_previewclient action, and all the website specific code for the website builder
In the website addon, we have two main points of interest:
static/src/client_actions/website_preview/contains the website preview actionstatic/src/builder/contains the WebsiteBuilder component, and is lazy loaded
Before talking about the website builders, it is useful to have some understanding on the architecture of the html editor.
The Editor is a class that is basically a plugin manager. It only instantiate
a set of plugins, and coordinate resources.
In the context of the website builder, the Editor class is instantiated by the
Builder component. It is given a set of plugins received in props from the WebsiteBuilder component.
Note that the set of plugin determines the feature that are active in the editor. For example, we have two different sets of plugin in the website builder, depending if we are in translation mode or not.
In general, a plugin in the context of the editor is a subclass of Plugin
(see html_editor/static/src/plugin.js). It is something that will perform a
kind of task, it may depends on other plugins (see dependencies), register
resources (basically handlers that will be called when some events happens),
setup and cleanup events listeners, and do all kind of work. The class is
actually quite small and commented.
The purpose of a plugin is:
- Organize the code with a group of determined set of technical and/or functional features
- Be the entry point to interact with the rest of the system (other plugins)
- Explicitly specify and require dependencies with other plugins
Resources can be declared and retrieved like this:
class APlugin extends Plugin {
resources = {
fruits: [{ id: 'apple' }, { id: 'banana' }]
}
}
class BPlugin extends Plugin {
resources = {
fruits: { id: 'orange' } // No need to specify an array if there is only one object.
}
}
class CPlugin extends Plugin {
setup() {
// [{id: 'apple'}, {id: 'banana'}, {id: 'orange'}]
console.log(this.getResource('fruits'));
}
}Plugin cooperate and communicate through 2 main mechanisms: shared methods and resources:
- Shared methods: when one plugin needs to call a specific method of another plugin (explicit dependency)
- Resources: for everything else (eg. notifying events)
class APlugin extends Plugin {
static id = "a";
static shared = ["mySharedMethod"];
mySharedMethod() {
console.log("hello")
}
}
class BPlugin extends Plugin {
static id = "b";
static dependencies = ["a"];
whateverMethod() {
// …
this.shared.a.mySharedMethod();
// …
}
}To cooperate with many plugins, it is common to register functions as a resource, and simply call them.
class APlugin extends Plugin {
resources = {
my_event_callbacks: (arg1, arg2) => {
console.log("plugin a handler", arg1, arg2);
}
}
}
class BPlugin extends Plugin {
resources = {
my_event_callbacks: (arg1, arg2) =>{
console.log("plugin b handler", arg1, arg2);
}
}
}
class CPlugin extends Plugin {
whateverMethod() {
this.dispatchTo("my_event_callbacks", arg1, arg2);
// which is implemented as:
// for (const callback of this.getResource("my_event_callbacks")) {
// callback(arg1, arg2);
// }
}
}The website builder is an owl application. It is a client action (a kind of action in the webclient that is basically a owl component). It's job is mostly to render the current page in an iframe, to start an editor in that iframe, to provide an interface to add "blocks" (snippets) and to customize various parameters of these blocks (options), and to save the views that are dirty when necessary.
The main concepts are:
snippets: basically small pieces of html that can be inserted by the user in the page. They are stored as views, usually (?) in the folderviews/snippetsin website. They are usually prefixed bys_, for examples_adventure.xml.options: define the UI for one or more feature of a visual aspects of the page. Basically, the various buttons and elements in the customize tab of the website builder (sidebar)builder components: options are usually built with some special builder components, that makes it easy to interact properly with the intended editing action, and to show the proper state depending on the state of the page (for example, if a feature is currently active, a BuilderButton will show that it is toggled)actions: an action specifies how a builder component with that action behave.
It is an owl component WebsiteBuilderClientAction (see static/src/client_actions/website_preview/website_builder_action.js)
that is displayed when opening the website application in the web client.
The website_preview client action adds 2 buttons in the systray: New and Edit.
The New button allows the user to create new pages, the Edit is there to let
the user edit the current page.
The client action takes over the url handling, and load the website inside an
iframe (actually 2, the second iframe (fallback) is there to make the navigation
smoohter). Once the user clicks on Edit, we lazy load the website.website_builder_assets
bundle which contains the WebsiteBuilder component. It also loads all website
snippets, then the user interface is displayed on the right sidebar and the
editing process starts.
WebsiteBuilderClientAction (from website)
-> LocalOverlayContainer
-> LazyComponent
-> WebsiteBuilder (from website)
-> Builder (from html_builder)
-> BlockTab (from html_builder)
...
Everything inside LazyComponent is lazy loaded.
These plugins are defined by the WebsiteBuilder component. Depending on the
editing situation (editing translations or website page), a different set of
plugins is constructed. Currently, the code has a white list of plugins for
the translation mode, and build the plugin list for the normal editing mode
from a registry website-plugins.
BuilderComponent BuilderGroupButton BuilderButton
BuilderSelect
BuilderSelectItem
BuilderNumberInput
BuilderTextInput
BuilderAction preview : Boolean: action peut etre preview ou pas reload : Object: doit forcer un reload lorsque l'action est appliqué
getReloadUrl: le reload de la page est fait sur une autre URL prepare() : Fonction permettant de récupérer de facon async des informations pour connaitre le valeur de l'action (Par exemple faire un rpc pour récupérer les vues actives) isApplied(): Permet de savoir si l'action est appliqué ou pas. (Est evalué a chaque modification du website (useDomState)) getValue(): Permet de récupérer la valeur dans le dom qui est observé par l action apply(): code appliqué lorsqu'on utiliser un BuilderComponent clean(): code appliqué losrqu'on veut retirer le comportement du apply load(): optimisation pour le mode preview. Permet de récupérer de l'info async nécessaire pour le apply
Plugin
Normalize
useDomState(callback) >< useState callback trigger a chaque step
useDomState: Par default, on doit l'utiliser dans BaseOptionComponent On doit toujours l'utiliser lorsqu'on observe le dom du website ou de l'état du website (par exemple les font)
useState: créé juste un réactif que l on peut modifier. Si modification, on trigger un rerender
BaseOptionComponent
normalize onDelete onClone