Navigator detail pages
In this lesson, we explore the second important part of the navigator pattern - the detail page.
Learning objectives
- Learn to create meaningful pages for the Contact, Company, and User cases.
In this lesson, we will focus on the structure of the detail page and not the specifics of different card patterns. For information on configuring banner, chart, and list cards as well as control details, see the relevant lessons in the Cards and the Controls sections.
Key concepts
We often use the navigator pattern to present a list of items, for example, Person, Opportunity, Service Request, and so on, which allows you to further navigate to the details for a particular item. Key workspace concepts include:
- Navigator - an optional workspace portlet that displays a list of items. Clicking on an item in the list typically allows you to navigate to a detail page.
- Page - arranges portlets into meaningful functional groups. You can display different pages depending on characteristics of the selected item.
- Banner - an optional banner portlet to display summary information at the top of a page.
- Tabs - optional grouping of portlets within a page.
The detail part of the navigator pattern is implemented as a page, or pages, with a summary banner portlet at the top with a number of tabs containing card portlets underneath. This arrangement looks something like this.
In simple cases, there is one detail page defined. For more involved cases, where the navigator list contains many different types, we define one page per subclass. For example, the Entity class hierarchy, looks like this:
Similar to what we did with dynamic columns based on type (Person, Company, and User) in the previous lesson, we will create a page per major subclass of Entity.
Entity Navigator → sysDetail event displays one of:
- Contact Page
- Company Page
- User Page
depending on the item selected in the navigator list.
In preparation for the learning activities in this lesson, make sure that your files include the following code. If not, copy this code into your files before continuing.
training:Contact.portal
<Portal caption="idsa.training.portal.Contact.caption">
<Tool name="toolNotifications" caption="ids.notifications" icon="icon:notifications" event="setSidebar" parameter="NOTIFICATIONS"/>
<WorkspaceRef name="refSandbox" workspace="training:Sandbox"/>
<WorkspaceRef name="refHome" workspace="mda:Home"/>
<WorkspaceRef name="refContacts" workspace="mda:Contacts"/>
<Drawer event="setSidebar" name="refNotifications" portlet="mda:NotificationDrawer"/>
</Portal>
training:Sandbox.workspace
<Workspace caption="idsa.training.workspace.Sandbox.caption" icon="icon:attach_money">
<Navigator name="training_EntityList_portlet" portlet="training:EntityList"/>
<Page name="page">
<Tab caption="IDS_DETAIL" layout="cols:3 fluid:true" name="tabDetail" type="grid">
<PortletRef name="training_EntityDetail_portlet" portlet="training:EntityDetail"/>
<PortletRef name="aflBus" portlet="app/bus"/>
</Tab>
</Page>
</Workspace>
training:EntityList.portlet
<Portlet context="EntityId">
<Script><![CDATA[(context-form-source (this'view) "EntityId" "training_EntityList_layout")]]></Script>
<Composite name="composite">
<LayoutRef association="training_EntityList_layout" layout="training:EntitySearch" name="training_EntitySearch_layout" query="true"/>
<LayoutRef layout="training:EntityList" name="training_EntityList_layout"/>
</Composite>
</Portlet>
training:EntitySearch.layout
<Layout class="Entity">
<Composite name="composite">
<Filter name="filter"/>
</Composite>
</Layout>
training:EntityList.layout
<Layout class="Entity">
<UIActions>
<UIAction event="sysProperties">
<Popup association="tblEntityList" edit="true" screen="training:mda:EntityProperties"/>
</UIAction>
<UIAction event="trainingShowSelection">
<Script><![CDATA[(logger'info ((@ model)'multipleSelection))
(@ confirm : (format "{0} items selected." ((@ model)'multipleSelectionCount)))]]></Script>
</UIAction>
</UIActions>
<Composite name="composite">
<Table name="tblEntityList" order="lastName" permanent="3" rows="(@)" switch="filter searchContext value">
<Menu name="mnuRightClick">
<Item caption="ids.edit2" event="sysProperties" icon="edit" name="mitProperties"/>
<Item event="sysRemove" name="deleteItem"/>
</Menu>
<SelectionMenu name="mnuMultiSelect">
<Item event="trainingShowSelection" name="mnuMulti"/>
</SelectionMenu>
<Column caption="idsa.training.layout.EntityList.type" icons="type iconDefault" name="colType"/>
<Column caption="idsa.training.layout.EntityList.icon" icons="(image . image)(data . mimeData)(type . mimeType)" name="colIcon" texts="initial"/>
<Column caption="idsa.training.layout.EntityList.name" event="sysDetail" length="100" name="colName" customizable="#f" values="fullName" visible="#t"/>
<Column caption="ids.Email" case="PSN" name="colPEmail" sortable="false" values="email fullAddress"/>
<Column caption="ids.phoneNumber" case="PSN" length="20" name="colPAddress" sortable="false" values="workPhone fullAddress"/>
<Column caption="ids.company" case="PSN" name="colFullCompany" values="companyName"/>
<Column caption="ids.caption.lastUpdated" case="PSN" name="colPLastUpdated" values="editTime"/>
<Column caption="IDS_LANGUAGE" case="PSN" name="colPLang" values="defaultLang caption"/>
<Column caption="ids.city" case="CO" name="colBranch" values="defaultAddress city"/>
<Column caption="IDS_TELEPHONE" case="CO" name="colCompAddress" sortable="false" values="workPhone fullAddress"/>
<Column caption="ids.caption.lastUpdated" case="CO" name="colCompLastUpdated" values="editTime"/>
<Column caption="ids.caption.lastUpdated" case="USR" name="colULastUpdated" values="editTime"/>
</Table>
</Composite>
</Layout>
training:EntityDetail.portlet
<Portlet context="EntityId">
<Script><![CDATA[(context-form-target (this'view) "EntityId" "training_EntityDetail_layout" ())]]></Script>
<Composite name="composite">
<LayoutRef layout="training:EntityDetail" name="training_EntityDetail_layout"/>
</Composite>
</Portlet>
EntityDetail.layout
<Layout class="Entity" caption="idsa.training.layout.EntityDetail.caption">
<Composite name="composite">
<Label caption="firstName" head="true" name="lblFirstName"/>
<Label caption="lastName" name="lblLastName"/>
</Composite>
</Layout>
training:mda:EntityProperties.dialog
<Dialog caption="ids.caption.editPopup">
<Composite cols="2" name="composite">
<LayoutRef layout="mda:EntityPropertyDashboard" head="true" name="mda_EntityPropertyDashboard_layout"/>
<Switch caption="ids.detail" head="true" name="grpDetailLayout" span="2" value="mda_EntityPropertyDashboard_layout">
<Composite case="UserPerson" name="compUser">
<LayoutRef association="mda_EntityPropertyDashboard_layout" layout="mda:UserPersonEditDetail" head="true" name="mda_UserPersonEditDetail_layout"/>
</Composite>
<Composite case="Person" name="compPerson">
<LayoutRef association="mda_EntityPropertyDashboard_layout" layout="mda:PersonEditDetail" head="true" name="mda_PersonEditDetail_layout"/>
</Composite>
<Composite case="Company" name="compOrg">
<LayoutRef association="mda_EntityPropertyDashboard_layout" layout="mda:CompanyEditDetail" head="true" name="mda_CompanyEditDetail_layout"/>
</Composite>
</Switch>
<LayoutRef association="mda_EntityPropertyDashboard_layout" head="true" layout="mda:EntityAuditFields" name="mda_EntityAuditFields_layout" span="2"/>
</Composite>
<Actions layout="mda:PopupButtons" name="mda_PopupButtons_layout"/>
</Dialog>
training:UIEvents.uievent
<UIEvents>
<UIEvent caption="idsa.training.uievents.UIEvents.showSelection" icon="icon:select" name="trainingShowSelection" tooltip="idsa.training.uievents.UIEvents.showSelection"/>
</UIEvents>
idsa.training.en.strings
idsa.training.layout.EntityDetail.caption=Entity Detail
idsa.training.layout.EntityList.caption=Entity List
idsa.training.layout.EntityList.icon=Icon
idsa.training.layout.EntityList.name=Name
idsa.training.layout.EntityList.type=Type
idsa.training.portal.Contact.caption=My Training Application
idsa.training.uievents.UIEvents.showSelection=Show Selection
idsa.training.workspace.Sandbox.caption=Sandbox
Detail page structure
Our current simple page definition in training:Sandbox.workspace
has one tab containing two portlets; our entityDetail
portlet and the AFL app/bus
portlet. The following:
training:Sandbox.workspace
...
<Page name="page">
<Tab caption="IDS_DETAIL" layout="cols:3 fluid:true" name="tabDetail" type="grid">
<PortletRef name="training_EntityDetail_portlet" portlet="training:EntityDetail"/>
<PortletRef name="aflBus" portlet="app/bus"/>
</Tab>
</Page>
...
renders as:
Learning activity
To add a page with more details:
Replace the page definition in your
training:Sandbox.workspace
with the following source code:training:Sandbox.workspace
XML<Workspace caption="idsa.training.workspace.Sandbox.caption" icon="icon:attach_money"> <Navigator name="training_EntityList_portlet" portlet="training:EntityList"/> <Page name="pgContactDetail"> <Banner name="mda_EntityDashboard_portlet" portlet="mda:EntityDashboard"/> <Tab caption="ids.fins.summary" layout="cols:3 fluid:true" name="tabSummary" type="grid"> <PortletRef name="mda_EntityBio_portlet" portlet="mda:EntityBio"/> <PortletRef name="mda_ChartActivitiesByEntityPieChart_portlet" portlet="mda:ChartActivitiesByEntityPieChart"/> <PortletRef name="mda_EntityNotes_portlet" portlet="mda:EntityNotes"/> <PortletRef name="mda_EntitySummaryActivities_portlet" portlet="mda:EntitySummaryActivities"/> </Tab> <Tab caption="ids.activities" layout="cols:3 fluid:true" name="tabPersonJournal" type="grid"> <PortletRef name="mda_EntityActivities_portlet" portlet="mda:EntityActivities"/> </Tab> </Page> </Workspace>
Save, re-seed and refresh your browser. When you click on a name in the navigator, your UI should look similar to this:.
This is a better detail page. It has a banner control across the top as defined with the following source code:XML<Banner name="mda_EntityDashboard_portlet" portlet="mda:EntityDashboard"/>
Two tabs of detail content as defined with the following source code:
XML... <Tab caption="ids.fins.summary" layout="cols:3 fluid:true" name="tabSummary" type="grid"> ... <Tab caption="ids.activities" layout="cols:3 fluid:true" name="tabPersonJournal" type="grid"> ...
and a collection of
PortletRefs
that determine the portlets that show up on the tabs.
At this point, if you click on a Person, Company, or User you will get the same page. We can display different pages by type by using the page's case property, which is the name of class that this detail page is capable of displaying.
Learning activity
To add cases for Person, Company, and User:
Replace the contents of
training:Sandbox.workspace
with the following code:training:Sandbox.workspace
XML<Workspace caption="idsa.training.workspace.Sandbox.caption" icon="icon:attach_money"> <Navigator name="training_EntityList_portlet" portlet="training:EntityList"/> <Page case="UserPerson" name="pgUserDetail"> <Banner name="usr_mda_EntityDashboard_portlet" portlet="mda:EntityDashboard"/> <Tab caption="ids.fins.summary" layout="cols:3 fluid:true" name="tabUsrSummary" type="grid"> <PortletRef name="usr_mda_EntityNotes_portlet" portlet="mda:EntityNotes"/> <PortletRef name="usr_mda_EntitySummaryActivities_portlet" portlet="mda:EntitySummaryActivities"/> </Tab> <Tab caption="IDS_DETAIL" layout="cols:3 fluid:true" name="tabOrgDetail" type="grid"> <PortletRef name="usr_mda_ChartActivitiesByEntityPieChart_portlet" portlet="mda:ChartActivitiesByEntityPieChart"/> </Tab> </Page> <Page case="Person" name="pgContactDetail"> <Banner name="psn_mda_EntityDashboard_portlet" portlet="mda:EntityDashboard"/> <Tab caption="ids.fins.summary" layout="cols:3 fluid:true" name="tabPsnSummary" type="grid"> <PortletRef name="psn_mda_EntityBio_portlet" portlet="mda:EntityBio"/> <PortletRef name="psn_mda_ChartActivitiesByEntityPieChart_portlet" portlet="mda:ChartActivitiesByEntityPieChart"/> <PortletRef name="psn_mda_EntityNotes_portlet" portlet="mda:EntityNotes"/> <PortletRef name="psn_mda_EntitySummaryActivities_portlet" portlet="mda:EntitySummaryActivities"/> </Tab> <Tab caption="ids.activities" layout="cols:3 fluid:true" name="tabPersonJournal" type="grid"> <PortletRef name="psn_mda_EntityActivities_portlet" portlet="mda:EntityActivities"/> </Tab> </Page> <Page case="Organization" name="pgOrganizationDetail"> <Banner name="org_mda_EntityDashboard_portlet" portlet="mda:EntityDashboard"/> <Tab caption="ids.fins.summary" layout="cols:3 fluid:true" name="tabOrgSummary" type="grid"> <PortletRef name="org_mda_ChartActivitiesByEntityPieChart_portlet" portlet="mda:ChartActivitiesByEntityPieChart"/> <PortletRef name="org_mda_EntityNotes_portlet" portlet="mda:EntityNotes"/> <PortletRef name="org_mda_EntitySummaryActivities_portlet" portlet="mda:EntitySummaryActivities"/> </Tab> <Tab caption="IDS_DETAIL" layout="cols:3 fluid:true" name="tabOrgDetail" type="grid"> <PortletRef name="org_mda_EntityAddresses_portlet" portlet="mda:EntityAddresses"/> <PortletRef name="org_mda_EntityCustomFields_portlet" portlet="mda:EntityCustomFields"/> <PortletRef name="org_mda_EntityCommunications_portlet" portlet="mda:EntityCommunications"/> </Tab> </Page> </Workspace>
- Save, re-seed and refresh your browser.
When you click on a name in the navigator, what you see depends on the type of item you've selected. The key here is thecase="..."
property on the pages. The page that displays is selected based on the page's case property matching the class of the selected item.
The name property of the portlet refs should be unique within the page. This is to allow automated testing tools to find the right location. A good standard is to prefix the name with some representation of the case property (here org, usr, or psn), replace : with _ in the portlet's name, and prefix with _portlet, so the mda:EntityAddresses
portlet in the Organization page would have a name of org_mda_EntityAddresses_portlet
.
Page selection and the case property
The first page that matches the current item's class is displayed. If no match is found, the last page in the list is used. Since the match is from top to bottom, you should place the most specific case in a class hierarchy at the top. For example, if there is a special page for UserPerson, this page should be placed above the Person page as it is more specific. Otherwise, the search matches on Person and uses that page, never reaching the UserPerson page because UserPerson is a subclass of Person.
That's about it for navigator pattern detail pages. The different types of portlets are discussed in the portlet patterns lessons.