Tuesday, March 24, 2009

Trinidad Draggable Dialogs

Users of Apache Trinidad may have run across the issue of not being able to move the Trinidad dialogs around the page. They always sit right in the center of your screen and of course block you from seeing the information behind. In release 1.02 the authors proposed that the dialogs should be made drag-able, however nothing has been done. The following is a simple solution I used to make the dialogs drag-able. If I had time, I would commit something to the project to handle this - although I believe the reason it has not yet been implemented is because of the amount of cross browser code needed to affect this. The following solution took about 5 hours to come up with and refine.

The trick with making the dialogs drag-able is that that the dialogs are generated after the page loads, use different markup depending on browser type and version, and the markup contains no ids on the DOM elements of interest. I implemented this for Firefox2, 3 and ie7. For other browsers, the dialog is rendered with standard behavior.


So lets get started:

Step 1: Add the mouse drag event script to the Trinidad dialog:

In your xhtml/jsp file that will display behind the dialog , add a trh:script call that is rendered conditionally based on browser. The call will need to wait until the page loads to execute, so I add a timer to delay invokation:

trh:script text="window.setTimeout('attachDraggabilityToDialog()', 500);"
rendered="#{UiUtils.supportsDialogDrag}"

partialTriggers="::pprDialogCallButton "


The text attribute calls the attachDraggabilityToDialog script 500 ms after the page loads ( I place the script at the bottom of the page. )

The rendered attribute calls a backing bean function that specifies whether the browser version is supported for dragging.

The Partial Triggers attribute references Trinidad element id that pops up the dialog - in this case the button.

Step 2. Create the script:

// the frame
var dialog_frameDiv= null;
// the title bar on the frame
var dialog_titleBar=null;
// the object to drag around
var dialog_dragObj = new Object();
var GECKO=0;
var IE=1;
var BR_VER=GECKO;
new Browser();




/**
* After the page has loaded, and the popup has been displayed,
* Call this function:
* e.g: window.setTimeout(attachDraggabilityToDialog, 500);
* NOTE _ WORKS IN FF2, 3 AND IE 7
*
*/
function attachDraggabilityToDialog() {
dialog_dragObj = new Object();
dialog_frameDiv= null;
dialog_titleBar=null;

if (BR_VER==GECKO) {
for(var i =0; i <
window.document.body.childNodes.length;i++) {
dialog_frameDiv= window.document.body.childNodes[i];
if(dialog_frameDiv!=null && dialog_frameDiv.nodeName=="DIV" && dialog_frameDiv.style.zIndex==5001) {
dialog_titleBar=dialog_frameDiv.childNodes[0];
break;
}
}
}
else if (BR_VER==IE) {
var _node = document.getElementsByTagName("iframe")[0].parentNode;
if(_node !=null && _node.tagName=="DIV"){
dialog_titleBar= dialog_frameDiv=_node;
}
}

if(dialog_titleBar !=null) {
if (BR_VER==IE) {
dialog_titleBar.attachEvent("onmousedown", callDrag);
dialog_titleBar.attachEvent("onmouseup", endDrag);
}
if (BR_VER==GECKO) {
dialog_titleBar.addEventListener("mousedown", callDrag, true);
dialog_titleBar.addEventListener("mouseup", endDrag, true);
}
}

}



Discussion: Based on browser version, we look through the generated markup in the page once the dialog has appeared and find the element based on attributes or expected location of the node in the tree. While not foolproof, this technique works in the many scenarios in our present application, as Trinidad generates the dialogs in a standard manner per browser and version.

The attachDraggabilityToScript injects event handlers onto the elements to be dragged. The event handlers are below:


function callDrag(event) {

var el;
var x, y;
dialog_dragObj.elNode = dialog_frameDiv;

if (BR_VER==IE) {
x = window.event.clientX + document.documentElement.scrollLeft + document.body.scrollLeft;
y = window.event.clientY + document.documentElement.scrollTop+ document.body.scrollTop;
}
if (BR_VER==GECKO) {
x = event.clientX + window.scrollX;
y = event.clientY + window.scrollY;
}


dialog_dragObj.cursorStartX = x;
dialog_dragObj.cursorStartY = y;
dialog_dragObj.elStartLeft = parseInt(dialog_dragObj.elNode.style.left, 10);
dialog_dragObj.elStartTop = parseInt(dialog_dragObj.elNode.style.top, 10);

if (isNaN(dialog_dragObj.elStartLeft)) dialog_dragObj.elStartLeft = 0;
if (isNaN(dialog_dragObj.elStartTop)) dialog_dragObj.elStartTop = 0;

if (BR_VER==IE) {
document.attachEvent("onmousemove", startDrag);
document.attachEvent("onmouseup", endDrag);
window.event.cancelBubble = true;
window.event.returnValue = false;
}
if (BR_VER==GECKO) {
document.addEventListener("mousemove", startDrag, true);
document.addEventListener("mouseup", endDrag, true);
event.preventDefault();
}

}


function startDrag(event) {

var x, y;


if (BR_VER==IE) {
x = window.event.clientX + document.documentElement.scrollLeft + document.body.scrollLeft;
y = window.event.clientY + document.documentElement.scrollTop+ document.body.scrollTop;
}
if (BR_VER==GECKO) {
x = event.clientX + window.scrollX;
y = event.clientY + window.scrollY;
}


// Move drag element by the same amount the cursor has moved.

dialog_dragObj.elNode.style.left =(dialog_dragObj.elStartLeft + x - dialog_dragObj.cursorStartX) + "px";
dialog_dragObj.elNode.style.top =(dialog_dragObj.elStartTop + y - dialog_dragObj.cursorStartY) + "px";

if (BR_VER==IE) {
window.event.cancelBubble = true;
window.event.returnValue = false;
}
if (BR_VER==GECKO) ;
event.preventDefault();
}


function endDrag(event) {
if (BR_VER==IE) {
document.detachEvent("onmousemove", startDrag);
document.detachEvent("onmouseup", stopDrag);
}
if (BR_VER==GECKO) {
document.removeEventListener("mousemove", startDrag, true);
document.removeEventListener("mouseup", stopDrag, true);
}
}



Discussion : The above event handlers move the DOM element based on mouse movements after the user mousesdown on the element, and releases once the user mouses up based on browser version.


Finally, I have a simple browser detect , knowing that my script will not be called unless the browser is Firefox2, 3 or IE 7.


function Browser() {
var ua, i;
ua = navigator.userAgent;
if ((i=ua.indexOf("MSIE")) >= 0) {
BR_VER=IE;
}
}


In my backing bean, I have the following browser detection code that conditionally renders the call to the script based on its findings:



/**
* Currently only ff2,3 ie 7
* @return tru if the browser is one of the above.
*/
public boolean getSupportsDialogDrag() {
String ua = UiBaseUtils.getRequest().getHeader("User-Agent");

if(ua ==null)
return false;

if(ua.contains("MSIE 7"))
return true;
if(ua.toUpperCase().contains("GECKO")){
if(ua.contains("Firefox/3."))
return true;
if(ua.contains("Firefox/2."))
return true;
}
return false;

}



That's it.

I believe the correct way to do this would be to donate time and code to the Trinidad Project. I hope to do this , but I needed to come up with a quick solution that worked. I was able to commit this in less than 24 hours after getting the requirement, so please take this solution with this caveat in mind. QA has found no issues, and user feedback has been positive.

Hibernate AliasToBean Transformer

Problem: You need to access values from tables that are not hibernate entities, in an aplication that uses hibernate to access the database.

Problem: You want to access a couple columns from any number of tables in your database without bringing back all the associated objects.

Solution: AliasToBean Transformer allows you to retrieve specific information in non entity beans.

This hibernate API call will allow you to run sql against your database and populate a list of pojos that are not a hibernate entity. This technique is great when you need specific information or perhaps you want information fom multiple tables.


Your pojo:

Class PlainOldObject {
String s1;
String s2;

public String getS1() {return s1;}
public String getS2() {return s2;}

public void setS1(String s1) {this.s1=s1;}
public void setS2(String s2) {this.s2=s2;}

}

Then in a hibernate dao :

publc List lookupPlainOldObects() {

// the sql with a reference to the s1 and s2 fields in our object
String sql="select tableX.col1 as s1, tableY.colx as s2 where blah blah blah";

List list = getCurrentSession().createSQLQuery(sql)
.addScalar("s1")
.addScalar("s2")
.setResultTransformer(Transformers.aliasToBean(PlainOldObject.class ) )
.setCachMode(CacheMode.GET)
.list();

return list;

}

This avoids your needing to cycle over multiple objects to pull in the information you need. It also alleviates the need to cycle over Object[] s to sort through your results.

A couple notes:

1. Use the set scalar method to convert the variables in your sql to the fields in your pojo.
2. Use CacheMode.GET to keep these ojects out of the hibernate session cache.




Monday, March 2, 2009

Passing Parameters via JSF

JSF allows us to cause updates to via various listeners: actionListseners, disclosureListeners, valueChangeListeners, and phaseListeners to name a few. Sometimes however, you may want to pass a parameter in to a generic method - perhaps to grab the value of an enumeration or access a users ability to view components based on Role.

One little known feature of JSF is that you may access elements of a java.util.Map. This allows us to pass in a key to a map object in one of our contexts, and the map passes back the value pair via the get(Object) method.

The notation in your jsf page would be:

value="${yourContextHandle.mapObject['mapKey']}"

where

yourContextHandle is an object in one of the contexts you use to access backing code
mapObject is a java.util.map that is available on the above object via getMapObject()
mapKey is the parameter passed in to find the object from the map.



In you backing code, generate an object that extends java.util.Map. You will need to implement quite a few methods, so I typically create a base object stub that handles the overriding of the methods beyond get(Object). In the below example, I call this MyBaseJSFMap. Then I extend this object and simply override get(Object);

But how do you get the answer you need?

Consider the following Map :


public Class MyJSFMap extends MyBaseJSFMap{

public Object get(Object o) {
return doSomething((String)o);
}

public String doSomething(String str) {
// your businessLogic here - perhaps a lookup to a database or a pull from a map.
}
}