Central page layout

Goal of this step: Have a central page app that takes care of the page layout, the menu, etc. In page instances, we will only provide the actual contents. This way we can manage the page layout centrally.

Filesystem layout

Here is how the above maps to files on the filesystem:

When a page instance is visited, Boomla will locate the .Request file on its type chain and execute it. The code shall get the children of the page instance and render them - these will be the individual content elements on the page.

Files are ordered

Files on the Boomla Filesystem are stored in order. When getting the children of a file, those will be returned in the order they were stored on disk. Thus, you will not need to worry about sorting the files in code.

Rendering content files

A content file can be rendered by calling the file's .Inline method. It's also said that the content is inlined. It works just like the .Request method: Boomla will look for an .Inline file on the file's type chain and execute it.

Here is a problem though. What if a page has both contents and sub-pages? We need a way to get them separately.

Buckets

On traditional filesystems, a directory is one big container. In Boomla, this container is sub-divided into 256 buckets. This allows one to group the children of a file.

You can select all the children of a file or you can select files in a certain bucket.

Each file has its own 256 buckets:

Each bucket may contain any number of files:

Bucket 0 is the default bucket. Your program files and assets should be placed there. As you will see later, it is typically used for hidden files.

You can use all the other buckets for any app-specific purpose you want:

In our page app, we will store sub-pages in bucket 1 and contents in bucket 2.

Here is an example filesystem tree with 4 pages (black), some of them having contents (green):

Notice how each page may have both sub-pages in bucket 1 and contents in bucket 2. Those buckets may also be empty. (Of course all other buckets exist as well, they are just not shown here for clarity.)

Make sure to move your pages in bucket 2.

The code

Embedded JavaScript

Okay, let's implement the above in code! Here is the HTML layout we will use:

<!DOCTYPE html>
<html>
<head>
    <title>TITLE</title>
</head>
<body>
    <div class="wrapper">
        <div class="menu">
            MENU
        </div>
        <div class="main">
            MAIN
        </div>
    </div>
</body>
</html>

And that's how you can return it with the sjs-4 JavaScript engine:

var p = '';
 
p += '<!DOCTYPE html>';
p += '<html>';
p += '<head>';
    p += '<title>TITLE</title>';
p += '</head>';
p += '<body>';
    p += '<div class="wrapper">';
        p += '<div class="menu">';
            p += 'MENU';
        p += '</div>';
        p += '<div class="main">';
            p += 'MAIN';
        p += '</div>';
    p += '</div>';
p += '</body>';
p += '</html>';
 
response.body(p);

That totally works but if you are like me, you would prefer a PHP like syntax where you can write HTML first and embed JavaScript where you need it. Boomla has an embedded JavaScript engine called sjs-4e to do just that. It's simply pre-processing your code to turn the embedded form into plain JS.

Make sure to change the type of the .Request file to sjs-4e.

To demonstrate how it works, let's embed the page file's title. It shall be stored in the page file's title property, which we can access via f.title() (remember, f points to the current page).

<!DOCTYPE html>
<html>
<head>
    <title><?= f.title() ?></title>
</head>
<body>
    <div class="wrapper">
        <div class="menu">
            MENU
        </div>
        <div class="main">
            MAIN
        </div>
    </div>
</body>
</html>

The content files are stored in bucket 2. We can get them via f.query(':2'). This returns a Collection of files. We can then inline (render) these files by calling f.query(':2').inline().

<!DOCTYPE html>
<html>
<head>
    <title><?= f.title() ?></title>
</head>
<body>
    <div class="wrapper">
        <div class="menu">
            MENU
        </div>
        <div class="main">
            <?== f.query(':2').inline() ?>
        </div>
    </div>
</body>
</html>

Note that we use <?== to embed HTML code, which does not need to be HTML escaped, while we use <?= for the title to embed an unescaped string, which does need to be escaped.

CSS modules

While we could link a simple CSS file for use, it's better to use a CSS module instead, which will namespace our CSS class names, etc., to avoid naming conflicts with other apps.

To do that, let's create the file /apps/page/style.css and set its type to css-2, which will do the namespacing for us.

Add some CSS code to it, for example:

body {
    background: #6DCCF5;
    margin: 0;
}
.wrapper {
    display: flex;
    width: 900px;
    margin: 50px auto;
    background: white;
    box-shadow: 0 0 6px rgba(0,0,0,0.2);
}
.menu {
    padding-top: 30px;
    width: 240px;
    background: #EEEEEE;
}
.main {
    width: 600px;
    margin: 30px;
    min-height: 800px;
}

When served, it will become something like:

body {
    background: #6DCCF5;
    margin: 0;
}
.NC174BCFA-wrapper {
    display: flex;
    width: 900px;
    margin: 50px auto;
    background: white;
    box-shadow: 0 0 6px rgba(0,0,0,0.2);
}
.NC174BCFA-menu {
    padding-top: 30px;
    width: 240px;
    background: #EEEEEE;
}
.NC174BCFA-main {
    width: 600px;
    margin: 30px;
    min-height: 800px;
}

In our JS code, we can select the CSS file using source.select('../style.css'). Note that the source variable points to the .Request file. The style.css file is its sibling, so we need to use ../style.css to select it. If you simply used style.css, Boomla would look for it under the .Request file, that is, at .Request/style.css. That's a consequence of the filesystem differences.

To load the file as a CSS module, use response.addCssModule(). It will return an object mapping unescaped class names like wrapper to their escaped equivalents like NC174BCFA-wrapper. Use it to dynamically inject the class names:

<?
    var css = response.addCssModule(source.select('../style.css'));
?>
<!DOCTYPE html>
<html>
<head>
    <title><?= f.title() ?></title>
</head>
<body>
    <div class="<?= css.wrapper ?>">
        <div class="<?= css.menu ?>">
            MENU
        </div>
        <div class="<?= css.main ?>">
            <?== f.query(':2').inline() ?>
        </div>
    </div>
</body>
</html>

Notice how you can use arbitrary JavaScript in within <? ... ?>.

Contents

At this point, all your page instances should display the shared page layout with no contents. Let's add a content element to each page to see that it works.

  • Create a file within each page instance file,

  • move it in bucket 2,

  • set its type to html-1, and

  • add some page specific hello world text the file's body.

Visit the pages in your browser to see that it worked. You can create multiple content files if you want, all of them will be displayed.

See how to do it (silent video)

Menu

We'll implement the menu in the next step.

Subscribe to our newsletter!