Blog

Dojox Charting Update – Part Two

  |   Blog   |   4 Comments

First of all, with the demo database, there are a couple of basic, standard changes. The first is that I’m using the XPages extension library, so that’s enabled in the Xsp Properties. That’s used for the Java code, to leverage the ExtLibUtil class to give easy access to standard XPages objects and virtually every Java XPages developer will be using ExtLibUtil. Whether or not you use Java in XPages is a matter of choice, personally I moved to using Java for anything significant after my first production XPages application, now I rarely write more than a handful of lines of SSJS in a block. But regardless of SSJS or Java, I would not be happy working on any XPages application now that does not use the extension library.

The second is I’ve removed the beforePageLoad code covering Internet Explorer 8. Internet Explorer 9 was released March 2011. New XPages applications should target at least that version, in my opinion. If a more modern browser is not available, there will be other applications that cannot be used. As of June 2017, according to NetMarketShare, IE 8 has 1.62% market share (indeed IE9 has 1.07%). One wonders how much of that 1.62% have only Internet Explorer 8.

The first big difference is no resource added to the XPage to load Dojo modules. That is no longer necessary. But we still have two divs, one for the chart and one for the legend. I’m using xp:divs, which is what you’ll want if you are using a custom control, in order to ensure unique IDs for DOM manipulation. I’ve also got an xe:objectData at the top of the page. There are various methods for accessing Java code – static methods in a utility class, managed beans, controller classes backing your XPage, or an xe:objectData as here. How the XPage gets to the Java code is not an important point here, more is that this maps to a Java class called uk.co.intec.ChartSample in the Code/Java area of the application.

The key, as before, is the Output Script block that writes (client-side) JavaScript to the browser to load the chart. The Output Script component just has the JavaScript in the value property of the component, so I’ll just show the JavaScript, for better clarity.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
require([
     // Require the basic chart class
    "dojox/charting/Chart",

    // Require the theme of our choosing
    "dojox/charting/themes/PlotKit/blue",

    //     We want to plot Lines
    "dojox/charting/plot2d/Lines",
    
    //		Add grid
    "dojox/charting/plot2d/Grid",

    // Load the Legend, Tooltip, and Magnify classes
    "dojox/charting/widget/Legend",
    "dojox/charting/action2d/Tooltip",
    "dojox/charting/action2d/Magnify",
    "dojox/charting/widget/SelectableLegend",
    "dojo/fx/easing",

    //    We want to use Markers
    "dojox/charting/plot2d/Markers",

    //    We'll use default x/y axes
    "dojox/charting/axis2d/Default",

    // Wait until the DOM is ready
    "dojo/domReady!"
], function(Chart, blue, LinesPlot, Grid, Legend, Tooltip, Magnify, SelectableLegend, easing) {

    // Create the chart within its "holding" node
    var chart1 = new Chart("#{id:simplechart}");

    // Set the theme
    chart1.setTheme(blue);

    // Add the only/default plot
    chart1.addPlot("default", {
        type: LinesPlot,
        markers: true,
        tension: 3,
        animate: {duration: 300, easing: easing.quadIn}
    });
    
    // Add a grid
    chart1.addPlot(Grid, {type: "Grid"});

    // Add axes
    var varNames = #{chartSample.axisXLabels};
    var funcLabels = function(text, value, precision) {
    	return varNames[value];
    }
    chart1.addAxis("x", { labelFunc: funcLabels, max: 7 });
    chart1.addAxis("y", { min: 0, vertical: true });

    // Add the series of data
    var data = #{chartSample.chartData};
    for (var key in data) {
    	chart1.addSeries(key, data[key]);
    }
    
    // Create the tooltip
    var tip = new Tooltip(chart1,"default", {
    	text: function(o) {
    		return o.run.name + " - " + o.y;
    	}
    });

    // Create the magnifier
    var mag = new Magnify(chart1,"default");

    // Render the chart!
    chart1.render();

    // Create the legend
    var selectableLegend = new SelectableLegend({chartRef: chart1, autoScale: true, horizontal: 8, style: "width:100%"}, "#{id:Legend}");
});

First, in lines 1 – 29 is a require call. This loads all the relevant Dojo modules and their dependencies dynamically from the JavaScript itself. This is a much better approach than having separate markup, it’s the typical Java approach and one that has become standard for many JavaScript frameworks. Particularly noteworthy is line 28, using dojo/domReady. This means the function loads when Dojo has finished. So there’s no longer any need to use XSP.addOnLoad().

Then comes the function itself, as the second argument passed to the require function. So we’re still creating a function variable to load the chart, it’s just that instead of passing it to XSP.addOnLoad() we’re passing it to require. But this time the function takes a number of arguments, namely the modules we’re requiring.

One difference is that when adding a plot, instead of using a String for the type property, we’re mapping directly to the Dojo module passed in as an argument to our function, for example in lines 39 and 46. Strings still seem to work (I didn’t notice I’d still got type: "Grid"), but it makes sense to pass the Dojo module object. Another new element added here (I think it was added in Dojo 1.6) is that we’re adding settings for the animate property on line 42, to add animation to the loading of the line plot.

In lines 49 – 53 we’re adding custom labels to the x axis. There are a couple of ways of doing this by passing a JSON object in or a label function, as here. However, if you look at all the JSON objects in this Dojo code, you’ll see the key is a variable, not a string. So for the x axis, it’s labelFunc and max, not "labelFunc" and "max". The Java code we’ll see later for creating the JSON objects makes the key a String, so the latter, which won’t work. So we have to use a label function. varNames is just a JavaScript array, so our function just gets the relevant index from that array for the tick on the x axis. varNames and data are populated from getters in the Java class, so I’ll just gloss over those for now. The data is a JSON object where the key is the name of the series (it also appears in the legend) and the value is an array of points to plot on the y axis. So we can just use a for loop to iterate over them and return the value, the array of points on the y axis.

One important point to note here (and I’ve not fully understood the reasons), but if you’re using tooltips on the chart, you need to enable “Use runtime optimized JavaScript and CSS resources” in the Xsp Properties. I’m not sure if there is something different in the files when they’re not combined into a single JS file, because the tooltip still works fine for the old format of the chart. However, bear in mind that with certain JavaScript files not designed to use AMD loading, enabling the “Use runtime optimized JavaScript and CSS resources” setting can impact this (files don’t necessarily get compiled in the order they’re placed on the page).

The Magnify widget on line 70 just magnifies the point when you hover on it and display the tooltip, same as with the previous version of the chart. Rendering the chart is the same as before.

Instead of the normal Legend, this time I’ve used the SelectableLegend widget on line 76. This puts checkboxes beside each series in the legend, allowing you to show or hide each without needing to re-draw the whole chart. One point of particular note is the horizontal property. In previous versions this was set to false and I wrote a Dojo widget extension specifically for my Lotusphere 2010 session on Dojo in order to allow it to be multi-column. When I started looking at the SelectableLegend widget I wanted to see if it supported multiple columns in a horizontal format. When I read the code this time, it became apparent that it’s not a boolean vallule but an int value, corresponding to the number of columns to display in a horizontal layout. So here you’ll see it’s 8, giving eight columns. The same is also true of the Legend.

Documentation is a bugbear of mine. Here again it rears its head. The API documentation lists all the properties of the SelectableLegend and Legend widgets, and includes the horizontal property. But it doesn’t specify what the expected values are. I don’t know whether something has changed in how this propertty works or whether I guessed wrong when I read the documentation back in 2009. But I had to guess and I guessed wrong.

Please let me be very clear here, this is not a criticism of Dojo’s documentation. The same criticism is valid for all sorts of APIs of all languages. It’s a criticism that’s borne out of the high quality of IBM’s LotusScript documentation in the past. For Dojo, this is JavaScript, but the same criticism is often valid for Javadocs. OpenAPI specification is praised by many now as a “must-have” for REST services, but having it or being able to automatically generate an OpenAPI specification won’t avoid the criticism either. Again, out-of-the-box an OpenAPI specification lists endpoints and parameters. It doesn’t tell you what those parameters should contain or anything else. The boast for any language, product or REST service technology (Domino or otherwise) to auto-generate documentation will never impress me unless that functionality generates errors or at least warnings if insufficient clarity has been included.

I said before that I would come back to the Java code that generates the JSON. It uses two classes that have been used in Domino Access Services, com.ibm.commons.util.io.json.JsonJavaArray and com.ibm.commons.util.io.json.JsonJavaObject. First let me show the code for the x axis labels.

JsonJavaArray jjo = new JsonJavaArray();
jjo.add("");
jjo.add("New");
jjo.add("Closed");
jjo.add("Open");
jjo.add("UAT");
jjo.add("Maint");
jjo.add("Long");
axisXLabels = jjo.toString();

This code is very straightforward. The JsonJavaArray acts like any Java List, so we just add elements to it. To output as JSON, we just call the toString() method. Using this instead of manually generating JSON for a list is an absolute no-brainer!

The code for the chart data is a bit more complicated, but not significantly so. Note, it doesn’t use ODA so I had to remember what to recycle and to ensure I didn’t create an infinite loop. I always use ODA now and I would strongly recommend anyone else to do so as well. Here is the code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
try {
	Database currDb = ExtLibUtil.getCurrentDatabase();
	View view1 = currDb.getView("liveCallsByClientContractCat");
	ViewNavigator nav = view1.createViewNav();
	ViewEntry ent = nav.getFirst();
	JsonJavaObject jjo = new JsonJavaObject();
	// Please, please, please, for your sanity, use ODA!
	while (null != ent) {
		Vector<Object> colVals = ent.getColumnValues();
		Double newCalls = (Double) colVals.get(3);
		Double closed = (Double) colVals.get(4);
		Double open = (Double) colVals.get(5);
		Double uat = (Double) colVals.get(6);
		Double maint = (Double) colVals.get(7);
		Double longCalls = (Double) colVals.get(8);
		// We only want to add series if there is anything to report and don't want the final total row 

		if ((newCalls + closed + open + uat + maint + longCalls) > 0 && !ent.isTotal()) {
			ArrayList<Double> series = new ArrayList<Double>();
			series.add(newCalls);
			series.add(closed);
			series.add(open);
			series.add(uat);
			series.add(maint);
			series.add(longCalls);
			jjo.put((String) colVals.get(0), series);
		}
		ViewEntry tmpEnt = nav.getNextSibling(ent);
		ent.recycle(colVals);
		ent.recycle();
		ent = tmpEnt;
	}
	chartData = jjo.toString();
} catch (Throwable t) {
	t.printStackTrace();
}

We get the data as we nornally would – I won’t cover that. But we create a JsonJavaObject at line 6 to hold our map of series. But notice we create an ArrayList for the series data itself. We don’t create a JsonJavaArray – we don’t need to. That’s because you can put Java Maps and Java Lists into a JsonJavaObject (or indeed a JsonJavaArray) and the toString() method will automatically convert them out. You just need to use JsonJavaObject or JsonJavaArray at the top level. Here we need a series label, so we use a JsonJavaObject (which corresponds to a Java Map), passing the series label as the key and the List of y-axis points as the value. Then we call the toString() method at the end. Again, this is much easier than manually constructing JSON data.

The only caveat is to be wary of dates. The in-built conversion in Domino 9.0.1 doesn’t include timezones properly, but if that’s not a problem, it’s fine. The alternative is to convert dates to the correct ISO format before you add them to your JSON.

I think this is a massive improvement over the original code I used, which is why I’ve took the time to update the series. The sample database is attached below.

Charts

AUTHOR - Paul Withers

Paul Withers has been an IBM Champion since 2011, has been an OpenNTF Board Member since 2013, has worked with XPages since 2009, co-authored XPages Extension Library and was technical editor for Mastering XPages 2nd Edition. He is one of the developers on OpenNTF Domino API as well as contributor to a variety of other OpenNTF projects.

4 Comments
  • Cameron Gregor | Aug 10, 2017 at 1:20 pm

    I find the dojo documentation quite hard to use. it often feels like it has everything there except the one thing i am trying to find!
    lotusscript doc was/is fantastic with well document properties/methods and many relevant examples.

  • Mark Maden | Nov 14, 2017 at 10:31 pm

    Perfect, exactly what I was looking for, thank you Paul.

    Is dojo dead, should would you recommend jquery?

    I have never used xe:objectData before, are there pros and cons when compared to say a managed bean?

    • Paul Withers | Nov 15, 2017 at 8:37 am

      Glad it helped. Dojo isn’t dead, but it’s not as popular and widely used as jquery. I don’t have much experience of jquery, but Dojo does have strengths in terms of the components and allowing them to be extended, in a similar way to a Java class can extend an existing Java class or an OSGi plugin can use other plugins. So it uses the code of whatever it extends and you just add additional. It has been heavily used across a variety of IBM technologies and IBM have contributed code back into the core. So that may also be a factor for decision-making. It can cause problems because it uses AMD loading, which some other JavaScript libraries don’t work well with. But the workarounds for disabling and re-enabling AMD loading are well-documented, and you can’t easily disable all of Dojo and still get the benefits of XPages (e.g. partial refresh). But most developers use jquery for their own JavaScript on an XPage and, with the bootstrap libraries available in more recent versions of the Extension Library, that doesn’t seem to have any technical limitations.
      xe:objectData and a managed bean are quite similar, except with a managed bean an instance is created when the relevant scope is first used. It uses a contructor that doesn’t have any parameters, but you can set default values for properties in the faces-config. It’s also automatically removed when that scope ends. xe:objectData is scoped to a smaller area – the highest is your XPage, but it could be on an individual Panel deeply embedded in your XPage hierarchy. And the createObject property is what you use to tell it how to initialise the object – you just point it to the constructor (return new my.package.objectClass()). Obviously it’s gets trickier for using it across pages, but it could be done by e.g. retrieving the object from sessionScope in the contructor or, if it doesn’t exist, creating it and adding it to sessionScope.

  • Mark Maden | Nov 15, 2017 at 11:57 am

    I think I will attempt to use the jquery charts as they seem more ‘polished’.

    The ‘scoping’ to a smaller area really clarifies when to use xe:objectData for me.

    Once again Paul many thanks.

Post A Comment