Notes on Mobile
Back on track

Hairshirt or not…. the experiment rolls forward!

Now that I got the JSON bug solved worked around I can get back to fleshing out the non-XPages version of the Customers On the GO! app.

The next page is the contact details. The XPages version of this page wasn’t too complicated and I wouldn’t normally dedicate an entire post about it, but this will give me a chance to walk through my Notes document as JSON function I talked about yesterday.

After clicking the ‘Contacts’ link at the bottom of the app, you are sent to the ‘contactsList.html’ page. The page itself is almost identical to the app’s home screen. The content for the page is collected using the JSON formatted output of a view in the CRM applciation using the following JS:

function loadContactsPageData(){
	var cat = getURLParameter("c");
	var parentId = ('00000000'+getURLParameter("documentId")).slice(-8);
	// Get the list data.
	
    $.getJSON('http://10.1.1.39/unplugged/tdtool.nsf/dataProxy?OpenAgent&db=unplugged/rm.nsf&v=JSONContacts&RestrictToCategory='+cat,
        
    	function(data) {
    		var contactArray = convertViewEntryToJSONObject(data);
    		$('#contactDetailsTemplate')                  // Select the template. 
    			.tmpl(contactArray)              // Bind it to the data. 
    			.appendTo('#contactsContainer');   // Render the output.
    		
    		$('#contactsContainer').listview('refresh');
       }
   );
}

The contact template is very basic as well and looks like this:

	<script id="contactDetailsTemplate" type="text/x-jquery-tmpl">
		<li>
		<a href="contact.html?openfileresource&documentId=${noteid}">
		{{if fpjrFunction_WB=="Developer"}}
		<img style="jobImage" src='designer.png'>
		{{else}}
		<img style="jobImage" src='admin.png'>
		{{/if}}
		<span style='contactName'>${fpnhName_WB}</span>
    	<br/><span style='contactDetails'>${fpjrPosition_WB}</span>
    	<br/><span style='contactDetails'>${fppbPhone_WB}</span>
		{{if fppbXtn_WB}}
    	<span style='contactDetails'>  Ext: ${fppbXtn_WB}</span>
		{{/if}}
		</a>
		</li>
	</script>

Not that complicated, and very similar to the first page, so it was really easy to throw together. 

Each item in the above list has a link that brings you to the ‘contact.html’ page where the individual contacts information is displayed. In the XPages version, there was nothing much to this page, and really didn’t offer any more information than the list did. Just the customers name, job role, phone number, and a big button called ‘Call’. So here is where the dataProxy agent comes into play. To get a document all I need is the following URL:
  unplugged/tdtool.nsf/dataProxy?OpenAgent&db=unplugged/crm.nsf/&outputformat=JSON&documentId=00000000

(Of course I would use the real note id, but this is an example). This URL I can use in my JQuery.getJSON call in the same way I was using it with the views.

NotesDocument.js

To make it easier to deal with the document object I create a few helper functions/objects. First was a little JS function to wrap the getJSON call for documents:

function getNotesDocument( db, docid, callback ){
	var url = 'http://10.1.1.39/unplugged/tdtool.nsf/dataProxy?OpenAgent';
	url += "&db=" + db;
	url += "&documentId=" + docid;
	url += "&outputformat=JSON";
    $.getJSON(url,function(data){
    	var doc = new NotesDocument(data);
    	callback(doc);
    });
}

This function takes 3 parameters, the db and docid as strings and the callback that the getJSON function should call. The passed in callback will be called with a NotesDocument object though instead of the direct results from the getJSON call itself. 

The NotesDocument object is simple and only has two functions, a constructor and a method called getItem. This is what it looks like:

var NotesDocument = function(val){
	this.JSON = val.document;
	this.items = {};
	// convert the array of items to named properties in this object
	// this will make accessing them a bit faster than looping through
	// the entire list looking for the item by name
	for( var i = 0; i< this.JSON.item.length; i++ ){
		for( val in this.JSON.item[i] ){
			if( val !== "name"){
				// values get put into a field named the same as their data type
				this.items[this.JSON.item[i].name] = this.JSON.item[i][val];
			}
		}
	}
	delete this.JSON.item; // no need to store the items twice
};

NotesDocument.prototype.getItem = function(item){
	if( this.items.hasOwnProperty(item) ){
		return this.items[item];
	}
	return "";
}

This little bit of code lets me access the item names in much the same way as if I was working directly with the built in NotesDocument object in Domino.

Finally the function that gets the document can be reduced down to this bit of JS:

function loadContactPage(){
		
	var cat = ('00000000'+getURLParameter("documentId")).slice(-8);
	// Get the list data.
	getNotesDocument( "unplugged/crm.nsf", getURLParameter("documentId"), 
		function(doc) {
			$('#contactTemplate')                  // Select the template. 
				.tmpl(doc)              // Bind it to the data. 
				.appendTo('#contactDetails');   // Render the output.
			
			// need to re-apply styles so the 'call' button will
			// be shown normally. For some reason the only way to 
			// get this to work with the link button is to 
			// 'refresh' the entire page.
			$('#contactDetail').trigger('create');
		}
	);
}

One of my biggest frustrations with JQuery Mobile is that it doesn’t deal with dynamic content very well. You probably noticed that there is a call to the ‘trigger()’ function above. After much trial and error, and google searching, it was the only way I could get the ‘Call’ button to render as a JQM Button widget and not a link. The reason all this is needed is that JQM loads a page and does all it’s markup tweaking before the getJSON() call returns. As a result, by the time the template code has been processed, JQM thinks it’s done with the page. To get around it you either need to add all the JQM classes and styles to the template code manually, or you need to force JQM to reprocess the page - which is what calling trigger(‘create’) does when you specify the DIV for the page.

And just like the rest of the pages, I add a event handler to call this function when the contactDetails page is loaded:

$(document).bind("mobileinit", function(){
    [...snip...]
	$('#contactDetail').live('pagebeforeshow',function(event, ui){
		loadContactPage();	
	});
    [...snip...]
}

With the Javascript ready to go all that is left is the page template that will display the data from the document. For that I’m going to use the getItem function in the NotesDocument object directly in my page template:

	<script id="contactTemplate" type="text/x-jquery-tmpl">
		<span class='contactName'>${$data.getItem("fpnhName_WB")}</span><br/>
	    <span class='contactDetails'>${$data.getItem("fpjrPosition_WB")}</span><br/>
		<span class='contactDetails'>${$data.getItem("fppbPhone_WB")}</span>
		{{if $data.getItem("fppbXtn_WB")}}
	    <span class='contactDetails'>  Ext: ${$data.getItem("fppbXtn_WB")}</span>
		{{/if}}
		<a id='callButton' href="tel:${$data.getItem("fppbPhone_WB")}" data-icon="grid" data-role="button">Call</a>
	</script>

When the JQuery template code evaluates the template, you can access some of the objects it passes in directly. The object here we are using is the $data object, which corresponds to the data that you passed to the jQuery.tmpl() function. With that ability we can call the getItem() function and get the value out of the field.

I’m feeling pretty happy with what I have up to this point. The dataProxy agent and the JSON document output is working well, and I’ve been able to keep the look and feel of the app intact.

Next up is the license page.

Just the basic details

Like with the XPages version, this page will be pretty simple in it’s overall structure:

<body>
	<div data-role="page"  id="details">
	<div  data-position="fixed" data-role="header">
	<a rel="externa" href="index.html" data-icon="arrow-l" data-iconpos="left">Home</a>
		<h1 id='companyName'></h1>
	</div>	
	<div data-role="content">
	<div id='companyDetails'></div>
	<div id='mapCanvas' style="margin-right:20px;width: 100%; height: 360px;"></div>
	
	<a href="#" data-icon="forward" data-role="button">Directions</a>
	<a href="#" data-icon="grid" data-role="button">Call</a>
	
	</div><!-- /content -->
	<div  data-position="fixed" data-role="footer">
		<div data-role="navbar">
		<ul id='footerNavLinks'>
			<li><a href="#" class="ui-state-persist ui-btn-active">Details</a></li>
			<li><a href="#" onclick="changeCategory('contactList.html')">Contacts</a></li>
			<li><a href="#" onclick="changeCategory('licenseList.html')">Licenses</a></li>
			<li><a href="#" onclick="changeCategory('notesList.html')">Notes</a></li>
		</ul>
		</div><!-- /navbar -->
	</div><!-- /footer -->
</div><!-- /page -->
</body>

In this block of code there are two things to keep an eye on. First is in the header <div>:

<h1 id='companyName'></h1>

Next is the <div> with the id ‘companyDetails’:

<div id='companyDetails'></div>

Also you may have noticed that in the footer, each link contains an onclick handler, but I’ll get to that later.

The two id’s above are going to be where my replacement text ends up. Notice how the replacement target it can be any thing you want, not just a <div>.

Here are the templates I’m going to use:

	<!-- 
		JQM only loads the [data-role='page'] element into the linked pages
		so the templates all need to be here, in that <div> not in the <head>
	-->
	<script id="companyNameTemplate" type="text/x-jquery-tmpl">
		${fcftCompany_WB}
	</script>
	<script id="companyDetailsTemplate" type="text/x-jquery-tmpl">
		<span style="display:none" id="address">${faddLocation_SS}</span>
		<span style="display:none" id="companyName">${fcftCompany_SS}</span>
		<span class='companyAddress'>${faddAddress_SS}</span><br/>
		{{if faddAddress_SS_1.length}}
		<span class='companyAddress'>${faddAddress_SS_1}</span><br/>
		{{/if}}
		<span class='companyAddress'>${faddTown_SS}, ${faddCounty_SS}   ${faddPostcode_SS}, ${faddCountry_SS}</span><br/>
	</script>
	

It is very important that these two <script> blocks go inside the <div data-role=’page’>. The reason, which took me a while to come to realize has to do with the way JQuery Mobile follows links. JQM uses AJAX to load the content of the page pointed to by all <a> tags unless you use the rel=”external” or its variants. This is done so the next page can have fancy transitions. The way a page slides in from the right is an example of this effect. The way it works is that in the background, JQM gets the div from the new page that contains the data-role=’page’ attribute and only loads that into the current page by replacing the current data-role=’page’ div. The effect is neat but it actually can cause really bizarre things to happen if you don’t watch out. For example, in the first page I created, I placed the template blocks in the <head> of my page. In the first version of the details page I did the same thing. After my first test, I couldn’t get the customer name to show up in the title bar and I would be getting ‘object has no method ‘appendTo” errors in the JS Console:

It took a while to figure this out, but the cause was that when JQM loaded details.html page, it only use the data-role=’page’ div. At that point I had two options. The first was to load the template into the <head> of the first page. Since that page is used as the target for the next page to load into, my template will be there. The other option, which I ended up going with, was to put all the things I wanted to load into the page div. I like the second approach because it keeps the ‘page’ more intact. Keeping everything in the first page just seems really messy to me.

The next step is to update the ‘mobileinit’ function in my app.js with the following code for my new details page:

$(document).bind("mobileinit", function(){
	$('#main').live('pageshow',function(event, ui){
		loadMainPageData();
	});
        $('#details').live('pagebeforeshow',function(event, ui){
		loadDetailsPageData();	
		loadMap();		
	});
});

Here is where I hit my first real problem. Getting a list of customers from a view is pretty easy. Getting a single customer document is not. There is no Domino URL parameter to read a document as JSON. This will have to be the first design change I make. I figure I have a couple options though. First option would be to create an agent I can call that returns the document I want in JSON format. The upside to this is the flexibility it would give me as I could add what ever I wanted, and it would make RichText possible. The downside to this is that I would have to be able to run code on the server which means signing the database with the correct Id. I don’t really want to deal with that right now. The second option is to still use a new view and categorize it on the Note ID or customer name of the document I want, and add all the fields I need as columns. This has two distinct advantages - it’s easy to create views and change them without needing special access, and it keeps the native Domino changes to a minimum. Agents require testing and they are not easily ported if I ever want to move the data off of Notes (gasp!! - I’m not in charge of IT so who knows what will happen tomorrow….). So the view I created is really simple. Categorized on NoteID and I added columns for each field I wanted off of the document. I have no RichText in these documents that I need, so this will work fine for now. With the view created I have the following function for loading a customer ‘document’:

function loadDetailsPageData(){
	var cat = ('00000000'+getURLParameter("documentId")).slice(-8);
	// Get the list data.
    $.getJSON('http://10.1.1.39/unplugged/rm.nsf/JSONCustomers?readviewentries&outputformat=json&RestrictToCategory='+cat,
    	function(data) {
	    	var doc=convertViewEntryToJSONObject(data);
	    	$('#companyNameTemplate')                  // Select the template. 
    			.tmpl(doc)              // Bind it to the data. 
    				.appendTo('#companyName');   // Render the output.
    		$('#companyDetailsTemplate')                  // Select the template. 
    			.tmpl(doc)              // Bind it to the data. 
    			.appendTo('#companyDetails');   // Render the output.

      		codeAddress();
       }
   );
}

function convertViewEntryToJSONObject(data){
	var obj = {};
	obj.noteid = data.viewentry[0]["@noteid"];
	for(var i=0;i<data.viewentry[0].entrydata.length;i++){
		obj[data.viewentry[0].entrydata[i]["@name"]]=data.viewentry[0].entrydata[i].text[0];
	}	
	return obj;
}

The document I want is loaded via a URL parameter just like before, and the URL I use is almost the same to get the JSON data. The only difference is that I added ‘RestrictToCategory’ to the url and used the NoteID as the parameter. This data returned by the view JSON is not quite what I want so the function convertViewEntryToJSONObject() takes the normal view JSON

and converts to another object that looks more like a document

Having this faux document object makes it much easier to create and maintain my templates in the html. My template can refer to actual field names just like in the Xpage version, instead of viewentry[0].entrydata[2].text[0]. Much easier to read.

Loading the map and displaying the address happen in exactly the same way, and I was able to use the same functions from earlier to do it. All I did was copy that code from the XPage and put it into the app.js file.

So this is a lot to cover in one post. I know it seems like a lot of code, but everything done today is laying the ground work for all the pages to come, so the rest should be much easier. Next time I’ll finish with this page and take a look at the footer and the links there.

We don’t need no stinking XPages!

With my mobile app complete, I wanted to see if I could start to decouple my UI from Domino and let it serve as just a backend data source. There are a couple reasons why I want to do this, which I’ll get to later, but for now I just want to stick with some more standard techniques.

Even though I’m going to be working with HTML files exclusively, I’m going to store them in my ‘Customers To GO!’ database for simplicity. At some point I would really like to get my design out of Domino Designer, but that will have to wait until later.

The Plan

The plan at the moment comes down to two main ingredients.

With these two things I think I can recreate the COG application with little change to the source data’s design. Remember the key goal here is to keep the UI completely Domino agnostic.

I’m going to walk though this in pretty much the same way I did with the XPages version and work through each page as I go. So the first page I’m going to work with is the customer list. I created a file resource in my database called ‘index.html’ and ‘app.js’. In addition to all the js script imports for JQuery, etc. the interesting part here is the body of my HTML:

<body>
	<div data-role="page" id="main">
		<div data-position="fixed" data-role="header">
			<h1>Customers On the GO!</h1>
		</div>
		<!-- /header -->
		<div data-role="content" class="companyList">
			<ul data-role="listview" data-inset="true" id="companiesContainer"></ul>
		</div>
	</div>
</body>

Much like the XPages version there really isn’t much to see here. To that, I’m going to add a <script> block that will contain my template that JQuery will use to do my replacements:

<script id="companyTemplate" type="text/x-jquery-tmpl">
	<li>	
	<a href="details.html?openfileresource&documentId=${$data['@noteid']}" >
	
	<span class='companyName'>${entrydata[0].text[0]}</span><br/>
	<span class='companyAddress'>${entrydata[2].text[0]}</span><br/>
	{{if entrydata[3].text[0].length}}
	<span class='companyAddress'>${entrydata[3].text[0]}</span><br/>
	{{/if}}
	<span class='companyAddress'>${entrydata[5].text[0]}, ${entrydata[6].text[0]}   ${entrydata[7].text[0]}</span><br/>
	
	</a></li>
</script>

So the way JQuery templates works is that you define a template like above and give it an ID and set the type to “text/x-jquery-tmpl” then inside that you place HTML that contains replacement sequences that some JavaScript will process later with data that you provide. Everything between the ‘{’ and ‘}’ refers to variable in the JSON object we get later. In my case I’m working with viewentry data so it’s the entrydata variable I want to access. Templates are actually powerfull enough that you can even include conditionally rendered parts using if statements or loops using the each statement.

For a more comprehensive tutorial on JQuery Templates check out this page. Or even just the examples in the templates doc.

For now, I’m going to place this script block in the <head> of my page.

The next thing I need is some JavaScript to do the replacements. I’m going to store almost all of my JavaScript in ‘app.js’.  This is what I have for the start:

/*
 * Everything starts here!
 */
$(document).bind("mobileinit", function(){
	$('#main').live('pageshow',function(event, ui){
		loadMainPageData();
	});
});

function loadMainPageData(){
	// Get the list data.
    $.getJSON(
        'http://10.1.1.39/unplugged/rm.nsf/ALLCOMPANIES?readviewentries&outputformat=json', 
            function(data) {
 
               $('#companyTemplate')                  // Select the template. 
               .tmpl(data.viewentry)              // Bind it to the data. 
               .appendTo('#companiesContainer');   // Render the output.
           }
       );
}

The first part is the JQM equivilant of JQuery’s $(document).ready() function. It let’s you bind actions to particular events that JQM raises. In this case, we can bind some init code to the pageshow event for the <DIV> with the id ‘main’. The function we want to call is loadMainPageData()

loadMainPageData() is where the heavy lifting for the template code does it’s work, though it’s pretty simple. First we use JQuery’s getJSON function to make a call to Domino and get my list of companies from CRM database (This is a view that already existed so double bonus!). Using the outputformat=JSON tells Domino to return the view in JSON (imagine that…). The second parameter to the getJSON call is a callback function that receives the JSON Object.

To actually perform the replacement you use the tmpl() call on the script block that contains the template you want to use - in this case the id I want is ‘companyTemplate’. This it’s just a matter of using appendTo() with the id of the DOM object where you want the new HTML to go - in this case ‘companiesContainer’. Then as they say, ‘Bob’s your uncle’!

Dang! OK, so here is the first thing that you will probably run into do this kind of thing with JQM and JQuery. Things don’t always happen in the way you want them to and stylizing lists is one of them. Because the HTML doesn’t actually contain any <li> tags, JQuery doesn’t make any changes to the underlying DOM when it sets up. Once the page is ready, only then do we add the <li> from the AJAX call to Domino. The work around is to tell JQuery to reapply it’s style markup to the list now that we have all the items in place. Simply adding the following call:

$('#companiesContainer').listview('refresh');

after the tmpl() call and all is well.

And that is pretty much it. This didn’t require any changes what so ever to the original applications and I ended up with exactly the result. So far, so good!

Do you want a license with that?

Moving on, we get to the License page. This lists all the licenses that the customer has for our tools. This is really handy especially when you are going on site for training or installation help as the customer has probably just purchased the tools and does not yet have them installed. The person on site typically has to call back to the office and have someone email the customer or the tech person the license details. It’s a huge pain if the person on site can’t get in touch with the office.

Once again we start with the same page template I developed earlier I once again drop a repeat control into the content section. This time however the data is coming from a different database, so I need to add a new datasource to the top of the XPage.

<xp:this.data>
	<xp:dominoDocument var="thisDoc"
		databaseName="server!!test/rm.nsf" action="openDocument"
		formName="CCLF00SS">
	</xp:dominoDocument>
	
	<xp:dominoView var="view1"
		databaseName="server!!test/licence.nsf"
		viewName="DBCompanyProducts" categoryFilter="#{thisDoc.fcftCompany_WB}">
	</xp:dominoView>
</xp:this.data>	  

The document object is created with the documentId url parameter. Then the view object is created. Notice that the categoryFilter attribute is computed based on the document object. This works out perfect since my license database already had a view that was categorized on the company name! The other nice thing about the view I am using is that the next column is a category also that splits the documents on the product the key is for. There are lots of times where a customer may have a number of licenses for the same product so grouping them is really helpful and I want to maintain that grouping in the mobile app. Here is where JQM can help out. There is a nice feature called ‘list dividers’ that I’m going to use. A list divider is basically just an <li> tag with the data-role=’list-divider’ attribute. The trick is how do you get the list divider only on a category?

JQM List dividers and categorized views

The way I did it was to use an <xp:scriptBlock> with a rendered attribute based on the note id of the current document.

<xp:scriptBlock rendered="#{javascript:lic.getNoteID()==''}">
	<li data-role="list-divider" data-dividertheme="b">
	<xp:text escape="true" id="computedField1"
			value="#{lic.fappName_LC}">
		</xp:text>
	</li>
</xp:scriptBlock>  

I used an <xp:scriptBlock> because the entire thing just goes away and doesn’t leave any trace if the rendered attribute is false. Other tags I tried left bits behind that would mess up the list. So this works great. I also added the data-dividertheme=”b” attribute to the <li> tag to give it a blue color.

Along with that script block I add another that works the opposite way:

<xp:scriptBlock rendered="#{javascript:lic.getNoteID()!=''}">
	<li>
		<a
			href="license.xsp?documentId=#{javascript:lic.getNoteID()}">
		<xp:text escape="true" id="computedField2"
			value="#{lic.fserKey_LC}">
		</xp:text>, 
		<xp:text escape="true" id="computedField4"
			value="#{lic.fserNumber_LC}">
		</xp:text>
		</a>
	</li>
</xp:scriptBlock>

With those two blocks in place I get a view that looks like this:

License Details

Just like the customer list page clicking on a license entry here will bring you to the license details page. This page isn’t fancy at all. I used a table to layout the data from the license document (passed in via the documentId url paramter) and a highlighted area to call out the license data itself along with an ‘email this’ button.

<div class="ui-body ui-body-e" >
	<h2><xp:label style="font-weight:bold" value="Serial Number" for="computedField7" id="label1"></xp:label><br/></h2>
	<xp:text escape="true" id="computedField7" value="#{thisDoc.fserNumber_LC}"></xp:text>
	<br/>
	<h2><xp:label value="Serial Key" for="computedField8"
			id="label5" style="font-weight:bold">
		</xp:label>
	<br/></h2>
	<xp:text escape="true" id="computedField8" value="#{thisDoc.fserKey_LC}"></xp:text>
	<br/>
	
	<a href="mailto:#{document1.fmctEMAddr_WB}?subject=#{thisDoc.fappName_LC} License Key&amp;body=Number: #{thisDoc.fserNumber_LC}%0AKey: #{thisDoc.fserKey_LC}%0A" data-theme="c" data-icon="grid" data-role="button">Email This</a>
</div>

The end result is this:

And there you have it! One last page to go and I’m done. Next up - Notes (not that one).

Now it starts to get interesting

One of my big design goals with this app is simplicity. As I said earlier, this app is really complex in Notes desktop form. I am not intending to make the mobile app a one to one translation of the desktop app. In fact, the mobile version will be down right boring in terms of features.

However, boring doesn’t mean not useful. I want this to be as useful as possible given the limitations the typical smart phone puts on an app. However, I still want make the most use of all the new capabilities that phones can bring to a person in the field. With each customer record I basically want the following things available to a user:

  • Details of the customer - Name, phone number, address
  • The Contacts we have with the customer at a particular location with names, phone numbers and positions.
  • The licenses for the products the customer has purchased. This is handy for knowing what they have for any up sell. It’s also handy so the user doesn’t have to phone the office again to have someone email them out - a common occurrence.
  • Notes that someone may want to add about the customer while on site.

That’s it. Short and simple - but very handy when traveling to a customer for a meeting.

At this point, I’m going to mock up most of that structure. To do this, I’m starting with a template XPage that I can copy around when I need to add new pages.

Footer

I am also going to add one more feature to the footer - A ‘navbar’:

<div data-role="page">
	<div  data-position="fixed" data-role="header">
	    <h1></h1>
	</div><!-- /header -->
	<div data-role="content">
	</div> <!--content-->
	<div  data-position="fixed" data-role="footer">
	   <div data-role="navbar">
		<ul>
		<li><a href="#" class="ui-state-persist ui-btn-active">Details</a></li>
		<li><a href="contactList.xsp?documentId=#{javascript:thisDoc.getNoteID()}">Contacts</a></li>
		<li><a href="licenseList.xsp?documentId=#{javascript:thisDoc.getNoteID()}">Licenses</a></li>
		<li><a href="notesList.xsp?documentId=#{javascript:thisDoc.getNoteID()}">Notes</a></li>
		</ul>
	   </div><!— /navbar —>
	</div><!— /footer—>
</div> <!—page—>

My navbar looks like this on the screen:

This lays out the basic navigation of the app and will be essentially the same across all the pages. I don’t want anyone getting lost.

The JQM navbar is made with an <ul> with a bunch of links. The highlight is determinded with the class=”ui-state-persist ui-btn-active”  in the first link. The rest of the links bring the user to different sections of the app and pass the NoteId of the current document in the URL.

Header

I’m also going to tweak the header. In the header I’m going to add a ‘Back’ button that will bring me back to the main page, and I’m also going to put the customer name in the title bar. The customer name is a piece of cake thanks to XPages with only the following needed inside the header <div>:

<h1>
  <xp:text escape="true" id="computedField1"	
	value="#{thisDoc.fcftCompany_WB}">
  </xp:text>
</h1>

The back button is similarly easy with this:

<a href="home.xsp" data-icon="arrow-l" data-iconpos="left">Home</a>

A few things to note with the link though. First is the attribute data-icon=”arrow-l” this tells JQM that the link should actually render the link as a button and use the ‘arrow-l’ icon (which is a left pointing icon). The attribute data-iconpos=”left” positions the icon image to the left of the link text. More details can be found here regarding header buttons. The result is this:

So, this post is getting kinda long, so I’ll stop here, but next is gonna be cool. I’m going to add some Google maps!

Adding JQuery Mobile to an XPage

So I now have a very basic view on the first page of my mobile app. The list is really kinda ugly though even though I did do a little bit of styling with a basic CSS file.

Since this is intended to be a mobile app I don’t want plain old links because those are hard to see and press when viewed on a small screen. This is where JQuery Mobile comes in. JQM is a smallish JS and CSS library that works to create pages that look and transition more like native phone apps do. It’s not iOS or Android specific in it’s behavior, but tries to create a native like experience that is the same across each of these devices.

To use JQM you need to do a few things to your markup according to the following documentation page. I’m using the hosted version, so to begin with I added the following to the source of my XPage:

<xp:script
	src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"
	clientSide="true">
</xp:script>
<xp:styleSheet
	href="http://code.jquery.com/mobile/1.0b1/jquery.mobile-1.0b1.min.css" />
<xp:script
	src="http://code.jquery.com/mobile/1.0b1/jquery.mobile-1.0b1.min.js"
	clientSide="true">
</xp:script>

The first is JQuery and that is needed by JQuery mobile. The second two are what you need for JQM.

The next step is to add the mark up that JQM uses to identify a page. It’s only a few lines:

<div data-role="page">
	<div data-role="content">
		<!-- <ul> and <xp:repeat> tags go in here --> 
	</div> <!--content-->
</div> <!--page-->

We still haven’t gotten anything new yet though. The page still looks the same. The big changes come when we apply a JQM style called “listview” to the list we created.

<ul data-role="listview" >

JQM uses the data-role attribute to apply it’s styles and things since there is more going on actually than just a style change. So now with just those little changes look what we have now!

Almost there!

Whoops! That isn’t quite what I was expecting! Turns out there is one more simple thing we need to do for this to all work right.

Simple now…. but this actually took a long time to figure out!

Back in the source to the XPage, remove the id=”repeat1” attribute form the repeat control. Once that was done I had what I wanted:

There is a lot more that JQM can do for us besides some simple list formatting. In addition it’s important to understand how JQm handles links and page transitions, which is what I will do next!