Notes on Mobile
Using source control in Lotus Notes

Niklas Heidloff recently blogged about how to use the new SVN support in Notes 8.5.3. However, something really stood out right at the begining:

There are three “repositories” that need to be kept in synch:
1) The server side managed SVN project 
2) The client side on disk project under SVN control 
3) The NSF project (using a virtual file system) 

I have to remember to keep 3 different copies of my NSF in sync?!?! (I’m not going to even mention the DXL aspects of this whole thing either) Don’t get me wrong, It’s great that IBM is finally getting around to doing this after 20 years, but it seems really complicated to me. Of course, there is a much easier way. A way that has been around for 10+ years. A way that doesn’t require any thing else complicated to manage like a subversion repository. Teamstudio CIAO!

Using Teamstudio CIAO! for XPages development

So how easy is it to add a database to source control using CIAO!? Once CIAO is installed, it takes only 3 steps.

Step 1. Open your database in Domino designer and click the CIAO icon in the toolbar.

Click yes

Step 2. Enter in the project information. Only the path to the CIAO! Log is required.

Step 3. Enter in a comment for the initial version of this database.

Relax a bit….

Done. That’s it! You can now check out and check in from the CIAO! dialog or if you are using Domino Designer 8.5.1 or higher you can check things out right from the application navigator in Designer!

Also, just like with the Subversion support in 8.5.3, you you can see what elements are checked out by other people on your team.

Finally here is a video that walks through the entire process. Don’t worry though, it only takes 1 minute and 13 seconds from start to finish…. Don’t try that with Subversion!

Getting the URL parameters in a Domino Java web agent

In my dataProxy agent I needed an easy way to get all the URL parameters. I figured I would post that in a separate entry so it wouldn’t get lost.

	private HashMap getURLParameters() throws NotesException {
        String url = agentContext.getDocumentContext().getItemValueString("QUERY_STRING");

        String[] vals = url.split("&");
        HashMap m = new HashMap();
        for( int i = 0; i < vals.length; i++){
        	String[] p = vals[i].split("=");
        	if( p.length == 2 ){
	        	try {
					m.put(p[0], URLDecoder.decode(p[1],"UTF-8") );
				} catch (UnsupportedEncodingException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
        	}        	
        }
		return m;
	}

I can then save them in a member of my agent as a HashMap like param.get(“db”) or param.containsKey(“db”) and can get at them where ever I am.

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.

Thanks for nothing…

Well that was a total waste of a morning. Long story short was that I wanted to be able to create documents in a Domino database, but not require a form to do it. Naturally I created an agent that my HTML form could post to. All was going well, but I decided that I also wanted to be able to attach an image as well as some simple form fields. So, after a little more work, my agent reading the ‘multipart/form-data’ with the help of the Apache commons fileupload library. That itself was a bit of a trick since all the examples assume you are using it in a servlet context. There is a way however, to use the agent’s DocumentContext document instead of the HTTPRequest object. All this was working fantastic… only when I started looking at the attachments I created did I discover a huge(!!!) problem. My images were corrupt. At first I though maybe I didn’t have something set right with the Apache code, but alas, after wasting an entire morning, it turns out Domino was to blame. On top of that, it apparently has been a problem for a long time and is still, broken on 8.5.2.

What is happening is that when Domino writes the binary data coming from the HTTP request to the DocumenContext document, it converts the contents to LMBCS and thus totally messes up my binary POST data. There is nothing I can do to get access to the original data, short of writing a actual servlet - which probably won’t work anyway.

ARRGGG!!!!!

Fine. Lesson learned. Domino is not a good HTTP server. Ya I know there are easier ways to do this with forms and file upload controls and what not but that’s not the point though. This stuff should work.

Notes documents as JSON

There are a couple of ways to do this online, but having a Java agent to do view JSON gave me a good opportunity to do it my own way. I have a couple things going for me at the moment - I don’t need rich text (yet), and I don’t need to be able to create or update documents (yet).

The ‘Document’ class in Java has a function called generateXML which has been around for a long time (5.0.3 according to the doc) so it should be relatively stable, but as with all things Domino… we shall see if it is in practice. That got me the DXL version of a Notes document. To get the JSON I went to github and got the org.json classes and imported them into a Java script library in Domino:

This has a lot of really handy classes in it, but what I really wanted was the XML class that has a toJSONObject method. With all these pieces, it was really easy to put the following method together:

    private void writeDocument(Database db, String documentId, boolean asJSON) throws NotesException, IOException, JSONException {
    	Document doc = db.getDocumentByID(documentId);
    	if( null== doc){
    		doc = db.getDocumentByUNID(documentId);
    	}
     	if( null== doc){
     		System.out.println("document not found");
     		return;
     	}
     	
		// write the document out
		java.io.Writer sw = new java.io.StringWriter();
     	doc.generateXML(sw);
     	sw.flush();     
		PrintWriter ao = getAgentOutput();
		if( asJSON ){
			ao.println("Content-type: application/json");
			ao.print(XML.toJSONObject(sw.toString()).toString());
		}
		else{
			ao.println("Content-type: text/xml");
			ao.print(sw.toString());
		}
     	ao.flush();
    }

I threw that into the dataProxy agent and added 2 URL parameters:

  • documentId= Lets me specify the document I want to access
  • outputformat=JSON Omitting this parameter keeps the output in DXL.

That’s it! With the next post I’ll show you how to integrate this into the mobile app.

Working around Domino

Seems like I do this a lot… so after running into a bug in the Domino JSON view output I was faced with a few choices. The first, and probably easier option, was to just rewrite the views I was using to fix the encoding problem I ran into. I ended up not using that option because it would mean that I would have to have custom views for all the data I was accessing. Even though I had created the views new for this exercise, ultimately I wanted to transition to preexisting views in the apps once I was ready to move to production. So now that I was faced with having to make design changes to the back end apps anyway, I decided to explore my other option which was to create a custom agent to serve up the data.

A custom agent has a couple disadvantages. First, it will require some special considerations when it comes time to deploy this regarding ACLs and agent signers. Second, it means that I have to re-implement something that already exists in Domino. The first one is just a fact of life and can be dealt with. The second one is more problematic because, I hate doing it, and I won’t be able to implement all the possible options and it will require a lot more testing.

On the up side, a custom agent does have many advantages also. First, I can make the output anything I want, or include as much or as little of the data. Second, I now have a chance to standardize an API that I can easily port/re-implement on other back-end systems without impacting my front end (big plus at the moment). Third, the ReadViewEntries does not support JSONP, which might come in handy at some point in this project I’m sure. And Finally, it can give me access to data a view cannot, like rich text. With these in mind the choice to custom code the agent became much easier to accept.

The dataProxy Agent

I wrote the agent in Java because LotusScript is just no fun anymore, and it gives me the most flexibility when I comes time to port this to some other platform. The basics of the agent was to try and get as close to the ReadViewEntries format as possible so I took the output from that and loaded it up in a text compare program so I could see it side-by-side with my output. Form there I just tweaked my code until I had the same, or nearly the same output. In about an hour I had the following code:

	private void writeViewJSON(Database db, String viewName) throws NotesException {
		// get the view
		View v = db.getView(viewName);
		v.setAutoUpdate(false);
		// get the navigator
		ViewNavigator nav = null;
		if (params.containsKey("RestrictToCategory")) {
			nav = v.createViewNavFromCategory((String) params.get("RestrictToCategory"));
		} else {
			nav = v.createViewNav();
		}

		// write the view out
		PrintWriter ao = getAgentOutput();
		ao.println("Content-type: application/json");
		ao.println("{");
		ao.println("\"@timestamp\": \"" + v.getLastModified() + "\",");
		ao.println("\"@toplevelentries\": \"" + v.getTopLevelEntryCount()
				+ "\",");
		ao.println("\"viewentry\": [");
		ViewEntry e = nav.getFirst();
		for (int i = 0; i < nav.getCount(); i++) {
			ao.println("{");
			ao.println("\"@position\": \"" + e.getPosition('.') + "\",");
			ao.println("\"@unid\": \"" + e.getUniversalID() + "\",");
			ao.println("\"@noteid\": \"" + e.getNoteID() + "\",");
			ao.println("\"@siblings\": \"" + e.getSiblingCount() + "\",");
			ao.println("\"entrydata\": [");
			int writeCount = 0;
			for (int j = e.getIndentLevel(); j < e.getColumnValues().size(); j++) {
				ViewColumn c = (ViewColumn) v.getColumns().elementAt(j);
				if (!c.isHidden()) {
					if (writeCount++ > 0) {
						ao.println(",");
					}
					Vector cv = writeColumn(ao, e, c);

					if (c.isCategory()) {
						break;
					}
				}
			}
			ao.println("]");

			ao.println("}");
			if (i + 1 < nav.getCount()) {
				ao.println(",");
			}
			ao.println();
			e = nav.getNext(e);
		}
		ao.println("]");
		ao.println("}");
	}

Some of the differences, though minor, are to do with categories and the NoteId and UNID. In the Domino output you get special NoteId’s and in my version they are just empty strings. Also, the timestamp that I output is a different format than the domino version.

The agent supports a few URL parameters:

  • s= Lets you specify the server.
  • db=   Lets you specify the database the view is defined in.
  • v= Lets you specify the view to read.

using these, I can get to any view in any database.

That was all I needed at this point on the server. On the client side, it only required a single change to the Javascript to change the URL in the JSON calls. 

Not the end of the story…

This isn’t the end of the agent though. I have also added another function that makes a huge difference in the client that I didn’t have the ability to do before. I will cover that in my next post.

JSON bug….

OK… this project isn’t as easy as it was yesterday…. So in the process of creating the contacts page I found a bug in the JSON output for my view. It really wasn’t easy to find either… 

I started as I normally do by creating the basic page template and JS code that I have done for the past two pages. I ran into a problem when my getJSON call wasn’t returning any results (the success callback wasn’t being called). I took the URL and pasted into a browser and every looked fine. The server was the same, so no cross domain issues. No errors on the server console. ACL was the same as the other database…. what was going on!

Reading though the doc on JQuery.getJSON I found this note:

Important: As of jQuery 1.4, if the JSON file contains a syntax error, the request will usually fail silently. Avoid frequent hand-editing of JSON data for this reason. JSON is a data-interchange format with syntax rules that are stricter than those of JavaScript’s object literal notation. For example, all strings represented in JSON, whether they are properties or values, must be enclosed in double-quotes. For details on the JSON format, see http://json.org/.

Hmmm….. could this be my problem? I went back to the output I got from the browser window and pasted it into a JSON validator and sure enough there was a problem.

Parse error on line 77:
...               "0": "Teamstudio CIAO\! C
-----------------------^
Expecting 'STRING', 'NUMBER', 'NULL', 'TRUE', 'FALSE', '{', '['

So it looks as if Domino is trying to escape a the ‘!’ character when it shouldn’t be. This won’t fly with the JSON.parse call that JQuery.getJSON uses.

CRAP!

I have to say, I seem to run into this way too often with Notes and Domino. There are so many useful features that are just not robust enough to use. Even worse is most of the bugs you find are not easily worked around and you never seem to find them until you are a week into a project. It is extremely frustrating!

I have a few alternatives that I can explore so unless anyone has any ideas how to fix this, I think I’m gonna ditch the views and switch to an agent. I think that even though the agent is more work, there are a lot of cool possibilities that it offers that wouldn’t have been possible with just a view. Though, I really did like the simplicity and ease of reuse the views offered…. they just don’t do what they advertise.

Small tweak to JS from yesterday

In prep for tomorrows post, I made a few tweaks to the Javascript I posted yesterday. I’m posting this now since I think the post tomorrow will be long enough, and I’m not ready to write it up yet. This function is handy in a general sense also, so this way it doesn’t get lost in a longer post.

To get the contacts list I needed to make a few changes to the JS functoin convertViewEntryToJSONObject:

function convertViewEntryToJSONObject(data){
	var entry = [];
	for( var j=0; j<data.viewentry.length; j++ ){
		var obj = {};
		obj.noteid = data.viewentry[j]["@noteid"];
		for(var i=0;i<data.viewentry[j].entrydata.length;i++){
			var prop = data.viewentry[j].entrydata[i]["@name"];
			var val = "";
			if('undefined'=== typeof data.viewentry[j].entrydata[i].text){
				val = data.viewentry[j].entrydata[i].textlist.text[0][0];
			}
			else{
				val = data.viewentry[j].entrydata[i].text[0];
			}

			obj[prop]=val;
		}
		entry.push( obj );
	}
	return entry;
}

The version I needed yesterday worked well when I only got one view entry back. Now it works with views that return multiple entries. This should make the view object I get back a lot easier to work with long term. 

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!

Customers on the GO! - XPages Conclusion

Now that the XPages version is done, before moving on, I wanted to put my overall thoughts down. So even though it took almost 2 weeks to write up all the posts, the actual app didn’t take more than a few afternoons of work to put together. My mission here was to simply extend our existing Notes CRM application and not transform it. There was no need to bring a decade of Notes crufty-ness to the web with XPages - that would have been a monster project and by the time it was done no one would have cared. Instead the goal was to get access to the few pieces of information already stored in the app when employees are on the road and don’t want to fire up laptops and Notes clients.

The Good

I was actually quite happy with how easy it was to get all the pages themselves in place and with just a few XP tags I got a page that looked pretty much exactly how I wanted it to. I really like the fact that the design of the COG app is in a completely different database form the original CRM app and the License app. It makes it really easy to extend existing apps without getting in other peoples way. 

The Bad

I just don’t like Domino Designer. I use Eclipse on a regular basis and DDE is just really flakey. It’s slow and buggy and the focus seems to jump around like crazy and I end up clicking where I don’t want to. I also don’t like how it takes way too many clicks, on targets that are way too small to get to a code window. Seriously could IBM have made that little blue diamond and smaller or harder to find?!?! So as a result of this, I generally spend all my time in the source tab of the XPages editor being driven crazy by the formatting.

The other bad thing about working with XPages is that they are just not well documented, or the doc that does exist is simply too hard to find. Maybe it’s me, but I can never seem to easily find how to accomplish a simple task. Compare that to the wealth of info on JSP, JavaScipt or PHP, and I don’t know why I would ever want to spend all my time in XPages.

So all that being said, using XPages to take an old crusty Notes client app to the mobile web was actually pretty easy and worked out well in this example. Of course, this app was a bit simplistic in it’s goals, but I don’t think a mobile app needs to be complicated to be useful.

To see all the posts related to this version of the app you can click on the ‘Customers on the GO!’ link in the right hand side bar, or follow this link. Thanks for reading so far, and stay tuned because there is much more coming!