Peter's Notes

Demystifying open source

Blog Archive

Thursday, July 17, 2008

Passing XML stored in a String into Oracle Stored Procs

For stored procedures, you can pass XML data stored in a java.lang.String into a procedure via a CLOB type. To cofigure you Oracle callablestatement, use setObject on the java.lang.String. It's harder to pass in XMLType because it's an opaque type and requires some fiddling. In your stored procedure, just create an XMLType from the input CLOB via code like v_arg := XMLType(p_arg);

If your XML is not stored in a String, I don't know what to say since I tend to grab XML from a request and it comes out as a String.

Wednesday, July 09, 2008

Eclipse, Exporting WAR files, and JARs

I have a common project that contains JAR files. To add these jar files to a new project, I had to do the following:
  1. Right click on the new project, select properties, and Java Build Path
  2. Click the Libraries tab and use the "Add Jar" button to add the JARs to the project - this removes NoClassDefFound errors
  3. Click the Export tab, and check each new jar file
  4. Select J2EE module dependencies in the left hand pane, and check each new jar file - this includes the jars when the project is exported
  5. Hit OK
  6. Right click on the project, export, and choose WAR file

Monday, June 09, 2008

Constaints, relevancy, etc in XForms

I found little info on how to set constraints, etc on Orbeon XForms elements. What info there is seems to be scattered or clear as mud. Orbeon uses an element called bind inside the xforms:model to set constraints. My particular issue was that some elements don't apply unless another element has some value. Luckily, XForms has an attribute called relevant for this.




<xforms:bind nodeset="instance('main')/element[@name='my-select-list-other-box']">
<xforms:bind nodeset="value" relevant="instance('main')/element[@name='my-select-list']/value='Other'"/>
</xforms:bind>


This is explain concisely at this URL.

Using "Symbols" or Special Characters in XHTML

Using the numbered codes located
http://www.elizabethcastro.com/html/extras/entities.html

Is working for me with Orbeon XForms. For example, lower case beta is Β but using Β does not work (Orbeon throws an error).

Wednesday, June 04, 2008

HTML and XForms custom attributes

In your HTML, you can add attributes with any name to an HTML element. This can be helpful for JavaScript libraries. However, if you try to access this attribute as you normally might-- document.getElementById('myelement').myattribute-- you'll get a null back. The solution is to use .getAttribute().

Additionally, with xforms:input and Orbeon, to add custom attributes you need to prefix the attribute with the xhtml namespace, like so:

xforms:input id="fun" xhtml:myattribute="more fun"

This attribute will be applied NOT to the generated HTML element, but to the span containing the HTML input element! So then you need to use the .parentNode attribute of the HTML element to access this SPAN.

Tuesday, June 03, 2008

Javascript Regular Expression to match a comma delimited list

This example uses the JavaScript String's match() method to parse a comma delimited list into a JavaScript array.


<html>
<head>
<script>
var str = "completeurl, debug, foo";

var m = str.match(/\w+(,\w+)*/g);

var count = m.length; // the count

function parseit() {
document.getElementById("_output").innerHTML="Total length:"+m.length+" ";
for(var i = 0; i < m.length; i++) {
document.getElementById("_output").innerHTML+="Token:\'"+m[i]+"\' ";
}
}
</script>
</head>
</html>
<body onload="parseit();">
<div id="_output" name="_output" style="border:1px solid black"> </div>
</body>
</html>

Monday, May 19, 2008

Dynamically Resize an IFrame in Firefox AND IE

So this is a big mystery out there on the net. There are a ton of silly solutions that half work. This one works for me on IE 6 & 7 and Firefox 2.x. I haven't tried it on anything else. Firefox actually accomplishes all this easily; just set the height of the IFrame to 100%. IE, on the other hand, won't behave. The IFrame needs to be inside a table, and sized to 100% to fill the table. Then you have to alter the size of the table. This doesn't work in FireFox though.



/* ATTACH THE FOLLOWING EVENT TO YOUR IFRAME */
// Resize the iframe to be the same size as the content, attach onload events for each browser
if(window.addEventListener) // Mozilla, FF, NS
reportIFrame.addEventListener('load', resizeEvent, false) ;
else // IE
reportIFrame.attachEvent("onreadystatechange", resizeEvent );

/* HERE IS THE EVENT DEFINITION */

function resizeEvent(evt) {
var ieKey = "srcElement";
var mozKey = "currentTarget";
var IFrameObj;
evt[mozKey] ? IFrameObj = evt[mozKey] : IFrameObj = evt[ieKey];
fitIframe(IFrameObj);
}

/* THIS FUNCTION FITS THE SIZE */

function fitIframe(IFrameObj) {
var IFrameDoc;
if (IFrameObj.contentDocument) {
// For NS6
IFrameDoc = IFrameObj.contentDocument;
IFrameObj.style.height=IFrameDoc.height+"px";
//alert('detected NS6');
} else if (IFrameObj.contentWindow) {
// For IE5.5 and IE6
IFrameDoc = IFrameObj.contentWindow.document;
if(IFrameDoc.body!=null) { // the IE event fires too often, and fires before IFrame is loaded, in which case doc==null. It fires AGAIN once it loads fully, so ignore the first time
try {
var table = document.getElementById(IFrameObj.parentName);
} catch (e) { alert('IE5.5+: Unable to located IFrames parent table: '+IFrameObj.parentName ); }
try {
var height=parseInt(IFrameDoc.body.clientHeight)+20;
table.style.height=height;//-IFrameDoc.body.offsetHeight;//+"px"; //IFrameDoc.body.scrollHeight-IFrameDoc.body.offsetHeight;
} catch (e) { alert('IE5.5+: Unable to get size of IFrame: '+IFrameObj.name ); }
} // if IFrameDoc!=null

} else if (IFrameObj.document) {
// For IE5
IFrameDoc = IFrameObj.document;
// document.getElementById("birtViewer_div").style.height=IFrameDoc.height;
try {
document.getElementById(IFrameObj.parentName).style.height=IFrameDoc.body.scrollHeight-IFrameDoc.body.offsetHeight;
} catch (e) { alert('IE 5.0: Unable to located IFrames parent table or get size of IFrame: '+IFrameObj.parentName ); }
//alert('detected ie5. IFrame body height:'+IFrameDoc.body.offsetHeight);
//IFrameObj.height="600"; //IFrameDoc.body.scrollHeight; //offsetheight works too
} else {
alert('fitIframe was unable to detect the browser version');
return true;
}
}

Attach an OnLoad event in FireFox and IE using JavaScript

IE uses the function attachEvent while FireFox uses addEventListener. IE calls the event onload, while Firefox calls it load. Your function can be called anything.


if(document.all)
window.attachEvent('onload',Yourfunction);
else
window.addEventListener("load",Yourfunction,false);

BIRT deployment

BIRT is an Eclipse reporting project. It comes packaged as a war file you can deploy and with an Eclipse plug in for designing reports. The plug in has some quirks that I will document in another post.

The web application is fairly straightforward to deploy. I extracted it to birt.war and moved the directory into the deploy directory of JBoss.

You can begin using the application by copying your reports into birt.war in the deploy directory and then use included servlets for display. The following link covers the various servlets (frameset, run, preview, output):

http://www.eclipse.org/birt/phoenix/deploy/viewerUsage2.2.php

That covers linking to reports or running them crudely inside IFrames. If you want tighter integration with your application, BIRT includes a tag library for running reports inside JSPs. It includes two main tags, one which inserts the ajax viewer application and one which inserts the report. Unfortunately, neither passes XHTML validation. The report tag COULD but it has a bug that makes the Javascript free option crash the whole page.

In case you don't need XHTML compliance, just copy the birt.tld file into your application's WEB-INF/tld directory. For example:
liferay-portal.ear/portal-web.war/WEB-INF/tld

If you want XHTML compliance... it gets tougher. The trouble is that you can't put JavaScript inline in an XHTML file because various logic characters are interpreted as XML. What you need then is a JavaScript file you can include that can then work its magic on designed static elements. Rather than rehash the details of the whole file, here it is, birt.js, in all its glory. To use it, just place a div in your XHTML, give it a unique id, and call registerViewer with the ID and report name. Parameters should be included as hidden inputs inside the div. Note, all the hackery you see inside this file is because the IE people can't seem to get anything right, so I had to throw all sorts of hacks in to make it work (like resizing an IFrame doesn't work unless its inside a table):


/* BIRT report loader for XHTML
Loads BIRT reports into IE and FF using XHTML compliant code.
Create a DIV with an ID. Inside the DIU, place a script tag that calls registerReportViewer
*/

if(document.all)
window.attachEvent('onload',loadViewers);
else
window.addEventListener("load",loadViewers,false);

var reportViewers = new Array();

function registerReportViewer(divId, reportFileName) {
if(divId==null || reportFileName==null)
alert('registerReportViewer: A required parameter was null');
else
reportViewers[reportViewers.length]=new Array(divId, reportFileName);
//loadViewer('birtViewer','nr');
}

function loadViewers() {
for(i=0; i<reportViewers.length; i++) {
loadViewer(reportViewers[i][0], reportViewers[i][1]);
}
}

function loadViewer(viewerId, reportfile){
var divObj = document.createElement( "DIV" );
var bodyObj = document.body;
if( !bodyObj )
bodyObj = document.createElement("BODY");
bodyObj.appendChild( divObj );
divObj.style.display = "none";
var formObj = document.createElement( "FORM" );
divObj.appendChild( formObj );
var paramContainer = document.getElementById(viewerId);
if(paramContainer==null)
alert('Couldnt find element with id '+viewerId);
var oParams = paramContainer.getElementsByTagName('input');
if( oParams )
{
for( var i=0;i<oParams.length;i++ )
{
var param = document.createElement( "INPUT" );
formObj.appendChild( param );
param.TYPE = "HIDDEN";
param.name= oParams[i].name;
param.value= oParams[i].value;
}
}
var reportTable = document.createElement( "TABLE" );
reportTable.name=viewerId+"_iframe_table";
reportTable.id=reportTable.name;
reportTable.width="100%";
reportTable.style.border="0px";
var reportTBody = document.createElement ("TBODY"); // required by IE only, doesn't hurt FF
var reportRow = document.createElement( "TR" );
var reportCell = document.createElement( "TD" );
reportRow.appendChild(reportCell);
reportTBody.appendChild(reportRow);
reportTable.appendChild(reportTBody);

var reportIFrame = document.createElement("IFRAME");
reportIFrame.name = viewerId+"_iframe";
reportIFrame.id = reportIFrame.name;
reportIFrame.scrolling="no";
reportIFrame.frameBorder="0";
reportIFrame.style.height = "100%";
reportIFrame.style.width = "100%";
reportIFrame.style.border="0px";
reportIFrame.parentName = reportTable.name;
reportCell.appendChild(reportIFrame);
paramContainer.appendChild(reportTable);

// Resize the iframe to be the same size as the content, attach onload events for each browser
if(window.addEventListener) // Mozilla, FF, NS
reportIFrame.addEventListener('load', resizeEvent, false) ;
else // IE
reportIFrame.attachEvent("onreadystatechange", resizeEvent );

// Submit the form
formObj.action = "/birt/preview?__format=html&__report="+reportfile+".rptdesign&__masterpage=true";
formObj.method = "post";
formObj.target = reportIFrame.name;

// Bug fix for IE: IFrames created by JavaScript don't actually have a "name" in IE's internals
if(document.all) {
if(self.frames[reportIFrame.id].name!=reportIFrame.name) /* THIS IS A BUG FIX FOR IE: TARGET DOESNT WORK WITH JAVASCRIPT CREATED IFRAMES */
self.frames[reportIFrame.id].name = reportIFrame.name;
}
formObj.submit( );
}

function resizeEvent(evt) {
var ieKey = "srcElement";
var mozKey = "currentTarget";
var IFrameObj;
evt[mozKey] ? IFrameObj = evt[mozKey] : IFrameObj = evt[ieKey];
fitIframe(IFrameObj);
}

function fitIframe(IFrameObj) {
var IFrameDoc;
if (IFrameObj.contentDocument) {
// For NS6
IFrameDoc = IFrameObj.contentDocument;
IFrameObj.style.height=IFrameDoc.height+"px";
//alert('detected NS6');
} else if (IFrameObj.contentWindow) {
// For IE5.5 and IE6
IFrameDoc = IFrameObj.contentWindow.document;
if(IFrameDoc.body!=null) { // the IE event fires too often, and fires before IFrame is loaded, in which case doc==null. It fires AGAIN once it loads fully, so ignore the first time
try {
var table = document.getElementById(IFrameObj.parentName);
} catch (e) { alert('IE5.5+: Unable to located IFrames parent table: '+IFrameObj.parentName ); }
try {
var height=parseInt(IFrameDoc.body.clientHeight)+20;
table.style.height=height;//-IFrameDoc.body.offsetHeight;//+"px"; //IFrameDoc.body.scrollHeight-IFrameDoc.body.offsetHeight;
} catch (e) { alert('IE5.5+: Unable to get size of IFrame: '+IFrameObj.name ); }
} // if IFrameDoc!=null

} else if (IFrameObj.document) {
// For IE5
IFrameDoc = IFrameObj.document;
// document.getElementById("birtViewer_div").style.height=IFrameDoc.height;
try {
document.getElementById(IFrameObj.parentName).style.height=IFrameDoc.body.scrollHeight-IFrameDoc.body.offsetHeight;
} catch (e) { alert('IE 5.0: Unable to located IFrames parent table or get size of IFrame: '+IFrameObj.parentName ); }
//alert('detected ie5. IFrame body height:'+IFrameDoc.body.offsetHeight);
//IFrameObj.height="600"; //IFrameDoc.body.scrollHeight; //offsetheight works too
} else {
alert('fitIframe was unable to detect the browser version');
return true;
}
}


Separate Deployment of Orbeon

Orbeon Forms has the slickest implementation of XForms for now. However, integrating it with other apps is a bit rough around the edges. The first hurdle was figuring out how to capture submissions and load data (discussed in previous posts here). However we were still quite limited by the static pages served by Orbeon, so we had to find a way to have Orbeon process JSP output. There are actually two methods to achieve this; one merges the Orbeon application into an existing one, but this would be messy and hard to upgrade. The other uses filters to process the output of one application.

After almost a solid day of guess-and-test, Ben made Liferay's JSPs produce xhtml that get interpreted correctly by Orbeon's filter. This is known as the "separate deployment". He followed the instructions at:

http://www.orbeon.com/ops/doc/reference-xforms-java

But, the missing piece of information is that the order in which the filters are setup in Liferay's web.xml are important. In that file, Liferay already has a bunch of filters (about 160 lines worth). The orbeon OPSXFormsFilter must be inserted AFTER the last standard Liferay
filter. This is a problem because there are other filters that handle *.jsp for the whole app. If the filter chain is not assembled in the correct order, the "resource URL" is not correctly
passed to Orbeon, and the parser will not receive any xhtml.

Just for the record, this is the bit of web.xml code which enables Orbeon to filter xhtml inside Liferay:


<!-- Declare and configure the Orbeon Forms XForms filter -->
<filter>
<filter-name>ops-xforms-filter</filter-name>
<filter-class>
org.orbeon.oxf.servlet.OPSXFormsFilter
</filter-class>
<init-param>
<param-name>oxf.xforms.renderer.context</param-name>
<param-value>/orbeon</param-value>
</init-param>
</filter>
<!-- Any web resource under /xforms-jsp is processed by the XForms engine -->
<filter-mapping>
<filter-name>ops-xforms-filter</filter-name>
<url-pattern>/html/xforms-jsp/*</url-pattern>
</filter-mapping>
<!-- This is necessary so that XForms engine resources can be served appropriately -->
<filter-mapping>
<filter-name>ops-xforms-filter</filter-name>
<url-pattern>/orbeon/*</url-pattern>
</filter-mapping>

Ben also copied these libraries to Liferay's WEB-INF/lib, but only the one mentioned in the Orbeon doc may be necessary.
  • orbeon-resources-private.jar
  • orbeon-resources-public.jar
  • orbeon-xforms-filter.jar

Contributors