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.

Routes

Each route has:

  • A path
  • A view
  • A controller

Paths

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.

Views

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.

Standard Tag Object Properties

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

Controllers allow you to define the data behind your view and allows you to handle click & form submission events.

View Bag

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

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!");
		}
	}
}

Injectable Services

TBC once security is completed across the system, this doesn't work correctly until that is completed.

Custom Tags

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.

Examples

The easiest way to understand how to use custom tags is to look at some examples.

Menu

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"
		}
	]
}

Wrapper with header & footer

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 &copy; 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"
		}
	]
}
⚠️ **GitHub.com Fallback** ⚠️