Writing Pages

« return to the manuals

Pages are written in a declarative fashion using the JSON format. They are defined using JSON objects whose properties will be explained in this document.

Preliminary readings:

Layouts and Areas

First of all, a page should specify the layout which defines the available widget areas and how they are arranged visually when rendered by the web browser. If a page is intended to be used as a base page for inheritance, the layout property should be omitted, as it is specified by the inheriting pages. Only one page in an extension chain may define a layout, and deferring this choice to the bottom of the hierarchy increases flexibility.

For each layout, a descriptor containing its canonical name is required:

// application/layouts/popups/layout-one/layout.json
{
   "name": "layout-one"
}

Now let us use the following layout HTML:

<!-- application/layouts/popups/layout-one/default.theme/layout-one.html -->
<div>
   <div ax-widget-area="header"></div>
   <div ax-widget-area="content"></div>
   <div ax-widget-area="footer"></div>
</div>

This layout defines three widget areas that can be occupied by widgets on the page.

Configuring the layout is done via the layout property of the page definition. Its value is a relative path within the layouts root (usually application/layouts). To fill the available areas, we add another top-level key areas parallel to layout. Its value is a map, where each key is the name of a widget area defined in the layout and the values are arrays, that will later contain the widgets to render. Having not added any layouts so far, we thus get the following page file:

{
   "layout": "popups/layout-one",
   "areas": {
      "header": [],
      "content": [],
      "footer": []
   }
}

When adding widgets to an area, their order determines the order in which the widgets will be rendered in the DOM. Each entry in the array is an object that can either reference a widget or a composition. It thus needs to specify either widget or composition as key. Additionally an id property can be provided, which may be useful for debugging and is actually required for widgets providing embedded widget areas such as the laxar-details-layer-widget. If specifying an ID, make sure that it is unique page-wide (even taking into account inheritance). Finally it is possible to provide the configuration for features of a widget or a composition under the key features.

Here is the example with a few basic widgets

// application/pages/my-page.json
{
   "layout": "popups/layout-one",
   "areas": {
      "header": [
         {
            "widget": "laxar-headline-widget",
            "features": {
               "headline": {
                  "i18nHtmlText": "Welcome!",
                  "level": 3
               }
            }
         }
      ],
      "content": [
         {
            "widget": "laxar-command-bar-widget",
            "features": {
               "next": {
                  "enabled": true
               }
            }
         },
         {
            "composition": "popup-composition",
            "features": {
               "openPopup": {
                  "onActions": [ "next" ]
               }
            }
         }
      ],
      "footer": [
         {
            "widget": "laxar-html-display-widget",
            "features": {
               "content": {
                  "resource": "footerTextResource"
               }
            }
         }
      ]
   }
}

The object under features needs to match the schema of the corresponding widget's widget.json descriptor. Before loading a page and its widgets, LaxarJS will validate the configuration provided in the page against the widget's schema and throw an error in case one or more constraints are violated. LaxarJS will also fill in defaults specified by the schema.

Special Purpose Widget Areas

Even when using the most basic layout, LaxarJS always provides three special widget areas:

  • axActivities is meant as a container for any activities on the page. You can put activities into any widget area, but since they cannot actually be rendered, there is no natural place for them. So, by convention they should be added to this area

  • axPopups is intended as a container for widgets that appear as modal popup windows. When such widgets become visible (e.g. upon a relevant action request), they should set the Bootstrap CSS class modal-open on the document body. This "greys out" the rest of the page, with the popup widget still visible in front.

  • axPopovers is meant to take up popover-style widgets that adjust their position depending on the currently active element to provide contextual functionality. These widgets appear in front of the page, but behind the modal-open layer (if that is visible at the same time).

Embedded Layouts

There are use cases where it is not sufficient to reference one single-page layout and place all widgets of a page within that layout. Sometimes more flexibility is needed. For example, when trying to reuse existing layouts, it may be necessary to embedded another layout within the area of the top-level page layout.

To support this in a hassle-free manner, layouts are first-class citizens within areas, just like widgets or compositions.

{
   "layout": "popups/layout-one",
   "areas": {
      "content": [
         {
            "layout": "other_layouts/small_columns",
            "id": "embedded"
         },
         {
            "widget": "laxar-command-bar-widget",
            "features": {
               "next": {
                  "enabled": true
               }
            }
         }
      ],
      "embedded.left": [
         {
            "widget": "laxar-html-display-widget",
            "features": {
               "content": {
                  "resource": "someResource"
               }
            }
         }
      ]
   }
}

As seen in the example above, the key layout should be used instead of widget. Its value is - just like with the main layout property of a page - the path of a specific layout directory relative to the layouts-root of the application. Providing an id is obligatory for layouts, as it is needed to reference the widget areas defined by the layout. Under the assumption that the layout other_layouts/small_columns exports a widget area named left, we can now insert widgets into it using the area name embedded.left for it.

Note that providing features to a layout entry does not lead to an error, but is simply ignored.

Inheritance

In every user interface there are some elements that never change across pages. The easiest way to reuse these parts of a page definition is by inheritance. Common widgets can be extracted into one or more base pages that have no layout. The base pages can then be extended by concrete pages, defining the layout necessary to display their contents.

Valid candidate widgets to put into base pages are application headlines, informational notes in a footer area or activities that provide common tasks for all pages. Let us apply this to our example from above and extract the laxar-headline-widget into a base page called base-page.json.

// // application/pages/base-page.json
{
   "areas": {
      "header": [
         {
            "widget": "laxar-headline-widget",
            "features": {
               "headline": {
                  "i18nHtmlText": "Welcome!",
                  "level": 3
               }
            }
         }
      ]
   }
}

We now can modify our original page using the keyword extends that references the base page relatively to the pages-root (usually application/pages). The parts already provided by the base page can then be deleted from the extending page:

{
   "layout": "popups/layout-one",
   "extends": "base-page",
   "areas": {
      "content": [
         {
            "widget": "laxar-command-bar-widget",
            "features": {
               "next": {
                  "enabled": true
               }
            }
         },
         {
            "composition": "popup-composition",
            "features": {
               "openPopup": {
                  "onActions": [ "next" ]
               }
            }
         }
      ],
      "footer": [
         {
            "widget": "laxar-html-display-widget",
            "features": {
               "content": {
                  "resource": "footerTextResource"
               }
            }
         }
      ]
   }
}

Also, an extending page can add widgets to an area that already contains widgets from the base page. Widgets added by the extending page will be appended to the corresponding area and thus appear in the DOM after the widgets from the base page. If a widget of the extending page needs to appear precisely before a specific widget of the base page, this can be achieved using the keyword insertBeforeId. For this to work, it is of course necessary to specify an id property for the widget in the base page.

Let us assume that we would like to add another additional headline in one extending page. We therefore change the base page first and add an ID to the existing headline:

{
   "areas": {
      "header": [
         {
            "widget": "laxar-headline-widget",
            "id": "mainHeadline",
            "features": {
               "headline": {
                  "i18nHtmlText": "Welcome!",
                  "level": 3
               }
            }
         }
      ]
   }
}

Hence the page that has the need to add content can reference the given ID using insertBeforeId like this:

{
   "layout": "popups/layout-one",
   "extends": "base-page",
   "areas": {
      "header": [
         {
            "widget": "laxar-headline-widget",
            "insertBeforeId": "mainHeadline",
            "features": {
                "headline": {
                   "i18nHtmlText": "You just won one billion dollar!"
                }
            }
         }
      ],
      "content": [ " ... some widgets ... " ],
      "footer": [ " ... some widgets ... " ]
   }
}

This is all one needs to build basic pages for LaxarJS.

Due to the intentional simplicity, inheritance is a somewhat limited way to reuse page definitions. It might become necessary to split pages into smaller, possibly reusable chunks, which is the task compositions where designed for. So if the need arises, read on in the manual for writing compositions.