Websites - PhilipSkinner/elemental-lowcode GitHub Wiki
Websites allows you to build simple web applications.
Each website is constructed of:
- Routes
- Views
- Controllers
- Tags
The websites (or interface) runtime hosts each website on a path that equals the websites name.
Eg. if your website is called "blog" then it will be hosted on the interface runtime on:
http://localhost:8005/blog
The websites service is designed to allow for maximum template re-use and is designed to be server side first - allowing for progressive enhancement of your interface should you require it.
Each route has:
- A path
- A view
- A controller
Paths are used to match incoming HTTP requests and can be used to match dynamic values. Examples of valid paths are:
/
/posts
/posts/:id
/posts/:id/comments
If a / path is not defined then your website will not host a page on its default route.
A view is a template in the form of a JSON document:
{
"tag" : "html",
"children" : [
{
"tag" : "body",
"children" : [
{
"tag" : "h1",
"text" : "Hello world!"
}
]
}
]
}
Each view is a single parent tag objects which then defines its child tag object. Custom tags can be defined and can be used to construct re-usable partial templates or wrapper templates.
The following properties are special - any other properties are used to render attributes on the DOM element (e.g. class can be used to specify css classes, id for the id of an element).
tag - required
This is the tag that is to be used to render the element within the DOM. This can be any valid HTML tag or custom tag.
This property is required.
children
An array of child tag objects - each of these will be rendered within the parent tag, for example:
{
"tag" : "div",
"class" : "block",
"children" : [
{
"tag" : "p",
"text" : "This is my first paragraph."
},
{
"tag" : "p",
"text" : "This is my second paragraph."
}
]
}
; would output the following HTML:
<div class="block">
<p>
This is my first paragraph.
</p>
<p>
This is my second paragraph.
</p>
</div>
text
This is the textual content to be added into the tag. Simple strings or arrays of strings are supported:
{
"tag" : "div",
"class" : "block",
"children" : [
{
"tag" : "p",
"text" : "This is my first paragraph."
},
{
"tag" : "p",
"text" : [
"This is my second paragraph.",
"With a second sentence."
]
}
]
}
; would output the following HTML:
<div class="block">
<p>
This is my first paragraph.
</p>
<p>
This is my second paragraph. With a second sentence.
</p>
</div>
repeat
Allows you to define a loop for repeating a tag - including its children:
{
"tag" : "select",
"children" : [
{
"tag" : "option",
"repeat" : "$.option in $.bag.options",
"value" : "$.option.value",
"text" : "$.option.name"
}
]
}
if
Allows for a series of conditionals to be combined to determine if this tag (and its children) are included in the rendered output:
{
"tag" : "p",
"text" : "An error occurred",
"if" : [
{
"statement" : "$.bag.validationError"
},
{
"statement" : "$.bag.insertionError",
"logicalOperator" : "or"
}
]
}
; will include the tag object in the output if either $.bag.validationError
or $.bag.insertionError
evaluate to a truthy value.
onclick
Configures an event which is fired and handled by the relevant controller:
{
"tag" : "span",
"text" : "Increment value : $.bag.value",
"onclick" : {
"eventName" : "increment"
}
}
; would output:
<a href="?event=increment"><span>Increment value</span></a>
; which can then be handled within the controller:
module.exports = {
bag : {
value : 0
},
events : {
load : function(event) {
var state = this.sessionState.retrieveSession();
if (state) {
this.bag.value = state.value;
}
},
increment : function(event) {
this.bag.value++;
this.sessionState.saveSession(this.bag);
}
}
};
submit
Can be attached to form tag objects and allows for events to be triggered back to the controller on submission. See the bind section below for more information on how to use this to retrieve values.
bind
Binds an input field to a particular bag value:
{
"tag": "html",
"children": [
{
"tag": "head",
"children": []
},
{
"tag": "body",
"children": [
{
"tag": "span",
"text": "Increment value : $.bag.value",
"onclick": {
"eventName": "increment"
}
},
{
"tag": "form",
"submit": {
"eventName": "submit"
},
"children": [
{
"tag": "input",
"bind": "$.bag.value"
},
{
"tag" : "button",
"type" : "submit",
"text" : "set value"
}
]
}
]
}
]
}
; and the following controller:
module.exports = {
bag : {
value : 0
},
events : {
load : function(event) {
var state = this.sessionState.retrieveSession();
if (state && state.value) {
this.bag.value = state.value;
} else {
this.bag.value = 0;
}
},
postback : function(event) {
Object.assign(this.bag, event.bag);
this.sessionState.saveSession(this.bag);
},
increment : function(event) {
this.bag.value++;
this.sessionState.saveSession(this.bag);
}
}
};
; shows a combination of using both a click based event and a form based submission to modify shared data - aswell as persisting this data within the users session.
Controllers allow you to define the data behind your view and allows you to handle click & form submission events.
Your view data is held within your view bag:
module.exports = {
bag : {
text : 'Hello world'
}
};
Variables can then be accessed within your views by using the following notation:
$.bag.text
To use this to inject some text into a span in your view:
{
"tag" : "span",
"text" : "This is the value : $.bag.text"
}
Events allow you to handle click & form submission events to update your view state:
module.exports = {
bag : {},
events : {
load : (event) => {
this.bag.text = 'Hello world';
}
}
}
There are a number of standard events that can be listened to:
load
postback
The load
event is triggered whenever an instance of your controller is loaded and should be used to bootstrap your pages initial state. If you want your page to maintain a persistent state then the sessionState
injectable should be used.
The postback
event is triggered whenever a form submission is sent. At the moment, named form submission events are not supported.
Click based events are named within your views and so you simply need to create an event with the same name:
{
"tag" : "span",
"text" : "click me",
"onclick" : {
"eventName" : "mycustomevent"
}
}
; would trigger an event named mycustomevent
on your controller:
module.exports = {
events : {
mycustomevent : (event) => {
console.log("Custom event fired!");
}
}
}
TBC once security is completed across the system, this doesn't work correctly until that is completed.
Custom tags allow you to define re-usable partial templates & wrappers for use within your applications.
Variables can be passed into these custom tags allowing you to define standard sections or to wrap your tag objects in a standard structure.
The easiest way to understand how to use custom tags is to look at some examples.
We can construct a menu using two custom tags, a menu
tag and a menuItem
tag.
menuItem
This is the definition for our menuItem
tag:
{
"tag" : "div",
"class" : "menu-item",
"children" : [
{
"tag" : "a",
"href" : "$.link",
"text" : "$.linkText"
}
]
}
menu
This is the definition for our menu
tag:
{
"tag" : "div",
"class" : "menu",
"children" : "$.menuItems"
}
using the menu
We can now use these tags to quickly and easily create a menu:
{
"tag" : "menu",
"menuItems" : [
{
"tag" : "menuItem",
"link" : "/page1",
"linkText" : "Page 1"
},
{
"tag" : "menuItem",
"link" : "/page2",
"linkText" : "Page 2"
}
]
}
We can construct a standard wrapper
tag for all of our pages, using our previously configured menu
and menuItem
tags:
{
"tag" : "html",
"children" : [
{
"tag" : "head",
"children" : [
{
"tag" : "title",
"text" : "$.pageTitle"
}
]
},
{
"tag" : "body",
"children" : [
{
"tag" : "header",
"children" : [
{
"tag" : "menu",
"menuItems" : [
{
"tag" : "menuItem",
"link" : "/page1",
"linkText" : "Page 1"
},
{
"tag" : "menuItem",
"link" : "/page2",
"linkText" : "Page 2"
}
]
}
]
},
{
"tag" : "div",
"class" : "main-content",
"children" : "$.pageContent"
},
{
"tag" : "footer",
"children" : [
{
"tag" : "small",
"text" : "Copyright © Elemental"
}
]
}
]
}
]
}
We can then use this wrapper to define all of our pages:
{
"tag" : "wrapper",
"pageTitle" : "Page 1",
"pageContent" : [
{
"tag" : "h1",
"text" : "Welcome to Page 1"
}
]
}