Commit ff86929c authored by Paul's avatar Paul

Bootstrap Templating Example

parent caa26efc
# Bootstrap Templates Example
This example is to show how to invert the dependencies in Simple
Template to create extensible templates from a base. It has been
created using [Bootstrap](https://getbootstrap.com/) version 4.
## The Bootstrap Base Template
We begin with a very generic base template for bootstrap, you can read
it in `templates/bootstrap_base.html`. You'll find lots and lots of
`{{ if some_var }}{{ dynamic_include some_var }}{{end}}` type
statements in there. For this reason the `config.pl` file sets
`st_undefined` to false, so these variables don't have to be set. This
creates an extensible base that we can use to create our
application.
## Easy Similar Templates
Often we want very similar templates with only a few small
differences, for example we'll want the same navigation, or same styles
or scripts on a bunch of related pages. The example shows one way to
achieve this in the `home_template/2` predicate in `main.pl`. This
accepts a `CustomDict` of unique content and joins it to
`Home`, which is shared content. Using this method you can create
different but similar templates for all your related routes.
## Gotchas
In this example, blocks clobber each other. Take a look at
`templates/bootstrap_base.html` and `templates/styles_eg.html`, you'll
see that the link to `bootstrap.min.css` had to be included in
`templates/styles_eg.html` because the inclusion of `styles`
overwrote the automatic inclusion of it. This is a deliberate design
choice to allow maximum flexibility and it could be expanded even
further to the whole head, body and html tags.
## Subtle `favicon.ico` trick
In `templates/bootstrap_base.html` you'll see a `<link>` tag that
defines the source of the `favicon.ico` to be in `static/images`. This
removes the headache of having to declare a route just for that.
config(st_undefined, false).
:- module(main,
[ serve/0
]
).
% Assert app dir so it can be run from anywhere
:- prolog_load_context(directory, Dir),
asserta(user:file_search_path(sw_app, Dir)).
:- use_module(library(sw/simple_web)).
%! join_dicts(+D1, +D2, -D3) is nondet.
% Join dicts together if there is no
% conflict
join_dicts(D1, D2, D3) :-
D1 >:< D2,
put_dict(D1, D2, D3).
%! home_template(+CustomDict, -Template) is nondet.
% Merge custom options into the common home template
% options.
home_template(CustomDict, Template) :-
Home = _{ navbar: navbar_eg
, styles: styles_eg
},
join_dicts(CustomDict, Home, Template).
sw:route('/', _Request) :-
home_template(template{ title: 'Hello Bootstrap'
, content: content_eg
, content_message: "You can include data for templates too!"
}, Template),
reply_template(bootstrap_base, Template).
serve :-
run(port(8000)).
This source diff could not be displayed because it is too large. You can view the blob instead.
body {
padding-top: 5rem;
}
.starter-template {
padding: 3rem 1.5rem;
text-align: center;
}
This diff is collapsed.
This diff is collapsed.
<!doctype html>
<html {{ if html_attrs }}{{= html_attrs }}{{ else }}lang="en"{{ end }}>
<head>
{{ if metas }}{{ dynamic_include metas }}{{ else }}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">{{ end }}
{{ if title }}<title>{{= title }}</title>{{ end }}
{{ if styles }}{{ dynamic_include styles }}{{ else }}
<link href="/static/css/bootstrap.min.css" rel="stylesheet">{{ end }}
<link rel="icon" href="/static/images/favicon.ico">
</head>
<body {{ if body_attrs }}{{= body_attrs }}{{ end }}>
{{ if navbar }}{{ dynamic_include navbar }}{{ end }}
<main role="main" class="container">
{{ if content }}{{ dynamic_include content }}{{ end }}
</main>
{{ if scripts }}{{ dynamic_include scripts }}{{ else }}
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="/static/js/vendor/jquery-slim.min.js"><\/script>')</script>
<script src="/static/js/vendor/popper.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script> {{ end }}
</body>
</html>
<div class="starter-template">
<h1>Bootstrap starter template</h1>
<p class="lead">Use this document as a way to quickly start any new project.<br> All you get is this text and a mostly barebones HTML document.</p>
<p><strong>{{= content_message }}</strong></p>
</div>
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#">Disabled</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="https://example.com" id="dropdown01" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</a>
<div class="dropdown-menu" aria-labelledby="dropdown01">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#">Something else here</a>
</div>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
</form>
</div>
</nav>
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
<link href="/static/css/main_styles.css" rel="stylesheet">
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment