/**
* Copyright (C) 2010-2014 Morgner UG (haftungsbeschränkt)
*
* This file is part of Structr <http://structr.org>.
*
* Structr is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Structr 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 Affero General Public License
* along with Structr. If not, see <http://www.gnu.org/licenses/>.
*/
package org.structr.web.entity.dom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.collections.map.LRUMap;
import org.apache.commons.lang3.StringUtils;
import org.neo4j.helpers.collection.Iterables;
import org.structr.common.CaseHelper;
import org.structr.common.PropertyView;
import org.structr.common.SecurityContext;
import org.structr.common.Syncable;
import org.structr.common.error.ErrorBuffer;
import org.structr.common.error.FrameworkException;
import org.structr.core.GraphObject;
import org.structr.core.Result;
import org.structr.core.app.App;
import org.structr.core.app.StructrApp;
import org.structr.core.entity.AbstractRelationship;
import org.structr.core.property.BooleanProperty;
import org.structr.core.property.GenericProperty;
import org.structr.core.property.Property;
import org.structr.core.property.PropertyKey;
import org.structr.core.property.PropertyMap;
import org.structr.core.property.StringProperty;
import org.structr.web.common.AsyncBuffer;
import org.structr.web.common.HtmlProperty;
import org.structr.web.common.RenderContext;
import org.structr.web.common.RenderContext.EditMode;
import org.structr.web.entity.dom.relationship.DOMChildren;
import org.structr.web.entity.html.Body;
import org.structr.web.entity.relation.PageLink;
import org.structr.web.entity.relation.Sync;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.TypeInfo;
//~--- classes ----------------------------------------------------------------
/**
*
* @author Axel Morgner
* @author Christian Morgner
*/
public class DOMElement extends DOMNode implements Element, NamedNodeMap {
private static final Logger logger = Logger.getLogger(DOMElement.class.getName());
private static final int HtmlPrefixLength = PropertyView.Html.length();
public static final long RENDER_TIMEOUT = TimeUnit.SECONDS.toMillis(30);
private static final String STRUCTR_ACTION_PROPERTY = "data-structr-action";
private static final Map<String, HtmlProperty> htmlProperties = new LRUMap(1000); // use LURMap here to avoid infinite growing
private static final String lowercaseBodyName = Body.class.getSimpleName().toLowerCase();
public static final Property<String> tag = new StringProperty("tag").indexed();
public static final Property<String> path = new StringProperty("path").indexed();
public static final Property<String> partialUpdateKey = new StringProperty("partialUpdateKey").indexed();
// Event-handler attributes
public static final Property<String> _onabort = new HtmlProperty("onabort");
public static final Property<String> _onblur = new HtmlProperty("onblur");
public static final Property<String> _oncanplay = new HtmlProperty("oncanplay");
public static final Property<String> _oncanplaythrough = new HtmlProperty("oncanplaythrough");
public static final Property<String> _onchange = new HtmlProperty("onchange");
public static final Property<String> _onclick = new HtmlProperty("onclick");
public static final Property<String> _oncontextmenu = new HtmlProperty("oncontextmenu");
public static final Property<String> _ondblclick = new HtmlProperty("ondblclick");
public static final Property<String> _ondrag = new HtmlProperty("ondrag");
public static final Property<String> _ondragend = new HtmlProperty("ondragend");
public static final Property<String> _ondragenter = new HtmlProperty("ondragenter");
public static final Property<String> _ondragleave = new HtmlProperty("ondragleave");
public static final Property<String> _ondragover = new HtmlProperty("ondragover");
public static final Property<String> _ondragstart = new HtmlProperty("ondragstart");
public static final Property<String> _ondrop = new HtmlProperty("ondrop");
public static final Property<String> _ondurationchange = new HtmlProperty("ondurationchange");
public static final Property<String> _onemptied = new HtmlProperty("onemptied");
public static final Property<String> _onended = new HtmlProperty("onended");
public static final Property<String> _onerror = new HtmlProperty("onerror");
public static final Property<String> _onfocus = new HtmlProperty("onfocus");
public static final Property<String> _oninput = new HtmlProperty("oninput");
public static final Property<String> _oninvalid = new HtmlProperty("oninvalid");
public static final Property<String> _onkeydown = new HtmlProperty("onkeydown");
public static final Property<String> _onkeypress = new HtmlProperty("onkeypress");
public static final Property<String> _onkeyup = new HtmlProperty("onkeyup");
public static final Property<String> _onload = new HtmlProperty("onload");
public static final Property<String> _onloadeddata = new HtmlProperty("onloadeddata");
public static final Property<String> _onloadedmetadata = new HtmlProperty("onloadedmetadata");
public static final Property<String> _onloadstart = new HtmlProperty("onloadstart");
public static final Property<String> _onmousedown = new HtmlProperty("onmousedown");
public static final Property<String> _onmousemove = new HtmlProperty("onmousemove");
public static final Property<String> _onmouseout = new HtmlProperty("onmouseout");
public static final Property<String> _onmouseover = new HtmlProperty("onmouseover");
public static final Property<String> _onmouseup = new HtmlProperty("onmouseup");
public static final Property<String> _onmousewheel = new HtmlProperty("onmousewheel");
public static final Property<String> _onpause = new HtmlProperty("onpause");
public static final Property<String> _onplay = new HtmlProperty("onplay");
public static final Property<String> _onplaying = new HtmlProperty("onplaying");
public static final Property<String> _onprogress = new HtmlProperty("onprogress");
public static final Property<String> _onratechange = new HtmlProperty("onratechange");
public static final Property<String> _onreadystatechange = new HtmlProperty("onreadystatechange");
public static final Property<String> _onreset = new HtmlProperty("onreset");
public static final Property<String> _onscroll = new HtmlProperty("onscroll");
public static final Property<String> _onseeked = new HtmlProperty("onseeked");
public static final Property<String> _onseeking = new HtmlProperty("onseeking");
public static final Property<String> _onselect = new HtmlProperty("onselect");
public static final Property<String> _onshow = new HtmlProperty("onshow");
public static final Property<String> _onstalled = new HtmlProperty("onstalled");
public static final Property<String> _onsubmit = new HtmlProperty("onsubmit");
public static final Property<String> _onsuspend = new HtmlProperty("onsuspend");
public static final Property<String> _ontimeupdate = new HtmlProperty("ontimeupdate");
public static final Property<String> _onvolumechange = new HtmlProperty("onvolumechange");
public static final Property<String> _onwaiting = new HtmlProperty("onwaiting");
// needed for Importer
public static final Property<String> _data = new HtmlProperty("data").indexed();
// Edit-mode attributes
public static final Property<Boolean> _reload = new BooleanProperty("data-structr-reload");
public static final Property<Boolean> _confirm = new BooleanProperty("data-structr-confirm");
public static final Property<String> _action = new StringProperty("data-structr-action");
public static final Property<String> _attributes = new StringProperty("data-structr-attributes");
public static final Property<String> _attr = new StringProperty("data-structr-attr");
public static final Property<String> _fieldName = new StringProperty("data-structr-name");
public static final Property<String> _hide = new StringProperty("data-structr-hide");
public static final Property<String> _rawValue = new StringProperty("data-structr-raw-value");
// Core attributes
public static final Property<String> _accesskey = new HtmlProperty("accesskey").indexed();
public static final Property<String> _class = new HtmlProperty("class").indexed();
public static final Property<String> _contenteditable = new HtmlProperty("contenteditable");
public static final Property<String> _contextmenu = new HtmlProperty("contextmenu");
public static final Property<String> _dir = new HtmlProperty("dir");
public static final Property<String> _draggable = new HtmlProperty("draggable");
public static final Property<String> _dropzone = new HtmlProperty("dropzone");
public static final Property<String> _hidden = new HtmlProperty("hidden");
public static final Property<String> _id = new HtmlProperty("id");
public static final Property<String> _lang = new HtmlProperty("lang");
public static final Property<String> _spellcheck = new HtmlProperty("spellcheck");
public static final Property<String> _style = new HtmlProperty("style");
public static final Property<String> _tabindex = new HtmlProperty("tabindex");
public static final Property<String> _title = new HtmlProperty("title").indexed();
public static final Property<String> _translate = new HtmlProperty("translate");
// The role attribute, see http://www.w3.org/TR/role-attribute/
public static final Property<String> _role = new HtmlProperty("role");
public static final org.structr.common.View publicView = new org.structr.common.View(DOMElement.class, PropertyView.Public,
name, tag, pageId, path, parent, children, restQuery, cypherQuery, xpathQuery, functionQuery, partialUpdateKey, dataKey, syncedNodes, sharedComponent
);
public static final org.structr.common.View uiView = new org.structr.common.View(DOMElement.class, PropertyView.Ui, name, tag, pageId, path, parent, children, childrenIds, owner,
restQuery, cypherQuery, xpathQuery, functionQuery, partialUpdateKey, dataKey, syncedNodes, sharedComponent,
renderDetails, hideOnIndex, hideOnDetail, showForLocales, hideForLocales, showConditions, hideConditions,
_accesskey, _class, _contenteditable, _contextmenu, _dir, _draggable, _dropzone, _hidden, _id, _lang, _spellcheck, _style,
_tabindex, _title, _translate, _onabort, _onblur, _oncanplay, _oncanplaythrough, _onchange, _onclick, _oncontextmenu, _ondblclick,
_ondrag, _ondragend, _ondragenter, _ondragleave, _ondragover, _ondragstart, _ondrop, _ondurationchange, _onemptied,
_onended, _onerror, _onfocus, _oninput, _oninvalid, _onkeydown, _onkeypress, _onkeyup, _onload, _onloadeddata,
_onloadedmetadata, _onloadstart, _onmousedown, _onmousemove, _onmouseout, _onmouseover, _onmouseup, _onmousewheel,
_onpause, _onplay, _onplaying, _onprogress, _onratechange, _onreadystatechange, _onreset, _onscroll, _onseeked,
_onseeking, _onselect, _onshow, _onstalled, _onsubmit, _onsuspend, _ontimeupdate, _onvolumechange, _onwaiting,
_reload, _confirm, _action, _attributes, _attr, _fieldName, _hide, _rawValue, _role
);
public static final org.structr.common.View htmlView = new org.structr.common.View(DOMElement.class, PropertyView.Html, _accesskey, _class, _contenteditable, _contextmenu, _dir,
_draggable, _dropzone, _hidden, _id, _lang, _spellcheck, _style, _tabindex, _title, _translate, _onabort, _onblur, _oncanplay,
_oncanplaythrough, _onchange, _onclick, _oncontextmenu, _ondblclick, _ondrag, _ondragend, _ondragenter, _ondragleave,
_ondragover, _ondragstart, _ondrop, _ondurationchange, _onemptied, _onended, _onerror, _onfocus, _oninput, _oninvalid,
_onkeydown, _onkeypress, _onkeyup, _onload, _onloadeddata, _onloadedmetadata, _onloadstart, _onmousedown, _onmousemove,
_onmouseout, _onmouseover, _onmouseup, _onmousewheel, _onpause, _onplay, _onplaying, _onprogress, _onratechange,
_onreadystatechange, _onreset, _onscroll, _onseeked, _onseeking, _onselect, _onshow, _onstalled, _onsubmit, _onsuspend,
_ontimeupdate, _onvolumechange, _onwaiting, _role
);
@Override
public boolean contentEquals(DOMNode otherNode) {
// two elements can not have the same content
return false;
}
@Override
public Node cloneNode(final boolean deep) {
if (deep) {
throw new UnsupportedOperationException("cloneNode with deep=true is not supported yet.");
} else {
Node node = super.cloneNode(deep);
for (Iterator<PropertyKey> it = getPropertyKeys(htmlView.name()).iterator(); it.hasNext();) {
PropertyKey key = it.next();
// omit system properties (except type), parent/children and page relationships
if (key.equals(GraphObject.type) || (!key.isUnvalidated()
&& !key.equals(GraphObject.id)
&& !key.equals(DOMNode.ownerDocument) && !key.equals(DOMNode.pageId)
&& !key.equals(DOMNode.parent) && !key.equals(DOMNode.parentId)
&& !key.equals(DOMElement.syncedNodes)
&& !key.equals(DOMNode.children) && !key.equals(DOMNode.childrenIds))) {
try {
((DOMNode) node).setProperty(key, getProperty(key));
} catch (FrameworkException ex) {
logger.log(Level.WARNING, "Could not set property " + key + " while cloning DOMElement " + this, ex);
}
}
}
return node;
}
}
@Override
public void updateFromNode(final DOMNode newNode) throws FrameworkException {
if (newNode instanceof DOMElement) {
final PropertyMap properties = new PropertyMap();
for (final Property key : htmlView.properties()) {
properties.put(key, newNode.getProperty(key));
}
// copy tag
properties.put(DOMElement.tag, newNode.getProperty(DOMElement.tag));
updateFromPropertyMap(properties);
}
}
@Override
public void updateFromPropertyMap(final PropertyMap properties) throws FrameworkException {
for (final Entry<PropertyKey, Object> entry : properties.entrySet()) {
final PropertyKey key = entry.getKey();
final Object value1 = this.getProperty(key);
final Object value2 = entry.getValue();
if (value1 == null && value2 == null) {
continue;
}
// copy attributes
this.setProperty(key, properties.get(key));
}
final String tag = properties.get(DOMElement.tag);
if (tag != null) {
// overwrite tag with value from source node
this.setProperty(DOMElement.tag, tag);
}
}
public Property[] getHtmlAttributes() {
return htmlView.properties();
}
public void openingTag(final SecurityContext securityContext, final AsyncBuffer out, final String tag, final EditMode editMode, final RenderContext renderContext, final int depth) throws FrameworkException {
out.append("<").append(tag);
for (PropertyKey attribute : StructrApp.getConfiguration().getPropertySet(entityType, PropertyView.Html)) {
String value = getPropertyWithVariableReplacement(securityContext, renderContext, attribute);
if (!(EditMode.RAW.equals(editMode) || EditMode.WIDGET.equals(editMode))) {
value = escapeForHtmlAttributes(value);
}
if (value != null) {
String key = attribute.jsonName().substring(PropertyView.Html.length());
out.append(" ").append(key).append("=\"").append(value).append("\"");
}
}
// include arbitrary data-* attributes
renderCustomAttributes(out, securityContext, renderContext);
// include special mode attributes
switch (editMode) {
case CONTENT:
if (depth == 0) {
String pageId = renderContext.getPageId();
if (pageId != null) {
out.append(" data-structr-page=\"").append(pageId).append("\"");
}
}
out.append(" data-structr-id=\"").append(getUuid()).append("\"");
break;
case RAW:
out.append(" data-hash=\"").append(getIdHash()).append("\"");
break;
}
out.append(">");
}
/**
* Render (inner) content.
*
* @param securityContext
* @param renderContext
* @param depth
* @throws FrameworkException
*/
@Override
public void renderContent(final SecurityContext securityContext, final RenderContext renderContext, final int depth) throws FrameworkException {
if (renderContext.hasTimeout(RENDER_TIMEOUT)) {
return;
}
if (isDeleted() || isHidden() || !displayForLocale(renderContext) || !displayForConditions(securityContext, renderContext)) {
return;
}
// final variables
final AsyncBuffer out = renderContext.getBuffer();
final EditMode editMode = renderContext.getEditMode(securityContext.getUser(false));
final boolean isVoid = isVoidElement();
final String _tag = getProperty(DOMElement.tag);
// non-final variables
Result localResult = renderContext.getResult();
boolean anyChildNodeCreatesNewLine = false;
renderStructrAppLib(out, securityContext, renderContext, depth);
if (depth > 0 && !avoidWhitespace()) {
out.append(indent(depth));
}
if (StringUtils.isNotBlank(_tag)) {
openingTag(securityContext, out, _tag, editMode, renderContext, depth);
try {
// in body?
if (lowercaseBodyName.equals(this.getTagName())) {
renderContext.setInBody(true);
}
// fetch children
List<DOMChildren> rels = getChildRelationships();
if (rels.isEmpty()) {
migrateSyncRels();
// No child relationships, maybe this node is in sync with another node
//Sync syncRel = getIncomingRelationship(Sync.class);
DOMElement _syncedNode = (DOMElement) getProperty(sharedComponent);
if (_syncedNode != null) {
rels.addAll(_syncedNode.getChildRelationships());
}
}
for (final AbstractRelationship rel : rels) {
final DOMNode subNode = (DOMNode) rel.getTargetNode();
if (subNode instanceof DOMElement) {
anyChildNodeCreatesNewLine = (anyChildNodeCreatesNewLine || !(subNode.avoidWhitespace()));
}
subNode.render(securityContext, renderContext, depth + 1);
}
} catch (Throwable t) {
logger.log(Level.SEVERE, "Error while rendering node {0}: {1}", new java.lang.Object[]{getUuid(), t});
out.append("Error while rendering node ").append(getUuid()).append(": ").append(t.getMessage());
t.printStackTrace();
}
// render end tag, if needed (= if not singleton tags)
if (StringUtils.isNotBlank(_tag) && (!isVoid)) {
// only insert a newline + indentation before the closing tag if any child-element used a newline
if (anyChildNodeCreatesNewLine) {
out.append(indent(depth));
}
out.append("</").append(_tag).append(">");
}
}
// Set result for this level again, if there was any
if (localResult != null) {
renderContext.setResult(localResult);
}
}
public boolean isVoidElement() {
return false;
}
public String getOffsetAttributeName(String name, int offset) {
int namePosition = -1;
int index = 0;
List<String> keys = Iterables.toList(this.getNode().getPropertyKeys());
Collections.sort(keys);
List<String> names = new ArrayList<>(10);
for (String key : keys) {
// use html properties only
if (key.startsWith(PropertyView.Html)) {
String htmlName = key.substring(HtmlPrefixLength);
if (name.equals(htmlName)) {
namePosition = index;
}
names.add(htmlName);
index++;
}
}
int offsetIndex = namePosition + offset;
if (offsetIndex >= 0 && offsetIndex < names.size()) {
return names.get(offsetIndex);
}
return null;
}
public List<String> getHtmlAttributeNames() {
List<String> names = new ArrayList<>(10);
for (String key : this.getNode().getPropertyKeys()) {
// use html properties only
if (key.startsWith(PropertyView.Html)) {
names.add(key.substring(HtmlPrefixLength));
}
}
return names;
}
// ----- protected methods -----
protected HtmlProperty findOrCreateAttributeKey(String name) {
HtmlProperty htmlProperty = null;
synchronized (htmlProperties) {
htmlProperty = htmlProperties.get(name);
}
if (htmlProperty == null) {
// try to find native html property defined in
// DOMElement or one of its subclasses
PropertyKey key = StructrApp.getConfiguration().getPropertyKeyForJSONName(entityType, name, false);
if (key != null && key instanceof HtmlProperty) {
htmlProperty = (HtmlProperty) key;
} else {
// create synthetic HtmlProperty
htmlProperty = new HtmlProperty(name);
htmlProperty.setDeclaringClass(DOMElement.class);
}
// cache property
synchronized (htmlProperties) {
htmlProperties.put(name, htmlProperty);
}
}
return htmlProperty;
}
// ----- private methods -----
// ----- interface org.w3c.dom.Element -----
@Override
public String getTagName() {
return getProperty(tag);
}
@Override
public String getAttribute(String name) {
HtmlProperty htmlProperty = findOrCreateAttributeKey(name);
return htmlProperty.getProperty(securityContext, this, true);
}
@Override
public void setAttribute(final String name, final String value) throws DOMException {
try {
HtmlProperty htmlProperty = findOrCreateAttributeKey(name);
if (htmlProperty != null) {
htmlProperty.setProperty(securityContext, DOMElement.this, value);
}
} catch (FrameworkException fex) {
throw new DOMException(DOMException.INVALID_STATE_ERR, fex.getMessage());
}
}
@Override
public void removeAttribute(final String name) throws DOMException {
try {
HtmlProperty htmlProperty = findOrCreateAttributeKey(name);
if (htmlProperty != null) {
htmlProperty.setProperty(securityContext, DOMElement.this, null);
}
} catch (FrameworkException fex) {
throw new DOMException(DOMException.INVALID_STATE_ERR, fex.getMessage());
}
}
@Override
public Attr getAttributeNode(String name) {
HtmlProperty htmlProperty = findOrCreateAttributeKey(name);
String value = htmlProperty.getProperty(securityContext, this, true);
if (value != null) {
boolean explicitlySpecified = true;
boolean isId = false;
if (value.equals(htmlProperty.defaultValue())) {
explicitlySpecified = false;
}
return new DOMAttribute((Page) getOwnerDocument(), this, name, value, explicitlySpecified, isId);
}
return null;
}
@Override
public Attr setAttributeNode(final Attr attr) throws DOMException {
// save existing attribute node
Attr attribute = getAttributeNode(attr.getName());
// set value
setAttribute(attr.getName(), attr.getValue());
// set parent of attribute node
if (attr instanceof DOMAttribute) {
((DOMAttribute) attr).setParent(this);
}
return attribute;
}
@Override
public Attr removeAttributeNode(final Attr attr) throws DOMException {
// save existing attribute node
Attr attribute = getAttributeNode(attr.getName());
// set value
setAttribute(attr.getName(), null);
return attribute;
}
@Override
public NodeList getElementsByTagName(final String tagName) {
DOMNodeList results = new DOMNodeList();
collectNodesByPredicate(this, results, new TagPredicate(tagName), 0, false);
return results;
}
@Override
public String getAttributeNS(String string, String string1) throws DOMException {
return null;
}
@Override
public void setAttributeNS(String string, String string1, String string2) throws DOMException {
}
@Override
public void removeAttributeNS(String string, String string1) throws DOMException {
}
@Override
public Attr getAttributeNodeNS(String string, String string1) throws DOMException {
return null;
}
@Override
public Attr setAttributeNodeNS(Attr attr) throws DOMException {
return null;
}
@Override
public NodeList getElementsByTagNameNS(String string, String string1) throws DOMException {
return null;
}
@Override
public boolean hasAttribute(String name) {
return getAttribute(name) != null;
}
@Override
public boolean hasAttributeNS(String string, String string1) throws DOMException {
return false;
}
@Override
public TypeInfo getSchemaTypeInfo() {
return null;
}
@Override
public void setIdAttribute(final String idString, boolean isId) throws DOMException {
checkWriteAccess();
final App app = StructrApp.getInstance(securityContext);
try {
setProperty(DOMElement._id, idString);
} catch (FrameworkException fex) {
throw new DOMException(DOMException.INVALID_STATE_ERR, fex.toString());
}
}
@Override
public void setIdAttributeNS(String string, String string1, boolean bln) throws DOMException {
throw new UnsupportedOperationException("Namespaces not supported.");
}
@Override
public void setIdAttributeNode(Attr idAttr, boolean isId) throws DOMException {
throw new UnsupportedOperationException("Attribute nodes not supported in HTML5.");
}
// ----- interface org.w3c.dom.Node -----
@Override
public String getLocalName() {
return null;
}
@Override
public String getNodeName() {
return getTagName();
}
@Override
public String getNodeValue() throws DOMException {
return null;
}
@Override
public void setNodeValue(String string) throws DOMException {
// the nodeValue of an Element cannot be set
}
@Override
public short getNodeType() {
return ELEMENT_NODE;
}
@Override
public NamedNodeMap getAttributes() {
return this;
}
@Override
public boolean hasAttributes() {
return getLength() > 0;
}
// ----- interface org.w3c.dom.NamedNodeMap -----
@Override
public Node getNamedItem(String name) {
return getAttributeNode(name);
}
@Override
public Node setNamedItem(Node node) throws DOMException {
if (node instanceof Attr) {
return setAttributeNode((Attr) node);
}
return null;
}
@Override
public Node removeNamedItem(String name) throws DOMException {
// save existing attribute node
Attr attribute = getAttributeNode(name);
// set value to null
setAttribute(name, null);
return attribute;
}
@Override
public Node item(int i) {
List<String> htmlAttributeNames = getHtmlAttributeNames();
if (i >= 0 && i < htmlAttributeNames.size()) {
return getAttributeNode(htmlAttributeNames.get(i));
}
return null;
}
@Override
public int getLength() {
return getHtmlAttributeNames().size();
}
@Override
public Node getNamedItemNS(String string, String string1) throws DOMException {
return null;
}
@Override
public Node setNamedItemNS(Node node) throws DOMException {
return null;
}
@Override
public Node removeNamedItemNS(String string, String string1) throws DOMException {
return null;
}
// ----- interface DOMImportable -----
@Override
public Node doImport(final Page newPage) throws DOMException {
DOMElement newElement = (DOMElement) newPage.createElement(getTagName());
// copy attributes
for (String _name : getHtmlAttributeNames()) {
Attr attr = getAttributeNode(_name);
if (attr.getSpecified()) {
newElement.setAttribute(attr.getName(), attr.getValue());
}
}
return newElement;
}
// ----- nested classes -----
@Override
public boolean onModification(SecurityContext securityContext, ErrorBuffer errorBuffer) throws FrameworkException {
for (Sync rel : getOutgoingRelationships(Sync.class)) {
DOMElement syncedNode = (DOMElement) rel.getTargetNode();
// sync HTML properties only
for (Property htmlProp : syncedNode.getHtmlAttributes()) {
syncedNode.setProperty(htmlProp, getProperty(htmlProp));
}
syncedNode.setProperty(name, getProperty(name));
}
final Sync rel = getIncomingRelationship(Sync.class);
if (rel != null) {
final DOMElement otherNode = (DOMElement) rel.getSourceNode();
if (otherNode != null) {
// sync both ways
for (Property htmlProp : otherNode.getHtmlAttributes()) {
otherNode.setProperty(htmlProp, getProperty(htmlProp));
}
otherNode.setProperty(name, getProperty(name));
}
}
try {
increasePageVersion();
} catch (FrameworkException ex) {
logger.log(Level.WARNING, "Updating page version failed", ex);
}
return true;
}
/**
* Render all the data-* attributes
*
* @param securityContext
* @param renderContext
*/
private void renderCustomAttributes(final AsyncBuffer out, final SecurityContext securityContext, final RenderContext renderContext) throws FrameworkException {
dbNode = this.getNode();
EditMode editMode = renderContext.getEditMode(securityContext.getUser(false));
Iterable<String> props = dbNode.getPropertyKeys();
for (String key : props) {
if (key.startsWith("data-")) {
String value = getPropertyWithVariableReplacement(securityContext, renderContext, new GenericProperty(key));
if (!(EditMode.RAW.equals(editMode) || EditMode.WIDGET.equals(editMode))) {
value = escapeForHtmlAttributes(value);
}
if (StringUtils.isNotBlank(value)) {
out.append(" ").append(key).append("=\"").append(value).append("\"");
}
}
}
if (EditMode.RAW.equals(editMode) || EditMode.WIDGET.equals(editMode)) {
Property[] rawProps = new Property[]{
dataKey, restQuery, cypherQuery, xpathQuery, functionQuery, hideOnIndex, hideOnDetail, showForLocales, hideForLocales, showConditions, hideConditions
};
// // In raw mode, add query-related data
// String _dataKey = getProperty(dataKey);
// String _restQuery = getProperty(restQuery);
// String _cypherQuery = getProperty(cypherQuery);
// String _xpathQuery = getProperty(xpathQuery);
//
// // Add filter to raw output
// boolean _hideOnIndex = getProperty(hideOnIndex);
// boolean _hideOnDetail = getProperty(hideOnDetail);
// String _showForLocales = getProperty(showForLocales);
// String _hideForLocales = getProperty(hideForLocales);
// String _showConditions = getProperty(showConditions);
// String _hideConditions = getProperty(hideConditions);
for (Property p : rawProps) {
if (p instanceof BooleanProperty) {
}
String htmlName = "data-structr-meta-" + CaseHelper.toUnderscore(p.jsonName(), false).replaceAll("_", "-");
Object value = getProperty(p);
if ((p instanceof BooleanProperty && (boolean) value) || (!(p instanceof BooleanProperty) && value != null && StringUtils.isNotBlank(value.toString()))) {
out.append(" ").append(htmlName).append("=\"").append(value.toString()).append("\"");
}
}
// if (StringUtils.isNotBlank(_restQuery)) {
// buffer.append(" ").append("data-structr-meta-rest-query").append("=\"").append(_restQuery).append("\"");
// }
//
// if (StringUtils.isNotBlank(_cypherQuery)) {
// buffer.append(" ").append("data-structr-meta-cypher-query").append("=\"").append(_cypherQuery).append("\"");
// }
//
// if (StringUtils.isNotBlank(_xpathQuery)) {
// buffer.append(" ").append("data-structr-meta-xpath-query").append("=\"").append(_xpathQuery).append("\"");
// }
}
}
/**
* Render script tags with jQuery and structr-app.js to current tag.
*
* Make sure it happens only once per page.
*
* @param out
*/
private void renderStructrAppLib(final AsyncBuffer out, final SecurityContext securityContext, final RenderContext renderContext, final int depth) throws FrameworkException {
EditMode editMode = renderContext.getEditMode(securityContext.getUser(false));
if (!(EditMode.RAW.equals(editMode) || EditMode.WIDGET.equals(editMode)) && !renderContext.appLibRendered() && getProperty(new StringProperty(STRUCTR_ACTION_PROPERTY)) != null) {
out
.append(indent(depth))
.append("<script>if (!window.jQuery) { document.write('<script src=\"/structr/js/lib/jquery-1.11.1.min.js\"><\\/script>'); }</script>")
.append(indent(depth))
.append("<script>if (!window.jQuery.ui) { document.write('<script src=\"/structr/js/lib/jquery-ui-1.11.0.custom.min.js\"><\\/script>'); }</script>")
.append(indent(depth))
.append("<script>if (!window.jQuery.ui.timepicker) { document.write('<script src=\"/structr/js/lib/jquery-ui-timepicker-addon.min.js\"><\\/script>'); }</script>")
.append(indent(depth))
.append("<script>if (!window.StructrApp) { document.write('<script src=\"/structr/js/structr-app.min.js\"><\\/script>'); }</script>")
.append(indent(depth))
.append("<script>if (!window.moment) { document.write('<script src=\"/structr/js/lib/moment.min.js\"><\\/script>'); }</script>")
.append(indent(depth))
.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"/structr/css/lib/jquery-ui-1.10.3.custom.min.css\">");
renderContext.setAppLibRendered(true);
}
}
/**
* This method concatenates the pre-defined HTML attributes and the
* optional custom data-* attributes.
*
* @param propertyView
* @return property key
*/
@Override
public Iterable<PropertyKey> getPropertyKeys(String propertyView) {
final List<PropertyKey> allProperties = new LinkedList();
final Iterable<PropertyKey> htmlAttrs = super.getPropertyKeys(propertyView);
for (PropertyKey attr : htmlAttrs) {
allProperties.add(attr);
}
dbNode = this.getNode();
Iterable<String> props = dbNode.getPropertyKeys();
for (String key : props) {
if (key.startsWith("data-")) {
allProperties.add(new GenericProperty(key));
}
}
return allProperties;
}
@Override
public boolean isSynced() {
return hasIncomingRelationships(Sync.class) || hasOutgoingRelationships(Sync.class);
}
// ----- interface Syncable -----
@Override
public List<Syncable> getSyncData() {
final List<Syncable> data = super.getSyncData();
data.add(getProperty(DOMElement.sharedComponent));
data.add(getIncomingRelationship(Sync.class));
if (isSynced()) {
// add parent
data.add(getProperty(ownerDocument));
data.add(getOutgoingRelationship(PageLink.class));
}
return data;
}
}