At work, the application has been suffering from the performance hit. After the investigation, we found the cause. It was the logic that loads additional object summary information from the DB and set title attributes of dynamic table cell via Ajax call. I want to load the tooltip information on demand when mouse over happens. I’d like to share the road blocks and solution from this implementation. Below screen shot is the Tooltip. I hide business specific information.
Issue
Performance hit when tooltip content is generated at the same time the table data is created.
Road Blocks
- Dynamic Table : When page load, send Ajax request to retrieve table data and dynamically populate. No HTML source code for the table.
- Object structure : Object has a collection as an attribute. Ajax table needs to display at the collection level. Object ID is not unique.
- jQuery Plugin for Tooltip : For the tooltip, I am using jQuery plugin – Tools tooltip. No detail API document.
- Dynamic binding : Mouse over sends Ajax request for the content and tooltip content needs to be updated dynamically.
- Delay Ajax request : Users can put their mouse over the id, but they may not want to see the tooltip. Another words, I don’t want multiple Ajax requests keep firing whenever mouse over happens.
- Prevent multiple Ajax request : No need to send Ajax request if the content is already retrieved and updated. Also, as a part of the solution caused multiple Ajax request firing issue.
Solution
Assign Unique ID & row number : The key was to assign unique ID and row number in TR tag. This information needs to be passed in from the server, so when JSON object is made, I have to populate object ID concatenated with collection index as a unique ID in the TR tag, so I can identify the element correctly. Row number is also important because I need to access default tooltip title to see if tooltip content is updated from the server or not.
Accessing cell.title : This was the key to the solution. Even though cell.title was populated with default value, accessing from DOM element is tricky. I thought I was able to access by using this.title, but it came back with undefined object. Because title is not a part of TR tag attribute, the tooltip uses next element after .tip classname is used as tooltip. Couldn’t find detail API regarding this.
In order to access tooltip content, I need to know the row number where mouse over happens and getTip() function that belongs to the tooltip.
$( $( ‘.tips’ )[rowIndex] ).tooltip().getTip()[0].innerHTML
Above line returns the cell.title content.
Delaying Ajax call : Another challenge that I have is that users typically scroll up and down the rows in the table, so their mouse over action may trigger multiple Ajax request, which I don’t want. I want to control user’s behavior by set mouse over the cell at least 2 seconds, so users can distinguish casual mouse over versus displaying a tooltip. I used setTimeout() method to to delay the Ajax call. I was able to find the logic from the internet.
Dynamic binding : After successful Ajax request/response, I was not able to refresh the tooltip. It still shows the default content. I need a event after retrieve the content of tooltip. jQuery has .on() and .live(). The .live() is deprecated and used under 1.7 version.
Prevent multiple Ajax requests : After implement all above, I found that somehow Ajax request fires multiple times. Some people suggested that I use .dead() before .live(), but I did a simple approach. I used event.handled check before Ajax request. Also, check the default content if it is already updated. Good resource here.
Table Population
var cell = row.insertCell(0); cell.align = "left"; cell.className = "tips"; cell.innerHTML = object.objectId; cell.id = object.objectStringId; cell.abbr = i; cell.title = "Please don't move your cursor to load the details.";
Tools Tooltip
$( '.tips' ).tooltip( { position: "center right", effect: 'fade', opacity: 0.8 } ).dynamic( { bottom: { direction: 'down', bounce: true } } );
Dynamic content retrieval
var pendingCall = { timeStamp: null, procID: null }; var objectid; var rowIndex; jQuery( '.tips' ).live( 'mouseenter', function( e ) { e.preventDefault(); var timeStamp = new Date(); objectid = this.id; rowIndex = this.abbr; var toolTipCall = function() { if ( $( $( '.tips' )[rowIndex] ).tooltip().getTip()[0].innerHTML == "Please don't move your cursor to load the details." ) { if ( e.handled != true ) { $.ajax( { url: servletUrl, dataType : 'json', type : 'POST', data: { Action : "<%=AjaxActionHelper.RETRIEVE_TOOL_TIP%>", MouseOverId : objectid }, cache: false, success : function( data ) { if ( pendingCall.timeStamp != timeStamp ) { return false; } $( $( '.tips' )[data.row] ).tooltip().getTip()[0].innerHTML = data.summaryToolTip; pendingCall.procID = null; } } );//end ajax e.handled = true; } return false; } }; if ( pendingCall.procID ) { clearTimeout( pendingCall.procID ) } //set the time before call 3000 = 3 seconds pendingCall = { timeStamp: timeStamp, procID: setTimeout( toolTipCall, 3000 ) }; } );//mouseover
Conclusion
Debugging Javascript is not easy thing to do unless you have a proper tool. I used FireBug to debug the code which helped me a lot and it gave me an opportunity to understand what’s happening underneath. Also, understanding DOM object tree is important knowledge to reach the goal. I wonder how Coffee Script or TypeScript can help me in terms of resolving this kind of issues…