Speaking at Engage: “Dev04. XPages and Office 365 development – more familiar that you think”

I am very proud to be speaking at the Engage 2016 User Group conference, in Eindhoven March 24th

Thursday, March 24 | 09:00 – 10:00 | D. Saturn

This is a very interesting session for me personally because it reflects how my job has allowed me to evolve into areas of development which are not just XPages. Don’t get me wrong, I am far from done with the XPages world, but with a lot of our customers having a mixed Domino and Office 365 environment the past year has presented some new challenges and opportunities.

I always believe that a picture is worth more than a thousand words and in this session I will show many demonstrations of what is possible and go into some details of how O365 development and modern Domino web development are really very similar.

I look forward to seeing as many people as possible bright and early on march 24th 🙂

Abstract

Many companies run a mixed IBM Notes and Microsoft Office 365 (O365) environment. SharePoint/Outlook and Domino should be viewed as a new opportunity to create rich and engaging user experiences. Using both IBM and Microsoft REST services as the core to the solution, this presentation will show how both technologies stacks can be integrated to maximize application functionality and present a seamless experience to the user. Mark will provide lots of demonstrations including Office Web Add-Ins, how to access O365 data from your Domino applications and many others. Come and see how your core Domino webdev skills are equally applicable to the Office 365 environment.

IBMSBT in XPages: My Communities

In this article I will describe how to display a list of “My Communities” in an XPage. To do this I will have to create an ArrayList of communities and use a repeat control in an XPage to display them.

Getting My Communities

As I showed in this previous blog post there were some interesting issues in setting up the managed beans for getting the communities out of connections/smartcloud.

But in quick review this is the code for getting my communities.

package com.xomino.sbt;

import java.util.Collection;
import javax.faces.context.FacesContext;

import com.ibm.sbt.services.client.ClientServicesException;
import com.ibm.sbt.services.client.connections.communities.Community;
import com.ibm.sbt.services.client.connections.communities.CommunityList;
import com.ibm.sbt.services.client.connections.communities.CommunityService;
import com.ibm.sbt.services.client.connections.communities.CommunityServiceException;
import com.ibm.sbt.services.endpoints.Endpoint;
import com.ibm.xsp.extlib.util.ExtLibUtil;

public class Utils {

	public static void checkAuthentication() throws ClientServicesException{
		Endpoint theEndpoint = getEndpoint();
		if (!theEndpoint.isAuthenticated()){
			theEndpoint.authenticate(true);
		}
	}

	public static Endpoint getEndpoint(){
		Endpoint endpoint = (Endpoint) ExtLibUtil.resolveVariable(FacesContext.getCurrentInstance(), "smartcloud");
		return endpoint;
	}

	public static Collection<Community> getCommunities() throws CommunityServiceException{
		CommunityService svc = new CommunityService();
		CommunityList communities = svc.getMyCommunities();
		return communities;
	}
}

What is interesting is that the svc.getMyCommunities() returns a “Collection” and the code assist not only told me this, but it changed the return of the method “Collection” and added the import java.util.Collection.

So that is all well and good – but then how do I get that only my XPage?

Well I actually used the JavaDoc !
c1

So then in the XPage I used a repeat control to iterate through the “collection”

<xp:repeat id="repeat2" rows="30"
	value="#{javascript:com.xomino.sbt.Utils.getCommunities()}" var="myCommunities">
	<xp:div styleClass="readonly" disableTheme="true">
		<xp:text disableTheme="true">
		  <xp:this.value><![CDATA[#{javascript:myCommunities.getTitle()}]]></xp:this.value>
		 </xp:text>
	</xp:div>
</xp:repeat>

and there we have it.

c2

So what is a Collection exactly? I guess I ought to find out……

IBMSBT in XPages: Getting set up with a Social Business Toolkit (Part #1)

In this article I will begin to introduce IBM Social Business Toolkit (SBT), provide links on how to get it set up, talk briefly about how it works and then provide my first demo on how to get “My Files”.

The whole of this series will be based on “Smart cloud (now Connections Cloud)” as opposed to Connections on premises. This should not however really affect too much other than how you make the initial connection. After that the API is pretty much the same (for my purposes anyway).

Thank you Kathy Brown

Setting up SBT is not the easiest thing to do – nothing IBM ever is. But I owe a huge thank you to Kathy who did the bulk of the hard work on this. Setting this up locally for me was pretty simple once she knew how. Part of the reason for me writing it down as I said before is self documentation ‘cos I know I will forget 🙂

IBM SBT

Here are some links on how to set it up and configure it – you will have to install it on your server as well as in your Domino Designer.

Setting up on Domino Designer 

Download the latest code from OpenNTF (I used this one). Unzip the file locally and within there you will find the following folder

  • sbtsdk/redist/domino/

Within there you will find the com.ibm.sbt.domino.updatesite-1.0.3.20140723-1200.zip!. That is the file you need to reference when you install the feature into Domino Designer. (If you want to run the playground locally you will also need to install from the playground updatesite)

Setting up the server

Within the zip file references above you will find the features and plugins folders.

j1

Copy them and paste them onto your server in the following directory. The features and folders for the server are listed there.

DominoIstallPath\data\domino\workspace\applications\eclipse

I am sure there is a better “updatesite” way of doing it, but this worked for me *shrugs*

The XPagesSBT.nsf

The database within the zip file gives you some examples of how to work with the API and provides links and examples for the JavaScript API, Java API, XPages snippets and other examples. I am not going to use those examples. There is some cool out of the box feature but

  • The JavaScript API is far from complete (which is why I am learning Java)
  • The Java examples are not complete (which is why I am doing this from scratch)
  • The XPages examples and snippets are very “XPagey” and I would rather do it myself

The faces-config.xml

Now this is the one useful thing you should get from the XPagesSBT.nsf because it contains all the managed beans used by the SBT to connect to Connections Cloud (and Facebook and Twitter and others). In there you will find examples for connecting to smartcloud and connections. You will need these in your quest to create an SBT application.

You will find this file through the Package Explorer, in the WEB-INF folder

j2

Setting up Connections OAuth app

Setting up a Smart (Connections) Cloud “App” for OAuth

Within Connections Cloud, not much less ew, but at least all browser based. This is how to register your app and get the OAuth tokens.

j1

 

 

j2

j3

 

j4

j6

j7

 

In the next article we will actually create our first example to Authorize and pull personal data from Connections Cloud.

 

 

 

 

Scrolling to the errors – Making XPages Error Handling more user friendly

In this article I will demonstrate how to easily improve the server side form validation process from a user’s perspective.

Introduction

I have to admit that I am not a fan of server side validation. While it suits the developer, it is often not a good user experience. If you complete a large form, hit submit and you have errors returned, it is had to ascertain where the error has occurred. This causes the user to have to scroll up and down the page looking for the error. In an ideal world the form needs to be validated fornt end and back end to provide the optimal experience, while ensure data integrity within the application.

When building the PSC Contest site http://contest.psclistens.com I wanted to make the server side validation as painless as possible for users.

Basic Server side XPages validation

To validate a field on an XPage we use validators and a custom message. In the following example, when the user submits a blank field, an error message will be returned and the JSF validation will prevent the form from saving.

<xp:inputText styleClass="form-control"  disableTheme="true"  value="#{competition.First}"
	id="first1">
	<xp:this.attrs>
		<xp:attr name="placeholder"
			value="Contact First Name">
		</xp:attr>
	</xp:this.attrs>
	<xp:this.validators>
		<xp:validateRequired loaded="true" message="This value is required">
		</xp:validateRequired>
	</xp:this.validators>
</xp:inputText>
<xp:message id="message6" for="first1"
	styleClass="error">
</xp:message>

Here is an example of the failing field. I have assigned a class = “error” to the message and that styles it red and bold.
c1

Scrolling to the error message

Within the onComplete event of the form’s submit button we can add some code to determine if there are any errors. If there are errors we can use some jQuery to “scrollTo()” the message on the screen and it is revealed to the user.

  • The code var temp = $(‘.error:visible’) selects all the error message which are visible to the user.
  • If the length of the selector is greater than 0 there must be an error on the page
  • So determine where it is on the page temp.eq(0).offset().top-250 and then
  • Animate a scroll to the element.
  • http://api.jquery.com/scrolltop/ tells us that we are able to ascertain the scroll bar height of the element and  “Setting the scrollTop positions the vertical scroll of each matched element.”
<xp:button value="Submit" styleClass="btn btn-primary theSubmit" id="button1">
	<xp:eventHandler event="onclick"
		submit="true" refreshMode="partial" immediate="false"
		save="true" refreshId="container">
		<xp:this.onComplete>
			<![CDATA[
				//Check to see if there are any errors
				var temp = $('.error:visible')
				if (temp.length >0){
					$('html, body').animate({scrollTop: temp.eq(0).offset().top-250}, 1000, function(){
					});
				} else {
					alert('Thank you for your submission. PSC will be in touch.')
					location.href="http://www.psclistens.com"
				}
			]]>
		</xp:this.onComplete>
		<xp:this.script><![CDATA[
			//check to see if project sponsor is checked
			if ($('.projectSponsor').is(':checked')){
				console.log('sponsor check')
				app.checkSponsor()
			}
		]]></xp:this.script>
	</xp:eventHandler>
</xp:button>

If there are no errors the user is thanked and redirected to the PSC homepage.

Don’t do a complete submit….

The only way we are able to run an “onComplete” on the button event handler is if we do not submit the entire form. If we do this then all the code will be lost and the page will revert to the top of the page.

Wrap the body of the form in a panel or div which can be refreshed.  Set the button click to partial refresh that section. This way the scroll position on the page will not be lost when the button is clicked.

 

c2

The contest site

Check out the contest site http://contest.psclistens.com before 13 Feb 2015 when PSC will announce the winner

 

Angular in XPages: Formatting Domino Data Services Date values with app.filter

In this article I will show how we can use the core angular date filter capabilities to format Date format, Domino data into an Angular.js based application

Introduction

In previous articles I have shown how to create a simple Angular.js application using a notes Domino Data Services feed from a notes database. If we want to add “date” information then we need a way to nicely format it. Using the Angular.js documentation page as reference I will show you how we can do this with Domino data.

Adding dates to our view

When we add a date field to a Domino Data Services feed we get something which is to the human eye pretty “ugly”

a1

And when we add lastModified into our template, it is well, less than appealing….

a2

 

Adding a formatting function to the template

We can modify the template to use a formatting function by changing up the template slightly and then adding a formatting filter to the application.

In the app.js we add the following

personApp.filter("formatDate", function () {
    return function (x) {
        return new Date(x);
    };
});

And then we reformat the template as such:

    <tr ng-repeat="person in people"  on-last-repeat>
        <td>{{person.firstname}}</td>
        <td>{{person.lastname}}</td>
        <td class="zipCode">{{person.zip}}</td>
        <td class="user">{{person.username}}</td>
        <td class="user">{{person.lastModified | formatDate | date:"dd MMM yyyy" }}</td>
        <td><a class="btn btn-info" href="#/person/{{person['@unid']}}">Edit</a></td>
        <td><a class="btn btn-warning" href="#/person/{{person['@unid']}}/delete">Delete</a></td>
    </tr>

The critical part is {{person.lastModified | formatDate | date:”dd MMM yyyy” }}.

The documentation unfortunately is not clear on this and I found this Stackoverflow example which worked perfectly. http://stackoverflow.com/a/25856275/1171653

The resulting page now looks formatted and much easier to read

a3

Conclusion

I spent which a lot of time failing to achieve this date formatting without doing it the angular way. One quick google (or three) and I had the answer. Do it the angular way and oo look that that nice and simple formatting.

Websockets in XPages: Improving on the automated partialRefresh interface

In this article I will further discuss how tom improve the user experience of an automated partial Refresh on an user’s XPage. Although these posts were originally about using Bluemix to host the node.js server I kinda feel that the focus has drifted onto websockets more than Bluemix. So in an attempt to make it easier to find I am going to use the Websockets in XPages title moniker for a few posts and then go back to Bluemix 🙂

Introduction

In the last article we looked at how to push a automated partialRefresh to a XPage application using websockets. In that article it was noted that the user experience was not ideal because the whole panel refreshed without the user knowing about it. For some apps that is appropriate and for others it may not be. At this point in his career Dave Leedy is impressed he gave someone else and idea and I quote: “wow! that’s fricken awesome!!!”

So, that’s not a great user experience – what if they were doing something at the time?

Yes I was thinking that too! So I believe we can improve the user experience a little and take what Dave suggested and tweak it a little. Now where have a seen something which let’s the user know there is new data changes but doesn’t refresh the page without their action……….

b4

oh yeah that.

Instead of refreshing the control automatically, we will make the message create a “refresh” icon on the page which the user can then update at their leisure.

b5

The modified code is all in what happens when the page receives the refresh socket message. I added a jQuery rotate function just for some added “je ne sais quoi“. In the function we can see that when the refresh event is detected by the socket code the refreshControl function is called. This in turn makes the hidden refreshIcon visible, adds an onClick event and then rotates it. The onClick event performed the partialRefreshGet as we saw in the previous example turning the page briefly grey. We then hide the icon and remove the click event (to avoid piling on multiple events as the page gets continually refreshed)

 

 // Function to add a message to the page
  var refreshControl = function(data) {

	  $('.refreshIcon')
	  	.css({display: 'block'})
	  	.on('click', function(){
	  	   var temp = $('[id*='+data.refreshId+']').css({background: '#CCCCCC', color: 'white'}).attr('id')
		   XSP.partialRefreshGet(temp, {})
		   $(this).css({display: 'none'}).off('click')
	  	})
	  	.rotate({
	      angle:0,
	      animateTo:360,
	      easing: function (x,t,b,c,d){        // t: current time, b: begInnIng value, c: change In value, d: duration
	          return c*(t/d)+b;
	      }
	   })
  };

  // When a refresh message is received from the server
  // action it
  socket.on('refresh', function(data) { refreshControl(data); });

The following video quickly demonstrates the new capability.

Conclusion

In this brief article we concentrated on how to improve a user experience by notifying them that changes were pending and then allowing them to determine when the changes were made.

I still don’t think this is as optimal as I would like but you get the idea. As I said a long time ago – the more DOM you are reloading the worse the user experience. With a viewPanel we are kinda limited on what we can and cannot refresh. A better option may be to architect the application just the new data and update as appropriate……….

 

XPages and Bluemix: Sending a targeted Websockets message to specific XPages

In this article I will demonstrate how the use of socket.io “rooms” enables us to send message to users who are only accessing specific pages within our application, rather than blanket messages to all users.

Introduction

In the previous article I demonstrated how to use a message POST to the node.js server which could then be turned into a chat message and sent out to all users. While this is a nice example it only serves as such and does not have significant business value. In this article I will begin the peel back the potential for much greater application flexibility through the controlled use of targeted Websockets messages to users of an application.

Most applications have more than one “page” within it and we may wish to send a message to users of one page rather than all pages. Conversely we may want to only send messages to users who are viewing certain types of information wherever they are within the application.

In this article we are still going to use the chat example but this will be the last time we use “chat” as the use case. In future articles I will look into more practical applications of Websockets messaging within an application.

Using rooms within socket.io

Looking at the socket.io documentation for rooms and namespaces you can see that the API exposes the ability to create individual communication channels between the server and the application users.

How this translates to our application on the node.js server looks like this:

  socket.on('joinRoom', function(room, name){
    socket.join(room);
    io.sockets.in(room).emit('notice', name+" has joined");
    console.log(name+" has joined room "+room)
  })

When the “joinRoom” event is registered on the server then the socket is joined to the room name which is passed in. A message is the broadcast specifically out to all the existing room members that the new user has joined. Note that this message is room specific because of the io.sockets.in(room) rather than a blanket message to all users.

With this code in place on any page we can register our application page (chat room in this case) with the socket server. In my case I created a generic function to take the name of the “room” from the XPage URL. The following client side JavaScript sends the “joinRoom” request to the server.

  nickname=$('.username').text()
  // Send message to server that user has joined WinX.nsf/xRoom1.xsp?open&login
  var xpageName = location.href.split('.nsf/')[1] //xRoom1.xsp?open&login
  xpageName = xpageName.split('.xsp')[0] //xRoom1
  socket.emit('joinRoom', xpageName, nickname);

On the server we can then see the console.log as someone joins the room:

s1
We can then have multiple users in individual “rooms” which are in this case 3 separate xPages xRoom1.xsp, xRoom2.xsp, xRoom3.xsp From the original chat room I am able to sen all three rooms a blanket message because they are all “listening” for “msg” as in the previous articles

 socket.on('msg', function(data) {
    console.log('msg here')
    console.log(data)
    redisClient.lpush('messages', JSON.stringify(data));
    redisClient.ltrim('messages', 0, 99);
    socket.broadcast.emit('msg', data);        //broadcast to all users indiscriminately
  });

s2

Sending a message to a specific room

Modifying the original POST code which was shown in the previous article I was then able to create an XPage which will send a targetted message to a specific room. To do this, all I had to do was pass in an additional POST parameter of the room I wanted to send a message to.

I create a new Master XPage which had 3 fields on it – one for each room. Each of the individual fields had a “room” attribute which allows me to pick up a value to specify which room to send it to.

<div id="msgRoom1" class="msgCenter">
	<input placeholder="Send a message to Room1" room="xRoom1" autocomplete="off" autofocus="autofocus" />
	<button type="button">Send</button>
</div>
<br/><br/>
<div id="msgRoom2" class="msgCenter">
	<input placeholder="Send a message to Room2"  room="xRoom2" autocomplete="off"  />
	<button type="button">Send</button>
</div>
<br/><br/>
<div id="msgRoom3" class="msgCenter">
	<input placeholder="Send a message to Room3"  room="xRoom3" autocomplete="off" />
	<button type="button">Send</button>
</div>

s3

In the following code we bind to each of the buttons so that when the are clicked they:

  • set msgField to be the jQuery object representing the pertinent field
  • create the data object to send to the socket server
  • create the newMessage passing in data

The new message function then:

  • POSTs the data object at the “/roomPost” path on the server
  var socketServerURL = (location.href.indexOf('copper.xomino.com')>0) ? "http://copper.xomino.com:3000" : 'http://xominosocket.mybluemix.net'
  var socket = io.connect(socketServerURL)

$('.msgCenter Button').on('click', function(){
  	var msgField = $(this).prev('INPUT')
	var data = { text: msgField.val(), nickname: nickname, when: new Date(), room: msgField.attr("room") };
	sendMessageToRoom(data)
	newMessage(data);
	// Clear the message field
	msgField.val('');
})

var sendMessageToRoom = function(data){
	console.log(data)
	$.ajax({ url:
	        socketServerURL+"/roomPost",
		type: "POST",
		data: data
	}).done(function( msg ) {
		console.log( msg);
	});
}

We can see the message come through on the server

s4

You can also see from the log that I was able to join 3 rooms from the Message Center page – this is as simple as creating three requests to join:

  //client side JavaScript
  socket.emit('joinRoom', 'xRoom1', nickname);
  socket.emit('joinRoom', 'xRoom2', nickname);
  socket.emit('joinRoom', 'xRoom3', nickname);

Site in action
The best way to see all this is to see it in action – as you can see from the video below.

Moving the application to Bluemix

Working locally I used to following code to determine if I was on copper.xomino.com or another server (demo.xomino.com or different again)

  var socketServerURL = (location.href.indexOf('copper.xomino.com')>0) ? "http://copper.xomino.com:3000" : 'http://xominosocket.mybluemix.net'
  var socket = io.connect(socketServerURL)

I had to make a slight change to my cors code in the local node server setup. I looked at the cors npm site to see how to dynamically add cors support and found the answer and modified my code accordingly. So you need to make sure you

//Marky adding CORS from copper
//app.use(cors()); //not used for whitelists

var whitelist = ['http://copper.xomino.com', 'http://demo.xomino.com', 'http://marky.psclistens.com'];
var corsOptions = {
  origin: function(origin, callback){
    var originIsWhitelisted = whitelist.indexOf(origin) !== -1;
    callback(null, originIsWhitelisted);
  }
};

marky.psclistens.com is the same local server as copper.xomino.com (I use a hosts file to make them both look at localhost). But this allows me to play with cors and in this case talk to the local or bluemix node server without needing another “Domino” server.

s6

Because the site running locally is already connected to the Jazz Hub Git repository, as with the previous examples all I have to do is commit the changes locally, push to the Jazz Hub repository, the application will then be re-built and re-deployed to Bluemix.

It’s really about as simple as that. Because the code is already primed to check to see if we are looking locally or at Bluemix, a new URL for the application now looks at Bluemix.

s5

 

So why do we need Bluemix again?

While the development was performed locally, the intention is to be able to create a capability which does not require the user to have a locally running node.js server. That is where Bluemix comes in. In essence what we are creating is a cloud hosted Websockets service which can be integrated into existing applications.

 

Conclusion

In this article we have seen that with a little creativity we are able to register different chat rooms within the same application. There are other ways to do this of course but the point of this example was to demonstrate the ability to send a specific message to specific users looking at an individual XPage.

In the next article we will take a look at the practical implications of this within an XPages application.

 

x$ – now a part of XPages Extension Library

I am very flattered to find out that not only is my x$ OpenNTF xSnippet being used more widely than I realized (over 600 downloads). It now being used in the latest release of the OpenNTF Extension library.

If you look here – http://bootstrap4xpages.com/xsp/.ibmxspres/.extlib/responsive/xpages/js/xsp-mixin.js and search for it you will find


//jQuery selector that works with XPages IDs
//See - http://openntf.org/XSnippets.nsf/snippet.xsp?id=x-jquery-selector-for-xpages
function x$(idTag, param){
	idTag = idTag.replace(/:/gi, "\\:") + (param ? param : "");
	return($("#" + idTag));
}

Who knew my first foray into the XPages community would have such an impact.

The lesson here boys and girls should be that you should *share* your work however small and insignificant you think it is. Like all scientific research, very little code is “a completely new way of doing things”. Generally everything grows incrementally and just because you don’t think something is significant, someone else might.

You are reading this – what have you shared recently?

🙂

 

Binding jQuery code to an XPages partialRefresh using DOM Mutation events

Introduction

In this article I will demonstrate how to bind to the event which triggers on the completion of an XPages partialRefresh. Using that binding we will then be able to action to contents of the newly added partialRefresh DOM elements.

Background

In multiple articles I have discussed the use of the onComplete event of a programmatically triggered partialRefresh to be able to re-apply a jQuery plugin’s bindings to an area of an XPage. This works very nicely and integrates with the Dojo events controlling the xhr request to the Domino server.

A problem arises when you do not have a programmatically controlled partialRefresh, say for example in a pager. XPages uses the same technology to execute a partial refresh on a viewPanel – but you and I do not have programmatic access to the onComplete event without hijacking it.

This was brought back to my attention when reading Brad Balassaitis’ excellent article on adding font awesome to his data view. In that case he does not have an event available to him through the XPages designer so he has to hijack the Dojo calls. A practical solution given the tools available.

In general though I have always found using the XPage events a non-elegant way of controlling the page and there has to be a better way – I think upon reflection this is a nice learning experience and “good to know” article but not practical in production.

DOM Mutation events

These events have been around for a while but are now “deprecated” in favor of the new MutationObserver() constructor which is unfortunately not implemented in Internet Explorer until IE11

  • DOMAttrModified
  • DOMAttributeNameChanged
  • DOMCharacterDataModified
  • DOMElementNameChanged
  • DOMNodeInserted
  • DOMNodeInsertedIntoDocument
  • DOMNodeRemoved
  • DOMNodeRemovedFromDocument
  • DOMSubtreeModified

As the mozilla article states – “The practical reasons to avoid the mutation events are performance issues…...” – watching the DOM for changes every time a change happen has very processor intensive – believe me in my experiments if you latch onto DOMSubTreeModified and you are using jQuery which is constantly changing the DOM – you can easily drag your browser to its knees.

So in this article I am going to demonstrate how to use the “old” method for IE<11 and the preferred new method. You can then decide for yourself on the right way to do things – Dojo hijacking, degrading DOM performance or if you are lucky enough to not have to support IE – the way of the future 🙂

An example of the general problem

If I have a simple view panel on a page and I use some jQuery to stripe the rows it looks pretty…..(yes there are other ways to stripe the rows this is just to demonstrate the point).

jq1

But as soon as I hit the pager – the striping is lost. The DOM elements are removed and the new elements do not have the striping applied

jq2

The partialRefresh

As I am sure most of you know the partialRefresh as genius as it is, works by POST-ing the field values on the form back to the server where the JSF licecycle processes these POST-ed values and then returns a new set of HTML to the browser. That new HTML is inserted as a direct replacement of the DOM element which was being refreshed. Looking at the response from the server you can see below that when paging through a viewPanel the viewPanel1_OUTER_TABLE is re-downloded from the server and replaces the existing Table element in the DOM.

jw3

So my striped table is deleted from the DOM and replaced – ergo no more striping.

DOM Node insertion

Using the DOM Mutation event DomNodeInserted it is actually relatively easy to re-stripe the table.

I first surrounded the viewPanel with a div wrapper “viewPanelWrapper”. This is what I will use to listen to changes for. Because the whole outer table is replaced I cannot listen to events on it – it is removed along with my binding.

The first piece of code will demonstrate the event listener

$('.viewPanelWrapper').on("DOMNodeInserted", function(){
    console.log('a node was inserted')
})

When I run the above code snippet through firebug you will see that nothing changes (1). But when I click Next the partialRefresh is triggered and “a node was inserted”
jq5

If we then take this a step further we can add in our striping code again

$('.viewPanelWrapper').on("DOMNodeInserted", function(){
    console.log('a node was inserted')
    $('.viewPanel TR:even').css({background: '#FFCCCC'})
})

And that’s pretty much it – pretty simple really.

jq6

So then extending this simple example you can see how a jQuery plugin could be reapplied to any page after a partialRefresh has been triggered – JUST BE AWARE THAT THERE IS A PRICE TO PAY IN PERFORMANCE. If you are going to do this then make sure that you pick the smallest area to check possible and that it does not change every second – your browsers and more importantly users will not thank you. On and applying a jQuery plugin almost certainly also modifies your DOM – be careful not to create an endless loop of pluging in your plugin.

So the “better way”

This article explains the reasoning behind the new MutationObserver and more importantly why it makes more sense than what I just showed you.

DOM MutationObserver – reacting to DOM changes without killing browser performance.

Check out the “So what are these good for” section at the end – obviously they were talking about XPages 😉

Using a slightly modified version of their example we get this

var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
  var list = document.querySelector('.viewPanelWrapper');

  var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {

        console.log(mutation.type);
        $('.viewPanel TR:even').css({background: '#FFCCCC'})
    });
  });

  observer.observe(list, {
  	attributes: true,
  	childList: true,
  	characterData: true
   });

jq7

Which works the same but as the article explains – WAY more efficient and also gives you the control to not screw up your plugins.

Remember though the caveat is modern browsers and that it is IE11 only

Conclusion

Overall this has been a fascinating learning experience for me. I can’t recommend using the DOMNodeInserted event listener because it definitely caused me pain and anguish in browser performance. The MutationObserver is a very interesting concept but I am not convinced I would use it in an application until I better understand it.