XForms/Dashboard Builder
Motivation
editYou want a single form that allows you to layout a single page dashboard. In this case a dashboard in a set of "Portlets" that each have separate content.
Method
editOur dashboard builder will control a library of dashboards. Each dashboard has a name, layout information and a list of portlets that are rendered in the dashboard. In our example a dashboard may have up to 36 portlets, but best practices usually contain 7 portlets plus or minus one or two.
Application Design
editOur dashboard layout will be one of three types:
- row oriented where all portlets in a row have the same height
- column oriented where all portlets in a column share the same width
- grid-oriented where all portlets must be contained in a regular grid structure
The tool will allow the user to create a new dashboard and give it a name. The user then selects one of the three layout types. Each layout type will allows the user to specify attributes of each row or column such as using absolute width/heights in pixels or relative height/width percentage of width/heights. Note that to allow dashboards to be resized to different screen sizes, relative sizes should be used.
Within each row, column or grid, the user can specify a set of portlets and then give each portlet some parameters.
When the user saves a dashboard it will save an XML instance document with the specification of the dashboard layout. This layout can be transformed using XQuery or XSLT into a CSS file that is loaded each time the dashboard is displayed.
Dashboard Layout Styles
edit-
Row
-
Column
-
Grid
Conditional Display of Row/Column Information
editThis form should only display the row height specification if either row or grid layout is chosen. Similarly, the column widths should only be displayed if either the column or grid layout is chosen.
The following code provides this logic.
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xf="http://www.w3.org/2002/xforms" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<title>Demonstration of relevant fields.</title>
<xf:model>
<xf:instance id="my-data" xmlns="">
<data>
<DashboardLayoutCode>row</DashboardLayoutCode>
</data>
</xf:instance>
<xf:instance id="views" xmlns="">
<data>
<row-info />
<col-info />
</data>
</xf:instance>
<xf:bind nodeset="instance('views')/row-info" relevant="instance('my-data')/DashboardLayoutCode!='col' " />
<xf:bind nodeset="instance('views')/col-info" relevant="instance('my-data')/DashboardLayoutCode!='row'" />
</xf:model>
<body>
<p>Demonstration of relevant fields. Grid layouts require both rows and columns to be specified.</p>
<xf:select1 ref="instance('my-data')/DashboardLayoutCode">
<xf:label>Enter Layout Type: </xf:label>
<xf:item>
<xf:label>Row</xf:label>
<xf:value>row</xf:value>
</xf:item>
<xf:item>
<xf:label>Col</xf:label>
<xf:value>col</xf:value>
</xf:item>
<xf:item>
<xf:label>Grid</xf:label>
<xf:value>grid</xf:value>
</xf:item>
</xf:select1>
<br />
<xf:group ref="instance('views')/row-info">
<h1>Row</h1>
</xf:group>
<xf:group ref="instance('views')/col-info">
<h1>Col</h1>
</xf:group>
</body>
</html>
Source Code
editNOTE: In development!
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xf="http://www.w3.org/2002/xforms">
<head>
<title>Form Title</title>
<style type="text/css">
@namespace xf url("http://www.w3.org/2002/xforms");
body {font-family:Helvetica, sans-serif;}
/* This line ensures all the separate input controls appear on their own lines */
xf|input, xf|select, xf|select1, xf|textarea {display:block; margin:5px 0;}
/* Makes the labels right aligned in a 170px wide column that floats to the left of the input controls. */
xf|input > xf|label, xf|select > xf|label, xf|select1 > xf|label, xf|textarea > xf|label, xf|output > xf|label
{text-align:right; padding-right:10px; width:170px; float:left; text-align:right; font-weight: bold;}
.DashboardDescriptionText textarea {
width: 400px;
height: 8em;
font-family:Helvetica, sans-serif;
}
/* anything marked as a cell is inside a table */
.cell * {display: inline; padding: 1px 1px;}
/* set to column widths in the portlet table */
.PortletID {width: 10px;}
.PortletRow {width: 10px;}
.RowPlacement {width: 10px;}
.PortletHeight {width: 10px;}
.PortletWidth {width: 10px;}
</style>
<xf:model>
<xf:instance id="my-dash" xmlns="">
<DashboardDocument>
<DashboardID>123</DashboardID>
<DashboardName>Sample Dashboard</DashboardName>
<DashboardDescriptionText>A full description of the </DashboardDescriptionText>
<DashboardKeywords>test, example, sample</DashboardKeywords>
<DashboardLayoutCode>row</DashboardLayoutCode>
<DashboardRowCount>3</DashboardRowCount>
<DashboardColumnCount>4</DashboardColumnCount>
<Rows>
<Row>
<RowHeight>20%</RowHeight>
</Row>
<Row>
<RowHeight>50%</RowHeight>
</Row>
<Row>
<RowHeight>30%</RowHeight>
</Row>
</Rows>
<Portlets>
<Portlet>
<PortletID>1</PortletID>
<PortletName>Portlet1</PortletName>
<RowPlacement>1</RowPlacement>
<PortletWidth>25%</PortletWidth>
<PortletHeight />
</Portlet>
<Portlet>
<PortletID>2</PortletID>
<PortletName>Portlet2</PortletName>
<RowPlacement>1</RowPlacement>
<PortletWidth>25%</PortletWidth>
<PortletHeight />
</Portlet>
<Portlet>
<PortletID>3</PortletID>
<PortletName>Portlet3</PortletName>
<RowPlacement>1</RowPlacement>
<PortletWidth>25%</PortletWidth>
<PortletHeight />
</Portlet>
<Portlet>
<PortletID>4</PortletID>
<PortletName>Portlet4</PortletName>
<RowPlacement>1</RowPlacement>
<PortletWidth>25%</PortletWidth>
<PortletHeight />
</Portlet>
<Portlet>
<PortletID>5</PortletID>
<PortletName>Portlet5</PortletName>
<RowPlacement>2</RowPlacement>
<PortletWidth>33%</PortletWidth>
<PortletHeight />
</Portlet>
<Portlet>
<PortletID>6</PortletID>
<PortletName>Portlet3</PortletName>
<RowPlacement>2</RowPlacement>
<PortletWidth>33%</PortletWidth>
<PortletHeight />
</Portlet>
<Portlet>
<PortletID>7</PortletID>
<PortletName>Portlet3</PortletName>
<RowPlacement>2</RowPlacement>
<PortletWidth>33%</PortletWidth>
<PortletHeight />
</Portlet>
<Portlet>
<PortletID>8</PortletID>
<PortletName>Portlet3</PortletName>
<RowPlacement>3</RowPlacement>
<PortletWidth>33%</PortletWidth>
<PortletHeight />
</Portlet>
<Portlet>
<PortletID>9</PortletID>
<PortletName>Portlet3</PortletName>
<RowPlacement>3</RowPlacement>
<PortletWidth>33%</PortletWidth>
<PortletHeight />
</Portlet>
</Portlets>
</DashboardDocument>
</xf:instance>
<!-- used to select the number of rows and columns -->
<xf:instance id="layout-items" xmlns="">
<data>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
</data>
</xf:instance>
<!-- named views that are conditionally relevent based on the rules -->
<xf:instance id="views" xmlns="">
<data>
<row-view />
<col-view />
</data>
</xf:instance>
<!-- the rules on how to display the views -->
<!-- if we have a row or grid oriented display get the number of rows -->
<xf:bind id="row-view" nodeset="instance('views')/row-view" relevant="instance('my-dash')/DashboardLayoutCode='row' | instance('my-dash')/DashboardLayoutCode='grid'" />
<!-- if we have a column or grid oriented display get the number of columns -->
<xf:bind id="col-view" nodeset="instance('views')/col-view" relevant="instance('my-dash')/DashboardLayoutCode='col' | instance('my-dash')/DashboardLayoutCode='grid'" />
</xf:model>
</head>
<body>
<xf:output ref="DashboardID">
<xf:label>Dashboard ID:</xf:label>
</xf:output>
<xf:input ref="DashboardName">
<xf:label>Dashboard Name</xf:label>
<xf:hint>A short name under 50 characters.</xf:hint>
</xf:input>
<xf:textarea ref="DashboardDescriptionText" class="DashboardDescriptionText">
<xf:label>Description:</xf:label>
<xf:hint>Full description text. Used for searching for dashboards.</xf:hint>
</xf:textarea>
<xf:input ref="DashboardKeywords">
<xf:label>Keywords:</xf:label>
<xf:hint>Use comma to separate keywords.</xf:hint>
</xf:input>
<xf:select1 ref="DashboardLayoutCode">
<xf:label>Layout Style:</xf:label>
<xf:item>
<xf:label>Row</xf:label>
<xf:value>row</xf:value>
</xf:item>
<xf:item>
<xf:label>Column</xf:label>
<xf:value>col</xf:value>
</xf:item>
<xf:item>
<xf:label>Grid</xf:label>
<xf:value>grid</xf:value>
</xf:item>
</xf:select1>
<xf:group ref="instance('views')/row-view">
<xf:group ref="instance('my-dash')">
<xf:select1 ref="DashboardRowCount">
<xf:label>Number of Rows: </xf:label>
<xf:itemset nodeset="instance('layout-items')/item">
<xf:item>
<xf:label ref="." />
<xf:value ref="." />
</xf:item>
</xf:itemset>
</xf:select1>
</xf:group>
</xf:group>
<xf:group ref="instance('views')/col-view">
<xf:group ref="instance('my-dash')">
<xf:select1 ref="DashboardColumnCount">
<xf:label>Number of Columns: </xf:label>
<xf:itemset nodeset="instance('layout-items')/item">
<xf:item>
<xf:label ref="." />
<xf:value ref="." />
</xf:item>
</xf:itemset>
</xf:select1>
</xf:group>
</xf:group>
<table>
<thead>
<tr>
<th class="cell PortletID">ID</th>
<th class="cell PortletName">Name</th>
<th class="cell RowPlacement">Placement</th>
<th class="cell PortletHeight">Height</th>
<th class="cell PortletWidth">Width</th>
</tr>
</thead>
</table>
<xf:repeat nodeset="instance('my-dash')/Portlets/Portlet">
<span class="cell PortletID">
<xf:input ref="PortletID"/>
</span>
<span class="cell PortletName">
<xf:input ref="PortletName"/>
</span>
<span class="cell RowPlacement">
<xf:input ref="RowPlacement"/>
</span>
<span class="cell PortletHeight">
<xf:input ref="PortletHeight"/>
</span>
<span class="cell PortletWidth">
<xf:input ref="PortletWidth"/>
</span>
</xf:repeat>
</body>
</html>
References
editDashboards are common features in web portals. For interoperability, the content of each portlet can be specified by CSS tags specified in the JSR-168 specification. JSR-168 is also designed to be consistent with the OASIS Web Services for Remote Portlets standard.
For an excellent guild to the human-factors best-practices creating portlets see Stephen Few's excellent book Information Dashboard Design. For an excellent reference on what measures to use see Performance Dashboards by Wayne W. Erickson Portal standards are also discussed in the book Portal Development with Open Source Tools by Richardson et. el.