/*
* #%L
* BroadleafCommerce Open Admin Platform
* %%
* Copyright (C) 2009 - 2013 Broadleaf Commerce
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package org.broadleafcommerce.openadmin.web.form.entity;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.CompareToBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.broadleafcommerce.common.presentation.client.SupportedFieldType;
import org.broadleafcommerce.common.web.BroadleafRequestContext;
import org.broadleafcommerce.openadmin.dto.SectionCrumb;
import org.broadleafcommerce.openadmin.web.form.component.ListGrid;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
public class EntityForm {
protected static final Log LOG = LogFactory.getLog(EntityForm.class);
public static final String HIDDEN_GROUP = "hiddenGroup";
public static final String MAP_KEY_GROUP = "keyGroup";
public static final String DEFAULT_GROUP_NAME = "Default";
public static final Integer DEFAULT_GROUP_ORDER = 99999;
public static final String DEFAULT_TAB_NAME = "General";
public static final Integer DEFAULT_TAB_ORDER = 100;
protected String id;
protected String parentId;
protected String idProperty = "id";
protected String ceilingEntityClassname;
protected String entityType;
protected String mainEntityName;
protected String sectionKey;
protected Boolean readOnly = false;
/**
* special member used for only for a Translation entity form. Set at the controller, in order to populate a hidden field in the form
* indicating that there were errors and the form should not be allowed to submit.
*/
protected Boolean preventSubmit = false;
/**
* a string representation of a Javascript object containing a map of fields => errors
* Useful when filling a translation form, as the (only) way to determine to which fields error messaging needs to be attached
*/
protected String jsErrorMap;
protected String translationCeilingEntity;
protected String translationId;
protected Set<Tab> tabs = new TreeSet<Tab>(new Comparator<Tab>() {
@Override
public int compare(Tab o1, Tab o2) {
return new CompareToBuilder()
.append(o1.getOrder(), o2.getOrder())
.append(o1.getTitle(), o2.getTitle())
.toComparison();
}
});
protected List<SectionCrumb> sectionCrumbs = new ArrayList<SectionCrumb>();
// This is used to data-bind when this entity form is submitted
protected Map<String, Field> fields = null;
// This is used in cases where there is a sub-form on this page that is dynamically
// rendered based on other values on this entity form. It is keyed by the name of the
// property that drives the dynamic form.
protected Map<String, EntityForm> dynamicForms = new LinkedHashMap<String, EntityForm>();
// These values are used when dynamic forms are in play. They are not rendered to the client,
// but they can be used when performing actions on the submit event
protected Map<String, DynamicEntityFormInfo> dynamicFormInfos = new LinkedHashMap<String, DynamicEntityFormInfo>();
protected List<EntityFormAction> actions = new ArrayList<EntityFormAction>();
protected Map<String, Object> attributes = new HashMap<String, Object>();
/**
* @return a flattened, field name keyed representation of all of
* the fields in all of the groups for this form. This set will also includes all of the dynamic form
* fields.
*
* Note that if there collisions between the dynamic form fields and the fields on this form (meaning that they
* have the same name), then the dynamic form field will be excluded from the map and the preference will be given
* to first-level entities
*
* @see {@link #getFields(boolean)}
*/
public Map<String, Field> getFields() {
if (fields == null) {
Map<String, Field> map = new LinkedHashMap<String, Field>();
for (Tab tab : tabs) {
for (FieldGroup group : tab.getFieldGroups()) {
for (Field field : group.getFields()) {
map.put(field.getName(), field);
}
}
}
fields = map;
}
for (Entry<String, EntityForm> entry : dynamicForms.entrySet()) {
Map<String, Field> dynamicFormFields = entry.getValue().getFields();
for (Entry<String, Field> dynamicField : dynamicFormFields.entrySet()) {
if (fields.containsKey(dynamicField.getKey())) {
LOG.info("Excluding dynamic field " + dynamicField.getKey() + " as there is already an occurrance in" +
" this entityForm");
} else {
fields.put(dynamicField.getKey(), dynamicField.getValue());
}
}
}
return fields;
}
/**
* Clears out the cached 'fields' variable which is used to render the form on the frontend. Use this method
* if you want to force the entityForm to rebuild itself based on the tabs and groups that have been assigned and
* populated
*/
public void clearFieldsMap() {
fields = null;
}
public List<ListGrid> getAllListGrids() {
List<ListGrid> list = new ArrayList<ListGrid>();
for (Tab tab : tabs) {
for (ListGrid lg : tab.getListGrids()) {
list.add(lg);
}
}
return list;
}
/**
* Convenience method for grabbing a grid by its collection field name. This is very similar to {@link #findField(String)}
* but differs in that this only searches through the sub collections for the current entity
*
* @param collectionFieldName the field name of the collection on the top-level entity
* @return
*/
public ListGrid findListGrid(String collectionFieldName) {
for (ListGrid grid : getAllListGrids()) {
if (grid.getSubCollectionFieldName().equals(collectionFieldName)) {
return grid;
}
}
return null;
}
public Tab findTab(String tabTitle) {
for (Tab tab : tabs) {
if (tab.getTitle() != null && tab.getTitle().equals(tabTitle)) {
return tab;
}
}
return null;
}
public Tab findTabForField(String fieldName) {
fieldName = sanitizeFieldName(fieldName);
for (Tab tab : tabs) {
for (FieldGroup fieldGroup : tab.getFieldGroups()) {
for (Field field : fieldGroup.getFields()) {
if (field.getName().equals(fieldName)) {
return tab;
}
}
}
}
return null;
}
public Field findField(String fieldName) {
fieldName = sanitizeFieldName(fieldName);
for (Tab tab : tabs) {
for (FieldGroup fieldGroup : tab.getFieldGroups()) {
for (Field field : fieldGroup.getFields()) {
if (field.getName().equals(fieldName)) {
return field;
}
}
}
}
return null;
}
/**
* Since this field name could come from the frontend (where all fields are referenced like fields[name].value,
* we need to strip that part out to look up the real field name in this entity
* @param fieldName
* @return
*/
public String sanitizeFieldName(String fieldName) {
if (fieldName.contains("[")) {
fieldName = fieldName.substring(fieldName.indexOf('[') + 1, fieldName.indexOf(']'));
}
return fieldName;
}
public Field removeField(String fieldName) {
Field fieldToRemove = null;
FieldGroup containingGroup = null;
findField: {
for (Tab tab : tabs) {
for (FieldGroup fieldGroup : tab.getFieldGroups()) {
for (Field field : fieldGroup.getFields()) {
if (field.getName().equals(fieldName)) {
fieldToRemove = field;
containingGroup = fieldGroup;
break findField;
}
}
}
}
}
if (fieldToRemove != null) {
containingGroup.removeField(fieldToRemove);
}
if (fields != null) {
fieldToRemove = fields.remove(fieldName);
}
return fieldToRemove;
}
public void removeTab(Tab tab) {
tabs.remove(tab);
}
public void removeTab(String tabName) {
if (tabs != null) {
Iterator<Tab> tabIterator = tabs.iterator();
while (tabIterator.hasNext()) {
Tab currentTab = tabIterator.next();
if (tabName.equals(currentTab.getTitle())) {
tabIterator.remove();
}
}
}
}
public ListGrid removeListGrid(String subCollectionFieldName) {
ListGrid lgToRemove = null;
Tab containingTab = null;
findLg: {
for (Tab tab : tabs) {
for (ListGrid lg : tab.getListGrids()) {
if (subCollectionFieldName.equals(lg.getSubCollectionFieldName())) {
lgToRemove = lg;
containingTab = tab;
break findLg;
}
}
}
}
if (lgToRemove != null) {
containingTab.removeListGrid(lgToRemove);
}
if (containingTab != null && containingTab.getListGrids().size() == 0 && containingTab.getFields().size() == 0) {
removeTab(containingTab);
}
return lgToRemove;
}
public void addHiddenField(Field field) {
if (StringUtils.isBlank(field.getFieldType())) {
field.setFieldType(SupportedFieldType.HIDDEN.toString());
}
addField(field, HIDDEN_GROUP, DEFAULT_GROUP_ORDER, DEFAULT_TAB_NAME, DEFAULT_TAB_ORDER);
}
public void addField(Field field) {
addField(field, DEFAULT_GROUP_NAME, DEFAULT_GROUP_ORDER, DEFAULT_TAB_NAME, DEFAULT_TAB_ORDER);
}
public void addMapKeyField(Field field) {
addField(field, MAP_KEY_GROUP, 0, DEFAULT_TAB_NAME, DEFAULT_TAB_ORDER);
}
public void addField(Field field, String groupName, Integer groupOrder, String tabName, Integer tabOrder) {
groupName = groupName == null ? DEFAULT_GROUP_NAME : groupName;
groupOrder = groupOrder == null ? DEFAULT_GROUP_ORDER : groupOrder;
tabName = tabName == null ? DEFAULT_TAB_NAME : tabName;
tabOrder = tabOrder == null ? DEFAULT_TAB_ORDER : tabOrder;
// Tabs and groups should be looked up by their display, translated name since 2 unique strings can display the same
// thing when they are looked up in message bundles after display
// When displayed on the form the duplicate groups and tabs look funny
BroadleafRequestContext context = BroadleafRequestContext.getBroadleafRequestContext();
if (context != null && context.getMessageSource() != null) {
groupName = context.getMessageSource().getMessage(groupName, null, groupName, context.getJavaLocale());
tabName = context.getMessageSource().getMessage(tabName, null, tabName, context.getJavaLocale());
}
Tab tab = findTab(tabName);
if (tab == null) {
tab = new Tab();
tab.setTitle(tabName);
tab.setOrder(tabOrder);
tabs.add(tab);
}
FieldGroup fieldGroup = tab.findGroup(groupName);
if (fieldGroup == null) {
fieldGroup = new FieldGroup();
fieldGroup.setTitle(groupName);
fieldGroup.setOrder(groupOrder);
tab.getFieldGroups().add(fieldGroup);
}
fieldGroup.addField(field);
}
public void addListGrid(ListGrid listGrid, String tabName, Integer tabOrder) {
// Tabs should be looked up and referenced by their display name
BroadleafRequestContext context = BroadleafRequestContext.getBroadleafRequestContext();
if (context != null && context.getMessageSource() != null) {
tabName = context.getMessageSource().getMessage(tabName, null, tabName, context.getJavaLocale());
}
Tab tab = findTab(tabName);
if (tab == null) {
tab = new Tab();
tab.setTitle(tabName);
tab.setOrder(tabOrder);
tabs.add(tab);
}
tab.getListGrids().add(listGrid);
}
/**
* Uses a zero based position. Use 0 to add to the top of the list.
* @param position
* @param action
*/
public void addAction(int position, EntityFormAction action) {
if (actions.size() > position) {
actions.add(position, action);
} else {
actions.add(action);
}
}
public void addAction(EntityFormAction action) {
actions.add(action);
}
public void removeAction(EntityFormAction action) {
actions.remove(action);
}
public void removeAllActions() {
actions.clear();
}
public EntityForm getDynamicForm(String name) {
return getDynamicForms().get(name);
}
public void putDynamicForm(String name, EntityForm ef) {
getDynamicForms().put(name, ef);
}
public DynamicEntityFormInfo getDynamicFormInfo(String name) {
return getDynamicFormInfos().get(name);
}
public void putDynamicFormInfo(String name, DynamicEntityFormInfo info) {
getDynamicFormInfos().put(name, info);
}
public Boolean getReadOnly() {
return readOnly;
}
public Boolean getPreventSubmit() {
return preventSubmit;
}
public void setPreventSubmit() {
this.preventSubmit = true;
}
public void setReadOnly() {
setReadOnly(true);
}
public void setReadOnly(boolean readOnly) {
if (getFields() != null) {
for (Entry<String, Field> entry : getFields().entrySet()) {
entry.getValue().setReadOnly(readOnly);
}
}
if (getAllListGrids() != null) {
for (ListGrid lg : getAllListGrids()) {
lg.setReadOnly(readOnly);
}
}
if (getDynamicForms() != null) {
for (Entry<String, EntityForm> entry : getDynamicForms().entrySet()) {
entry.getValue().setReadOnly(readOnly);
}
}
actions.clear();
this.readOnly = readOnly;
}
public List<EntityFormAction> getActions() {
List<EntityFormAction> clonedActions = new ArrayList<EntityFormAction>(actions);
Collections.reverse(clonedActions);
return Collections.unmodifiableList(clonedActions);
}
public FieldGroup collapseToOneFieldGroup() {
Tab newTab = new Tab();
FieldGroup newFg = new FieldGroup();
newTab.getFieldGroups().add(newFg);
for (Tab tab : getTabs()) {
for (FieldGroup fg : tab.getFieldGroups()) {
for (Field field : fg.getFields()) {
newFg.addField(field);
}
}
}
getTabs().clear();
getTabs().add(newTab);
return newFg;
}
public String getTranslationCeilingEntity() {
return translationCeilingEntity == null ? ceilingEntityClassname : translationCeilingEntity;
}
public void setTranslationCeilingEntity(String translationCeilingEntity) {
this.translationCeilingEntity = translationCeilingEntity;
}
public String getTranslationId() {
return translationId == null ? id : translationId;
}
public void setTranslationId(String translationId) {
this.translationId = translationId;
}
/* *********************** */
/* GENERIC GETTERS/SETTERS */
/* *********************** */
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
public String getIdProperty() {
return idProperty;
}
public void setIdProperty(String idProperty) {
this.idProperty = idProperty;
}
public String getCeilingEntityClassname() {
return ceilingEntityClassname;
}
public void setCeilingEntityClassname(String ceilingEntityClassname) {
this.ceilingEntityClassname = ceilingEntityClassname;
}
public String getEntityType() {
return entityType;
}
public void setEntityType(String entityType) {
this.entityType = entityType;
}
public String getMainEntityName() {
return StringUtils.isBlank(mainEntityName) ? "" : mainEntityName;
}
public void setMainEntityName(String mainEntityName) {
this.mainEntityName = mainEntityName;
}
public String getSectionKey() {
return sectionKey.charAt(0) == '/' ? sectionKey : '/' + sectionKey;
}
public void setSectionKey(String sectionKey) {
this.sectionKey = sectionKey;
}
public Set<Tab> getTabs() {
return tabs;
}
public void setTabs(Set<Tab> tabs) {
this.tabs = tabs;
}
public Map<String, EntityForm> getDynamicForms() {
return dynamicForms;
}
public void setDynamicForms(Map<String, EntityForm> dynamicForms) {
this.dynamicForms = dynamicForms;
}
public Map<String, DynamicEntityFormInfo> getDynamicFormInfos() {
return dynamicFormInfos;
}
public void setDynamicFormInfos(Map<String, DynamicEntityFormInfo> dynamicFormInfos) {
this.dynamicFormInfos = dynamicFormInfos;
}
public void setActions(List<EntityFormAction> actions) {
this.actions = actions;
}
public List<SectionCrumb> getSectionCrumbsImpl() {
return sectionCrumbs;
}
public void setSectionCrumbsImpl(List<SectionCrumb> sectionCrumbs) {
if (sectionCrumbs == null) {
this.sectionCrumbs.clear();
return;
}
this.sectionCrumbs = sectionCrumbs;
}
public void setSectionCrumbs(String crumbs) {
List<SectionCrumb> myCrumbs = new ArrayList<SectionCrumb>();
if (!StringUtils.isEmpty(crumbs)) {
String[] crumbParts = crumbs.split(",");
for (String part : crumbParts) {
SectionCrumb crumb = new SectionCrumb();
String[] crumbPieces = part.split("--");
crumb.setSectionIdentifier(crumbPieces[0]);
crumb.setSectionId(crumbPieces[1]);
if (!myCrumbs.contains(crumb)) {
myCrumbs.add(crumb);
}
}
}
sectionCrumbs = myCrumbs;
}
public String getSectionCrumbs() {
StringBuilder sb = new StringBuilder();
int index = 0;
for (SectionCrumb section : sectionCrumbs) {
sb.append(section.getSectionIdentifier());
sb.append("--");
sb.append(section.getSectionId());
if (index < sectionCrumbs.size() - 1) {
sb.append(",");
}
index++;
}
return sb.toString();
}
public Map<String, Object> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, Object> attributes) {
this.attributes = attributes;
}
public String getJsErrorMap() {
return jsErrorMap;
}
public void setJsErrorMap(String jsErrorMap) {
this.jsErrorMap = jsErrorMap;
}
}