Using XControls for developing cards-based UI for mobile and desktop applications

Introduction

For a project I reviewed several frameworks to deliver a web interface. Among them was XControls which is a project on OpenNTF. XControls promises to deliver:

  • Faster design and assembly of modern user interfaces, using Card & List objects.
  • Easy the auto-optimization for smartphones, tablets and PCs.

bc-ipad-contacts-framed

XControls is built with Bootstrap and the Bootcards project.

Bootcards

Bootcards is a cards-based UI and it is built on top of Bootstrap. Unlike most other UI frameworks, it includes a dual-pane interface for tablet users.

The documentation describes well the structure and options for the two main objects, card and list (analagous to Forms and Views in traditional Notes development).

Basically you have 2 types of list:

  • Normal.
  • Detailed.

Below is an example of the Detailed list:

bc-list-detailed

And 8 types of cards:

  • Base.
  • Form.
  • Table.
  • Chart.
  • Summary.
  • Media.
  • File.
  • Rich text.

Below is an example of the Media card:

card-media-ios

Bootstrap

Bootstrap claims to be the most popular HTML, CSS, and JS framework for developing responsive, mobile first projects on the web. We will not introduce this framework further.

Getting started with XControls

Part of the documentation for XControls is the Getting started page which will walk you through creating a new application using XControls. I suggest to read this first and keep the primer documentation side by side.

Download the files

The required files can be either downloaded from OpenNTF or Github. Unfortunately XControls is not available as a plugin for your XPages development so you need to copy the required files manually in your existing project/NSF or you can use the provided NTF as a starter-kit for a new project.  The files are:

  • All custom controls starting with UnpBoot naming.
  • All Xpages starting with Unp naming.
  • All Resources\Files.
  • The”blank” theme (set this as application theme in XSP Properties file).

After that you are ready to develop the UI for your application! A recommended tip is to install the sampler application provided with the OpenNTF download and open that one in a browser and Domino Designer.

Test: Notebook/Journal

For my review I decided to apply the XControls methodology on a simple existing Notes application: the Notebook. This application allows you store simple documents which can combine rich text and file attachments. You are also able to ‘tag’ these documents by applying categories.

I will describe the steps taken to apply the XControls cards-based UI to this application.

Step: Copy the required files

The documentation does not describe to copy the unpCommon server-side javascript library.

Step: Create XPage unpMain

When creating an application, if it is going to be used on Teamstudio Unplugged mobile devices then we recommend that the home XPage is called UnpMain. Otherwise you are free to name your XPage.

Step: Add UnpBootResources

Every XPage that will be opened by the end-user needs the UnpBootResources custom control added. This adds CSS, JavaScript and Server Side JavaScript to your application.

<unp:UnpBootResources></unp:UnpBootResources>

Step: Create a common application layout

To provide a common application layout the Common Header and Common Footer custom controls should be included in the XPages that are accessible by the end-user.

Common Header

In case you want the app to develop for Mobile and Desktop then add a UnpBootNavigator and UnpBootHeader.

  • UnpBootNavigator: for mobile (the so-called ‘hamburger’ menu).
  • UnpBootHeader for desktop.

Also define the navitems in UnpBootHeader.

<unp:UnpBootHeader title=”${javascript:return @DbTitle()}”>
<unp:this.navitems><![CDATA[#{javascript:[
{label: “All Entries”, hasSubMenu: false, page: “/UnpMain.xsp”, icon: “fa-database”},
{label: “By Category”, hasSubMenu: false, page: “/vwCategory.xsp”, icon: “fa-list”},
{label: “By Entry Date”, hasSubMenu: false, page: “/vwEntryDate.xsp”, icon: “fa-calendar”},
{label: “Trash”, hasSubMenu: false, page: “/vwTrash.xsp”, icon: “fa-trash-o”}
]}]]></unp:this.navitems>
</unp:UnpBootHeader>

unpl_header

<unp:UnpBootNavigator>
<unp:this.menuitems><![CDATA[#{javascript:[
{label: “All Entries”, hasSubMenu: false, page: “/UnpMain.xsp”, icon: “fa-database”},
{label: “By Category”, hasSubMenu: false, page: “/vwCategory.xsp”, icon: “fa-list”},
{label: “By Entry Date”, hasSubMenu: false, page: “/vwEntryDate.xsp”, icon: “fa-calendar”},
{label: “Trash”, hasSubMenu: false, page: “/vwTrash.xsp”, icon: “fa-trash-o”}
]}]]></unp:this.menuitems>
</unp:UnpBootNavigator>

unpl_navigator

Font Awesome

Font Awesome gives you scalable vector icons that can instantly be customized – size, color, drop shadow, and anything that can be done with the power of CSS. XControls supports Font Awesome icons everywhere.

As you can see in the code above Font Awesome is for example used in the common header.

Common Footer

In case you want to include synch (for when running on Unplugged) add the UnpBootFooter as the common footer. You can also apply tabs in the footer if desired.

<unp:UnpBootFooter synctype=”none”>
<unp:this.tabs><![CDATA[#{javascript:[
{label: “Google”, hasSubMenu: false, page: “http://www.google.com&#8221;, icon: “fa-google”},
{label: “Like us”, hasSubMenu: false, page: “http://www.facebook.com/infowaresweden&#8221;, icon: “fa-facebook”}
]}]]></unp:this.tabs>
</unp:UnpBootFooter>

unpl_footer

 

Best practice tip

Since you will place the application layout just created across multiple XPages it is wise to store them in re-usable custom controls e.g. commonheader and commonfooter.

Step: Create the first content list

In the navigation we have declared that the initial XPage shall contain the All Entries overview. This overview simply lists the documents in a flat structure. You can use the UnpBootFlatView custom control for this.

Apply wrappers

To layout the content according to the bootcards definition apply a wrapper for the content:

<div id=”main” class=”container bootcards-container”>
<div class=”row fullheightrow”>
<!– add your list –>
<div id=”doccontent” class=”col-sm-7 bootcards-cards hidden-xs”>
<!– add your initial content e.g. a card–>
</div>
</div>

In the dual pane UI the target for dropping card content will be the div with ID doccontent.

Apply a list

<unp:UnpBootFlatView viewname=”($All)” summarycolumn=”$52″
numberofrows=”20″ newlink=”entry.xsp” xpagedoc=”UnpMain.xsp”
title=”All Entries” ajaxload=”Yes”>
</unp:UnpBootFlatView>

A description for all the available properties for the UnpBootFlatview can be read here: link. Property xpagedoc defines the xpage to load documents with, by default this would be the same as the current XPage.

Below is the result (only 2 documents in the database at the moment):

unpl_list

 

Below is the result when clicking on the Add link:

unpl_newentry

The XPage entry.xsp will be opened in a dialog. For now entry.xsp contains an unconfigured Form Editor custom control only. As you can see this requires more work.

 

 

Apply a Form Viewer

A Form Viewer is a wrapper that allows you to display document data inside a card. I created a custom control named docviewer and placed a UnpBootFormViewer inside it and configured it as followed:

<unp:UnpBootFormViewer editxpagewithajax=”yes”
formname=”JournalEntry” title=”Notebook Entry”
titleiconfield=”thumbnail” showbuttons=”true”
editxpagename=”entry.xsp”>
<unp:this.footertext><![CDATA[#{javascript:var id = context.getUrlParameter(“documentId”);
if (id != “”){
var doc:NotesDocument = database.getDocumentByUNID(id);
if (doc !=null){
var date = doc.getLastModified();
return “Last modified: ” + date
}
}}]]></unp:this.footertext>
<xp:this.rendered><![CDATA[#{javascript:context.getUrlParameter(“documentId”) != “”}]]></xp:this.rendered>
<xp:this.facets>
<xp:panel id=”list-group” xp:key=”facet_1″ styleClass=”panel”>
<div class=”list-group”>
<div class=”list-group-item”>
<label>Subject</label>
<xp:text id=”subject” tagName=”h4″ styleClass=”list-group-item-heading”>
<xp:this.value><![CDATA[#{javascript:docview.getItemValueString(“Subject”)}]]></xp:this.value>
</xp:text>
</div>
<div class=”list-group-item”>
<xp:label value=”Category” id=”lblCategory” for=”category”></xp:label>
<xp:text tagName=”h4″ id=”category” value=”#{docview.Categories}” styleClass=”list-group-item-heading”></xp:text>
</div>
<div class=”list-group-item”>
<xp:label value=”Date” id=”lblDate” for=”date”></xp:label>
<xp:text tagName=”h4″ id=”date” value=”#{docview.DiaryDate}” styleClass=”list-group-item-heading”></xp:text>
</div>
<div class=”list-group-item”>
<xp:label value=”Content” id=”lblCnt” for=”content”></xp:label>
<xp:text tagName=”h4″ id=”content” value=”#{docview.Body}” styleClass=”list-group-item-heading” escape=”false”></xp:text>
</div>
</div>
</xp:panel>
</xp:this.facets>
</unp:UnpBootFormViewer>

Again the entry.xsp is set to offer the document in edit mode (in a dialog). It appears that docview is the representation of the document. If you want to use data from the document outside the facet e.g. in the card footer you seem not to be able to refer to docview.

Below is an example of a selected document from the list:

list_docview

If you select Edit the document is presented in a dialog via XPage entry.xsp which is still empty. Let’s work on that one right now!

Apply a Form Editor

A Form Editor is a wrapper into which you can insert form fields for creating and editing documents. Fields can include auto clearing, type ahead, date pickers, numbers and rich text.

Below is the code that I included in the Xpage entry.xsp which is shown in dialog boxes when editing or creating a new document:

<unp:UnpBootFormEditor showbuttons=”true”
viewxpagename=”UnpMain.xsp” title=”Entry” formname=”JournalEntry”
footertext=”You can use your Notebook as a diary, to store meeting minutes or status reports, or simply as a place to create and store draft documents until they are ready for publication.”>
<xp:this.facets>
<xp:panel xp:key=”facet_1″ id=”list-group”>
<div class=”form-group”>
<xp:label styleClass=”col-xs-4 control-label” for=”subject” value=”Subject”></xp:label>
<div class=”col-xs-8″>
<xp:inputText id=”subject” value=”#{docedit.subject}” styleClass=”form-control required”>
<xp:this.attrs>
<xp:attr name=”placeholder” value=”Subject”></xp:attr>
</xp:this.attrs>
</xp:inputText>
<a href=”” class=”bootcards-clearinput”>
<i class=”fa fa-lg fa-times-circle”></i>
</a>
</div>
</div>
<div class=”form-group”>
<xp:label value=”Date” id=”datetime_dairydatelabel” for=”dairydate”
styleClass=”col-xs-4 control-label”>
</xp:label>
<div class=”col-xs-8″>
<xp:inputText id=”dairydate”
styleClass=”form-control”>
<xp:this.attrs>
<xp:attr name=”datevalue”>
<xp:this.value><![CDATA[#{javascript:try{
var date:lotus.domino.local.DateTime = docedit.getItemValueDateTime(‘DiaryDate’);
return date.toJavaDate().getTime();
}catch(e){
return new Date().getTime();
}}]]></xp:this.value>
</xp:attr>
</xp:this.attrs>
<xp:this.converter>
<xp:convertDateTime type=”date”
dateStyle=”short”>
</xp:convertDateTime>
</xp:this.converter>
</xp:inputText>

</div>
</div>
<div class=”form-group”>
<xp:label styleClass=”col-xs-4 control-label” for=”Categories” value=”Category”></xp:label>
<div class=”col-xs-8″>
<xp:inputText id=”Categories” value=”#{docedit.Categories}” styleClass=”form-control required”>
<xp:this.attrs>
<xp:attr name=”placeholder” value=”Category”></xp:attr>
</xp:this.attrs>
</xp:inputText>
<a href=”” class=”bootcards-clearinput”>
<i class=”fa fa-lg fa-times-circle”></i>
</a>
</div>
</div>
<div class=”form-group”>
<xp:label value=”Content” id=”biolabel” for=”content” styleClass=”col-xs-4 control-label”>
</xp:label>
<div class=”col-xs-8″>
<unp:UnpBootRichTextEditor fieldname=”Body”></unp:UnpBootRichTextEditor>
</div>
</div>
</xp:panel>
</xp:this.facets>
</unp:UnpBootFormEditor>

Notes:

  • For the viewxpagename property I have chosen the UnpMain.xsp Xpage, this is the XPage to open after saving the document. Normally this would be the same as the current XPage.
  • The date field is bound to a date-picker.
  • For the rich text field I included a UnpBootRichTextEditor custom control. This will give the field some edit options.

Below is an example of the result. As you can see I did not at so many bells & whistles to the form editor (yet) to improve usability:

unpl_newentrydate

Step: Create the categorized content list

Can you imagine a Notes application without a categorized view? Probably not. In XControls you seem to have 2 options:

  • A flat list.
  • An accordion list.

A flat list for categorization, really? If you bind the UnpBootFlatView custom control to a categorized view as in the example below, the categorized view will be displayed as a flat list, but with documents/entries grouped in the provided categories:

<unp:UnpBootFlatView title=”Entries By Category”
summarycolumn=”$52″ viewname=”By Category” numberofrows=”20″
ajaxload=”Yes” detailcolumn=”$44″ xpagedoc=”vwCategory.xsp”
newlink=”entry.xsp” footertext=”A Flat List but with Categories :-?”>
</unp:UnpBootFlatView>

Below is an example of the result:

unplflatlistcategorized

Accordion list

However, if you want to apply a collapsible Notes-a-like feature, you can choose the UnpBootAccordion custom control to provide an Accordion list.

<unp:UnpBootAccordionView title=”Dates” summarycolumn=”$39″
viewname=”By Diary Date” expandfirstcategory=”no” ajaxload=”Yes”
loaddocumenttarget=”doccontent” detailcolumn=”$44″
xpagedoc=”vwEntryDate.xsp” newdatatarget=”#editModal”
newlink=”entry.xsp”>
<unp:this.footertext><![CDATA[#{javascript:var date = @Date(@Today());
return “Today is: ” + date.toDateString()}]]></unp:this.footertext>
</unp:UnpBootAccordionView>

Below is an example of the result:

unplaccordion

The ‘categories’ are collapsible. Note that I had to modify the column value for the date: @Text(DiaryDate).

unplaccordioncollapsed

Summary

So far my initial review took. It seems fair enough to say that XControls enables you to deliver a dual pane cards-based UI for your Notes application without too much amount of development time.

I did explore many other features the framework offers (calendaring, carousels, charts) but it is definitely an OpenNTF project to keep an eye on. Especially if you are not capable of running a full blown solution like Worklight or you want to test early feedback for delivering existing Notes applications to mobile devices.

(extended) Managed Bean for SBT SDK

In a previous post I mentioned my blogpost ‘Developing social applications with the Social Business Toolkit SDK‘. In the post I defined a managed bean to connect to the Files service  and return data via the getMyFiles method. In this post I will extend that bean to access other services in IBM Connections:

  • Activity Stream
  • Blogs
  • Bookmarks
  • Communities
  • Files
  • Forums
  • Profiles

Later I will refine the ServiceBean object with new methods derived from the ones available in the SBT SDK. I shall also provide UI examples (custom controls) to present the information from the services.

ServiceBean

For now I have come so far:

package com.quintessens.bornsocial.sbt;

import java.io.Serializable;

import com.ibm.sbt.services.client.connections.activitystreams.ActivityStreamEntityList;
import com.ibm.sbt.services.client.connections.activitystreams.ActivityStreamService;
import com.ibm.sbt.services.client.connections.blogs.BlogList;
import com.ibm.sbt.services.client.connections.blogs.BlogService;
import com.ibm.sbt.services.client.connections.bookmarks.BookmarkList;
import com.ibm.sbt.services.client.connections.bookmarks.BookmarkService;
import com.ibm.sbt.services.client.connections.communities.CommunityList;
import com.ibm.sbt.services.client.connections.communities.CommunityService;
import com.ibm.sbt.services.client.connections.files.FileList;
import com.ibm.sbt.services.client.connections.files.FileService;
import com.ibm.sbt.services.client.connections.files.FileServiceException;
import com.ibm.sbt.services.client.connections.forums.ForumList;
import com.ibm.sbt.services.client.connections.forums.ForumService;
import com.ibm.sbt.services.client.connections.forums.TopicList;
import com.ibm.sbt.services.client.connections.profiles.Profile;
import com.ibm.sbt.services.client.connections.profiles.ProfileList;
import com.ibm.sbt.services.client.connections.profiles.ProfileService;
import com.ibm.sbt.services.client.connections.profiles.ProfileServiceException;

public class ServiceBean implements Serializable {
private static final long serialVersionUID = 1L;

public ActivityStreamEntityList getAllStatusUpdates() {
ActivityStreamService service = new ActivityStreamService();
try {
return service.getAllUpdates();
} catch (Throwable e) {
return null;
}
}

public ActivityStreamEntityList getMyStatusUpdates() {
ActivityStreamService service = new ActivityStreamService();
try {
return service.getMyStatusUpdates();
} catch (Throwable e) {
return null;
}
}

public ActivityStreamEntityList getMyNetworkStatusUpdates() {
ActivityStreamService service = new ActivityStreamService();
try {
return service.getStatusUpdatesFromMyNetwork();
} catch (Throwable e) {
return null;
}
}

public ActivityStreamEntityList getMyNetworkUpdates() {
ActivityStreamService service = new ActivityStreamService();
try {
return service.getUpdatesFromMyNetwork();
} catch (Throwable e) {
return null;
}
}

public ActivityStreamEntityList getUpdatesIFollow() {
ActivityStreamService service = new ActivityStreamService();
try {
return service.getUpdatesFromPeopleIFollow();
} catch (Throwable e) {
return null;
}
}

public FileList getMyFiles() {
FileService service = new FileService();
try {
return service.getMyFiles();
} catch (FileServiceException e) {
return null;
}
}

public ProfileList getMyColleagues() {
ProfileService service = new ProfileService();
try {
Profile profile = service.getMyProfile();
ProfileList profiles = service.getColleagues(profile.getUserid());
return profiles;
} catch (Throwable e) {
return null;
}
}

public Profile getMyProfile() {
ProfileService service = new ProfileService();
try {
Profile profile = service.getMyProfile();
return profile;
} catch (Throwable e) {
return null;
}
}

public BlogList getAllBlogs() {
BlogService service = new BlogService();
try {
BlogList entries = service.getAllBlogs();
return entries;
} catch (Throwable e) {
return null;
}
}

public BlogList getMyBlogs() {
BlogService service = new BlogService();
try {
BlogList entries = service.getMyBlogs();
return entries;
} catch (Throwable e) {
return null;
}
}

public BookmarkList getAllBookmarks() {
BookmarkService svc = new BookmarkService();
try {
BookmarkList bookmarks = svc.getAllBookmarks();
return bookmarks;
} catch (Throwable e) {
return null;
}
}

public BookmarkList getPopularBookmarks() {
BookmarkService svc = new BookmarkService();
try {
BookmarkList bookmarks = svc.getPopularBookmarks();
return bookmarks;
} catch (Throwable e) {
return null;
}
}

public BookmarkList getMyBookmarks() {
BookmarkService svc = new BookmarkService();
try {
BookmarkList bookmarks = svc.getMyNotifications();
return bookmarks;
} catch (Throwable e) {
return null;
}
}

public CommunityList getAllCommunities() {
CommunityService svc = new CommunityService();
try {
CommunityList comms = svc.getPublicCommunities();
return comms;
} catch (Throwable e) {
return null;
}
}

public CommunityList getMyCommunities() {
CommunityService svc = new CommunityService();
try {
CommunityList comms = svc.getMyCommunities();
return comms;
} catch (Throwable e) {
return null;
}
}

public ForumList getMyForums() {
ForumService svc = new ForumService();
try {
ForumList forums = svc.getMyForums();
return forums;
} catch (Throwable e) {
return null;
}
}

public TopicList getMyForumsTopics() {
ForumService svc = new ForumService();
try {
TopicList forums = svc.getMyForumTopics();
return forums;
} catch (Throwable e) {
return null;
}
}

}

My communities sample

Below you see an example of a widget showing the communities I am member of:

Screenshot_1

The code for the custom control is as followed:

<?xml version=”1.0″ encoding=”UTF-8″?>
<xp:view
xmlns:xp=”http://www.ibm.com/xsp/core&#8221;
xmlns:xe=”http://www.ibm.com/xsp/coreex”&gt;

<xe:widgetContainer
id=”widgetContainer1″
titleBarText=”My communities”>
<xp:panel>
<xp:dataTable
id=”dataTable1″
rows=”5″
value=”#{javascript:ServiceBean.getMyCommunities();}”
var=”comm”>
<xp:column
id=”column4″>
<xp:this.facets>
<xp:label
value=”Community”
id=”label4″
xp:key=”header” />
</xp:this.facets>
<h4>
<xp:link
escape=”true”
text=”#{javascript:comm.getTitle()}”
id=”link1″
value=”#{javascript:comm.getCommunityUrl()}”
title=”Open community…” />
</h4>
</xp:column>
<xp:column
id=”column5″>
<xp:this.facets>
<xp:label
value=”Creator”
id=”label5″
xp:key=”header” />
</xp:this.facets>
<xp:text
escape=”true”
id=”computedField4″>
<xp:this.value><![CDATA[#{javascript:var d = comm.getContributor().getName();
return d;
}]]></xp:this.value>
<xp:this.converter>
<xp:convertDateTime
type=”date”
dateStyle=”short” />
</xp:this.converter>
</xp:text>
</xp:column>
<xp:column
id=”column6″>
<xp:this.facets>
<xp:label
value=”Type”
id=”label6″
xp:key=”header” />
</xp:this.facets>
<xp:text
escape=”true”
id=”computedField5″
value=”#{javascript:comm.getCommunityType() }”>
<xp:this.converter>
<xp:convertNumber
type=”number”
integerOnly=”true” />
</xp:this.converter>
</xp:text>
</xp:column>
</xp:dataTable>
<xp:pager
layout=”Previous Group Next”
partialRefresh=”true”
id=”pager1″
for=”dataTable1″ />
</xp:panel>
</xe:widgetContainer>
</xp:view>

Some thoughts

The presentation of the data from the services in Connections may vary for each type of information. Some information is more suitable to present in a tabular format, others e.g. in a Data View control. Also at this moment I am not fully aware about the available methods for each service and what data they may provide. Here the trial and error method I will simply apply, or provide multiple UI’s for the same type of information.

Time to publish this blog so I can continue with the presentation layer…

Crash course application layout control

I am working on a small project that should deliver a web application to support online marketing campaigns. The application will provide different types of content (e.g. pages, video, images, forum, agenda). For most of these items I have found custom controls on OpenNTF or in the Extension Library.

For the layout I have choosen the Application Layout control from the Extension Library. Thomas Adrian showed a wizard function in version 8.5.3 but unfortunately our environment is still on 8.5.2.

There is a video available from OpenNTF how to use the Application Layout control. However if that goes too fast for you (I guess most people) on Michelle’s Universe (2,3)I found a couple of posts that give you some more to read.

Finally I found the wiki article: AD116 XPages Extension Library: Making Application Development Even Easier which contains the code for setting up a layout design definition.

In case you know a better resource that describes the Application Layout control then you are welcome to drop a link here… =)

UI Example LN application

Even though Notes 8.5.1 can be expected soon reality learns us however that a lot of users are still on versions 7 or even 6. Ofcourse you could use the limitations in the UI as an argument to force them to upgrade to newer versions of Notes, but then still a lot would run the basic client, not the standard one, so nice looking Java Views are still a long way to go.

I wrote before something about UI’s in Lotus Notes but now I was thinking about adding an example so those who are interested can test it and give feedback or use it for their projects. Whatever.

Here is the link to the download. Below you can find some snapshots of the UI:

Notes View used as horizontal menu
Notes View used as horizontal menu
Collapsible Vertical Menus
Collapsible Vertical Menus
Vertical Menu placed in preview pane
Vertical Menu placed in preview pane
Preview on bottom by default
Preview on bottom by default
Option to switch place for preview pane
Option to switch place for preview pane
Form example 1
Form example 1
Form example 2
Form example 2
Form example 3
Form example 3
Form example 4
Form example 4

Well hopefully one day we have a “CSS Zen Garden” equivalent somewhere one OpenNTF where people can post good UI examples. Further may we say:

with every Notes Client installation with a version of Notes 7 or older a kitten dies?

faster pussycat
faster pussycat