This library contains code for the composition of HTML pages out of multiple HTML pages, containing fragments.
Every service delivers a functional HTML user interface in form of complete HTML pages. This way a service is self contained and can be developed and tested by it's own. A UI-Service can request multiple pages from different services and compose them to one HTML page. To support this, the HTML pages from the services contain a special HTML vocabulary.
The composition is done in the following steps:
- A UI-Service has a
CompositionHandler
in it's handler chain, which answers these which need composition. - The
CompositionHandler
has a callback from the UI-Service. This callback gets ahttp.Request
object as argument and returns a List of FetchResult. - For each request this callback is triggered. So the UI-Service can add a
ContentFetcher
for this request and adds FetchDefinitions for Page usingContentFetcher.AddFetchJob()
. - The ContentFetcher loads the Pages and recursively their dependencies in parallel. For the actual loading and parsing, it uses the
HtmlContentParser
. - When all
Content
objects are loaded, theCompositionHandler
merges them together, usingContentMerge
.
The merging itself is very simple:
- The MetaJSON is calculated by adding all fields of the loaded MetaJSON to one global map.
- All Head fragments are concatenated within the
<head>
. - For rendering of the body part, the default fragment of the page with the name
layout
is rendered first. This rendering may recursively include other fragments. - All Tail fragments are concatenated at the end of the
<body>
.
Attention: The execution order of the Content Objects is determined by the order in which they are returned from the ContentFetcher
.
Currently this is only deterministic within the FetchDefinitions added by ContentFetcher.AddFetchJob()
. The recursive dependencies are loaded from them in a random order.
This may cause nondeterministic behaviour, if they contain fragments with the same name or which provide the same MetaJSON attributes.
Caching is provided at the level of framents, if a cache from caching package is configured.
A UI-Service has to remove the element marked with this attribute and all its subelements.
Be careful to have a correct open and closing structure in the HTML. The standard selfclosing tags are
allowed, e.g. both are working <br>
and <br/>
, but if there is a structure error with e.g. a div,
uic-remove
may lead to strange behaviour.
Example:
<link uic-remove rel="stylesheet" type="text/css" href="testing.css"/>
Where: Everywhere (head, body, within fragments)
A HTML page may contain a script of type text/uic-meta
, with a JSON object as content.
The UI-Service has to add the contents of the JSON object to its global meta data object.
Example:
<script type="text/uic-meta">
{
"foo": "bar",
"boo": "bazz",
"categories": ["animal", "human"]
}
</script>
Where: head
The UI-Service interpretes an HTML page as a set of fragments. All those fragments are optional.
- One Head Fragment, identified by the child elements of the HTML
<head>
tag. - One Body Default Fragement, identified by the child elements of the
<body>
tag or by auic-fragment
without a name attribute. - Multiple Named Body Fragments, identified by
uic-fragment
tag within the body. - One Tail Fragment, identified by the
uic-tail
tag.
The complete contents of the head is interpreted as the head fragment. The elements marked with uic-remove
and the uic-meta
script are not cleaned out of the head fragment. If the head framents only contains whitespace,
it is interpreted as not existing.
Example: The Head Fragment contains <title>The Title</title>
<head>
<title>The Title</title>
<link uic-remove rel="stylesheet" type="text/css" href="special.css"/>
<script type="text/uic-meta">
{}
</script>
</head>
All other elements fragments and those elements, marked with uic-remove
, are removed from the body
and the remaining fragment is taken as Body Default Fragment. The Body Default Fragment is just a fragment with
the empty name (""). If there is a uic-fragment
tag without the name in the body, this overwrites the default fragment.
Example: The Default Fragment contains <h1>Hello World</h1>
<body>
Hello World
<ul uic-remove>
<!-- A Navigation for testing -->
</ul>
<uic-fragment name="headline">
<h1>This is a headline</h1>
</uic-fragment>
</body>
The complete contents of the body is interpreted as the head fragment. The elements marked with uic-remove
and the uic-meta
script are not cleaned out of the head fragment. If the head fragments only contains whitespace,
it is interpreted as not existing.
Example: The Default Fragment contains <h1>This is the default</h1>
<body>
<h1>Hello World</h1>
<uic-fragment>
<h1>This is the default</h1>
</uic-fragment>
</body>
The body of an HTML page may contain multiple uic-fragment
tags, which contain the fragments for the page.
All content within the tag is taken as fragment content. Nested Fragment tags are not allowed.
The fragment tag may have a name
attribute, for naming the fragment. If no attribute is given, or the name is empty,
the Body Default Fragment is overwritten by this fragment.
Example: Contains two fragments headline and w
<body>
<uic-fragment name="headline">
<h1>This is a headline</h1>
</uic-fragment>
<uic-fragment name="w">
Bli Bla blub
<div uic-remove>
Some element for testing
</div>
</uic-fragment>
</body>
Where: body
All fragments (except the Head Fragment) may contain minimal templating directives which have to be resolved by the UI-Service. There are two forms of includes and a syntax for variable replacement.
The UI-Service has to replace variables by the corresponding path out of the global meta data. If the variable name contains a '.', at first, it is attempted to match the full path as one string, after that, it is attempted to traverse a tree of maps.
Example:
§[ foo ]§
or
§[ foo.bar ]§ // tried to match MetaJSON['foo.bar'] and than MetaJSON['foo']['bar']
There are some predefined variables, constructed out of the request.
{'request': {
'base_url': 'http://example.com/' // the base url of the service, calculated out of the request, e.g.
'params: {..} // a map with the GET Query parameters of the request.
}
}
On an unspecified include, the UI-Service has to replace the include by a previously loaded fragment. If the required fragment is missing, the composition will fail.
Example: Will be replaced by the Default Body Fragment of example.com/foo.
§[> example.com/foo]§
Example: Will be replaced by the content fragment of example.com/foo.
§[> example.com/foo#content]§
Example: Will be replaced by the content fragment of any random choosen page.
§[> #content]§
There is a syntax for optional includes with an alternative text.
Example: Will be replaced by the contents of foo or by the alternative content, if no such element foo exists or an error occurs while replacing with foo.
§[#> foo]§ alternative content §[/foo]§
There is also an html syntax for includes, as following:
<uic-include src="example.com/foo" required="true"/>
The default is required=false
, if not specified.
The alternative content for optional html includes is currently not implemented.
The HTML Syntax allows to specify includes, which are not preloaded, but will be loaded from the ui service on demand. For the case, it is also possible to specify parameters on the include, which allow the ui service to influence the loading of the content containing the fragment.
Example:
<uic-include src="example.com/foo#content" param-foo="bar" param-bli="bla"/>
In this example, the ui service is requested to load the content associated with the fetch definition named 'example.com/foo' and the parameter map {foo: bar, bli: bla}
.
The content will only be loaded, if the there was not an content with the name example.com/foo
before. Otherwise, the paramters will be ignored.
Attention: Parameter names are always converted to lower case names, because of the underlaying html parser,
It is possible to specifiy a url for additional content to load, while the composition takes place.
Example:
<uic-fetch src="example.com/foo" timeout="42000" required="false" name="foo"/>
The URL, referenced with the src Attribute will be fetched. It can than be referenced by the spcified name. E.g. like so:
<uic-include src="foo#content"/>