Category Archives: AJAX

Dynamic tooltip using JQuery Ajax and JQuery Tools

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

  1. Dynamic Table : When page load, send Ajax request to retrieve table data and dynamically populate. No HTML source code for the table.
  2. Object structure : Object has a collection as an attribute. Ajax table needs to display at the collection level. Object ID is not unique.
  3. jQuery Plugin for Tooltip : For the tooltip, I am using jQuery plugin – Tools tooltip. No detail API document.
  4. Dynamic binding : Mouse over sends Ajax request for the content and tooltip content needs to be updated dynamically.
  5. 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.
  6. 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…

Advertisements

JQuery vs. custom JS AJAX

I’d like to share my experience using custom Javascript library and JQuery for AJAX work. Also, I used gson to make JSON object from the collection. gson makes object conversion really easy. All sample codes below are not complete one. Just wanted to show you an idea of how things are working.

AJAX Javascript

  • Import Javascript in all JSP where Ajax action is happening.
  • Handling different browsers logic needs to be coded.
  • Conversion to JSON and object parse need to be coded.
/*
 * Create tye most optimal XML HTTP request
 * object for the browser.
 */
function createRequest()
{
    var v;

    try
    {
        v = new XMLHttpRequest(); //Mozilla and IE7
    }
    catch ( e )
    {
        v = createActivexXMLHttp(); //IE6 and back
    }

    if ( v == null )
        throw new Error("XMLHttpRequest not supported");

    return v;
}

function createActivexXMLHttp()
{
    var aVersions = [ "MSXML2.XMLHttp.5.0",
        "MSXML2.XMLHttp.4.0","MSXML2.XMLHttp.3.0",
        "MSXML2.XMLHttp","Microsoft.XMLHttp"];

    for ( var i = 0; i  0 )
        {
            body = body + "&";
        }
        body = body + escape(name) + "=" + escape(requestData[name]);
    }
    xhr.open("POST", url, true);
    xhr.setRequestHeader("Content-Type",
            "application/x-www-form-urlencoded; charset=UTF-8");
//    logger.trace("Post body:" + body);
    xhr.send(body);
}
/*
 * Converts a DOM document into XML text in a browser
 * neutral fashion.
 */
function getXMLString( doc )
{
    var xml = null;
    try
    {
        //Mozilla
        var ser = new XMLSerializer();
        xml = ser.serializeToString(doc);
    }
    catch ( e )
    {
        //IE
        xml = doc.xml;
    }

    return xml;
}

/*
 * Utility method that posts a DOM document.
 */
function ajaxPostDocument( url, doc, onLoad, data )
{
    var xhr = createRequest();

    xhr.onreadystatechange = function()
    {
        if ( xhr.readyState == 4 )
        {
            if ( xhr.status == 200 )
            {
                onLoad(xhr, data);
            }
            xhr = null; //Prevent memory leak in IE
        }
    }
    var xml = getXMLString(doc);
    xhr.open("POST", url, true);
    xhr.setRequestHeader("Content-Type",
            "application/x-www-form-urlencoded; charset=UTF-8");
    xhr.send("xml=" + escape(xml));
}

ajaxPost() function was used from JSP to call Ajax request.

Object to JSON without gson

JSONArray was used to convert objects into JSON. Also JSONWritter was used to pass other parameters from server to client.

StringWriter aWriter = new StringWriter();
JSONWriter theWriter = new JSONWriter( aWriter );
JSONArray jsonArray = new JSONArray();
JSONObject object = new JSONObject(ajaxBean, PROPERTY_LIST); 
//Simple javabean with properties and String array with properties
jsonArray.put(object);

theWriter.object().
        key( "outages" ).value( jsonArray ).        
        key( "successfulIndicator" ).value( "1" ).        
        key( "error" ).value( "" ).
        endObject();
aWriter.toString();

Parse in JSP

function displayGrid( reply )
{
  var jsonString = reply.responseText;

  if ( jsonString != null && jsonString != "" )
  {
    var tableData = eval("(" + jsonString + ")");
    if ( tableData.error != "" )
    {
      $("#error").text(tableData.error);
    }
    else
    {
      $("#error").empty();
      var outageList = tableData.outages;
      for ( var i = 0; i < outageList.length; ++i )
      {
            var outage = outageList[i];
            var cell = row.insertCell(0);
            cell.innerHTML = outage.id;
            ....
      }
    }
  }
}

Lots of JS code…

AJAX JQuery using gson

public void doPost( HttpServletRequest aRequest, HttpServletResponse aResponse )
{
   Collection lockList 
   = convertToAjaxBean(getService().getWorkProtectionJobController().retrieveAllDataLock());

   Gson gson = new Gson();
   String json = gson.toJson( lockList );
   aResponse.setContentType( "application/json" );
   aResponse.getWriter().write( output );
}

Parse JSON from JSP

function retrieveAll()
  {
    $.ajax({
      url: servletUrl,
      dataType: 'json',
      data: {Action : "retrieveAll"},
      cache: false,
      beforeSend: function()
      {
        messageBox_show();
      },
      success: function(data)
      {
        drawTable(data);
      },
      complete: function()
      {
        messageBox_hide();
      }
    });
  }

  function drawTable(data)
  {
    var table = document.getElementById("ajaxTable");
    for(var i = 0; i < data.length; i++)
    {
      var lock = data[i];
      var rowIdx = table.rows.length;
      var row = table.insertRow(rowIdx);

      var cell = row.insertCell(0);
      cell.align = "left";
      cell.className = "tips";
      cell.innerHTML = lock.objectId;
      ....
    }
}

Because JSON is not nested with another array, I could just access JSON data from JQuery ajax call on Success.

Conclusion

JQuery and gson combination makes development a lot easier.  Although some shorthanded JQuery usage is really tricky, but once I master the actual JQuery Ajax syntax, it doesn’t seem too bad to understand shorthanded format. Some situation, returned JSON object contains complex structure such as nested JSON array. I found printing out JSON and validate its format through JSONLint before parsing JSON is helpful.