Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
de.tsl2.nano.h5.Html5Presentation Maven / Gradle / Ivy
/*
* 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.Locale;
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) {
BeanPresentationHelper 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));
pres = model.getPresentationHelper();
}
form = ((Html5Presentation)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", "grid"));
} 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", "flex"));
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", "flex"));
}
}
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) {
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, TAG_STYLE,
content(
ENV.get("app.frame.style", CSS_BACKGROUND_FADING_KEYFRAMES + templateStyles())));
appendElement(head, "meta", "name", "author", "content", "tsl2.nano.h5 (by Thomas Schneider/2012-2025)");
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("