/**
* Copyright (C) 2009 BonitaSoft S.A.
* BonitaSoft, 31 rue Gustave Eiffel - 38000 Grenoble
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2.0 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.bonitasoft.forms.client.view.common;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.bonitasoft.forms.client.model.HeadNode;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.RootPanel;
/**
* Utility Class form DOM manipulation
*
* @author Anthony Birembaut
*/
public class DOMUtils {
/**
* ID of the element containing the static html part of the entry point
*/
public static final String STATIC_CONTENT_ELEMENT_ID = "static_application";
/**
* Mandatory frame id for the forms application when the form is in a frame
*/
public static final String DEFAULT_FORM_ELEMENT_ID = "bonita_form";
/**
* Id of the element of the page template in witch the title has to be injected
*/
public static final String PAGE_LABEL_ELEMENT_ID = "bonita_form_page_label";
/**
* Id of the element of the form frame
*/
public static final String FORM_FRAME_ID = "bonitaframe";
/**
* action type for postMessage to notify parent frame
*/
public static final String START_PROCESS_ACTION_FOR_NOTIF = "Start process";
/**
* action type for postMessage to notify parent frame
*/
public static final String SUBMIT_TASK_ACTION_FOR_NOTIF = "Submit task";
/**
* data form success value for parent frame notification
*/
public static final String FORM_SUBMITTED_MESSAGE = "{\"version\":\"6.x\"}";
/**
* Instance attribute
*/
protected static DOMUtils INSTANCE = null;
private final String CSS_ID_PRE = "CSS_";
/**
* @return the view controller instance
*/
public static DOMUtils getInstance() {
if (INSTANCE == null) {
INSTANCE = new DOMUtils();
}
return INSTANCE;
}
/**
* Private contructor to prevent instantiation
*/
protected DOMUtils() {
super();
}
/**
* Deals with the whole page template injection
*
* @param headNodes
* @param pageHTMLPanel
* @param bodyAttributes
* @param list
* @param elementId
* @return the onload attribute value if it exists, null otherwise
*/
public String insertPageTemplate(final List<HeadNode> headNodes, final HTMLPanel pageHTMLPanel, final Map<String, String> bodyAttributes,
final HTMLPanel processHTMLPanel, final String elementId) {
addHeaders(headNodes);
addPageContent(pageHTMLPanel, processHTMLPanel, elementId);
final String onloadValue = addBodyAttributes(bodyAttributes);
if (isPageInFrame()) {
setBodyTransparency();
}
return onloadValue;
}
/**
* set the body of the page transparency
*/
protected void setBodyTransparency() {
final String existingClassName = RootPanel.getBodyElement().getClassName();
if (existingClassName != null && existingClassName.length() > 0) {
RootPanel.getBodyElement().setClassName("bonita_transparent_body " + existingClassName);
} else {
RootPanel.getBodyElement().setClassName("bonita_transparent_body");
}
}
protected Element getHeadElement() {
Element headElement = null;
final Element documentElement = DOM.getParent(RootPanel.getBodyElement());
final int documentChildrenCount = DOM.getChildCount(documentElement);
for (int i = 0; i < documentChildrenCount; i++) {
final Element documentChildElement = DOM.getChild(documentElement, i);
if ("head".equalsIgnoreCase(documentChildElement.getNodeName())) {
headElement = documentChildElement;
break;
}
}
return headElement;
}
/**
* @param headNodes
*/
public void addHeaders(final List<HeadNode> headNodes) {
final Element headElement = getHeadElement();
if (headElement != null) {
for (final HeadNode headNode : headNodes) {
if (isHeadNodeUseful(headNode)) {
if (!isContainHeadNode(headElement, headNode)) {
// removeOldHeaderElementIfPresent(headElement, headNode);
if (headNode.getTagName() == null) {
//for comment nodes
final Node textNode = Document.get().createTextNode(headNode.getInnerHtml());
headElement.appendChild(textNode);
} else {
//for other nodes
final Element headChildElement = DOM.createElement(headNode.getTagName());
for (final Entry<String, String> attributeEntry : headNode.getAttributes().entrySet()) {
headChildElement.setAttribute(attributeEntry.getKey(), attributeEntry.getValue());
}
if (headNode.getInnerHtml() != null && headNode.getInnerHtml().length() > 0) {
headChildElement.setInnerText(headNode.getInnerHtml());
}
headElement.appendChild(headChildElement);
}
}
if ("title".equalsIgnoreCase(headNode.getTagName())) {
Window.setTitle(headNode.getInnerHtml());
}
}
}
}
}
private boolean isContainHeadNode(final Element headElement, final HeadNode headNode) {
boolean isContained = false;
final Map<String, String> attributeMap = headNode.getAttributes();
final String headInnerHtml = headNode.getInnerHtml();
final int childrenCount = DOM.getChildCount(headElement);
for (int i = 0; i < childrenCount; i++) {
final Element childElement = DOM.getChild(headElement, i);
boolean isSameNode = true;
int theIndex = 0;
if (headNode.getTagName() != null && headNode.getTagName().equalsIgnoreCase(childElement.getTagName())) {
final String childElementValue = childElement.getInnerHTML();
boolean innerHtml = false;
if (headInnerHtml == null && childElementValue == null) {
innerHtml = true;
} else if (headInnerHtml != null && childElementValue != null) {
if (headInnerHtml.equals(childElementValue)) {
innerHtml = true;
}
}
if (innerHtml) {
for (final Entry<String, String> attributeEntry : attributeMap.entrySet()) {
if (!isSameNode) {
break;
}
final String attributeKey = attributeEntry.getKey();
final String attribeteValue = attributeEntry.getValue();
if (childElement.hasAttribute(attributeKey)) {
final String childElementAttributeValue = childElement.getAttribute(attributeKey);
isSameNode = childElementAttributeValue.equals(attribeteValue);
if (isSameNode) {
theIndex++;
}
}
}
}
if (theIndex == attributeMap.size()) {
isContained = true;
break;
}
}
}
return isContained;
}
/**
* Deals with the whole application template injection
*
* @param headNodes
* @param applicationHTMLPanel
* @param bodyAttributes
* @return the onload attribute value if it exists, null otherwise
*/
public String insertApplicationTemplate(final List<HeadNode> headNodes, final HTMLPanel applicationHTMLPanel, final Map<String, String> bodyAttributes) {
addHeaders(headNodes);
addApplicationContent(applicationHTMLPanel, STATIC_CONTENT_ELEMENT_ID);
cleanBodyAttributes();
return addBodyAttributes(bodyAttributes);
}
/**
* Add a stylesheet link is the link does not still exist
*/
public void addStyleSheetLink(final String cssFileName) {
final String theme = cssFileName.substring(0, cssFileName.length() - 4); // remove ".css"
final List<HeadNode> cssLinks = new ArrayList<HeadNode>();
if (!isStylesheetExist(theme)) {
final HeadNode cssLink = new HeadNode();
cssLink.setTagName("link");
final Map<String, String> attributes = new HashMap<String, String>();
attributes.put("type", "text/css");
attributes.put("rel", "stylesheet");
attributes.put("href", "consoleResource?location=" + cssFileName);
attributes.put("title", theme);
attributes.put("id", theme);
cssLink.setAttributes(attributes);
cssLinks.add(cssLink);
DOMUtils.getInstance().addHeaders(cssLinks);
}
}
/**
* Add a theme stylesheet link is the link does not still exist
*/
@Deprecated
public void addThemeStyleSheetLink(final String themeName, final String[] cssNames, final String[] cssFileNames) {
final List<HeadNode> cssLinks = new ArrayList<HeadNode>();
for (int i = 0; i < cssNames.length; i++) {
if (!isStylesheetExist(CSS_ID_PRE + cssNames[i])) {
final HeadNode cssLink = new HeadNode();
cssLink.setTagName("link");
final Map<String, String> attributes = new HashMap<String, String>();
attributes.put("type", "text/css");
attributes.put("rel", "stylesheet");
attributes.put("href", "themeResource?location=" + cssFileNames[i] + "&theme=" + themeName);
attributes.put("title", cssNames[i]);
attributes.put("id", CSS_ID_PRE + cssNames[i]);
cssLink.setAttributes(attributes);
cssLinks.add(cssLink);
DOMUtils.getInstance().addHeaders(cssLinks);
}
}
}
/**
* Find if a link of a stylesheet is in the header
*/
protected boolean isStylesheetExist(final String cssName) {
boolean result = false;
final Element cssElement = DOM.getElementById(cssName);
if (cssElement != null) {
result = true;
}
return result;
}
/**
* clean the body attributes
*/
protected void cleanBodyAttributes() {
final Element bodyElement = RootPanel.getBodyElement();
bodyElement.removeAttribute("class");
bodyElement.removeAttribute("style");
bodyElement.removeAttribute("onload");
}
/**
* Replace the content of the body by the template body
*
* @param applicationHTMLPanel
* @param elementId
*/
protected void addApplicationContent(final HTMLPanel applicationHTMLPanel, final String elementId) {
RootPanel container = RootPanel.get(elementId);
if (container == null) {
container = RootPanel.get();
}
container.clear();
container.add(applicationHTMLPanel);
addScriptElementToDOM(applicationHTMLPanel.getElement(), container.getElement());
}
/**
* Replace the content of the body by the template body
*
* @param pageHTMLPanel
* @param processHTMLPanel
* @param elementId
*/
protected void addPageContent(final HTMLPanel pageHTMLPanel, final HTMLPanel processHTMLPanel, final String elementId) {
if (processHTMLPanel != null) {
processHTMLPanel.add(pageHTMLPanel, elementId);
addScriptElementToDOM(pageHTMLPanel.getElement(), processHTMLPanel.getElement());
} else {
final RootPanel container = RootPanel.get(STATIC_CONTENT_ELEMENT_ID);
container.clear();
container.add(pageHTMLPanel);
addScriptElementToDOM(pageHTMLPanel.getElement(), container.getElement());
}
}
/**
* Add the body attributes
*
* @param bodyAttributes
* @return the onload attribute value if it exists, null otherwise
*/
protected String addBodyAttributes(final Map<String, String> bodyAttributes) {
String onloadValue = null;
if (bodyAttributes != null && !bodyAttributes.isEmpty()) {
final Element bodyElement = RootPanel.getBodyElement();
for (final Entry<String, String> bodyAttribute : bodyAttributes.entrySet()) {
if ("class".equalsIgnoreCase(bodyAttribute.getKey())) {
bodyElement.setClassName(bodyAttribute.getValue());
} else {
if ("onload".equalsIgnoreCase(bodyAttribute.getKey())) {
onloadValue = bodyAttribute.getValue();
}
// fix for IE7 and IE8 failing to evaluate scripts in inserted HTML pages headers
if ("ieonload".equalsIgnoreCase(bodyAttribute.getKey()) && (isIE7() || isIE8())) {
onloadValue = onloadValue == null ? bodyAttribute.getValue() : onloadValue + bodyAttribute.getValue();
} else {
bodyElement.setAttribute(bodyAttribute.getKey(), bodyAttribute.getValue());
}
}
}
}
return onloadValue;
}
/**
* @return true if the current web browser is ie7
*/
public boolean isIE7() {
return getUserAgent().indexOf("MSIE 7.") != -1;
}
/**
* @return true if the current web browser is ie8
*/
public boolean isIE8() {
return getUserAgent().indexOf("MSIE 8.") != -1;
}
/**
* @return the user Agent string in lower case
*/
private String getUserAgent() {
return Window.Navigator.getUserAgent();
}
/**
* @param headNode
* @return true if the head node present in the template is useful
*/
protected boolean isHeadNodeUseful(final HeadNode headNode) {
if (headNode.getTagName() != null && headNode.getTagName().equalsIgnoreCase("meta")) {
final Map<String, String> attributes = headNode.getAttributes();
if (attributes.get("http-equiv") != null && attributes.get("http-equiv").equalsIgnoreCase("content-type")) {
return false;
}
}
return true;
}
/**
* Remove an element of the entry point header if it's present in the template (only for certain elements)
*
* @param parentElement
* @param headNode
*/
protected void removeOldHeaderElementIfPresent(final Element parentElement, final HeadNode headNode) {
final int headChildrenCount = DOM.getChildCount(parentElement);
if (headNode.getTagName() != null && headNode.getTagName().equalsIgnoreCase("title")) {
for (int i = 0; i < headChildrenCount; i++) {
final Element headChildElement = DOM.getChild(parentElement, i);
if (headNode.getTagName().equalsIgnoreCase(headChildElement.getTagName())) {
DOM.removeChild(parentElement, headChildElement);
break;
}
}
}
}
/**
* insert a content inside an element
*
* @param htmlPanel
* @param pageLabelElementId
* @param pageLabel
*/
public void insertInElement(final HTMLPanel htmlPanel, final String elementId, final String content) {
insertInElement(htmlPanel, elementId, content, false);
}
/**
* insert a content inside an element
*
* @param htmlPanel
* @param pageLabelElementId
* @param pageLabel
* @param allowHTML
*/
public void insertInElement(final HTMLPanel htmlPanel, final String elementId, final String content, final boolean preventHTML) {
if (htmlPanel != null) {
final HTML htmlContent = new HTML();
if (preventHTML) {
htmlContent.setText(content);
} else {
htmlContent.setHTML(content);
}
htmlPanel.add(htmlContent, elementId);
} else {
final Element element = DOM.getElementById(elementId);
if (element != null) {
if (preventHTML) {
element.setInnerText(content);
} else {
element.setInnerHTML(content);
}
}
}
}
/**
* Remove the required element from the body and remove its css classes
*
* @param elementToRemoveId
*/
public void cleanBody(final String elementToRemoveId) {
final Element elementToRemove = DOM.getElementById(elementToRemoveId);
if (elementToRemove != null) {
DOM.removeChild(RootPanel.getBodyElement(), elementToRemove);
}
RootPanel.getBodyElement().setClassName("");
}
/**
* Indicates whether the page is in a frame or not this method is meant to be called in the form frame (not in the
* application window)
*
* @return true if the page is in a frame
*/
native public boolean isPageInFrame()
/*-{
var applicationWindow = window.parent;
if (applicationWindow == window.top) {
return false;
} else {
return true;
}
}-*/;
/**
* Resolve the action attribute of the postMessage to send to notify the parent frame
*
* @param urlContext
* @return
*/
public String getActionForNotif(final Map<String, Object> urlContext) {
String actionForNotif;
if (urlContext.containsKey(URLUtils.TASK_ID_PARAM)) {
actionForNotif = DOMUtils.SUBMIT_TASK_ACTION_FOR_NOTIF;
} else if (urlContext.containsKey(URLUtils.PROCESS_ID_PARAM)) {
actionForNotif = DOMUtils.START_PROCESS_ACTION_FOR_NOTIF;
} else {
actionForNotif = "";
}
return actionForNotif;
}
/**
* Indicates to the parent frame that a form was submitted
*
* @param action
*/
public void notifyParentFrameSuccess(final String action) {
notifyParentFrame(FORM_SUBMITTED_MESSAGE, action, false);
}
/**
* Indicates to the parent frame that a form was submitted (and if the response was success or error)
*
* @param error error message
* @param action
*/
public void notifyParentFrameError(final String error, final String action) {
notifyParentFrame("\"" + error + "\"", action, true);
}
/**
* Indicates to the parent frame that a form was submitted (and if the response was success or error)
*
* @param data
* @param action
* @param isError
*/
native public void notifyParentFrame(String data, String action, boolean isError)
/*-{
var dataToSend;
if(isError) {
dataToSend = '{"message":"error","action":"' + action + '","dataFromError":' + data + '}';
} else {
dataToSend = '{"message":"success","action":"' + action + '","dataFromSuccess":' + data + '}';
}
$wnd.parent.postMessage(dataToSend, '*');
}-*/;
/**
* Override the web browser native inputs if the js to do so is present
*
* @return true if the Javascript was applied
*/
native public boolean overrideBrowserNativeInputs()
/*-{
try {
$wnd.overrideBrowserNativeInputs();
return true;
} catch(e) {
//do nothing (the javascript to override the browser native inputs isn't in the header)
return false;
}
}-*/;
/**
* Override a checkbox or radiobuton behavior after it is checked or unchecked
*
* @param checbox
* @param checked
*/
public void overrideNativeInputAfterUpdate(final CheckBox checbox, final boolean checked) {
final NodeList<com.google.gwt.dom.client.Element> labelNodes = checbox.getElement().getElementsByTagName("label");
if (labelNodes.getLength() > 0) {
if (checked) {
labelNodes.getItem(0).addClassName("checked");
} else {
labelNodes.getItem(0).removeClassName("checked");
}
}
}
/**
* perform a Javascript evaluation
*
* @param jsSourceCode
* the source code to execute
*/
native public void javascriptEval(String jsSourceCode)
/*-{
$wnd.eval(jsSourceCode);
}-*/;
/**
* To make script in scriptElements work , need to add script elements in the currentElement to parentElement
*
* @param currentElement
* @param parentElement
*/
public void addScriptElementToDOM(final Element currentElement, final Element parentElement) {
final List<Element> list = new ArrayList<Element>();
final NodeList<com.google.gwt.dom.client.Element> scripts = currentElement.getElementsByTagName("script");
for (int i = 0; i < scripts.getLength(); i++) {
list.add((Element) scripts.getItem(i));
}
for (int i = 0; i < list.size(); i++) {
final Element e = list.get(i);
e.removeFromParent();
final Element scriptElement = DOM.createElement("script");
final String type = e.getAttribute("type");
if (!isEmpty(type)) {
scriptElement.setAttribute("type", type);
}
final String language = e.getAttribute("language");
if (!isEmpty(language)) {
scriptElement.setAttribute("language", language);
}
final String src = e.getAttribute("src");
if (!isEmpty(src)) {
scriptElement.setAttribute("src", src);
}
scriptElement.setInnerText(e.getInnerText());
parentElement.appendChild(scriptElement);
}
}
private boolean isEmpty(final String str) {
return str == null || str.trim().length() == 0 || str.equals("null");
}
public boolean isInternetExplorer() {
return "Microsoft Internet Explorer".equals(Window.Navigator.getAppName());
}
/**
* Hide loading
*/
public void hideLoading() {
final Element loadingElement = DOM.getElementById("loading");
if (loadingElement != null) {
loadingElement.getStyle().setProperty("display", "none");
}
}
/**
* Display a message to let the user know that the data are not yet
* available.
*/
public void displayLoading() {
// Show the loading message and display the GUI.
Element theElement;
theElement = DOM.getElementById("loading");
if (theElement != null) {
theElement.getStyle().setProperty("display", "table");
}
}
}