Skip to content

Commit

Permalink
Merge pull request #791 from peuter/config-templates
Browse files Browse the repository at this point in the history
add templating feature for config files
  • Loading branch information
ChristianMayer committed Dec 30, 2018
2 parents 7d38554 + 0d8fc8e commit 6a86725
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 48 deletions.
143 changes: 143 additions & 0 deletions doc/manual/de/config/xml-format.rst
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,149 @@ Der Statusbar befindet sich am unteren Bildschirmrand und erlaubt das zB. Abzeig
</statusbar>
</meta>
.. _xml-format_templates:

Templates
---------

Im Metabereich können Templates für oft verwendete Konfigurationsausschnitte erstellt werden. In der Regel möchte man z.B.
seine Heizungs in jeden Raum auf die gleiche Weise darstellen. Diese kann aber aus mehrere Widgets bestehen, z.B. einem
Slider zur Darstellung und Bedienung der Ventilstellung, einem Info-Widget zur Anzeige der aktuellen Ist-Temperatur
und einem InfoTrigger-Widget für die aktuelle Soll-Temperatur. Diese Struktur ist in jedem Raum gleich, lediglich
die benutzen Addresse ändern sich. Mit einem Template muss man diese Struktur nur einmal schreiben und kann sie in
jedem Raum wiederverwenden.

In der Template-Definition werden Platzhalter für Variablen verwendet, welche dann beim benutzen des Templates durch
die entsprechenden Werte ersetzt werden. Das folgende Beispiel zeigt, wie man ein Template definiert und benutzt.

.. code-block:: xml
:caption: Beispiel eines Templates für eine Heizung und dessen Verwendung in verschiedenen Räumen
<pages...>
<meta>
<template name="Heizung">
<group name="Heizung">
{{{ additional_content }}}
<slide min="0" max="100" format="%d%%">
<label>
<icon name="sani_heating" />
Heizung
</label>
<address transform="OH:dimmer" variant="">{{ control_address }}</address>
</slide>
<info format="%.1f °C">
<label>
<icon name="temp_temperature" />
Ist
</label>
<address transform="OH:number" variant="">{{ currenttemp_address }}</address>
</info>
<infotrigger uplabel="+" upvalue="0.5" downlabel="-"
downvalue="-0.5" styling="BluePurpleRedTemp"
infoposition="middle" format="%.1f °C" change="absolute" min="15" max="25">
<label>
<icon name="temp_control" />
Soll
</label>
<address transform="OH:number" variant="">{{ targettemp_address }}</address>
</infotrigger>
</group>
</template>
</meta>
<pages...>
<page name="Wohnzimmer"...>
...
<template name="Heizung">
<value name="control_address">Heating_FF_Living</value>
<value name="currenttemp_address">Temperature_FF_Living</value>
<value name="targettemp_address">Temperature_FF_Living_Target</value>
</template>
...
</page>
<page name="Küche"...>
...
<template name="Heizung">
<value name="control_address">Heating_FF_Kitchen</value>
<value name="currenttemp_address">Temperature_FF_Kitchen</value>
<value name="targettemp_address">Temperature_FF_Kitchen_Target</value>
<value name="additional_content">
<text><label>Heizung Küche</label></text>
</value>
</template>
...
</page>
</pages>
</pages>
.. HINT::
Für die Templates wird `mustache.js <https://github.com/janl/mustache.js>`_ benutzt. Für weitere Informationen
kann die mustache.js Dokumentation zu Rate gezogen werden.

Alternativ zum obigen Beispiel, kann der Inhalt des Templates auch in eine externe Datei ausgelagert werden.

.. code-block:: xml
:caption: Beispiel einer Template-Definition aus einer externen Datei
<pages...>
<meta>
<template name="Heizung" ref="resource/config/media/heizung.template.xml"/>
</meta>
<pages...>
<page name="Wohnzimmer"...>
...
<template name="Heizung">
<value name="control_address">Heating_FF_Living</value>
<value name="currenttemp_address">Temperature_FF_Living</value>
<value name="targettemp_address">Temperature_FF_Living_Target</value>
</template>
...
</page>
<page name="Küche"...>
...
<template name="Heizung">
<value name="control_address">Heating_FF_Kitchen</value>
<value name="currenttemp_address">Temperature_FF_Kitchen</value>
<value name="targettemp_address">Temperature_FF_Kitchen_Target</value>
<value name="additional_content">
<text><label>Heizung Küche</label></text>
</value>
</template>
...
</page>
</pages>
</pages>
.. code-block:: xml
:caption: Inhalt der externen Datei ``resource/config/media/heizung.template.xml``
<group name="Heizung">
{{{ additional_content }}}
<slide min="0" max="100" format="%d%%">
<label>
<icon name="sani_heating" />
Heizung
</label>
<address transform="OH:dimmer" variant="">{{ control_address }}</address>
</slide>
<info format="%.1f °C">
<label>
<icon name="temp_temperature" />
Ist
</label>
<address transform="OH:number" variant="">{{ currenttemp_address }}</address>
</info>
<infotrigger uplabel="+" upvalue="0.5" downlabel="-"
downvalue="-0.5" styling="BluePurpleRedTemp"
infoposition="middle" format="%.1f °C" change="absolute" min="15" max="25">
<label>
<icon name="temp_control" />
Soll
</label>
<address transform="OH:number" variant="">{{ targettemp_address }}</address>
</infotrigger>
</group>
.. _xml-format_pages:

Aufbau der Visu-Seiten
Expand Down
27 changes: 14 additions & 13 deletions source/class/cv/Application.js
Original file line number Diff line number Diff line change
Expand Up @@ -466,19 +466,20 @@ qx.Class.define("cv.Application",
if (!cv.Config.cacheUsed) {
this.debug("starting");
this.__detectInitialPage();
engine.parseXML(xml);
this.loadPlugins();
this.loadStyles();
this.loadScripts();
this.loadIcons();
this.debug("done");

if (cv.Config.enableCache) {
// cache dom + data when everything is ready
qx.event.message.Bus.subscribe("setup.dom.finished", function() {
cv.ConfigCache.dump(xml);
}, this);
}
engine.parseXML(xml, function () {
this.loadPlugins();
this.loadStyles();
this.loadScripts();
this.loadIcons();
this.debug("done");

if (cv.Config.enableCache) {
// cache dom + data when everything is ready
qx.event.message.Bus.subscribe("setup.dom.finished", function() {
cv.ConfigCache.dump(xml);
}, this);
}
}.bind(this));
}
},

Expand Down
7 changes: 4 additions & 3 deletions source/class/cv/TemplateEngine.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,9 @@ qx.Class.define('cv.TemplateEngine', {
/**
* Read basic settings and meta-section from config document
* @param loaded_xml {Document} XML-configuration document
* @param done {Function} callback that is called when the parsing has finished
*/
parseXML: function (loaded_xml) {
parseXML: function (loaded_xml, done) {
/*
* First, we try to get a design by url. Secondly, we try to get a predefined
*/
Expand Down Expand Up @@ -414,7 +415,7 @@ qx.Class.define('cv.TemplateEngine', {
// start with the plugins
settings.pluginsToLoad = qx.lang.Array.append(settings.pluginsToLoad, metaParser.parsePlugins(loaded_xml));
// and then the rest
metaParser.parse(loaded_xml);
metaParser.parse(loaded_xml, done);
this.debug("parsed");
},

Expand Down Expand Up @@ -541,7 +542,7 @@ qx.Class.define('cv.TemplateEngine', {
* @param type {String} page type (text, 2d, 3d)
*/
createPages: function (page, path, flavour, type) {

cv.parser.WidgetParser.renderTemplates(page);
var parsedData = cv.parser.WidgetParser.parse(page, path, flavour, type);
if (!Array.isArray(parsedData)) {
parsedData = [parsedData];
Expand Down
70 changes: 68 additions & 2 deletions source/class/cv/parser/MetaParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ qx.Class.define("cv.parser.MetaParser", {
*/
members: {

parse: function(xml) {
parse: function(xml, done) {
// parse external files
this.parseFiles(xml);

Expand All @@ -45,6 +45,8 @@ qx.Class.define("cv.parser.MetaParser", {
qx.bom.Selector.query('meta > statusbar status', xml).forEach(this.parseStatusBar, this);

this.parseStateNotifications(xml);

this.parseTemplates(xml, done);
},

parseFiles: function (xml) {
Expand Down Expand Up @@ -294,6 +296,70 @@ qx.Class.define("cv.parser.MetaParser", {
});
});
cv.core.notifications.Router.getInstance().registerStateUpdateHandler(stateConfig);
},

/**
* Parses meta template definitions and add them to the WidgetParser
* @param xml {HTMLElement}
*/
parseTemplates: function (xml, done) {
var __loadQueue = new qx.data.Array();

var check = function () {
if (__loadQueue.length === 0 && done) {
done();
}
};
var templates = qx.bom.Selector.query('meta > templates template', xml);
if (templates.length === 0) {
done();
} else {
templates.forEach(function (elem) {
var templateName = qx.bom.element.Attribute.get(elem, 'name');
qx.log.Logger.debug(this, 'loading template:', templateName);
var ref = qx.bom.element.Attribute.get(elem, 'ref');
if (ref) {
// load template fom external file
var areq = new qx.io.request.Xhr(ref);
__loadQueue.push(ref);
qx.log.Logger.debug(this, 'loading template from file:', ref);
areq.set({
accept: "text/plain",
cache: !cv.Config.forceReload
});

areq.addListenerOnce("success", function (e) {
var req = e.getTarget();
cv.parser.WidgetParser.addTemplate(
templateName,
// templates can only have one single root element, so we wrap it here
'<root>' + req.getResponseText() + '</root>'
);
__loadQueue.remove(areq.getUrl());
qx.log.Logger.debug(this, 'DONE loading template from file:', ref);
check();
}, this);
areq.addListener("statusError", function () {
var message = {
topic: "cv.config.error",
title: qx.locale.Manager.tr("Template loading error"),
severity: "urgent",
deletable: true,
message: qx.locale.Manager.tr("Template '%1' could not be loaded from '%2'.", templateName, ref)
};
cv.core.notifications.Router.dispatchMessage(message.topic, message);
}, this);
areq.send();
} else {
cv.parser.WidgetParser.addTemplate(
templateName,
// templates can only have one single root element, so we wrap it here
'<root>' + qx.bom.element.Attribute.get(elem, 'html') + '</root>'
);
check();
}
}, this);
}
}
}
});
});
25 changes: 25 additions & 0 deletions source/class/cv/parser/WidgetParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ qx.Class.define('cv.parser.WidgetParser', {
lookupM : [ 0, 2, 4, 6, 6, 6, 6, 12, 12, 12, 12, 12, 12 ],
lookupS : [ 0, 3, 6, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12 ],
model: cv.data.Model.getInstance(),
__templates: {},

addTemplate: function(name, templateData) {
this.__templates[name] = templateData;
},

addHandler: function (tagName, handler) {
this.__handlers[tagName.toLowerCase()] = handler;
Expand All @@ -41,6 +46,26 @@ qx.Class.define('cv.parser.WidgetParser', {
return this.__handlers[tagName.toLowerCase()] || this.__handlers.unknown;
},

/**
* Renders templates into the config file, if they are used
* @param rootPage
*/
renderTemplates: function (rootPage) {
qx.bom.Selector.query('template', rootPage).forEach(function (elem) {
var templateName = qx.bom.element.Attribute.get(elem, 'name');
var variables = {};
qx.dom.Hierarchy.getChildElements(elem).forEach(function (variable) {
variables[qx.bom.element.Attribute.get(variable, 'name')] = qx.bom.element.Attribute.get(variable, 'html');
}, this);

if (this.__templates.hasOwnProperty(templateName)) {
var renderedString = qx.bom.Template.render(this.__templates[templateName], variables);
// replace existing element with the rendered template (without <root> </root>)
elem.outerHTML = renderedString.substring(6, renderedString.length - 7);
}
}, this);
},

/**
* Parses the widgets XML configuration and extracts the given information
* to a simple key/value map.
Expand Down
2 changes: 1 addition & 1 deletion source/resource/config/media/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
!.gitignore

0 comments on commit 6a86725

Please sign in to comment.