Motivation
We had a customer visit a few weeks ago, where customer wanted to see development process for a web application emphasizing master-detail view of data. We did not have quite a good answer back then so I decided to build such an application to see how difficult it is.Choosing Technology
I have already played with Ajax so I decided to use it for the detail view. Roman was talking briefly about NetBeans support of Google Web Toolkit during the visit, so I started to look at that. The rough idea is that you write Java code which is then compiled to Javascript. After seeing the generated code I decided to move on. Not because it does not work - I just could not read it and that meant no go for me.Next tech was jMaki. There is a nice video here, with example which is sort of master-detail. jMaki got green light :-)
My application is a bit extended example from here. That was my starting point.
The detail data are stored in a new table called COMPANY_DETAILS. The table is simple one - just ID and an address. I generated an entity class for the table repeating steps from the original application.
The bad
The original app, uses a table which does not store the original company Id. I did not want to waste time to change that so I used company name as a unique attribute. Provider of the detail data is a new jsp called detailsData.jsp. It expects that it is called with an URL parameter companyName. The jsp is one big example of bad practices, it is left as an exercise to the reader to find them.
<%@ page import="java.util.*" %>
<%@ page import="server.CompanyDetails"%>
<%@ page import="server.Company"%>
<%@ page import="javax.persistence.*" %>
<%@ page import="java.lang.reflect.*" %>
<%
String compName = request.getParameter("companyName");
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jmaki-jpaPU");
EntityManager em = emf.createEntityManager();
Company company = (Company) em.createNamedQuery("Company.findByCompanyname").
setParameter("companyname", compName).getResultList().get(0);
CompanyDetails details = (CompanyDetails) em.createNamedQuery("CompanyDetails.findById").
setParameter("id", company.getId()).getResultList().get(0);
if (details != null) {
out.println(" ");
out.print("{ companyId : '" + details.getId() + "', " +
"address : '" + details.getAddress() + "' } ");
//out.println();
out.println(" ");
}
%>
Publish
I was attracted to jMaki mainly by its approach to events - publish/subscribe. So if I click on the master table, how do I get the right data, that is company name (well that is not quite the right data), and post the data to get the detail?I needed to add to glue.js following:
function getCompanyDetail(company) {
var encodedName = encodeURIComponent(company);
var url = jmaki.webRoot + "/detailsData.jsp?companyName=" + encodedName;
jmaki.doAjax({url: url, callback: function(req) { var _req=req; postProcessCompDetail(_req);}});
function postProcessCompDetail(req) {
jmaki.log("process? : " + req.status);
if (req.readyState == 4) {
if (req.status == 200) {
var response = eval("(" + req.responseText + ")");
//jmaki.publish("/yahoo/dataTable/detail", req.responseText);
jmaki.publish("/yahoo/dataTable/detail/addRow", {
value : response
});
}
}
}
}
jmaki.subscribe("/yahoo/dataTable/on*", function(args) {
if (args.widgetId == 'master') {
var masterWidget = jmaki.getWidget('master');
var sids = masterWidget.dataTable.getSelectedRows();
var company = masterWidget.dataTable.getRecordSet().getRecord(sids[0]).getData('companyName');
//jmaki.log("targetId : onSelect request from: " + args.targetId);
//jmaki.publish('/yahoo/dataTable/detail', company);
getCompanyDetail(company);
jmaki.log("targetId : onSelect request from: " + args.targetId);
}
});
Unfortunately this piece of code took me several hours to complete. First, I did not see in the docs that there is something like dataTable variable in the widget - I had to examine javascript source for the widget, so I was able to find which row is selected. Second, even from sources I was not able to find out that there is that getRecordSet() function. I had to read Yahoo developer documentation. As I was in a place without internet connection, my attempts were constantly failing until reading the docs.
My first attempts were focused on using args.targetId a masterWidget.getIdMapping(), but those attempts really did not go anywhere - I just did not know if it should have work or if I found the right way how to get data.
I had also problems with lack of typechecking in Javascript. E.g. sids were displaying as e.g. 0, or 1 in debug prints, but getRecord just returned null or nothing, driving me crazy, because I thought that I passed string there and it did not work. It turned out that sids[0] is the answer.
getCompany detail is more or less copy/paste from some other example on the jMaki web site. From the docs I thought that just publishing req.responseText is enough. Again, it silently did not work, again problem with types, where API expects a map like object, not just string.
Subscribe
The detail table needs to get updated when Ajax call finishes. Here we go (from glue.js):
jmaki.subscribe("/yahoo/dataTable/detail*", function(args) {
jmaki.getWidget('detail').clear();
jmaki.getWidget('detail').addRow(args);
});
The page
The page is very simple, containing something like:
<div id="content" style="height:400px">
<a:widget name="yahoo.dataTable" service="data.jsp" id="master"
publish="/jmaki/master/onClick" args="rowSingleSelect=true"/>
<br/>
<a:widget name="yahoo.dataTable" id="detail" subscribe="/yahoo/dataTable/detail/"
value="{columns : [
{ label : 'Company Id', id : 'companyId'},
{ label :'Address', id : 'address'}
],
rows: []}"
/>
</div>
It does not look like subscribe for the detail table does anything useful, I guess I am using bad format or something, so I update the table not by sending right messages to the table but by using the API.
The application
The application is one where master contains more data than detail :-)
If I click on the last entry, the detail table is automatically refreshed with new data:
Posted by anovak
@ 06:03 odp. CET