Skip to content

Instantly share code, notes, and snippets.

@ged-odoo
Last active October 22, 2025 13:37
Show Gist options
  • Select an option

  • Save ged-odoo/02a3feaa21500e036d8f83c112809359 to your computer and use it in GitHub Desktop.

Select an option

Save ged-odoo/02a3feaa21500e036d8f83c112809359 to your computer and use it in GitHub Desktop.
notes on website builder

Website Builder: Technical notes

Table of Content

1. Overview

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_editor is the low level html wysiwyg editor (mostly about editing text). The core class is named Editor. It is organized with plugins that cooperate. Everything inside this is maintained by the html editor team.
  • html_builder is 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 the website_preview client 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 action
  • static/src/builder/ contains the WebsiteBuilder component, and is lazy loaded

2. HTML Editor

Before talking about the website builders, it is useful to have some understanding on the architecture of the html editor.

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.

Plugins

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);
    // }
  }
}

3. Website Builder

Overview

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 folder views/snippets in website. They are usually prefixed by s_, for example s_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.

Website Preview Action

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 Component Tree

WebsiteBuilderClientAction        (from website)
  -> LocalOverlayContainer
  -> LazyComponent
    -> WebsiteBuilder             (from website)
      -> Builder                  (from html_builder)
        -> BlockTab               (from html_builder)
          ...

Everything inside LazyComponent is lazy loaded.

Website Plugins

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.

Builder Components

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment