When creating a page, server side will generate a new config for this page. Some data will be read from database too. This includes:
- layout
- page name
The class used to generate new config is:
PageConfig
-- PageMeta
-- LayoutConfig
-- List<LegoConfig>
PageMeta will be generated with empty data, and page name will be saved to layout params.
{
layout: {
name: "kiwi.layout",
params: { layout: { title: "New Tile" } },
meta: null,
data: { }
},
legos: [ ],
meta: {
pageMeta: {
pageMeta: [
[
{ name: "name", value: "description" },
{ name: "content", value: "" }
],
[
{ name: "name", value: "keywords" },
{ name: "content", value: "" }
]
]
}
}
}
Server will render this config and send to client (will be discussed later), and client can then start to modify the page config, and finally send it back to server.
Client will send the whole page config back to server to compile. Server will save the page and then rerender the page.
composePageDraftDao.saveContentForDefaultPage(hostSite, target, path, config);
draftWithoutContentCache.evict(hostSite, target, path);
pageRenderingService.notifyPageChanged(hostSite, target, path, ConfigState.DRAFT);
In notifyPageChanged, it will actually render the whole whole page again.
renderTemplate
will be called. And in renderTemplate
, we'll fetch
pageConfig from database.
Backend will parse all legos specified as valid lego, and parse the meta data inside it, then it will send these lego meta data to front end as lego list.
PageConfig
has a method toString
which will convert the JSON config to a
handlebars string.
{
"layout": {
"name": "kiwi.layout",
"params": {
"layout": {
"title": "New Tile",
...
},
"additionalStyle": {
"_style": ".wrapper .compose-column,.lego-columns-content>div, .generic-column>div {padding-left: 6px;padding-right: 6px;}"
},
"styleConf": { "fontFamilies": "{}" }
},
"meta": null,
"data": {
"additionalStyle": {
...
}
}
},
"legos": [
{
"id": "T3JepX",
"name": "generic.textblock",
"params": {
"style": { ... },
"template": { ... },
"additionalStyle": {
"_style": " a{font-weight:inherit;font-style:inherit;text-decoration:underline;color:#3399cc;} a:hover{font-weight:inherit;font-style:inherit;text-decoration:underline;color:#3399cc;}"
}
},
"block": "<p>Write something...</p>",
"legos": null,
"meta": null,
"data": { ... },
"source": null,
"global": false
},
{
"id": "6Z15JZ",
"name": "generic.textblock",
"params": {
"style": { ... },
"template": { ... },
"additionalStyle": {
"_style": " a{font-weight:inherit;font-style:inherit;text-decoration:underline;color:#3399cc;} a:hover{font-weight:inherit;font-style:inherit;text-decoration:underline;color:#3399cc;}"
}
},
"block": "<h1 style=\"text-align: center;\"><span style=\"font-size: 120px;\">Hello World !</span></h1>",
"legos": null,
"meta": null,
"data": { ... },
"source": "hello-world",
"global": true
}
],
"meta": {
"pageMeta": {
"pageMeta": [
[{ "name": "name", "value": "description" }, { "name": "content", "value": "" }],
[{ "name": "name", "value": "keywords" }, { "name": "content", "value": "" }]
]
}
}
}
This mechanism allows us to transfer pageConfig between front end and back end and when rendering, it can make use of handlebars to render it.
Several things to be noticed:
LegoConfig.toExternalString
will be called inPageConfig.toString
_id
will be changed to_id
as attribute of lego helperparams.template
andparams.style
will be merged to parameters and put to the attributes of lego helperparams.additionalStyle._style
will be put to_style
in helperblock
will be directly put into the handlebars template- global lego will be transformed to a
global
helper
Known bug Since parameters are injected to lego helper through attributes, it cannot differentiate string and boolean, so all boolean are converted to string.
Rendering happens in these situations:
- visit site directly
- preview
- open compose (renderPageConfig in ComposeConfigController)
- email preview
- send email
- send test email
And all of them will actually use the handlebars template shown in 2. Compile Page to render the page.
{{#partial "pageMeta"}} -> rendered in page.base.hbs by partial helper
{{#layout}} -> kiwi.layout.hbs -> page.base.hbs
{{#lego "lego.name"}} -> lego.name.hbs
{{#global}} -> handlebar global helper
Notice, template
inside lego's definition will be rendered into the templates,
And style
and javascript
will be covered in the later section.
In template
, style
, javascript
, they have access to the hash value defined
in the lego attributes.
Hash value can be accessed by:
{{param $.hash.key}}
{{css $.hash.key}}
{{$.hash.key}}
They are considered handlebar variables and will be replaced by real value during the rendering.
The drawback would be, it's very expensive to do this variable replacement everytime, especially if it's static data.
viewMode
and editMode
are designed to separate editor and live mode.
In each lego def, it allows a script
definition. During handlebar compilation,
these script will be collected by the handlebar helper, and stored in a
collection, they will be merged together and put into a scripts
helper which
is actually in page.base.hbs
.
Currently inlined styles are not used. During handlebar compilation, it will
collect all styles from each lego, merge them and compile(less) them. The
rendered result will be store in cache, which is accessible through
StaticFileController
- home.compose.css. For production, a
composeResourceVersion is used to handle the CDN updates.
else if (path.contains(COMPOSE_EXT)) {
ComposeHostSite composeHostSite = site.asComposeHost();
LegoTarget legoTarget = LegoTarget.fromString(target);
ConfigState pageConfigState = ConfigState.valueOf(configState.toUpperCase());
String pagePath = path.substring(path.lastIndexOf("/") + 1).replace(COMPOSE_EXT, "");
css =
composeStylesManager.getBundleStyles(
composeHostSite, legoTarget, pagePath, pageConfigState, version);
if (pageConfigState == ConfigState.LIVE) {
cache = true;
}
}
Each saved tile has its own cache, it's rendered by global
helper.
Everytime one saved tile is updated, it will flush the cache for styles,
generate new resourceVersion, and recompile all tiles and generate new styles.
When draging new saved tiles, it will generate new id, and compile the whole tile.
{
"id": "__ID__",
"name": "generic.textblock",
"params": {
"template": {
"hideMobile": false,
"hideTablet": false,
"hideDesktop": false,
"visibilityLabel": "Show on all devices",
"visibilityClass": "",
"tabletData": { "legoStyle": { }, "legoParams": { }, "legoControl": { }, "legoBlock": "" },
"phoneData": { "legoStyle": { }, "legoParams": { }, "legoControl": { }, "legoBlock": "" },
"customAttrs": ""
},
"additionalStyle": {
"_style": " a{font-weight:inherit;font-style:inherit;text-decoration:underline;color:#3399cc;} a:hover{font-weight:inherit;font-style:inherit;text-decoration:underline;color:#3399cc;}"
},
"style": {
"borderWidth": "0px",
"paddingTop": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingBottom": "0px",
"backgroundColor": "transparent"
}
},
"block": "<p>sad</p>",
"legos": null,
"meta": null,
"data": {
"linkStyle": {
"normal": { "fontWeight": "", "fontStyle": "", "textDecoration": "underline", "color": "#3399cc" },
"hover": { "fontWeight": "", "fontStyle": "", "textDecoration": "underline", "color": "#3399cc" }
},
"additionalStyle": {
"styles": {
"main": {
"linkStyle": ".generic-textblock-__ID__ a{font-weight:inherit;font-style:inherit;text-decoration:underline;color:#3399cc;}.generic-textblock-__ID__ a:hover{font-weight:inherit;font-style:inherit;text-decoration:underline;color:#3399cc;}"
},
"raw": {
"linkStyle": " a{font-weight:inherit;font-style:inherit;text-decoration:underline;color:#3399cc;} a:hover{font-weight:inherit;font-style:inherit;text-decoration:underline;color:#3399cc;}"
}
}
}
},
"source": "sad",
"global": true
}
id
is always marked as 'ID' as a placeholder. In both front end and back
end we are doing some string replacement to make sure the global tile is using
the correct id.
Currently email triggers handlebars compile multiple times and replace those html tags to handelbar vars and then compile. It does not affect the currect tile engine. So for new tile engine we can actually leave it there for now. Though it does need a complte rewrite...
So I'll ignore this part for now..
html, js, css, meta
TBD:
- database
- file
Should still support:
- get tile list
- render single tile
Move current layout/template to front end, and need to make sure it does not break handlebar rendering. This might be very related to Rendering part
When working in edit mode, the tile should be changed instantly. The way to handle it might be:
- Rerender the whole tile and replace html/style
- Keep separate viewMode and editMode and continue using angular
- Using virtual dom
This require some performance benchmark measurement
Compile stuff to handlebar templates which will still support all our old handlebar statement
The rendered template (hbs template) should be stored in database, when visiting directly to sites, it should be able to fetched from database and rendered correctly.
The styles has placeholder with saved tiles styles
The caching logic should remain the same, but with new tile engine on, it should fetch styles from database, and replace the saved tiles' placeholder, then send the styles as css file. CDN logic is also the same.
For saved tile, we have to use handlebar helper to render, cuz it's actally dynamic data. We'll reuse the old caching logic, but slightly modified it to adapt to new tile engine.
The styles will no longer be less file, but will be a handlebar file, mostly is css, but might have some placeholder for saved tiles.
Saved tile require new persistent layer of rendered result, including both html and css