Blog-Core framework

Fork me on GitHub

Getting started

This guide assumes that you have successfully installed the Blog-Core framework.

The main file

We start by creating the main.pl file. It creates the main module and imports some useful modules.

:- module(main, []).

:- use_module(library(arouter)).
:- use_module(library(docstore)).
:- use_module(library(bc/bc_main)).
:- use_module(library(bc/bc_view)).

:- bc_main('site.docstore').

To start SWI-Prolog with this file we issue the command

swipl -q -s main.pl -- --port=8000

in the directory where the main.pl file is. Arguments in the command have the following meaning:

  • -q helps to keep the console clean by not displaying the SWI-Prolog banner.
  • -s sets the main source file.
  • -- separates swipl arguments from the app arguments.
  • --port=8000 selects the TCP port for serving requests.

After running the command, information about starting the server should be displayed:

Running in development mode!
% ... <lots of messages>
% Started server at http://localhost:8000/

Front page handler

An handler generates a response to an URL path (such as /example) that is requested with a specific HTTP method (GET, POST, PUT or DELETE). The following code has to be added into the main.pl file:

:- route_get(/, send_front).

send_front:-
    (   ds_find(entry, slug=about, [About])
    ->  bc_view_send(views/page, _{
            html: About.html,
            title: About.title
        })
    ;   bc_view_not_found).

Route setup starts with a directive route_get. This connects the path expression to a predicate that will serve requests on the matching path(s).

The predicate to serve the request should always produce a response. In the case when the predicate fails, an error page with the status 500 is sent as a response. Same happens when the predicate throws an exception.

In the predicate we attempt to find the entry with the slug about. This is done by running the query against the docstore database:

ds_find(entry, slug=about, [About])

As we are interested in a single entry we explicitly unify the retrieved list with a list containing a single item.

When the entry is found, we render the front page view with the entry data:

bc_view_send(views/page, _{
    html: About.html,
    title: About.title
})

Otherwise the not-found page is shown.

After the changes have been saved into the file, run make. in the SWI-Prolog console. This will load the changes.

Front page view

The next step is to create a view file for displaying HTML for the front page. This is done by saving the following template in the views subdirectory of the project. The file name must be page.html.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>{{= title }}</title>
  </head>
  <body>
    <h1>{{= title }}</h1>
    <div>{{- html }}</div>
  </body>
</html>

The template consists of HTML with placeholders (instructions) for view variables. There are two instructions for displaying variable values:

  • {{= x }} will escape the value of x with HTML entities;
  • {{- x }} will not escape the value of x.

Templates are automatically reloaded during rendering the response in the development mode.

About entry

An entry with slug about has to be created in order to display the front page. Navigate to http://localhost:8000/admin and log into the admin interface. The default username and password are admin@example.com and admin.

Locate to Blocks list and add a new entry. Enter "About" (without the quotes) into the title field and "My first Blog-Core site" into the content field. The form should place about automatically into the slug field.

Navigate to http://localhost:8000 after the entry is saved. You should see the page with the entered contents.

Generic page handler

Now we are ready to add a generic page handler:

:- route_get(page/Slug, send_page(Slug)).

send_page(Slug):-
    (   ds_find(entry, slug=Slug, [Page])
    ->  bc_view_send(views/page, _{
            html: Page.html,
            title: Page.title
        })
    ;   bc_view_not_found).

The handler is similar to the front page handler except the entry slug is extracted from the URL path. For example, the URL path /page/hello would trigger the handler with the slug hello. After we add a new page under the Pages section of the admin UI and reload our changes with make. we should see the new page when navigating to http://localhost:8000/page/hello.

Note that there is no constraint in the ds_find/3 query which tells to retrieve a page. Posts, pages and blocks are internally handled in the same way. The constraint for the entry to be a page can be added by modifying the query to be:

ds_find(entry, (slug=Slug, type=page), [Page])

Static files

No handlers have to be added for serving static files. Static files can be just added to the public directory and are served directly. A file path /style.css corresponds to the file public/style.css.

However, for including links to files inside the HTML code, a special technique known as cache busting is supported. This makes it possible to add long expire headers and have the browser still reload them when the site is updated. As an example, a link to the style file can be added as

<link rel="stylesheet" href="/t-{{= cache_token }}/style.css" />

The cache_token is a special variable provided by the framework. The prefix containing the token is automatically removed by the dispatcher.

Save the following file as public/style.css:

h1 {
  color: green;
}

Then modify the views/page.html to have the following in the <head> section:

<link rel="stylesheet" href="/t-{{= cache_token }}/style.css" />

After navigating to http://localhost:8000/page/hello you should see the title in green.

Files can also be uploaded to the public directory using the "Files" section of the administration interface.

This concludes the current guide. Read the documentation for the further information. The project containing all files created during the guide can be found in this GitHub respository.