Documentation

AdminPresentation

EntityForm Building Blocks

  • Tabs
  • Groups
  • Fields
  • Collections

Building an EntityForm

The best way to learn how EntityForms are built using AdminPresentation, is to analyze the components of a given EntityForm. For the following examples, we'll take a look at the class-level AdminPresentation defined in the OfferAdminPresentation.java interface and its associated field-level definitions in OfferImpl.java. We'll then explore how the EntityForm layout can be modified and extended.

Offer Class-level Annotations

Lets dive straight into exploring the relationship between Tabs and Groups. Here is a shortened version of the AdminPresentationClass annotation defined in OfferAdminPresentation.java, which establishes the general structure of the Offer EntityForm:

@AdminPresentationClass(populateToOneFields = PopulateToOneFieldsEnum.TRUE, friendlyName = "OfferImpl_baseOffer",
    tabs = {
        @AdminTabPresentation(name = OfferAdminPresentation.TabName.General,
            order = OfferAdminPresentation.TabOrder.General,
            groups = {
                @AdminGroupPresentation(name = OfferAdminPresentation.GroupName.Description,
                    order = OfferAdminPresentation.GroupOrder.Description,
                    untitled = true),
                @AdminGroupPresentation(name = OfferAdminPresentation.GroupName.RuleConfiguration,
                    order = OfferAdminPresentation.GroupOrder.RuleConfiguration,
                    untitled = true),
                @AdminGroupPresentation(name = OfferAdminPresentation.GroupName.ActivityRange,
                    order = OfferAdminPresentation.GroupOrder.ActivityRange,
                    column = 1),

                    ...
            }
        ),

        ...

        @AdminTabPresentation(name = OfferAdminPresentation.TabName.Codes,
            order = OfferAdminPresentation.TabOrder.Codes,
            groups = {
                @AdminGroupPresentation(name = OfferAdminPresentation.GroupName.Codes,
                    order = OfferAdminPresentation.GroupOrder.Codes,
                    untitled = true)
            }
        ),

        ...
    }
)
public interface OfferAdminPresentation {

    public static class TabName {
        public static final String General = "General";
        public static final String Marketing = "OfferImpl_Marketing_Tab";
        public static final String Qualifiers = "OfferImpl_Qualifiers_Tab";
        public static final String Codes = "OfferImpl_Codes_Tab";
        public static final String Advanced = "OfferImpl_Advanced_Tab";
    }

    public static class TabOrder {
        public static final int General = 1000;
        public static final int Qualifiers = 2000;
        public static final int Marketing = 3000;
        public static final int Codes = 4000;
        public static final int Advanced = 5000;
    }

    public static class GroupName {
        public static final String Description = "OfferImpl_Description";
        public static final String ActivityRange = "OfferImpl_Activity_Range";
        public static final String Restrictions = "OfferImpl_Restrictions";
        public static final String Customer = "OfferImpl_Customer";
        public static final String CombineStack = "OfferImpl_Combine_Stack";

        ...

        public static final String Codes = "OfferImpl_Codes_Tab";
        public static final String ShouldBeRelated = "OfferImpl_ShouldBeRelated";

    }

    public static class GroupOrder {
        public static final int Description = 1000;
        public static final int ActivityRange = 2000;
        public static final int Customer = 3000;
        public static final int Restrictions = 5000;

        ...

        public static final int Codes = 1000;
        public static final int ShouldBeRelated = 1000;
    }

    public static class FieldOrder {
        public static final int Name = 1000;
        public static final int Description = 2000;
        public static final int Message = 3000;
        public static final int TemplateType = 4000;
        public static final int Amount = 5000;
        public static final int OfferType = 5000;
        public static final int DiscountType = 5000;

        ...
    }

}

From a quick skim over these annotations, we can determine the general hierarchy of Tabs and Groups that define the EntityForm. As you can see, these Tab and Group annotations take advantage of predefined constants from the OfferAdminPresentation interface. Later, we'll see why this is done, but for now, the important thing to note is that Tabs and Groups both define name and order properties. This information allows us to place a label on the Tab or Group, and order the elements relative to one another.

You may have also noticed that Groups have a few additional properties. The following Group attributes are Enterprise-only:

  • untitled - Determines whether or not the Group's title will be rendered.
  • column - Determines which column the Group will be placed into. By default, column 0 is the main column and column 1 is the narrow, right-hand-side column.
  • collapsed - If collapsed = true, then the Group will be collapsed on page load.

Now, we have an understanding of the general layout of the Offer EntityForm, but we still need to place fields and collections into the form.

Placing Fields & Collections into the EntityForm

Since we've previously defined the Tab/Group structure where each Tab and Group has a unique name, each Field and Collection only needs to identify the name of the Group that it should be placed into. Additionally, each Field and Collection should set the order property to organize elements within the given Group.

Simple Example
@AdminPresentation(friendlyName = "OfferImpl_Offer_Name",
    group = GroupName.Description, order = FieldOrder.Name,
    prominent = true, gridOrder = 1,
    defaultValue = "New Offer")
protected String name;
@AdminPresentationCollection(friendlyName = "offerCodeTitle",
    group = GroupName.Codes, order = FieldOrder.OfferCodes,
    addType = AddMethodType.PERSIST)
protected List<OfferCode> offerCodes = new ArrayList<OfferCode>(100);

As you can see in the following screenshots, the name Field has been placed into the Description Group (untitled) within the General Tab.

AdminPresentation Field in Group

Additionally, the offerCodes Collection has been placed into the Codes Group (untitled) within the Codes Tab.

AdminPresentation Collection in Group

Placing a Collection into a Tab

Fields can only be placed into groups, whereas Collections can be placed into Groups or Tabs. The following, is an example of how we could place our offerCodes collection into the General Tab:

@AdminPresentationCollection(friendlyName = "offerCodeTitle",
    tab = TabName.General,
    addType = AddMethodType.PERSIST)
protected List<OfferCode> offerCodes = new ArrayList<OfferCode>(100);

AdminPresentation Collection in Tab

Modifying Existing Layout

So far, we've seen how to place Fields and Collections into Groups and Tabs that were predefined in our class-level annotations. What if we don't like the placement of a particular Field or Collection, and we want to move it? Additionally, what if the desired Group or Tab destination is not specified in the class-level annotations? What options do we have?

XML Overrides

XML Metadata Overrides are the most powerful means of modifying the EntityForm structure for Broadleaf domain. Leveraging these XML entries, we have the ability to modify the AdminPresentation properties for any Field or Collection.

The following is an example of how we would move the name Field, that we previously discussed, to the Advanced Group, in the Advanced Tab:

<mo:overrideItem ceilingEntity="org.broadleafcommerce.core.offer.domain.Offer">
    <mo:field name="name">
        <mo:property name="group" value="OfferImpl_Advanced" />
    </mo:field>
</mo:overrideItem>

If we decided that the name Field should actually be placed into a new Group named Customer Facing Details in the General Tab, we would use the following entries:

<mo:overrideItem ceilingEntity="org.broadleafcommerce.core.offer.domain.Offer">
    <mo:group tabName="General" groupName="Customer Facing Details">
        <mo:property property="order" value="0"/>
    </mo:group>
    <mo:field name="name">
        <mo:property name="group" value="Customer Facing Details" />
    </mo:field>
</mo:overrideItem>

Alternatively, if we wanted the name Field to go into a new Tab and new Group, we would use the following entries:

<mo:overrideItem ceilingEntity="org.broadleafcommerce.core.offer.domain.Offer">
    <mo:tab tabName="New Tab">
        <mo:property property="order" value="9999999" />
    </mo:tab>
    <mo:group tabName="New Tab" groupName="Customer Facing Details">
        <mo:property property="order" value="0"/>
    </mo:group>
    <mo:field name="name">
        <mo:property name="group" value="Customer Facing Details" />
    </mo:field>
</mo:overrideItem>

For more details on field-level presentation modifications, see the Metadata Overrides doc.

Modifying the EntityForm Leveraging a Domain Extension

Many Broadleaf-based solutions end up extending the out-of-box domain in some way, but don't worry, your EntityForms can easily grow with your domain.

The key item to remember when editing EntityForms for domain extensions is that these extensions inherit AdminPresentationClass data. Therefore, only modifications and additions need to be made to the extension's AdminPresentationClass definition.

NOTE: If a property of a Tab or Group is overridden for out-of-box domain, then the same override will be applied
to any extensions unless extension has its own override.

Just as before, the ultimate goal is to define a Tab/Group hierarchy and then specify where each Field or Collection should be placed in that hierarchy.

Tab/Group Overrides on a Domain Extension

To override the properties of an inherited Tab or Group definition, we would leverage the tabOverrides or groupOverrides properties of the extension's AdminPresentationClass annotation.

The following is an example of how we would override the name of the Codes Tab and the order of the Advanced Group:

@AdminPresentationClass(
    tabOverrides = {
        @AdminTabPresentationOverride(tabName = OfferAdminPresentation.TabName.Codes,
            property = PropertyType.AdminTabPresentation.NAME,
            value = "Offer Codes")
    },
    groupOverrides = {
        @AdminGroupPresentationOverride(tabName = OfferAdminPresentation.TabName.Advanced,
            groupName = OfferAdminPresentation.GroupName.Advanced,
            property = PropertyType.AdminTabPresentation.ORDER,
            value = "9999999")
    }
)
Adding Tab or Group for Domain Extension

The following is an example of how we would establish a new Tab and Group, and a new Group in a inherited Tab:

@AdminPresentationClass(
    tabs = {
        @AdminTabPresentation(name = "NEW_TAB", order = 1,
            groups = {
                @AdminGroupPresentation(name = "NEW_GROUP", order = 1)
            }
        ),
        @AdminTabPresentation(name = OfferAdminPresentation.TabName.Codes,
            groups = {
                @AdminGroupPresentation(name = "NEW_GROUP_IN_INHERITED_TAB", order = 1)
            }
        )
    }
)

Best Practices

  • Moving the AdminPresentationClass annotation to an interface
    • In many cases, the AdminPresentationClass annotation can grow to a fairly significant size if the domain object has a large amount of fields. Because of this, we decided that moving the annotation to its own interface that the domain object would implement. The main intent here is to avoid cluttering the domain definition.
  • Tab & Group Constants
    • We found that by introducing subclasses that contain the necessary Tab and Group constants to the domain definition's AdminPresentation interface, we were able to avoid repetition of the values and reduce the potential for bugs.
  • Only use the group & order properties on fields
    • While it is possible to use the group, groupOrder, tab, and tabOrder properties to create new EntityForm Tabs or Groups, this is merely intended for backwards compatibility. We prefer to define Tabs and Groups at the class-level to avoid ambiguity of situations where two Fields may have the same tab (name) property but different tabOrder properties.
  • When extending an entity, consider updating the AdminSection's ceiling entity or creating a new AdminSection to manage the entity.

Possible Hangups

  • AdminPresentationClass annotation on class & interface
    • The AdminPresentationClass processor will first look at the desired class, then move to any associated interfaces looking for the AdminPresentationClass annotation, and it will always use the first one that it finds. Therefore, if your Tab/Group structure is not being built properly, make sure that the AdminPresentationClass annotation is only present once for a given level of extension.
  • Key vs processed value
    • The out-of-box TabName and GroupName constants generally use values like OfferImpl_Description and OfferImpl_Activity_Range which are actually keys for display values stored in a *.properties file. When the EntityForm is rendered, the Thymeleaf view processor will retrieve the necessary display values from the *.properties files. For the purpose of associating Fields/Collections to Tabs/Groups, these property keys are the values that you should use.
  • Group/Tab not defined in the class-level structure
    • If you find that a field is unexpectedly being placed into a group under the General tab, then it is likely the case that you have not correctly targeted or defined the Group.