Tracking your XPages app’s usage with Google Analytics

google-analytics-logoOne of the latest requirements from one of my customers was to be able to track the usage of their application. My first though was (obviously): let’s use Google Analytics for that.

The application is almost completely built as a Single Page Application (SPA) using one of my favorite ExtLib components: Dynamic Content. That means that for every change in content, only a part of the page is updated. That also means that the URL of the page is never updated. Those two things are important to remember.

Including Google Analytics in an application is simple: just include the script block that you can get after setting up an Analytics account. It looks similar to this:

<!-- Google Analytics -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');
</script>
<!-- End Google Analytics -->

This script is standard, except for the line that says ga(‘create’, ‘UA-XXXXX-Y’, ‘auto’); In that line a unique identifier is set that identifies your own Google Analytics property.

When you change the content (facet) of a Dynamic Content component, it also automatically executes any JavaScript blocks that are part of the new content. That happens every time the content is shown again. Very convenient. So I just had to add the script block to all the content that I’m showing and wanted to track.

Now we’re left with only one problem. The Google Analytics script will automatically include the current URL and page title when registering a page view. But since the URL of the SPA is never updated, Google Analytics cannot register requests for different content. Luckily Analytics offers some additional features for Single Page Applications. By making an extra call to the ga() function (before the ga(‘send’, ‘pageview’) call is made) you can set (or fake) the current page’s URL and title:

ga('create', 'UA-XXXXX-Y', 'auto');
ga('set', { page : '/9F2', title : 'Title that identifies the dynamic content'});
ga('send', 'pageview');

The ‘page’ property identifies the ‘URL’. It doesn’t have to be a real URL, just use something unique that’s the same every time that specific dynamic content section is shown. The application features (amongst other things) a CMS where I’ve created documents for every unique page. I computed the ‘page’ property to be the document’s NoteID and the ‘title’ as the subject of the content.

Dropzone.js in XPages: it doesn’t get easier than this

After reading this question on StackOverflow by Daniele Grillo I decided to have a quick shot myself at integrating dropzone.js with XPages. Daniele already gave the solution to his issue himself (no bonus points for me today), but maybe others can benefit from a full example.

Source | Database

dropzone-logo

Dropzone.js is a JavaScript library that allows you to easily add a drop area to your web page where users can drop files. Files dropped there are automatically uploaded to the server. Integration is really simple: add the Dropzone JS file to your XPage (I’ve also included a Dropzone sample CSS file from their demo page for some styling), create an XPage and accompanying Java class to handle the uploaded files (see here), and write some JavaScript to enable and configure the dropzone:

<xp:scriptBlock id="scriptBlock1">
<xp:this.value><![CDATA[
Dropzone.autoDiscover = false;

var myDropzone = new Dropzone(".dropzone", {
url: "uploadHandler.xsp",
paramName: "uploadedFile", //used to transfer the file
clickable:true,
uploadMultiple:false,
maxFilesize: 2
});
]]></xp:this.value>
</xp:scriptBlock>

It doesn’t get easier than this!

Quick tip: updating URL hashes in XPages

I’m a big fan of the ExtLib Dynamic Content control. Use it in probably all my single-page XPage apps. Recently I was working on a page that had 2 nested Dynamic Content controls. I wanted to allow users to bookmark the page, including the state of both controls. I wanted to use a URL hash to do that. The useHash option apparently only supports one level, so I had to write a solution involving some client side JavaScript to update the URL hash. Here’s what I used to access the URL hash and update it:

//get the current hash value as a JavaScript object
var hash = dojo.queryToObject( dojo.hash() );

//add a parameter
hash['newParam'] = newValue;

//update the hash
dojo.hash( dojo.objectToQuery(hash) );

File uploads to Domino servlets (with an Angular demo)

(Updated: add required java security policy changes)

For one of my customers I’ve started working on an application that uses AngularJS for the frontend that talks to a REST API served by IBM Domino. For the REST API we decided use servlets: something that’s covered in great detail on Ed McCormick’s excellent blog. If you haven’t used servlets in a Domino database yet, go read some of his posts on this topic. Make sure you also take a look at the demo application he created containing examples of using servlets for a REST API.

One of the functions in the app (and probably in almost every app you’re going to work on too) is uploading files. Since I couldn’t find any info on how to do that with a servlet running on Domino, I decided to figure it out myself.

In case you’re only here for the code: download the demo app here. The GitHub repo is here.

Screenshot 2015-06-24 10.45.23

The demo shows how you upload files to the servlet and store it in documents in the NSF. I added a simple Angular front end that uses the angular-file-upload plugin to handle the uploads (MIT licensed). Out of the box that gives you multiple file select, image previews, progress bars and drag-and-drop. The code for the upload servlet can be found in the UploadServlet class in the database. Included in the demo database is also a very simple form showing that you can also use a

<input multiple="multiple" type="file" />

to upload multiple files in a single request. Something that (AFAIK) can’t be done on Domino if you’re going the XPages route.

My first attempt at the servlet was to use the same code I wrote a while age to process file uploads to an XAgent. That didn’t really work, because XAgents go through the XPages runtime and that does some pre-processing for you. In a servlet context you don’t get that: you have to work with the unmodified HttpServletRequest object. So I looked to see how the rest of the Java world was handling file uploads with servlets and discovered the Apache FileUpload project. That package abstracts the complexity of dealing with multipart/form-data. In a servlet context it gives you easy access to the uploaded files. So I added the required JARs to my database, read the docs and copied some sample code to get a handle on the uploaded file:

// Create a factory for disk-based file items
DiskFileItemFactory factory = new DiskFileItemFactory();
// Configure a repository (to ensure a secure temp location is used)
ServletContext servletContext = this.getServletConfig().getServletContext();
File repository = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
factory.setRepository(repository);

// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);

// Parse the request
List items = upload.parseRequest(request);

The items List in the code above contains all the items in the incoming request: note that every item can be either a form field or a file. You can differentiate between the two by using the isFormField() method.

Since I didn’t copy the required imports in the Java class, I ended up with some error messages about classes that couldn’t be resolved. Clicking on the errors revealed something interesting: I could solve the error by importing the Apache packages from org.apache.commons.fileupload (from the JARs that I just added to the database), but the list also showed a second option: use the com.ibm.xsp.http.fileupload package. Apparently Domino uses the same code under the hood to process uploads. That’s good stuff and means you don’t have to import any JARs to get the upload code to work 🙂

 

Screen Shot 2015-06-22 at 14.06.48

 

The rest of the code in the UploadServlet class in the demo database speaks for itself: I get the uploaded file(s) using the Apache FileUpload class and store all files received (in a single POST request) in the same document. Since the angular-file-upload plugin sends a request to the server for every file, they all end up in separate documents. Of course you can change this behavior by adding some logic to store them all in the same document, but I’ll leave that up to you.

One thing to note: the code in the demo database requires access to the getClassLoader method in Java, so you have to allow that in your Java security policy. Best practice on a Domino server is to create a file called java.pol in the <domino install>/jvm/lib/security/ folder and add:

grant {
permission java.lang.RuntimePermission "getClassLoader";
};

It was already in my settings, because a lot of other libraries need this too.

Enjoy!

 

 

Marky & Mark’s ‘mobile first’ ConnectED Sessions demo app

In about two weeks time IBM’s ConnectED takes off. We (that’s me and Marky Roden) were fortunate enough to be allowed to speak there with a brand new session titled “The Future of Web Development – Write Once, Run Everywhere with AngularJS and Domino”.

Screenshot 2015-01-14 10.12.56And of course there’s gonna be demos. Lots of them. But we wanted to make them useful too. So we decided to create a ‘mobile first’ Sessions demo app. Check it out at

and be convinced to come to our session!

A little about the app: it was built using AngularJS as the MVC framework. It uses Bootstrap for the UI (with the Bootswatch United theme to be exact). The data comes from a Domino database that’s exposed using the standard REST API from Domino Access Services. The session data comes from the Totally Unofficial Totally Unsupported IBM ConnectED Session Database by Mat Newman and others. Huge thanks to them for putting this together again!

Oh and if we haven’t convinced you to come to the session, listen to this:

Fun with Domino, AngularJS and CORS (not really)

For a mobile app I’m currently working on (more on that soon) I’m using Domino Access Services. After fixing the issue with the number of entries returned by a view entry service, I quickly ran into other issues.

I’m using a frontend build with Angular that’s running on a different domain name. So I have to add CORS headers (Cross Origin Resource Sharing). That’s easy: create a response document for the Internet Site in the Domino Directory and add an Access-Control-Allow-Origin header with a value of *. That worked Ok. For GET requests.

If you try to make a POST request, the default CORS behavior is to do a so called preflight request (before it sends the POST) in which the browser asks the target server what options it supports. It does this by sending an OPTIONS request. And that failed.

I first checked in the internet site if the OPTIONS method is allowed at all (tab ‘Configuration’ -> ‘Allowed methods’). It was, but I got an error that the Access-Control-Allow-Origin header wasn’t present, so I wasn’t allowed to make the request. That should have been taken care of by the website rule I created. Luckily I found a comment from Mark Barton here: turns out that the HTTP response code for OPTION calls is 204. If you think hard you might remember that you need to set response codes in the web site rule. In mine that only had 200 (Ok) and 206 (Partial Content) in it. I added 204 and… the OPTION request came through. I then ran into the next issue.

According to this the Content-Type request header is required for a POST request and needs to be set to ‘application/json‘. If you want to do that cross domain, the target server needs to allow you to set it. So I had to add another CORS header to the internet site: Access-Control-Allow-Headers: Content-Type.

All set now? Almost… When a new document is created, it responds with a 201 response code so I needed to add that to the internet site rule too.

And finally (for this part): the 201 response is sent without any content: it returns a response header named ‘Location’ that contains the location of the newly created document. Its value looks like this: http:///.nsf/api/data/documents/unid/.

To be able to read that I needed to add an argument to my success callback function (‘headers’) and use a call to headers(‘Location’) to read that header. Correct, but… remember that we’re working on different domains? By default response headers aren’t exposed to the originating domain, so here goes another response header in the web site rule: Access-Control-Expose-Headers: Location.

Can’t wait to see what CORS issues I’ll run into next (and I’m running out of web site rule response headers 🙁 – wonder if there’s a hack for that without installing a proxy and keep using the standard DAS).

This is what my website rule now looks like:

cors

Domino Access Services: ‘Limit Exceeded’ error for view entry collections

If you’re using Domino Access Services to get a view entry collection and include the count=XXX parameter (like described here), you’ll get an error if the number is larger than 100:

Limit exceeded.  Cannot read more than 1500 entries.

According to this, this limit was introduced with the 901v00_10 version of the Extension Library. I just ran into that limit myself and decided to do some digging in the ExtLib source. In one of the source files I found that the maximum can actually be controlled by a notes.ini parameter called DataServiceMaxViewEntries.

I just set that on my server and it works like a charm!

(not sure if this is a supported parameter, or if it will remain in the ExtLib, so you’re on your own with this…)

 

Bootstrap in XPages: now part of the Extension Library

bootstrap-logoToday IBM released a new version of the Extension Library (901v00_10 to be exact). The biggest news being that Bootstrap is now an official part of that plugin. They essentially took the Bootstrap4XPages plugin that Phil Riand and I have been working on and integrated it in the project. It’s great to see that IBM fully embraces Bootstrap to make XPages responsive (and good looking) and too see how a community driven project becomes part of the core product.

I already had a play with the new version and must say that Brian Gleeson and the rest of the team did an excellent job in extending the support of Bootstrap in the core controls: an area that could definitely need some work. They also improved the wizard to create a new application layout.

Unfortunately some parts of the plugin didn’t make it to the Extension Library, like the embedded Select2 support. I hope they’ll be able to add that again (and/or other useful Bootstrap plugins) in a future release.

If you want to see the new plugin in action: just visit Bootstrap4XPages.com. Good to know: the Select2 demos on that site are still powered by the Bootstrap4XPages plugin showing that that plugin can happily coexist with the Extension Library on the same server!

Deploying angular-seed to Heroku

heroku-LogoAngular-seed is an application skeleton for AngularJS apps. It helps you to quickly bootstrap new applications. I’m currently using it for an app I’m writing and wanted to deploy that to Heroku for testing. Since that wasn’t a very straightforward process, I thought I’d share my experiences here.

If you’re reading this, you’ve probably already cloned the repo from https://github.com/angular/angular-seed and wrote the app. Of course you can also just deploy the sample app that comes with angular-seed, but you’ll need to make a couple of changes to prepare it for Heroku.

bower-logoBower

Angular-seed uses bower (check package.json: it’s already listed there as a devDependency), but in order for Heroku to use it, we’ll need to add it to the dependencies section too:

npm install bower --save

We also need to update the path to bower in package.json:

"postinstall" : "./node_modules/bower/bin/bower install",

Express as a web server

Angular-seed uses http-server as the web server to test your changes, but I didn’t find a way to use that on Heroku. Instead, I just add a simple Express based static files server.

First we’ll add Express as a node module:

npm install express --save

Next create a new file in the root of your project named app.js to serve static files from angular-seed’s app folder:

var express = require('express');
var app = express();
app.use(express.static(__dirname + '/app'));
app.listen(process.env.PORT || 3000);

Define a Procfile

We need to tell Heroku what it needs to do to start your app: create a file named Procfile in the root of your project and add:

web: node app.js

This tells Heroku to launch node using the app.js file.

Heroku

We’re ready to push our app to Heroku. Log in to Heroku and create an app. Install the Heroku toolbelt on your computer so we can call heroku commands from the command line.

To send your project to Heroku, we’ll need to add Heroku as a git remote

heroku git:remote -a <your_app_name>

Next, commit all the changes we’ve made in the local project and send your project to Heroku:

git add .
git commit
git push -u heroku master

In the terminal you’ll see your app being deployed, compiled and started on Heroku. You can now view it using the shortcut command ‘heroku open‘.

Screen Shot 2014-10-07 at 15.55.53

There are probably different ways to do this, but this method worked for me. Please let me know if this worked for you too or if you would do it different!

Escaping the yellow bubble at ICON UK

IMAG1819
Last Friday I was in Londen for what was yet again an excellent ICON UK. Tim Clark really did a great job in organising it and I hope he can now relax again. Kudos also to Tony Holder for organising the speaker dinner at Wilton’s. Didn’t know you can eat that well in the UK!

The venue (IBM Client Centre) added a nice touch to the conference and was conveniently located at the center of Londen. That allowed me to try out a Boris Bike and see what cycling on the left side of the road is like. Straight sections didn’t cause me any problems, but sweat ran down my back at every crossing. I’m glad I’m still alive to write this. But hey: no guts, no glory!

I was once again allowed to host a session and decided this time to NOT do it about XPages or any other IBM technology, but share my experiences with an alternative web development stack called MEAN (that’s MongoDb, Express, AngularJS and Node). Thanks to anyone who was in the room for attending. For all of you who weren’t, here’s the deck: