de.tsl2.nano.h5.Html5Presentation Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tsl2.nano.h5 Show documentation
Show all versions of tsl2.nano.h5 Show documentation
TSL2 Framework Html5 Extensions (WebServer, Html5Presentation, RuleCover, BeanConfigurator, LogicTable-Sheet, Expression-Descriptors for Actions, Rules, URLs, Queries)
/*
* File: $HeadURL$
* Id : $Id$
*
* created by: Thomas Schneider, Thomas Schneider
* created on: Oct 1, 2012
*
* Copyright: (c) Thomas Schneider 2012, all rights reserved
*/
package de.tsl2.nano.h5;
import static de.tsl2.nano.bean.def.IBeanCollector.MODE_ASSIGNABLE;
import static de.tsl2.nano.bean.def.IBeanCollector.MODE_MULTISELECTION;
import static de.tsl2.nano.bean.def.IPresentable.POSTFIX_SELECTOR;
import static de.tsl2.nano.bean.def.IPresentable.STYLE_ALIGN_CENTER;
import static de.tsl2.nano.bean.def.IPresentable.STYLE_ALIGN_RIGHT;
import static de.tsl2.nano.bean.def.IPresentable.STYLE_DATA_FRAME;
import static de.tsl2.nano.bean.def.IPresentable.STYLE_DATA_IMG;
import static de.tsl2.nano.bean.def.IPresentable.STYLE_MULTI;
import static de.tsl2.nano.bean.def.IPresentable.TYPE_ATTACHMENT;
import static de.tsl2.nano.bean.def.IPresentable.TYPE_DATA;
import static de.tsl2.nano.bean.def.IPresentable.TYPE_DATE;
import static de.tsl2.nano.bean.def.IPresentable.TYPE_INPUT;
import static de.tsl2.nano.bean.def.IPresentable.TYPE_INPUT_EMAIL;
import static de.tsl2.nano.bean.def.IPresentable.TYPE_INPUT_MULTILINE;
import static de.tsl2.nano.bean.def.IPresentable.TYPE_INPUT_NUMBER;
import static de.tsl2.nano.bean.def.IPresentable.TYPE_INPUT_PASSWORD;
import static de.tsl2.nano.bean.def.IPresentable.TYPE_INPUT_SEARCH;
import static de.tsl2.nano.bean.def.IPresentable.TYPE_INPUT_TEL;
import static de.tsl2.nano.bean.def.IPresentable.TYPE_INPUT_URL;
import static de.tsl2.nano.bean.def.IPresentable.TYPE_OPTION;
import static de.tsl2.nano.bean.def.IPresentable.TYPE_OPTION_RADIO;
import static de.tsl2.nano.bean.def.IPresentable.TYPE_SELECTION;
import static de.tsl2.nano.bean.def.IPresentable.TYPE_TIME;
import static de.tsl2.nano.h5.HtmlUtil.*;
import java.io.File;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.logging.Log;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import de.tsl2.nano.action.CommonAction;
import de.tsl2.nano.action.IAction;
import de.tsl2.nano.bean.BeanContainer;
import de.tsl2.nano.bean.BeanUtil;
import de.tsl2.nano.bean.ValueHolder;
import de.tsl2.nano.bean.def.Attachment;
import de.tsl2.nano.bean.def.AttributeCover;
import de.tsl2.nano.bean.def.AttributeDefinition;
import de.tsl2.nano.bean.def.Bean;
import de.tsl2.nano.bean.def.BeanCollector;
import de.tsl2.nano.bean.def.BeanDefinition;
import de.tsl2.nano.bean.def.BeanPresentationHelper;
import de.tsl2.nano.bean.def.BeanValue;
import de.tsl2.nano.bean.def.GroupBy;
import de.tsl2.nano.bean.def.GroupingPresentable;
import de.tsl2.nano.bean.def.IAttributeDefinition;
import de.tsl2.nano.bean.def.IBeanCollector;
import de.tsl2.nano.bean.def.IPageBuilder;
import de.tsl2.nano.bean.def.IPresentable;
import de.tsl2.nano.bean.def.IPresentableColumn;
import de.tsl2.nano.bean.def.IValueDefinition;
import de.tsl2.nano.bean.def.IsPresentable;
import de.tsl2.nano.bean.def.MethodAction;
import de.tsl2.nano.bean.def.Presentable;
import de.tsl2.nano.bean.def.SecureAction;
import de.tsl2.nano.bean.def.ValueExpressionFormat;
import de.tsl2.nano.bean.def.ValueGroup;
import de.tsl2.nano.core.AppLoader;
import de.tsl2.nano.core.ENV;
import de.tsl2.nano.core.ISession;
import de.tsl2.nano.core.ManagedException;
import de.tsl2.nano.core.Messages;
import de.tsl2.nano.core.cls.BeanClass;
import de.tsl2.nano.core.cls.IAttribute;
import de.tsl2.nano.core.cls.IValueAccess;
import de.tsl2.nano.core.cls.PrivateAccessor;
import de.tsl2.nano.core.exception.Message;
import de.tsl2.nano.core.log.LogFactory;
import de.tsl2.nano.core.messaging.ChangeEvent;
import de.tsl2.nano.core.messaging.IListener;
import de.tsl2.nano.core.util.BitUtil;
import de.tsl2.nano.core.util.ByteUtil;
import de.tsl2.nano.core.util.CollectionUtil;
import de.tsl2.nano.core.util.DateUtil;
import de.tsl2.nano.core.util.FileUtil;
import de.tsl2.nano.core.util.MapUtil;
import de.tsl2.nano.core.util.NetUtil;
import de.tsl2.nano.core.util.NumberUtil;
import de.tsl2.nano.core.util.StringUtil;
import de.tsl2.nano.core.util.Util;
import de.tsl2.nano.format.GenericParser;
import de.tsl2.nano.format.RegExpFormat;
import de.tsl2.nano.h5.collector.Controller;
import de.tsl2.nano.h5.collector.QueryResult;
import de.tsl2.nano.h5.collector.Statistic;
import de.tsl2.nano.h5.configuration.BeanConfigurator;
import de.tsl2.nano.h5.configuration.ExpressionDescriptor;
import de.tsl2.nano.h5.expression.Query;
import de.tsl2.nano.h5.plugin.IDOMDecorator;
import de.tsl2.nano.h5.websocket.WSEvent;
import de.tsl2.nano.h5.websocket.WebSocketRuleDependencyListener;
import de.tsl2.nano.h5.websocket.dialog.WSDialog;
import de.tsl2.nano.persistence.DatabaseTool;
import de.tsl2.nano.persistence.Persistence;
import de.tsl2.nano.plugin.Plugins;
import de.tsl2.nano.script.ScriptTool;
import de.tsl2.nano.specification.Pool;
import de.tsl2.nano.specification.rules.RuleDependencyListener;
/**
* is able to present a bean as an html page. main method is {@link #build(Element, String)}.
*
* @author Thomas Schneider, Thomas Schneider
* @version $Revision$
*/
/**
* @param
* @author Tom, Thomas Schneider
* @version $Revision$
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public class Html5Presentation extends BeanPresentationHelper implements IPageBuilder {
private String SUBMIT = ENV.get("html5.action.type", "submit");
//this class is not serializable - but simple-xml will serialize <- we need transient modifiers
protected transient int tabIndex;
private transient List availableshortCuts;
private transient static final Character SHORTCUTS[];
static {
SHORTCUTS = new Character[222];
for (char i = 0; i < SHORTCUTS.length; i++) {
SHORTCUTS[i] = (char) (i + 33);
}
}
private transient String row1style;
private transient String row2style;
/** on sidenav action bars, all action are integrated into this one sidebar */
private transient Element sideNav;
static final Log LOG = LogFactory.getLog(Html5Presentation.class);
private transient boolean isAuthenticated;
private static transient String jsWebsocketTemplate;
public static final String L_GRIDWIDTH = "layout.gridwidth";
public static final String PREFIX_BEANREQUEST = "~~~";
/** indicator for server to handle a link, that was got as link (method=GET) not as a file */
public static final String PREFIX_ACTION = PREFIX_BEANREQUEST + "!!!";
/** indicator for server to handle a link, that was got as link (method=GET) not as a file */
public static final String PREFIX_BEANLINK = PREFIX_BEANREQUEST + "--)";
public static final String ID_QUICKSEARCH_FIELD = "field.quicksearch";
static final String MSG_FOOTER = "progress";
static final String ICON_DEFAULT = "icons/trust_unknown.png";
private static final String CSS_CLASS_PANEL_ACTION = "panelaction";
private static final String CSS_CLASS_ACTION = "action";
private static final String CSS_CLASS_SELECTOR = "selector";
/**
* constructor
*/
public Html5Presentation() {
super();
}
/**
* constructor
*
* @param bean
*/
public Html5Presentation(BeanDefinition bean) {
super(bean);
}
@SuppressWarnings("serial")
@Override
public Collection getApplicationActions(ISession session) {
boolean firstTime = appActions == null;
super.getApplicationActions(session);
if (firstTime) {
if (bean instanceof Bean && !isBeanConfiguration()) {
appActions.add(new SecureAction(bean.getClazz(),
"configure",
IAction.MODE_UNDEFINED,
false,
"icons/compose.png") {
@Override
public Object action() throws Exception {
return BeanConfigurator.create((Class) bean.getClazz());
}
});
}
//extensions of beancollector may be configured (the beancollector is defined through beandefinition!)
//there are a lot of exceptions, so we constraint it to a debug level
else if (LogFactory.isEnabled(LogFactory.DEBUG)
&& bean instanceof BeanCollector && !BeanCollector.class.equals(bean.getClass())) {
appActions.add(new SecureAction(bean.getClass(),
"configure",
IAction.MODE_UNDEFINED,
false,
"icons/compose.png") {
@Override
public Object action() throws Exception {
return Bean.getBean(bean);
}
});
}
}
return appActions;
}
/**
* addAdministrationActions
*/
@SuppressWarnings("serial")
@Override
protected void addAdministrationActions(final ISession session, Bean bEnv) {
if (ENV.get("app.login.administration", true)) {
bEnv.addAction(new SecureAction(bean.getClazz(),
"Scripttool",
IAction.MODE_UNDEFINED,
false,
"icons/go.png") {
Bean beanTool;
@Override
public Object action() throws Exception {
/*
* show the script tool to do direct sql or ant
*/
if (beanTool == null) {
BeanConfigurator.defineAction(null);
final ScriptTool tool = ScriptTool.createInstance();
beanTool = Bean.getBean(tool);
beanTool.setAttributeFilter("sourceFile", "selectedAction", "name", "text"/*, "result"*/);
beanTool.getAttribute("text").getPresentation().setType(TYPE_INPUT_MULTILINE);
beanTool.getAttribute("text").getConstraint().setLength(100000);
beanTool.getAttribute("text").getConstraint()
.setFormat(null/*RegExpFormat.createLengthRegExp(0, 100000, 0)*/);
// beanTool.getAttribute("result").getPresentation().setType(TYPE_TABLE);
beanTool.getAttribute("sourceFile").getPresentation().setType(TYPE_ATTACHMENT);
beanTool.getAttribute("selectedAction").setRange(tool.availableActions());
beanTool.addAction(tool.runner());
String id = "scripttool.define.query";
String lbl = ENV.translate(id, true);
IAction queryDefiner = new CommonAction(id, lbl, lbl) {
@Override
public Object action() throws Exception {
String name = tool.getName();
if (Util.isEmpty(name)) {
name = tool.getSourceFile() != null ? tool.getSourceFile().toLowerCase() : FileUtil
.getValidFileName(tool.getText().replace('.', '_'));
}
//some file-systems may have problems on longer file names!
name = StringUtil.cut(name, 64);
Query query =
new Query(name, tool.getText(), tool.getSelectedAction().getId()
.equals("scripttool.sql.id"),
null);
ENV.get(Pool.class).add(query);
QueryResult qr = new QueryResult(query.getName());
qr.getPresentable().setIcon("icons/barchart.png");
qr.saveDefinition();
return "New created specification-query: " + name;
}
@Override
public String getImagePath() {
return "icons/save.png";
}
};
beanTool.addAction(queryDefiner);
id = "scripttool.define.urltodatabase";
lbl = ENV.translate("database tool", true);
IAction dbToolURL = new CommonAction(id, lbl, lbl) {
@Override
public Object action() throws Exception {
return new DatabaseTool(Persistence.current()).getSQLToolURL();
}
@Override
public boolean isEnabled() {
return super.isEnabled() && Persistence.current() != null && new DatabaseTool(Persistence.current()).getSQLToolURL() != null;
}
@Override
public String getImagePath() {
return "icons/equipment.png";
}
};
beanTool.addAction(dbToolURL);
}
return beanTool;
}
});
}
super.addAdministrationActions(session, bEnv);
}
@Override
public Collection getSessionActions(ISession session) {
if (sessionActions == null) {
sessionActions = super.getSessionActions(session);
if (session != null && sessionActions.size() > 0) {
sessionActions.add(new SecureAction(bean.getClazz(),
"RESTful",
IAction.MODE_UNDEFINED,
false,
"icons/point-green.png") {
@Override
public Object action() throws Exception {
return ENV.get("service.url", "") + ARESTDynamic.BASE_PATH;
}
});
sessionActions.add(new SecureAction(bean.getClazz(),
"RESTfulUI",
IAction.MODE_UNDEFINED,
false,
"icons/point-yellow.png") {
@Override
public Object action() throws Exception {
return ENV.get("service.url", "") + ARestUI.BASE_PATH;
}
});
}
}
return sessionActions;
}
@SuppressWarnings("serial")
@Override
public Collection getPageActions(ISession session) {
boolean firstTime = pageActions == null;
super.getPageActions(session);
if (firstTime && bean.isMultiValue()) {
pageActions.add(new SecureAction(bean.getClazz(),
"statistic",
IAction.MODE_UNDEFINED,
false,
"icons/barchart.png") {
@Override
public Object action() throws Exception {
BeanValue from = null, to = null;
if (bean instanceof BeanCollector) {
BeanCollector collector = (BeanCollector) bean;
from = (BeanValue) collector.getBeanFinder().getFilterRange().getAttribute("from");
to = (BeanValue) collector.getBeanFinder().getFilterRange().getAttribute("to");
}
Statistic s = new Statistic(bean.getDeclaringClass(), from.getValue(), to.getValue());
s.getPresentable().setIcon("icons/barchart.png");
s.saveDefinition();
return s;
}
@Override
public boolean isEnabled() {
return super.isEnabled() && bean.isPersistable() && !bean.getDeclaringClass().isArray();
}
});
pageActions
.add(new MethodAction(Replication.getReplicationMethod()) {
@Override
public Object action() throws Exception {
setParameter(new Replication(((BeanCollector)bean).getCurrentData()));
return super.action();
}
@Override
public String getLongDescription() {
return "replicates the selected beans";
}
@Override
public boolean isEnabled() {
return super.isEnabled() && bean.isPersistable() && !bean.getDeclaringClass().isArray();
}
});
}
return pageActions;
}
@Override
public void reset() {
AttributeCover.resetTypeCache();
ENV.get(Pool.class).reset();
super.reset();
//clear template cache
jsWebsocketTemplate = null;
tableDivStyle = null;
}
/**
* {@inheritDoc}
*/
@Override
public String build(ISession session,
BeanDefinition> model,
Object message,
boolean interactive,
BeanDefinition>... navigation) {
try {
Element form;
if (model != null) {
Html5Presentation pres = model != null ? model.getPresentationHelper() : this;
//TODO: don't forget the old presentation instance
if ((model != null && !(model.getPresentationHelper() instanceof Html5Presentation))) {
model.setPresentationHelper(createHelper(model));
}
form = pres.createPage(session, null,
message instanceof String && !(message.toString().contains(".") && NumberUtil.isNumber(message))
&& !isXml(message.toString())
? ENV.translate(message, true) : message,
interactive,
navigation);
} else if (message != null && isXml(message.toString())) {
return HtmlUtil.createMessagePage("information", message.toString());
} else {
form = createPage(session, null, "Leaving Application!
Restart", false, navigation);
}
//Some external extensions...
Plugins.process(IDOMDecorator.class).decorate(form.getOwnerDocument(), session);
String html = HtmlUtil.toString(form.getOwnerDocument());
if (LOG.isDebugEnabled()) {
FileUtil.writeBytes(html.getBytes(), ENV.getConfigPath() + "html-server-response.html", false);
}
return html;
} catch (Exception ex) {
return HtmlUtil.createMessagePage(ENV.translate("tsl2nano.error", true), message + "" +
ManagedException.toRuntimeEx(ex, true, true).getMessage());
}
}
Element createFormDocument(ISession session, String name, String image, boolean interactive) {
Message.send("building page " + name);
isAuthenticated = session.getUserAuthorization() != null;
Element body = createHeader(session, name, image, interactive);
Element glasspane = createGlasspane(body);
Element form = appendElement(glasspane,
TAG_FORM,
ATTR_ID,
"page.form",
ATTR_ACTION,
"?",
ATTR_METHOD,
ENV.get("html5.http.method", "post"),
enable("autocomplete", ENV.get("html5.form.autocomplete", true)), null);
if (session.getUserAuthorization() != null)
addAntiCSRFTokenToForm(session, form);
return form;
}
protected Element createGlasspane(Element body) {
/* Display it on the layer with index 1001. cover the whole screen
* Make sure this is the highest z-index value used by layers on that page
*/
return appendElement(body, TAG_DIV, ATTR_ID, "glasspane", ATTR_STYLE,
ENV.get("app.page.glasspane.style", "background: transparent;z-index:1001;"));
}
Element createHeader(ISession session, String title, String image, boolean interactive) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
/*
* try to read html-page from a template. if not existing, create header and
* body programatically.
* TODO: cache the page
*/
Document doc;
Element body = null;
File metaFrame = new File(ENV.getConfigPath() + "css/meta-frame.html");
boolean useCSS = metaFrame.canRead();
if (useCSS) {
try {
doc = factory.newDocumentBuilder().parse(metaFrame);
NodeList childs = doc.getFirstChild().getChildNodes();
for (int i = 0; i < childs.getLength(); i++) {
if (childs.item(i).getNodeName().equals(TAG_BODY)) {
body = (Element) childs.item(i);
break;
}
}
if (body == null) {
throw new IllegalStateException("error on loading file " + metaFrame.getAbsolutePath()
+ ": missing body tag!");
}
} catch (Exception e) {
LOG.error("error on loading file " + metaFrame.getAbsolutePath());
ManagedException.forward(e);
return null;
}
} else {
doc = factory.newDocumentBuilder().newDocument();
Element html = doc.createElement(TAG_HTML);
doc.appendChild(html);
body = createMetaAndBody(session, html, title, interactive);
}
return createHeaderElements(session, title, image, interactive, body, useCSS);
} catch (ParserConfigurationException e) {
ManagedException.forward(e);
return null;
}
}
private Element createHeaderElements(ISession session, String title, String image, boolean interactive, Element body,
boolean useCSS) {
/*
* create the header elements:
* c1: left: App-Info with image
* c2: Page-Buttons
* c3: center: Page-Title with image
*/
Element row = appendTag(createGrid(body, "page.header.table", "page.header.table", 0), TABLE(TAG_ROW));
Element c1 = appendTag(row, TABLE(TAG_CELL, "onclick", "toggleOnClick()", "content", "T"));
Element c2 = appendTag(row, TABLE(TAG_CELL));
Element c3 = appendTag(row, TABLE(TAG_CELL));
String localDoc = ENV.getConfigPath() + "nano.h5.html";
String docLink =
new File(localDoc).canRead() ? "./nano.h5.html" : "https://sourceforge.net/p/tsl2nano/wiki/";
c1 = appendElement(c1, TAG_LINK, ATTR_HREF, docLink);
appendElement(c1,
TAG_IMAGE,
content(),
ATTR_SRC,
"icons/beanex-logo-micro.jpg",
ATTR_TITLE,
"Framework Version: " + ENV.getBuildInformations());
if (image != null) {
c3 = appendElement(c3, TAG_H3, content(), ATTR_ALIGN, ALIGN_CENTER);
appendElement(c3,
TAG_IMAGE,
content(getTitleWithLink(title, session)),
ATTR_SRC,
image,
ATTR_CLASS,
"title",
ATTR_STYLE,
style("display", "inline"));
} else {
String docURL = "UNKNOWN";
if (bean != null) {
if ( ENV.class.isAssignableFrom(bean.getClazz()))
docURL = FileUtil.userDirFile("./").getAbsolutePath();
else
docURL = ENV.get("doc.url." + bean.getName().toLowerCase(),
"doc/" + StringUtil.toFirstLower(title) + "/index.html");
}
if (new File(ENV.getConfigPath() + docURL).canRead() || (!docURL.contains(" ") && NetUtil.isURL(docURL))) {
c3 = appendElement(c3, TAG_H3, ATTR_ALIGN, ALIGN_CENTER, ATTR_STYLE,
style("display", "inline"));
appendElement(c3, TAG_LINK, content(title), ATTR_HREF, ENV.getConfigPath() + docURL, ATTR_CLASS, "title");
} else {
c3 = appendElement(c3, TAG_H3, content(getTitleWithLink(title, session)), ATTR_ID, "title", ATTR_ALIGN, ALIGN_CENTER, ATTR_STYLE,
style("display", "inline"));
}
}
if (interactive && bean != null) {
//fallback: setting style from environment-properties
appendAttributes((Element) c2.getParentNode(), ATTR_STYLE,
ENV.get("layout.header.grid.style", /*"background-image: url(icons/spe.jpg);"*/"background: transparent;"));
c2 = appendElement(c2,
TAG_FORM,
ATTR_ACTION,
"?",
ATTR_METHOD,
ENV.get("html5.http.method", "post"), ATTR_STYLE,
style("display", "inline"));
if (session.getUserAuthorization() != null) {
addAntiCSRFTokenToForm(session, c2);
if (!useSideNav(99)) {
Element menu = createMenu(c2, "Menu");
createSubMenu(menu, ENV.translate("tsl2nano.application", true), "icons/equipment.png",
getApplicationActions(session));
createSubMenu(menu, ENV.translate("tsl2nano.session", true), "icons/home.png",
getSessionActions(session));
createSubMenu(menu, ENV.translate("tsl2nano.page", true), "icons/full_screen.png",
getPageActions(session));
} else {
Collection actions = new ArrayList(getPageActions(session));
actions.addAll(getApplicationActions(session));
actions.addAll(getSessionActions(session));
if (!useSideNav(1 + actions.size()))
;//c3 = createExpandable(c3, "Menu", ENV.get("layout.header.menu.open", false));
createActionPanel(c2, actions,
ENV.get("layout.header.button.text.show", true),
ATTR_ALIGN, ALIGN_RIGHT);
}
}
}
return body;
}
private void addAntiCSRFTokenToForm(ISession session, Element c2) {
if (WebSecurity.useAntiCSRFTokenInContent() && session instanceof NanoH5Session) {
String token = ((NanoH5Session)session).createAntiCSRFToken();
appendElement(c2, TAG_INPUT, ATTR_NAME, WebSecurity.CSRF_TOKEN, ATTR_ID, WebSecurity.CSRF_TOKEN, ATTR_VALUE, token, ATTR_HIDDEN);
}
}
private String getTitleWithLink(String title, ISession session) {
if (session.getUserAuthorization() == null || isXml(title)) {
return title;
} else {
String ddlName = StringUtil.substring(Persistence.current().getJarFile(), "/", ".jar", true);
String defaultUrl = "https://editor.ponyorm.com/user/pony/" + ddlName + "/designer";
String url = ENV.get("app.database.ddl.designer.url", defaultUrl);
return "" + title + "";
}
}
private boolean useSideNav(int actionCount) {
// use sideNav after login...
return isAuthenticated && ENV.get("layout.sidenav", false)
&& actionCount > ENV.get("layout.sidenav.min.count.action", 3);
}
private Element createMetaAndBody(ISession session, Element html, String title, boolean interactive) {
appendElement(html, TAG_STYLE,
content(
ENV.get("app.frame.style", CSS_BACKGROUND_FADING_KEYFRAMES + tableDivStyles())));
appendAttributes(html, "manifest", ENV.get("html5.manifest.file", "tsl2nano-appcache.mf"));
Element head = appendElement(html, TAG_HEAD, ATTR_TITLE, "Nano-H5 Application: " + title);
appendElement(head, "meta", "name", "author", "content", "tsl2.nano.h5 (by Thomas Schneider/2012-2020)");
appendElement(head, "meta", "name", "viewport", "content",
"width=device-width, height=device-height, initial-scale=1");
// appendElement(head, "link", "rel", "stylesheet", "href", "css/style.css");
appendElement(head, "meta", "charset", "UTF-8");
/*
* WebSocket integration
*/
if (interactive) {
createWebSocket(session, head, MSG_FOOTER);
}
/*
* The body
*/
Element body =
appendElement(html, TAG_BODY, ATTR_ID, bean != null ? bean.getPresentable().getLabel() : title);
if (interactive) {
String style =
ENV.get("app.page.style", STYLE_BACKGROUND_RADIAL_GRADIENT
+ STYLE_BACKGROUND_FADING_KEYFRAMES);
appendAttributes(body, /*"background", "icons/spe.jpg", */ATTR_STYLE,
style, "ononline", "showStatusMessage('ONLINE')", "onoffline", "showStatusMessage('-- OFFLINE --')");
}
return body;
}
/**
* createWebSocket
*
* @param session
*
* @param parent
*/
private void createWebSocket(ISession session, Element parent, String elementId) {
if (ENV.get("websocket.use", true)) {
if (jsWebsocketTemplate == null) {
InputStream jsStream = ENV.getResource("websocket.client.js.template");
jsWebsocketTemplate = String.valueOf(FileUtil.getFileData(jsStream, "UTF-8"));
ENV.get("websocket.window.alert.message", true);
ENV.get("websocket.speak.alert.message", true);
ENV.get("app.login.secure", false);
}
Element script = appendElement(parent, TAG_SCRIPT, ATTR_TYPE, ATTR_TYPE_JS, "nonce", (ENV.isTestMode() ? "1" : session.getRequestId()));
TreeMap p = new TreeMap<>();
//on reset, before re-loading ENV, it may be null
if (ENV.isAvailable())
p.putAll(ENV.getProperties());
URL url = NanoH5.getServiceURL(null);
p.put("websocket.server.ip", url.getHost());
p.put("websocket.server.port", session.getWebsocketPort());
p.put("websocket.element.id", elementId);
String jsWebSocket = StringUtil.insertProperties(jsWebsocketTemplate, p);
if (ENV.get("app.mode.strict", false))
if (jsWebSocket.matches("^(//).*\\$\\{\\}"))
throw new IllegalStateException("websocket.client.js.template has unfilled variables of type ${...}");
script.appendChild(script.getOwnerDocument().createTextNode(
jsWebSocket));
}
}
@Override
public String buildDialog(Object title, Object model) {
if (model instanceof String) {
String t = (String) model;
if (t.contains("/>"))
model = "" + model + "
";
}
return embedToHtml(WSDialog.createHtmlFromBean(String.valueOf(title), model));
}
private String embedToHtml(String htmlContent) {
if (htmlContent.contains("