Merge pull request #5219 from VOREStation/vplk-nanoui-multi-template

NanoUI multiple templates example.
This commit is contained in:
Atermonera
2018-05-03 11:12:56 -07:00
committed by GitHub
9 changed files with 262 additions and 280 deletions
+2 -1
View File
@@ -653,8 +653,9 @@ var/global/list/obj/item/device/pda/PDAs = list()
// the ui does not exist, so we'll create a new() one
// for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
ui = new(user, src, ui_key, "pda.tmpl", title, 520, 400, state = inventory_state)
// add templates for screens in common with communicator.
ui.add_template("atmosphericScan", "atmospheric_scan.tmpl")
// when the ui is first opened this is the data it will use
ui.set_initial_data(data)
// open the new ui window
ui.open()
@@ -118,6 +118,8 @@
// the ui does not exist, so we'll create a new() one
// for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
ui = new(user, src, ui_key, "communicator.tmpl", "Communicator", 475, 700, state = key_state)
// add templates for screens in common with communicator.
ui.add_template("atmosphericScan", "atmospheric_scan.tmpl")
// when the ui is first opened this is the data it will use
ui.set_initial_data(data)
// open the new ui window
-5
View File
@@ -519,8 +519,3 @@ nanoui is used to open and update nano browser uis
*/
/datum/nanoui/proc/update(var/force_open = 0)
src_object.ui_interact(user, ui_key, src, force_open, master_ui, state)
/datum/nanoui/proc/append_template(var/key, var/filename)
add_template(key, filename)
open()
update(1)
+39
View File
@@ -0,0 +1,39 @@
# NanoUI Templates
NanoUI uses doT (https://olado.github.io/doT/index.html) as its templating engine.
## Template Markup Tags
Markup tags are used to add dynamic content to the template.
TODO - This documentation is incomplete.
### Print Tag
- The print tag outputs variable as text to the UI.
`{{:data.variable}}`
### If Tag
- The if tag displays content conditionally based on the provided expression being true.
- When combined with the else tag the if tag can also show content if the provided expression is false.
- The else tag can optionally have an expression provided (e.g. "`{{else expression2}}`"), giving it "elseif" functionality.
`{{if expression}} <expression true content> {{/if}}`
`{{if expression}} <expression true content> {{else}} <expression false content> {{/if}}`
`{{if expression1}} <expression1 true content> {{else expression2}} <expression2 true content> {{/if}}`
### For Tag
- Loop through entries in an array (an array is a list with a numeric index (it does not use strings as keys).
- Each time the `for` tag iterates though the array it sets a variable (default "value") to the data of the current entry (another variable, default "index", contains the index). An example of this is using the print tag to print the contents (e.g. `{{:value.key1}}` and `{{:value.key2}}`).
- If combined with an `empty` tag the for tag can display content when the array is empty.
`{{for array}} <list entry content> {{/for}}`
`{{for array}} <list entry content> {{empty}} <empty list content> {{/for}}`
### Tansclusion Tag
- Include the contents of another template which has been added to the ui.
`{{#def.atmosphericScan}}`
- You first must have added a template to the ui server side in your DM code:
`ui.add_template("atmosphericScan", "atmospheric_scan.tmpl")`
- Then you can reference it in the main template. The tag will be replaced by the contents of the named template. All tags in the named template are evaluated as normal.
+78 -74
View File
@@ -7,7 +7,8 @@ function NanoStateClass() {
return;
}
this.key = this.key.toLowerCase();
this.key = this.key.toLowerCase();
NanoStateManager.addState(this);*/
}
@@ -17,99 +18,102 @@ NanoStateClass.prototype.contentRendered = false;
NanoStateClass.prototype.mapInitialised = false;
NanoStateClass.prototype.isCurrent = function () {
return NanoStateManager.getCurrentState() == this;
return NanoStateManager.getCurrentState() == this;
};
NanoStateClass.prototype.onAdd = function (previousState) {
// Do not add code here, add it to the 'default' state (nano_state_defaut.js) or create a new state and override this function
NanoBaseCallbacks.addCallbacks();
NanoBaseHelpers.addHelpers();
// Do not add code here, add it to the 'default' state (nano_state_defaut.js) or create a new state and override this function
NanoBaseCallbacks.addCallbacks();
NanoBaseHelpers.addHelpers();
};
NanoStateClass.prototype.onRemove = function (nextState) {
// Do not add code here, add it to the 'default' state (nano_state_defaut.js) or create a new state and override this function
NanoBaseCallbacks.removeCallbacks();
NanoBaseHelpers.removeHelpers();
// Do not add code here, add it to the 'default' state (nano_state_defaut.js) or create a new state and override this function
NanoBaseCallbacks.removeCallbacks();
NanoBaseHelpers.removeHelpers();
};
NanoStateClass.prototype.onBeforeUpdate = function (data) {
// Do not add code here, add it to the 'default' state (nano_state_defaut.js) or create a new state and override this function
data = NanoStateManager.executeBeforeUpdateCallbacks(data);
return data; // Return data to continue, return false to prevent onUpdate and onAfterUpdate
// Do not add code here, add it to the 'default' state (nano_state_defaut.js) or create a new state and override this function
data = NanoStateManager.executeBeforeUpdateCallbacks(data);
return data; // Return data to continue, return false to prevent onUpdate and onAfterUpdate
};
NanoStateClass.prototype.onUpdate = function (data) {
// Do not add code here, add it to the 'default' state (nano_state_defaut.js) or create a new state and override this function
try
{
if (!this.layoutRendered || (data['config'].hasOwnProperty('autoUpdateLayout') && data['config']['autoUpdateLayout']))
{
$("#uiLayout").html(NanoTemplate.parse('layout', data)); // render the 'mail' template to the #mainTemplate div
this.layoutRendered = true;
}
if (!this.contentRendered || (data['config'].hasOwnProperty('autoUpdateContent') && data['config']['autoUpdateContent']))
{
var content = "";
var keys = NanoTemplate.getKeys();
for (i = 0; i < keys.length; i++) {
if(keys[i] == "layout"){ continue; }
content += NanoTemplate.parse(keys[i], data);
}
$("#uiContent").html(content); // render the 'mail' template to the #mainTemplate div
this.contentRendered = true;
}
if (NanoTemplate.templateExists('mapContent'))
{
if (!this.mapInitialised)
{
// Add drag functionality to the map ui
$('#uiMap').draggable();
$('#uiMapTooltip')
.off('click')
.on('click', function (event) {
event.preventDefault();
$(this).fadeOut(400);
});
this.mapInitialised = true;
}
// Do not add code here, add it to the 'default' state (nano_state_defaut.js) or create a new state and override this function
$("#uiMapContent").html(NanoTemplate.parse('mapContent', data)); // render the 'mapContent' template to the #uiMapContent div
try
{
if (!this.layoutRendered || (data['config'].hasOwnProperty('autoUpdateLayout') && data['config']['autoUpdateLayout']))
{
$("#uiLayout").html(NanoTemplate.parse('layout', data)); // render the 'layout' template to the #uiLayout div
this.layoutRendered = true;
}
if (!this.contentRendered || (data['config'].hasOwnProperty('autoUpdateContent') && data['config']['autoUpdateContent']))
{
$("#uiContent").html(NanoTemplate.parse('main', data)); // render the 'main' template to the #uiContent div
this.contentRendered = true;
}
if (NanoTemplate.templateExists('mapContent'))
{
if (!this.mapInitialised)
{
// Add drag functionality to the map ui
$('#uiMap').draggable();
if (data['config'].hasOwnProperty('showMap') && data['config']['showMap'])
{
$('#uiContent').addClass('hidden');
$('#uiMapWrapper').removeClass('hidden');
}
else
{
$('#uiMapWrapper').addClass('hidden');
$('#uiContent').removeClass('hidden');
}
}
if (NanoTemplate.templateExists('mapHeader'))
{
$("#uiMapHeader").html(NanoTemplate.parse('mapHeader', data)); // render the 'mapHeader' template to the #uiMapHeader div
}
if (NanoTemplate.templateExists('mapFooter'))
{
$("#uiMapFooter").html(NanoTemplate.parse('mapFooter', data)); // render the 'mapFooter' template to the #uiMapFooter div
}
}
catch(error)
{
alert('ERROR: An error occurred while rendering the UI: ' + error.message);
return;
}
$('#uiMapTooltip')
.off('click')
.on('click', function (event) {
event.preventDefault();
$(this).fadeOut(400);
});
this.mapInitialised = true;
}
$("#uiMapContent").html(NanoTemplate.parse('mapContent', data)); // render the 'mapContent' template to the #uiMapContent div
if (data['config'].hasOwnProperty('showMap') && data['config']['showMap'])
{
$('#uiContent').addClass('hidden');
$('#uiMapWrapper').removeClass('hidden');
}
else
{
$('#uiMapWrapper').addClass('hidden');
$('#uiContent').removeClass('hidden');
}
}
if (NanoTemplate.templateExists('mapHeader'))
{
$("#uiMapHeader").html(NanoTemplate.parse('mapHeader', data)); // render the 'mapHeader' template to the #uiMapHeader div
}
if (NanoTemplate.templateExists('mapFooter'))
{
$("#uiMapFooter").html(NanoTemplate.parse('mapFooter', data)); // render the 'mapFooter' template to the #uiMapFooter div
}
}
catch(error)
{
alert('ERROR: An error occurred while rendering the UI: ' + error.message);
return;
}
};
NanoStateClass.prototype.onAfterUpdate = function (data) {
// Do not add code here, add it to the 'default' state (nano_state_defaut.js) or create a new state and override this function
NanoStateManager.executeAfterUpdateCallbacks(data);
// Do not add code here, add it to the 'default' state (nano_state_defaut.js) or create a new state and override this function
NanoStateManager.executeAfterUpdateCallbacks(data);
};
NanoStateClass.prototype.alertText = function (text) {
// Do not add code here, add it to the 'default' state (nano_state_defaut.js) or create a new state and override this function
alert(text);
// Do not add code here, add it to the 'default' state (nano_state_defaut.js) or create a new state and override this function
alert(text);
};
+82 -89
View File
@@ -1,15 +1,15 @@
var NanoTemplate = function () {
var _templateData = {};
var _templateData = {};
var _templates = {};
var _compiledTemplates = {};
var _templates = {};
var _compiledTemplates = {};
var _helpers = {};
var init = function () {
// We store templateData in the body tag, it's as good a place as any
var init = function () {
// We store templateData in the body tag, it's as good a place as any
_templateData = $('body').data('templateData');
if (_templateData == null)
@@ -18,101 +18,94 @@ var NanoTemplate = function () {
}
loadNextTemplate();
};
};
var loadNextTemplate = function () {
// we count the number of templates for this ui so that we know when they've all been rendered
var templateCount = Object.size(_templateData);
var loadNextTemplate = function () {
// we count the number of templates for this ui so that we know when they've all been rendered
var templateCount = Object.size(_templateData);
if (!templateCount)
{
$(document).trigger('templatesLoaded');
return;
}
if (!templateCount)
{
$(document).trigger('templatesLoaded');
return;
}
// load markup for each template and register it
for (var key in _templateData)
{
if (!_templateData.hasOwnProperty(key))
{
continue;
}
// load markup for each template and register it
for (var key in _templateData)
{
if (!_templateData.hasOwnProperty(key))
{
continue;
}
$.when($.ajax({
url: _templateData[key],
cache: false,
dataType: 'text'
}))
.done(function(templateMarkup) {
$.when($.ajax({
url: _templateData[key],
cache: false,
dataType: 'text'
}))
.done(function(templateMarkup) {
templateMarkup += '<div class="clearBoth"></div>';
templateMarkup += '<div class="clearBoth"></div>';
try
{
NanoTemplate.addTemplate(key, templateMarkup);
}
catch(error)
{
alert('ERROR: An error occurred while loading the UI: ' + error.message);
return;
}
try
{
NanoTemplate.addTemplate(key, templateMarkup);
}
catch(error)
{
alert('ERROR: An error occurred while loading the UI: ' + error.message);
return;
}
delete _templateData[key];
delete _templateData[key];
loadNextTemplate();
})
.fail(function () {
alert('ERROR: Loading template ' + key + '(' + _templateData[key] + ') failed!');
});
loadNextTemplate();
})
.fail(function () {
alert('ERROR: Loading template ' + key + '(' + _templateData[key] + ') failed!');
});
return;
}
}
return;
}
}
var compileTemplates = function () {
var compileTemplates = function () {
for (var key in _templates) {
try {
_compiledTemplates[key] = doT.template(_templates[key], null, _templates)
}
catch (error) {
alert(error.message);
}
}
};
for (var key in _templates) {
try {
_compiledTemplates[key] = doT.template(_templates[key], null, _templates)
}
catch (error) {
alert(error.message);
}
}
};
return {
init: function () {
init();
},
addTemplate: function (key, templateString) {
_templates[key] = templateString;
},
templateExists: function (key) {
return _templates.hasOwnProperty(key);
},
getKeys: function () {
var _keys = [];
for(var key in _templates) {
_keys.push(key);
}
return _keys;
},
parse: function (templateKey, data) {
if (!_compiledTemplates.hasOwnProperty(templateKey) || !_compiledTemplates[templateKey]) {
if (!_templates.hasOwnProperty(templateKey)) {
alert('ERROR: Template "' + templateKey + '" does not exist in _compiledTemplates!');
return '<h2>Templateerror (does not exist)</h2>';
}
compileTemplates();
}
if (typeof _compiledTemplates[templateKey] != 'function') {
alert(_compiledTemplates[templateKey]);
alert('ERROR: Template "' + templateKey + '" failed to compile!');
return '<h2>Template error (failed to compile)</h2>';
}
return _compiledTemplates[templateKey].call(this, data['data'], data['config'], _helpers);
},
return {
init: function () {
init();
},
addTemplate: function (key, templateString) {
_templates[key] = templateString;
},
templateExists: function (key) {
return _templates.hasOwnProperty(key);
},
parse: function (templateKey, data) {
if (!_compiledTemplates.hasOwnProperty(templateKey) || !_compiledTemplates[templateKey]) {
if (!_templates.hasOwnProperty(templateKey)) {
alert('ERROR: Template "' + templateKey + '" does not exist in _compiledTemplates!');
return '<h2>Template error (does not exist)</h2>';
}
compileTemplates();
}
if (typeof _compiledTemplates[templateKey] != 'function') {
alert(_compiledTemplates[templateKey]);
alert('ERROR: Template "' + templateKey + '" failed to compile!');
return '<h2>Template error (failed to compile)</h2>';
}
return _compiledTemplates[templateKey].call(this, data['data'], data['config'], _helpers);
},
addHelper: function (helperName, helperFunction) {
if (!jQuery.isFunction(helperFunction)) {
alert('NanoTemplate.addHelper failed to add ' + helperName + ' as it is not a function.');
@@ -136,7 +129,7 @@ var NanoTemplate = function () {
delete _helpers[helperName];
}
}
}
}
}();
+57
View File
@@ -0,0 +1,57 @@
<div class="item">
{{if data.aircontents.reading == 1}}
<div class="itemLabel">
Pressure:
</div>
<div class = "itemContent">
{{:helper.string('<span class="{0}">{1} kPa</span>', data.aircontents.pressure < 80 || data.aircontents.pressure > 120 ? 'bad' : data.aircontents.pressure < 95 || data.aircontents.pressure > 110 ? 'average' : 'good' , data.aircontents.pressure)}}
</div>
<div class="itemLabel">
Temperature:
</div>
<div class = "itemContent">
{{:helper.string('<span class="{0}">{1} &deg;C</span>', data.aircontents.temp < 5 || data.aircontents.temp > 35 ? 'bad' : data.aircontents.temp < 15 || data.aircontents.temp > 25 ? 'average' : 'good' , data.aircontents.temp)}}
</div>
<br>
<div class="itemLabel">
Oxygen:
</div>
<div class = "itemContent">
{{:helper.string('<span class="{0}">{1}%</span>', data.aircontents.oxygen < 17 ? 'bad' : data.aircontents.oxygen < 19 ? 'average' : 'good' , data.aircontents.oxygen)}}
</div>
<div class="itemLabel">
Nitrogen:
</div>
<div class = "itemContent">
{{:helper.string('<span class="{0}">{1}%</span>', data.aircontents.nitrogen > 82 ? 'bad' : data.aircontents.nitrogen > 80 ? 'average' : 'good' , data.aircontents.nitrogen)}}
</div>
{{if data.aircontents.carbon_dioxide > 0}}
<div class="itemLabel">
Carbon Dioxide:
</div>
<div class = "itemContent">
{{:helper.string('<span class="{0}">{1}%</span>', data.aircontents.carbon_dioxide > 5 ? 'bad' : 'good' , data.aircontents.carbon_dioxide)}}
</div>
{{/if}}
{{if data.aircontents.phoron > 0}}
<div class="itemLabel">
Phoron:
</div>
<div class = "itemContent">
{{:helper.string('<span class="{0}">{1}%</span>', data.aircontents.phoron > 0 ? 'bad' : 'good' , data.aircontents.phoron)}}
</div>
{{/if}}
{{if data.aircontents.other > 0}}
<div class="itemLabel">
Unknown:
</div>
<div class = "itemContent">
<span class="bad">{{:data.aircontents.other}}%</span>
</div>
{{/if}}
{{else}}
<div class="itemContent" style="width: 100%;">
<span class="average"><b>Unable to get air reading</b></span>
</div>
{{/if}}
</div>
+1 -57
View File
@@ -243,63 +243,7 @@ Used In File(s): code\game\objects\items\devices\communicator\communicator.dm
<div class="itemContent" style="width: 100%;">{{:helper.link('Home', 'home', {'switch_tab' : 1})}}</div><BR>
<h4>Current Conditions:</h4>
<div class="item">
{{if data.aircontents.reading == 1}}
<div class="itemLabel">
Pressure:
</div>
<div class = "itemContent">
{{:helper.string('<span class="{0}">{1} kPa</span>', data.aircontents.pressure < 80 || data.aircontents.pressure > 120 ? 'bad' : data.aircontents.pressure < 95 || data.aircontents.pressure > 110 ? 'average' : 'good' , data.aircontents.pressure)}}
</div>
<div class="itemLabel">
Temperature:
</div>
<div class = "itemContent">
{{:helper.string('<span class="{0}">{1} &deg;C</span>', data.aircontents.temp < 5 || data.aircontents.temp > 35 ? 'bad' : data.aircontents.temp < 15 || data.aircontents.temp > 25 ? 'average' : 'good' , data.aircontents.temp)}}
</div>
<br>
<div class="itemLabel">
Oxygen:
</div>
<div class = "itemContent">
{{:helper.string('<span class="{0}">{1}%</span>', data.aircontents.oxygen < 17 ? 'bad' : data.aircontents.oxygen < 19 ? 'average' : 'good' , data.aircontents.oxygen)}}
</div>
<div class="itemLabel">
Nitrogen:
</div>
<div class = "itemContent">
{{:helper.string('<span class="{0}">{1}%</span>', data.aircontents.nitrogen > 82 ? 'bad' : data.aircontents.nitrogen > 80 ? 'average' : 'good' , data.aircontents.nitrogen)}}
</div>
{{if data.aircontents.carbon_dioxide > 0}}
<div class="itemLabel">
Carbon Dioxide:
</div>
<div class = "itemContent">
{{:helper.string('<span class="{0}">{1}%</span>', data.aircontents.carbon_dioxide > 5 ? 'bad' : 'good' , data.aircontents.carbon_dioxide)}}
</div>
{{/if}}
{{if data.aircontents.phoron > 0}}
<div class="itemLabel">
Phoron:
</div>
<div class = "itemContent">
{{:helper.string('<span class="{0}">{1}%</span>', data.aircontents.phoron > 0 ? 'bad' : 'good' , data.aircontents.phoron)}}
</div>
{{/if}}
{{if data.aircontents.other > 0}}
<div class="itemLabel">
Unknown:
</div>
<div class = "itemContent">
<span class="bad">{{:data.aircontents.other}}%</span>
</div>
{{/if}}
{{else}}
<div class="itemContent" style="width: 100%;">
<span class="average"><b>Unable to get air reading</b></span>
</div>
{{/if}}
</div>
{{#def.atmosphericScan}}
<h4>Weather Reports:</h4>
{{for data.weather}}
+1 -54
View File
@@ -318,60 +318,7 @@ Used In File(s): \code\game\objects\items\devices\PDA\PDA.dm
{{else data.mode == 3}}
<H2>Atmospheric Scan</H2>
<div class="statusDisplay" style="height: 250px; overflow: auto;">
<div class="item">
{{if data.aircontents.reading == 1}}
<div class="itemLabel">
Pressure:
</div>
<div class = "itemContent">
{{:helper.string('<span class="{0}">{1} kPa</span>', data.aircontents.pressure < 80 || data.aircontents.pressure > 120 ? 'bad' : data.aircontents.pressure < 95 || data.aircontents.pressure > 110 ? 'average' : 'good' , data.aircontents.pressure)}}
</div>
<div class="itemLabel">
Temperature:
</div>
<div class = "itemContent">
{{:helper.string('<span class="{0}">{1} &deg;C</span>', data.aircontents.temp < 5 || data.aircontents.temp > 35 ? 'bad' : data.aircontents.temp < 15 || data.aircontents.temp > 25 ? 'average' : 'good' , data.aircontents.temp)}}
</div>
<br>
<div class="itemLabel">
Oxygen:
</div>
<div class = "itemContent">
{{:helper.string('<span class="{0}">{1}%</span>', data.aircontents.oxygen < 17 ? 'bad' : data.aircontents.oxygen < 19 ? 'average' : 'good' , data.aircontents.oxygen)}}
</div>
<div class="itemLabel">
Nitrogen:
</div>
<div class = "itemContent">
{{:helper.string('<span class="{0}">{1}%</span>', data.aircontents.nitrogen > 82 ? 'bad' : data.aircontents.nitrogen > 80 ? 'average' : 'good' , data.aircontents.nitrogen)}}
</div>
<div class="itemLabel">
Carbon Dioxide:
</div>
<div class = "itemContent">
{{:helper.string('<span class="{0}">{1}%</span>', data.aircontents.carbon_dioxide > 5 ? 'bad' : 'good' , data.aircontents.carbon_dioxide)}}
</div>
<div class="itemLabel">
Phoron:
</div>
<div class = "itemContent">
{{:helper.string('<span class="{0}">{1}%</span>', data.aircontents.phoron > 0 ? 'bad' : 'good' , data.aircontents.phoron)}}
</div>
{{if data.aircontents.other > 0}}
<div class="itemLabel">
Unknown:
</div>
<div class = "itemContent">
<span class="bad">{{:data.aircontents.other}}%</span>
</div>
{{/if}}
{{else}}
<div class="itemContent" style="width: 100%;">
<span class="average"><b>Unable to get air reading</b></span>
</div>
{{/if}}
</div>
{{#def.atmosphericScan}}
</div>