buildkit-server
v0.1.14
Published
Easily create pages from templates and blocks
Downloads
11
Readme
Buildkit Server
The term 'buildkit' is used to refer to the suite of pages, templates and other assets that you end up handing over to the back-end guys for the integration into your main application (often driven via .net or Java for example).
The aim of this project is to speed up the creation of a modular based HTML framework. The project does not include any CSS pre-compilers or client-side JavaScript libraries. It is purely focused on the rendering of HTML page.
All functionality is made available via a nodejs package.
Getting started
This guide assume that you've initialised a blank nodejs project with
npm init
, though of course you could easily add this to an existing
project.
Install the buildkit-server module with
npm install buildkit-server --save
Create a new JavaScript file, say
buildkit.js
, and add paste in the following code (take specific note of the '.default' part after the require call):var BuildkitServer = require('buildkit-server').default; var buildkit = new BuildkitServer(); buildkit.start();
On the command line, run
node buildkit
. After doing this you should see some text printed to the console telling you that the buildkit server has been started and is available at http://localhost:9000/Open http://localhost:9000/ in your browser and you should see a message telling you that no documentation system is installed
That's it. Your server is up and running. You can now start creating blocks, templates and pages which will then be visible through the server.
Creating our first page
First, a quick bit on terminology. There are three concepts that its key to understand, and which will likely be very familiar to you as a UI developer:
Templates - you may know these are 'layouts'. They are the skeleton HTML that defines the different types of layout that you have. You may for example have a template which is a one column template, and another which maybe has a sidebar and a content area. I've often found that I end up with no more than 5 or 6 templates for even the biggest of sites. Your needs may be different though. Templates are defined as a Handlebars file. Blocks - these are just a chunks of HTML. You might call them components, modules, whatever you like. The term 'blocks' is just something that I used on a previous project and stuck with me. The same block might be used many times, or just once. It's up to you. Pages - pages are defined simply as a JSON file. This JSON file allows you to specify the template that the page uses, and the blocks that you want to drop on to your page. The approach of using a JSON file ensures that all markup is kept as separate blocks, so that you don't have random bits of HTML scattered all over the place.
Create a block
By default, all blocks must be stored in a folder named blocks that should sit in the root of your project (where your package.json is defined). That folder won't be created automatically, so go ahead and create it, then create another folder inside it and call it menu
Inside this menu folder, create two more files, one called block.json and the other called menu.hbs
So now, among perhaps other things, you'll have the following folder structure:
\blocks
\menu
menu.hbs
block.json
You can leave the block.json file empty for now, but lets add a little bit of markup to the menu.hbs file:
<ul>
<li><a href="#">Electricals</a></li>
<li><a href="#">Homeware</a></li>
<li><a href="#">Kids</a></li>
<li><a href="#">Luxury</a></li>
<li><a href="#">Books</a></li>
</ul>
It's important that both block.json and menu.hbs exist, as the block won't work without them. Every block must have it's own folder, and in that folder must be a block.json file and also an hbs file whose name matches the name of the directory.
Create a template
By default, all templates must be stored in a folder named templates that should sit in the root of your project. The templates folder won't be created automatically, so please create it. Then, inside the templates folder, go ahead and create a file called one-column.hbs and add the following content to it:
<!doctype html>
<html class="no-js" lang="en-gb">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
{{{content}}}
</body>
</html>
They key thing to notice in that template is the {{{content}}} expression. If you've used Handlebars before then you'll probably recognise this a simple identifier expression. The triple curly braces mean that any content we pass it are not escaped. This identifier will end up containing the output of our blocks, and so it's important that the markup isn't escaped. I call these identifiers template areas. So in this case we have just created a template area named 'content'.
Create a page
This is where we combine the template and block that we've just created.
By default, all pages must be stored in a folder named pages that should sit in the root of your project. The pages folder won't be created automatically, so please create it. Then, inside the pages folder, create a file called my-first-page.json and add the following content to it:
{
"templateName": "one-column",
"templateContent": {
"content": [
{
"blockName": "menu"
}
]
}
}
Hopefully that makes sense. First of all it says that we want to use the 'one-column' template via the templateName property. The templateContent property is an object, where each property should map to a template area defined in the 'one-column' template (remember our template contained the {{{content}}} identifier)
Lets view the page
Now you should be able to go to http://localhost:9000/documentation/pages and see the page listed. Click on the link and you should see your new page.
Driving block content via JSON files
The idea of a block is that it's a re-usable chunk of HTML. It doesn't absolutely have to be re-usable, but in general you'll probably want it to be. For example, you may have an ecommerce site where you have the multiple marketing banners, all using the same markup, just with different content. Perhaps you have some menu markup that you re-use in different places. Other times there might just be one block, like a site footer for example, where you don't ever expect it to be used more than once. We can cater for changing content in all of these scenarios, without having to change the markup.
Continue through the sections below to find out more...
Adding another copy of the same block
Lets say that for some reason, we want two of those menu blocks on our page. We can edit the my-first-page.json file and add it in again:
{
"templateName": "one-column",
"templateContent": {
"content": [
{
"blockName": "menu"
},
{
"blockName": "menu"
}
]
}
}
Refresh the page and unsurprisingly you'll see another menu.
Adding block data via the page JSON file
Now what if you want to have the same sort of markup, in other words you want to keep the HTML pattern, but you want to have different content for the menu? You can do that by using the blockData property. Take a look at the example below:
{
"templateName": "one-column",
"templateContent": {
"content": [
{
"blockName": "menu",
"blockData": {
"items": [
{
"label": "Electricals",
"url": "#"
},
{
"label": "Homeware",
"url": "#"
},
{
"label": "Kids",
"url": "#"
},
{
"label": "Luxury",
"url": "#"
},
{
"label": "Booke",
"url": "#"
}
]
}
},
{
"blockName": "menu",
"blockData": {
"items": [
{
"label": "Login",
"url": "#"
},
{
"label": "Register",
"url": "#"
},
{
"label": "My Account",
"url": "#"
}
]
}
}
]
}
}
Add this to your current page JSON file and refresh the page. Nothing will happen, because of course we still have our menu hardcoded.
Bear in mind that the blockData property must be an object, and is passed in as-is to the block's Handlebars file (menu.hbs). It will be available via an identifier called content. So we can update our hbs file to loop over this array of menu items like so:
<ul>
{{#each content.items}}
<li><a href="{{url}}">{{label}}</a></li>
{{/each}}
</ul>
Now refresh your page and you should have two of the same menu structures, but with different data.
Adding block data via the block JSON file
If your menu almost always contains the same data, then you may not want to have to define it on every page that uses it. Instead you probably just want to be able to define the default data and then be able to override it when you need to. You can do this by adding content in to the block.json file within the menu block folder. Open that JSON file and add the following content:
{
"content": {
"items": [
{
"label": "Electricals",
"url": "#"
},
{
"label": "Homeware",
"url": "#"
},
{
"label": "Kids",
"url": "#"
},
{
"label": "Luxury",
"url": "#"
},
{
"label": "Booke",
"url": "#"
}
]
}
}
After updating block.json with the content above and then remove the blockData property from the first block defined in the my-first-page.json
Refresh the page and you should find that nothing's changed visually. However, we're now getting the content for the first menu block from the menu block's block.json file, while the second menu block has it's content overriden at the page level.
Overriding properties
Lets add a title to our menu. In our menu's block.json file we can just add it in as another property on the content object:
{
"content": {
"title": "Categories",
"items": [
{
"label": "Electricals",
"url": "#"
},
{
"label": "Homeware",
"url": "#"
},
{
"label": "Kids",
"url": "#"
},
{
"label": "Luxury",
"url": "#"
},
{
"label": "Booke",
"url": "#"
}
]
}
}
Then we just update out menu block's hbs file to refer to this:
<h3>{{content.title}}</h3>
<ul>
{{#each content.items}}
<li><a href="{{url}}">{{label}}</a></li>
{{/each}}
</ul>
Refresh the page and you should now see that both menu's have the new title. For the second menu, it's using the items from the page level content, while still using the title from the block level content. The system merges the block's content property and the page's blockData property together before passing the merged object to the block's hbs via the content object.
Adding another template area
Create another template under the templates folder and name it two-column.hbs, and add in the following content:
<!doctype html>
<html class="no-js" lang="en-gb">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<main>
{{{content}}}
</main>
<aside>
{{{subcontent}}}
</aside>
</body>
</html>
Notice that this is very similar to our previous template, but we have an additional template area named subcontent. Now create another page called my-second-page.hbs and tell it to use this new template. Then add a menu block in to both template areas. You should have a page JSON file that looks something like this (feel free to add a 'blockData' property and play around with the content):
{
"templateName": "two-column",
"templateContent": {
"content": [
{
"blockName": "menu"
}
],
"subcontent": [
{
"blockName": "menu"
}
]
}
}
View this page via http://localhost:9000/pages/my-second-page and you should see that you have a menu instance in each of the template areas.
Passing page data to a template
The properties templateName and templateContent are both special. You can however add any other variables that you like to this object. Those properties will then be made available to the template that the page uses, via the page object.
So lets say that I have a page JSON file that looks like this:
{
"title": "My Page Title",
"templateName": "two-column",
"templateContent": {
"content": [
{
"blockName": "menu"
}
],
"subcontent": [
{
"blockName": "menu"
}
]
}
}
You could then use this in a template like this:
<!doctype html>
<html class="no-js" lang="en-gb">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>{{page.title}}</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<main>
{{{content}}}
</main>
<aside>
{{{subcontent}}}
</aside>
</body>
</html>
Using Handlebars partials in your blocks
If your block contains a lot of markup then you might want to break it down in to separate Handlebars partials. To use a partial, you need to first of all create a folder named partials within your block folder. Any .hbs files that you create in here will then be registered as partials for use by the block who's folder they're located in.
For example, lets say that we want to use a partial to represent a sub-menu for our menu block. Our block folder would look like this:
\blocks
\menu
menu.hbs
block.json
\partials
submenu.hbs
Note that we now have a partials folder, and within that we have a submenu.hbs file, which is our submenu partial.
To use that submenu partial inside menu.hbs, you would simply use the standard Handlebars partial syntax, so our menu block might look like this:
<h3>{{content.title}}</h3>
<ul>
{{#each content.items}}
<li>
<a href="{{url}}">{{label}}</a>
{{> submenu}}
</li>
{{/each}}
</ul>
You can see the submenu partial being used within the each loop.
Adding partials sub-folders
Inside the partials folder you can add sub-folders, nested to any level you like. Lets say we add an additional folder so our new block structure looks like this:
\blocks
\menu
menu.hbs
block.json
\partials
submenu.hbs
\tertiary
submenu.hbs
Here you can see that we've added another submenu.hbs file under the tertiary folder. We can access this partial using the following syntax:
{{> tertiary/submenu}}
So we really just remove the .hbs file the partial filename, and separate folders with a forward slash.
Where partials can be used
Partials defined within a block folder can only be used within that block's Handlebars file, or within any of the other partials for that particular block. Partials will only work within the block under which they were defined. For example, if we have a menu block, and maybe a header block, then partials defined under the menu block will not be available within the header block.
Creating your own documentation
When you go to http://localhost:9000/ you are seeing a basic UI which is powered by a few Handlebars files within the documentation folder which you'll find under the node_modules/buildkit-server folder. At the moment there's little more than a basic listing of pages, templates and blocks.
You may want to create your own documentation. To do this it's easiest to take a copy of the documentation folder described above and place it in your project directory.
The Handlebars files that you have in the documentation folder are accessible via the same name, but minus the '.hbs' extension. So the file pages.hbs is available via http://localhost:9000/documentation/pages. You can create as many additional files and folders as you like. So if you wanted to document your menu block, you could create a folder called blocks and then a file called menu.hbs under this. You can then access that file via http://localhost:9000/documentation/blocks/menu
You'll see that your new pages will always have the same template and style. This is because they're always wrapped in the _template.hbs file before being rendered. At the moment this is hardcoded and can't be changed. The _template.hbs file must sit in under the documentation folder otherwise you'll receive an error.
If you add an index.hbs folder to any folder or sub-folder within documentation, this will be used without having to specify the filename in the URL.
Using blocks in your documentation
You can use blocks in your documentation by adding a Handlebars partial call, the same way as you would include a nested block. For example, to render our menu block you can use the following Handlebars partial in any of your documentation:
{{> menu}}
As your documentation probably doesn't contain styles to make your menu look how it should, you're much more likely to want to print out the raw HTML of the block, so that you can talk about this in your documentation. To do this you can use the following code:
{{raw "block:menu"}}
Note the double quotes around the name. It won't work without them. We're prefixing the block name here with 'block:' to indicate that we want to output a block. You could also use a template using the code below:
{{raw "template:one-column"}}
or you can output page:
{{raw "page:my-first-page"}}
I tend to then use something like https://highlightjs.org/ for highlighting code fragments.
Global variables in Handlebars
It can be handy to be able to set global variables that can be referenced in any Handlebars template. This is where the buildkit.json file comes in. If this file exists in your base directory, the buildkit server reads it (it must be a valid JSON object) and exposes it as an object named site.
Here's a simple example:
{
"title": "My Example Site",
"enableAdobeAnalytics": false
}
Here you can see I've defined two variables that I might then use in my template files, like this for example:
<!doctype html>
<html class="no-js" lang="en-gb">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>{{site.title}}</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<main>
{{{content}}}
</main>
<aside>
{{{subcontent}}}
</aside>
{{#if site.enableAdobeAnalytics}}
<script src="/path/to/adobeAnalytics.js"></script>
{{/if}}
</body>
</html>
The title property is used to set the value of the title element, while the enableAdobeAnalytics is used to determine whether or not to include some analytics file. There are obviously many ways that you could use variables like this.
Accessing page properties from a template
You may want to access variables in a template, that are defined for a page. The entire JSON object that you create for a page is available within a template through the page object. For example, here we use the page's title property in the title element:
<title>{{page.title}} - {{site.title}}</title>
Mocking responses
Lets say that you have a block that represents your product information. In that block you have an 'Add to basket' button. When the user clicks on this link you want the request to go off to a URL where you can return a JSON object representing the success or failure response. This is where mocks come in.
By default, mocks are stored in a directory call mocks. You create a mock file by simply creating a nodejs module that exports a function called serve. This function takes two parameters: req and res. These are the http.IncomingMessage and http.ServerResponse respectively.
Say I create a file called add-to-basket.js under the mocks folder:
exports.serve = function(req, res) {
res.statusCode = 200;
res.setHeader('Content-type', 'application/json');
res.end(JSON.stringify({
success: true,
items: 3,
total: 124.43
}));
};
This mock is then available by going to http://localhost:9000/mocks/add-to-basket
You can create as many mocks as you like, and nest them to whatever depth you like.
If you create a mock, you are entirely responsible for the output of that request. The buildkit server takes no other action after handing the request off to your mock code. You must for example, set the status code, content type, write the content that you want to return, and most importantly, remember to call res.end().
Options
When you create an instance of the BuildkitServer class, you can pass in a number of different parameters:
port
default: 9000
The port on which the server should run.
logLevel
default: info
The level of logging that you want output to the console. Must be one of: log, trace, debug, info, warn, error
baseDir
default: current directory
The directory in which the following folders/files are expected to be located:
- blocks (folder)
- templates (folder)
- pages (folder)
- buildkit.json (file - can be changed with siteConfigPath option)
Can be absolute to relative to the executing script.
siteConfigPath
default: buildkit.json
Path to the site configuration file. The path is relative to the baseDir setting.
blockConfigFilename
default: block.json
The name of the file that exists within each block folder, which contains the JSON configuration for that particular block.
debug
default: true
Whether to output debug information in the HTML when rendering blocks, pages and templates.