<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-5318719657729013964</id><updated>2011-07-08T05:38:34.373-04:00</updated><category term='suggest'/><category term='jquery'/><category term='meta'/><category term='hibernate'/><category term='iphone'/><category term='appstore'/><category term='javascript'/><category term='struts'/><category term='sql'/><category term='java'/><category term='web'/><category term='ajax'/><category term='junit'/><category term='servlet'/><category term='testing'/><category term='oracle'/><title type='text'>Notes from Life Science Software Development</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://krokicki.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://krokicki.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Konrad</name><uri>http://www.blogger.com/profile/12388023926322793474</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>11</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-5318719657729013964.post-5932518021557584089</id><published>2009-08-10T15:41:00.003-04:00</published><updated>2009-08-10T15:43:52.965-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='appstore'/><category scheme='http://www.blogger.com/atom/ns#' term='iphone'/><title type='text'>caBIO iPhone Application 1.1</title><content type='html'>The 1.1 update for the caBIO iPhone App is now live on the App Store. This release fixes a bug with the cached "brca1" search which is loaded when the app first starts. In addition, the interface has been improved with result caching, a refresh button, and new toolbar icons.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5318719657729013964-5932518021557584089?l=krokicki.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://krokicki.blogspot.com/feeds/5932518021557584089/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5318719657729013964&amp;postID=5932518021557584089' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/5932518021557584089'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/5932518021557584089'/><link rel='alternate' type='text/html' href='http://krokicki.blogspot.com/2009/08/cabio-iphone-application-11.html' title='caBIO iPhone Application 1.1'/><author><name>Konrad</name><uri>http://www.blogger.com/profile/12388023926322793474</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5318719657729013964.post-1331814178776928253</id><published>2009-06-29T22:03:00.006-04:00</published><updated>2009-08-10T16:22:38.447-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='appstore'/><category scheme='http://www.blogger.com/atom/ns#' term='iphone'/><title type='text'>caBIO iPhone Application 1.0</title><content type='html'>I've been working on an iPhone App for the &lt;a href="http://cabioapi.nci.nih.gov"&gt;caBIO data service&lt;/a&gt; and just submitted it to the App Store for review. This site will serve as the "support site" which Apple requires each app to have.
&lt;br&gt;&lt;br&gt;
Update (7/18/2009): The application is now live on the App Store. Search for "cabio" to find and install it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5318719657729013964-1331814178776928253?l=krokicki.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://krokicki.blogspot.com/feeds/1331814178776928253/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5318719657729013964&amp;postID=1331814178776928253' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/1331814178776928253'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/1331814178776928253'/><link rel='alternate' type='text/html' href='http://krokicki.blogspot.com/2009/06/cabio-iphone-application.html' title='caBIO iPhone Application 1.0'/><author><name>Konrad</name><uri>http://www.blogger.com/profile/12388023926322793474</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5318719657729013964.post-546244358124335216</id><published>2009-02-13T12:15:00.002-05:00</published><updated>2009-02-13T12:33:27.197-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='struts'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>Struts 2: Putting i18n messages in the page context</title><content type='html'>Struts 2 has very sparse documentation when compared with Struts 1, so this took me longer than necessary to figure out, and I thought I'd document it here. Let's say we have a property called &lt;tt&gt;my.property&lt;/tt&gt; in a properties file called &lt;tt&gt;messages.properties&lt;/tt&gt;. We want to display a substring of it on a JSP. The first thing to do is modify struts.properties to load the properties:
&lt;pre&gt;
struts.custom.i18n.resources=messages
&lt;/pre&gt;
This will load &lt;tt&gt;messages.properties&lt;/tt&gt; into Struts. There's a similar setting available in struts.xml, but beware of a &lt;a href="https://issues.apache.org/struts/browse/WW-1668"&gt;bug&lt;/a&gt; which renders it useless in versions prior to 2.0.4. The project I'm working on happened to be using Struts 2.0.1, so I had to add the struts.properties file for this one setting. You can just drop this file into the classpath (i.e. WEB-INF/classes).
&lt;p&gt;
Now if we just want to display this property in a JSP, it's actually quite easy. First make sure to load the Struts 2.x Tag Library:
&lt;pre&gt;
&amp;lt;%@taglib prefix=&amp;quot;s&amp;quot; uri=&amp;quot;/struts-tags&amp;quot;%&amp;gt;
&lt;/pre&gt;
And then just display the property:
&lt;pre&gt;
&amp;lt;s:text name=&amp;quot;my.property&amp;quot;/&amp;gt;
&lt;/pre&gt;
But now lets say we want to do some mangling on the value which would be difficult or impossible with OGNL alone. We need to get the value into the page context:
&lt;pre&gt;
&amp;lt;s:set name=&amp;quot;myprop&amp;quot; scope=&amp;quot;page&amp;quot; value=&amp;quot;%{getText(&amp;#039;my.property&amp;#039;)}&amp;quot;/&amp;gt;
&amp;lt;%
    String url = (String)pageContext.getAttribute(&amp;quot;url&amp;quot;);
    // do stuff to myprop
%&amp;gt;
&amp;lt;%=myprop%&amp;gt;
&lt;/pre&gt;
Now, I realize that modern JSP theory frowns on introducing any Java code directly into the page, but there are cases where this is necessary (for example if you don't have access to anything else!). Figuring out how to get a value from the property bundle and set it into the page context is not really documented in any Struts documentation and involves some tricky syntax.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5318719657729013964-546244358124335216?l=krokicki.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://krokicki.blogspot.com/feeds/546244358124335216/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5318719657729013964&amp;postID=546244358124335216' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/546244358124335216'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/546244358124335216'/><link rel='alternate' type='text/html' href='http://krokicki.blogspot.com/2009/02/struts-2-putting-i18n-messages-in-page.html' title='Struts 2: Putting i18n messages in the page context'/><author><name>Konrad</name><uri>http://www.blogger.com/profile/12388023926322793474</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5318719657729013964.post-5332622574427231366</id><published>2009-01-30T11:14:00.006-05:00</published><updated>2009-01-30T11:43:37.145-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sql'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='suggest'/><category scheme='http://www.blogger.com/atom/ns#' term='servlet'/><category scheme='http://www.blogger.com/atom/ns#' term='oracle'/><title type='text'>Auto-complete: the server side</title><content type='html'>Last time we looked at developing a auto-complete implementation, but we stopped short of implementing the server side. We do have a servlet that returns a list of strings, but those strings are hard-coded. In this post, we'll look at creating a more useful servlet which returns relevant suggestions.
&lt;br/&gt;
First, we note that the jquery.suggest plugin always sends a parameter to the server called &lt;tt&gt;q&lt;/tt&gt; which is the text currently typed into the search box. We should implement our servlet in such a way that it returns ~10 results relevant to the query string &lt;tt&gt;q&lt;/tt&gt;. What is "relevant" is obviously very application dependent. It's important that the search return as quickly as possible, because the result list must be updated upon each keystroke. 
&lt;br/&gt;
The servlet can be very simple, depending on your needs. If your text box is for a disease name, just have the script search the list of diseases for something starting with &lt;tt&gt;q&lt;/tt&gt; and you're done! But suppose you want to auto-complete against a full domain model. We may have several relevant objects in our database, like Gene, Disease, Drug, Organ, and so forth. How do we build our phrase list, and more importantly, how do we order it so that the most useful results are presented first? 
&lt;br/&gt;
Let's create the database table of keywords first.
&lt;pre&gt;
    CREATE TABLE KEYWORD ( 
        ID NUMBER NOT NULL,
        SCORE NUMBER NOT NULL,
        VALUE VARCHAR2(512) NOT NULL
    );
    ALTER TABLE KEYWORD ADD CONSTRAINT PK_KEYWORD PRIMARY KEY (ID);
&lt;/pre&gt;
We can very easily dump words and phrases into here from any class, but what about the score? When the user types "b" we want to return the 10 best results starting with "b", but we may have 10000 of them in the KEYWORD table, so how do we score them to pick the top 10?
&lt;br/&gt;
Generally, it is most useful to sort results by popularity. If you are Google and have an abundance of users, then you can simply keep track of past search results and put popular searches first. But in the life sciences, it's far more likely that you're creating a smaller application, with a limited user base. In that case, one way to judge the popularity of a phrase is to count how many times it is referenced by other objects. For example, suppose you have a Gene object with an association to an ExpressionArrayReporter. For each Gene, count how many reporters it is referenced by. In this way, you get a rough estimate of how popular that Gene is. 
&lt;br/&gt;
Of course, when you estimate popularity like this, the average scores will vary wildly across classes. To normalize the data, we can order the keywords in each class and then take the order as our real score. This guarantees that each class is equally represented. 
&lt;br/&gt;
After you populate the KEYWORD table you will need to query it from the servlet. I will assume you're using Hibernate, and I'll skip over the ORM which can be easily generated. Presumably you now have a Keyword class which maps to your KEYWORD table in the database. Let's write a method to fetch the word list for a given query string. With auto-complete, it's usually most natural to only match at the beginning of words. The following Hibernate code does just that.

&lt;pre&gt;
    public List&lt;Keyword&gt; getKeywords(String s) throws ApplicationException {
        List&lt;Keyword&gt; results = null;
        Session session = null;
        String lwrs = s.toLowerCase();
        try {
            session = sessionFactory.openSession();
            Criteria c = session.createCriteria(Keyword.class);
            c = c.add(Restrictions.or(
                new EscapingLikeExpression("value",escape(lwrs)+"%"),
                new EscapingLikeExpression("value","% "+escape(lwrs)+"%")));
            c = c.addOrder(Order.desc("score"));
            c.setMaxResults(10);
            results = c.list();
        }
        finally {
            session.close();
        }
        return results;
    }

    private String escape(String s) {
        return s.replace("\\", "\\\\").replace("_", "\\_").replace("%", "\\%");
    }
    
    private class EscapingLikeExpression extends LikeExpression {
        EscapingLikeExpression(String propertyName, String value) {
            super(propertyName, value, '\\', false);
        }
    }
&lt;/pre&gt;

Note that escaping special characters is &lt;a href="http://opensource.atlassian.com/projects/hibernate/browse/HHH-2077"&gt;not that straight-forward&lt;/a&gt; with Hibernate's Criteria objects. In any case, you can now call getKeywords in your servlet and print out all the Keyword values returned. 

&lt;pre&gt;
public class AutoCompletionService extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {

        String q = request.getParameter("q");
        PrintWriter pw = new PrintWriter(response.getOutputStream());
        
        response.setContentType("text/plain");
        
        try {
            for(Keyword k : getKeywords(q)) {
                pw.println(k.getValue());
            }
        }
        catch (ApplicationException e) {
            log.error("AutoCompletion error",e);
        }
        
        pw.close();
    }
}
&lt;/pre&gt;
You should have a working auto-complete, minus error handling. You can tweak the population algorithm for the KEYWORD table to heart's delight. You might expect different words to come up when you type in a particular string. Tuning that is more of an art than a science.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5318719657729013964-5332622574427231366?l=krokicki.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://krokicki.blogspot.com/feeds/5332622574427231366/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5318719657729013964&amp;postID=5332622574427231366' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/5332622574427231366'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/5332622574427231366'/><link rel='alternate' type='text/html' href='http://krokicki.blogspot.com/2009/01/auto-complete-server-side.html' title='Auto-complete: the server side'/><author><name>Konrad</name><uri>http://www.blogger.com/profile/12388023926322793474</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5318719657729013964.post-2106720157171501246</id><published>2009-01-26T09:39:00.008-05:00</published><updated>2009-01-30T11:23:33.547-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='suggest'/><category scheme='http://www.blogger.com/atom/ns#' term='jquery'/><category scheme='http://www.blogger.com/atom/ns#' term='ajax'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>Auto-complete: the client side</title><content type='html'>Adding an "auto-complete" or "suggestion" feature for your search is an easy way to greatly enrich your users' experience. This is especially true in applications related to the life sciences, where difficult, lengthy terms are commonplace. Try typing "mechl" into Google, and it immediately suggests "mechlorethamine." 

The implementation of such a feature is naturally divided along the client/server chasm. In this post I will describe the very simple client-side implementation. Next time, I'll describe the server-side, which may become complicated if you have a very large database without user search statistics.
&lt;br/&gt;
In the case of a web application, the client will be a simple Javascript-driven text box. Popular Ajax frameworks like jQuery have auto-completion implementations out of the box. For example, jQuery has a number of &lt;a href="http://plugins.jquery.com/search/node/suggest+type%3Aproject_project"&gt;suggestion plugins&lt;/a&gt;. The best for my needs ended up being &lt;a href="http://plugins.jquery.com/project/suggest"&gt;jquery.suggest&lt;/a&gt;, an extremely simple plugin, that nonetheless features all the important basics such as client-side caching. Implementing the client-side was a matter of importing a few files, and enabling the listener on my input box:

&lt;pre&gt;
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; href=&amp;quot;css/jquery.suggest.css&amp;quot;/&amp;gt;

&amp;lt;script src=&amp;quot;js/jquery-1.2.3.min.js&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;js/jquery.bgiframe.js&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;js/jquery.dimensions.min.js&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;js/jquery.suggest.js&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;input name=&amp;quot;searchString&amp;quot; id=&amp;quot;searchString&amp;quot; value=&amp;quot;&amp;quot;&amp;gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;
    jQuery(function() {
        jQuery(&amp;quot;#searchString&amp;quot;).suggest(&amp;quot;suggest&amp;quot;,{minchars:1});
    });
&amp;lt;/script&amp;gt;
&lt;/pre&gt;

That's it! I passed in minchars:1 because the default is 2, and I wanted the user to get immediate feedback when they were typing. To have this work well, I also had to make a slight tweak to jquery.suggest.js to line 100:

&lt;pre&gt;
} else if (($input.val().length != prevLength)||(prevLength === 0)) {
&lt;/pre&gt;

There are other customizations you can do in the CSS, and by passing additional options to the suggest() function. At this point your server-side should just be a dummy servlet located at /suggest, which returns a newline-delimited list of strings. When you type in the input box, the list returned by your script should appear below. In the next post, I will describe how to make that script return relevant results for what the user is typing.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5318719657729013964-2106720157171501246?l=krokicki.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://krokicki.blogspot.com/feeds/2106720157171501246/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5318719657729013964&amp;postID=2106720157171501246' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/2106720157171501246'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/2106720157171501246'/><link rel='alternate' type='text/html' href='http://krokicki.blogspot.com/2009/01/auto-complete-client-side.html' title='Auto-complete: the client side'/><author><name>Konrad</name><uri>http://www.blogger.com/profile/12388023926322793474</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5318719657729013964.post-7289482023567416301</id><published>2008-09-30T17:43:00.003-04:00</published><updated>2008-10-01T10:57:44.382-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='ajax'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>Highlight occurrences with Javascript</title><content type='html'>Just a simple function today which takes a list of words (strings) and highlights all occurrences of each word in some text. This is useful for presenting search results from an Ajax query for example, where the highlighting is not done on the server side. 

&lt;pre&gt;
function highlight(label, wordList) {
    var h = label;
    for(var i=0; i&amp;lt;wordList.length; i++) {
        h = h.replace(new RegExp(&amp;quot;(&amp;quot;+wordList[i]+&amp;quot;)&amp;quot;,&amp;quot;gi&amp;quot;), &amp;quot;&amp;lt;b&amp;gt;$1&amp;lt;/b&amp;gt;&amp;quot;);
    }
    return h;
}
&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5318719657729013964-7289482023567416301?l=krokicki.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://krokicki.blogspot.com/feeds/7289482023567416301/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5318719657729013964&amp;postID=7289482023567416301' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/7289482023567416301'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/7289482023567416301'/><link rel='alternate' type='text/html' href='http://krokicki.blogspot.com/2008/09/highlight-occurrences-with-javascript.html' title='Highlight occurrences with Javascript'/><author><name>Konrad</name><uri>http://www.blogger.com/profile/12388023926322793474</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5318719657729013964.post-2219900599596439897</id><published>2008-09-27T16:42:00.017-04:00</published><updated>2008-09-28T10:56:18.771-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>Named parameters in Javascript</title><content type='html'>Many Javascript programmers are aware of the benefits of using hashes for parameters to constructors and other complicated functions. Using a hash effectively gives you &lt;i&gt;named parameters&lt;/i&gt;:

&lt;pre&gt;
  function MyWidget(opt_opts) {
    this.background = opt_opts.background  || "white";
    this.border = opt_opts.name || "1px solid black";
  } 
&lt;/pre&gt;

Named parameters are often used for optional parameters that have good defaults. If the programmer is happy with the defaults, they can instantiate a class very easily, passing only the parameters they want to change.  

&lt;pre&gt;
  var widget = new MyWidget({background:"red"});
&lt;/pre&gt;

Establishing the defaults can be very easy with the || trick, as above. If no parameter is given, the || operator yields its second operand, the default value for the parameter. However, one has to be careful with boolean parameters. Here's some code I noticed recently in the production version of a popular Javascript library:

&lt;pre&gt;
  function MyWidget(opt_opts) {
    this.editable = opt_opts.editable || true;
  } 
&lt;/pre&gt;

See the problem? The editable variable will always be set to true, no matter what the user passes in! It's easy to get carried away with the || trick, and start thinking of it as a "default value operator" when in fact it has boolean semantics. In fixing this, you end up writing something like:

&lt;pre&gt;
  function MyWidget(opt_opts) {
    this.editable = (opt_opts.editable!==undefined) ? opt_opts.editable : true;
  } 
&lt;/pre&gt;

Not quite as pretty, is it? One alternative method is to define the defaults up front, with a hash literal, and then copy in the user options as "overrides":

&lt;pre&gt;
  function MyWidget(opt_opts) {
    this.options = {
      background: "white",
      border: "1px solid black"
    };
    for (var s in opt_opts) {
      this.options[s]=opt_opts[s];
    }
  }
&lt;/pre&gt;

Of course, you could always take the fool-proof, Java-inspired approach taken by &lt;a href="http://gmaps-utility-library.googlecode.com/svn/trunk/markertracker/1.0/src/markertracker.js" target="_blank"&gt;markertracker.js&lt;/a&gt;. Each option is meticulously defined with a default "constant", and then the user option is only copied if it's defined:

&lt;pre&gt;
  this.iconScale_ = MarkerTracker.DEFAULT_ICON_SCALE_;
  if (opts.iconScale != undefined ) {
    this.iconScale_ = opts.iconScale;
  }
&lt;/pre&gt;

It's not pithy, but I suppose it works. Personally, I'll stick with the first approach. It reminds me of my Perl days.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5318719657729013964-2219900599596439897?l=krokicki.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://krokicki.blogspot.com/feeds/2219900599596439897/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5318719657729013964&amp;postID=2219900599596439897' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/2219900599596439897'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/2219900599596439897'/><link rel='alternate' type='text/html' href='http://krokicki.blogspot.com/2008/09/default-options-in-javascript.html' title='Named parameters in Javascript'/><author><name>Konrad</name><uri>http://www.blogger.com/profile/12388023926322793474</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5318719657729013964.post-34506942466964837</id><published>2008-09-25T16:37:00.014-04:00</published><updated>2008-09-28T10:53:07.325-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='hibernate'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><category scheme='http://www.blogger.com/atom/ns#' term='junit'/><title type='text'>Unit testing eager fetching</title><content type='html'>When unit testing Hibernate queries, it's sometimes useful to assert that a particular association was eager loaded. For example, suppose you are querying for Genes and want to eager fetch the Gene's &lt;i&gt;aliasCollection&lt;/i&gt; and &lt;i&gt;taxon&lt;/i&gt;. The first is (obviously) a One-to-Many association, and second is a Many-to-One. Your criteria might look something like this (leaving out restrictions for simplicity):

&lt;pre&gt;
    DetachedCriteria criteria = 
        DetachedCriteria.forClass(Gene.class);
    criteria.setFetchMode("aliasCollection", FetchMode.JOIN);
    criteria.setFetchMode("taxon", FetchMode.JOIN);
&lt;/pre&gt;

Or in HQL:

&lt;pre&gt;
    select gene from gov.nih.nci.cabio.domain.Gene gene 
    left join fetch gene.geneAliasCollection as aliases
    left join fetch gene.taxon as taxon
&lt;/pre&gt;

Well, in a JUnit test you would definitely want to ensure the eager fetches are actually being done. If the associations are not eager loaded, no error will occur. Hibernate will just silently do additional queries, slowing down your system inexplicably. But how can we test for such a thing? We need to look inside the Hibernate proxy to see if the object has been loaded, or if it's just an empty shell. This is fairly straightforward for Collections (One-to-Many or Many-to-Many associations):

&lt;pre&gt;
    // get the underlying target object
    Object target = ((Advised)obj).getTargetSource().getTarget();
    Gene gene = (Gene)target;
    // get the collection and cast to PersistentSet
    PersistentSet ps = (PersistentSet)gene.getGeneAliasCollection();
    // assert that the set was eagerly loaded
    assertTrue(ps.wasInitialized());
&lt;/pre&gt;

Things are slightly more convoluted in the case of a Many-to-One or One-to-One association. If the object is eager loaded, it is instantiated as its native class. But if not, then a proxy is created which can do additional queries later. We can detect these proxies by looking at the class name of the associated object. Proxies have class names like &lt;tt&gt;gov.nih.nci.cabio.domain.Taxon$$EnhancerByCGLIB$$e4aca6e7&lt;/tt&gt;. Here I've elected to just check for the CGLIB token:

&lt;pre&gt;
    // get the underlying target object
    Object target = ((Advised)obj).getTargetSource().getTarget();
    Gene gene = (Gene)target;
    // get the associated object
    Taxon taxon = gene.getTaxon();
    // ensure the object was eager loaded
    assertFalse(target.getClass().getName().contains("CGLIB"));
&lt;/pre&gt;

Now, that's a lot of code to write for each test case. To make it easier, I've created a TestCase subclass with extra assert methods. It is &lt;a href="https://gforge.nci.nih.gov/plugins/scmsvn/viewcvs.php/cabioapi/trunk/test/src/gov/nih/nci/cabio/util/ORMTestCase.java?root=cabiodb&amp;view=log"&gt;available&lt;/a&gt; in the caBIO code base.
&lt;p&gt;
Now you can simply write:
&lt;pre&gt;
    assertPreloaded(gene,"geneAliasCollection");
    assertPreloaded(gene,"taxon");
&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5318719657729013964-34506942466964837?l=krokicki.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://krokicki.blogspot.com/feeds/34506942466964837/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5318719657729013964&amp;postID=34506942466964837' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/34506942466964837'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/34506942466964837'/><link rel='alternate' type='text/html' href='http://krokicki.blogspot.com/2008/09/testing-for-hibernate-eager-fetching.html' title='Unit testing eager fetching'/><author><name>Konrad</name><uri>http://www.blogger.com/profile/12388023926322793474</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5318719657729013964.post-4341797841056387679</id><published>2008-09-24T16:34:00.008-04:00</published><updated>2008-09-24T18:07:34.836-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='struts'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>Application scope variables with Struts</title><content type='html'>Often times you want to load some static data just once, when an application starts up, and put it in the application scope for use in JSPs. With Struts, it is not immediately obvious how to do this. Just check out the myriad of answers given in &lt;a href="http://forums.sun.com/thread.jspa?threadID=625773&amp;messageID=3821676"&gt;this thread&lt;/a&gt;. The discussion there eventually settles on a sub-optimal solution involving a servlet that checks if the variable is set in the application context, and setting it otherwise. 
&lt;br/&gt;&lt;br/&gt;
There is a better solution (conceptually and in terms of performance) involving Struts Plugins. Let's say we have a class GlobalQueries, which is initialized with data by its constructor. We can implement a simple Plugin to create a singleton of our class, and put it in the application context.

&lt;pre&gt;
public class AppLoadPlugin implements org.apache.struts.action.PlugIn {
    public void destroy() {}
    public void init(ActionServlet actionServlet, ModuleConfig moduleConfig)
            throws ServletException {
        actionServlet.getServletContext().setAttribute(
            "globalQueries", new GlobalQueries());
    }
}
&lt;/pre&gt;

Then register it in the struts-config.xml:

&lt;pre&gt;
    &amp;lt;plug-in className="your.package.AppLoadPlugin"/&amp;gt;
&lt;/pre&gt;

We can use globalQueries directly in any JSP, using JSTL expression language, or Struts taglibs.

For example, we might write something like this to populate a select list (see previous post):

&lt;pre&gt;
    &amp;lt;html:options name="globalQueries" property="stateValues"/&amp;gt;
&lt;/pre&gt;

Or in a JSTL forEach statement:

&lt;pre&gt;
    &amp;lt;c:forEach var="state" items="${globalQueries.stateValues}"&amp;gt;
&lt;/pre&gt;

So that's a very easy way to expose global data to your application.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5318719657729013964-4341797841056387679?l=krokicki.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://krokicki.blogspot.com/feeds/4341797841056387679/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5318719657729013964&amp;postID=4341797841056387679' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/4341797841056387679'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/4341797841056387679'/><link rel='alternate' type='text/html' href='http://krokicki.blogspot.com/2008/09/application-scope-variables-with-struts.html' title='Application scope variables with Struts'/><author><name>Konrad</name><uri>http://www.blogger.com/profile/12388023926322793474</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5318719657729013964.post-6564174820632732912</id><published>2008-09-23T14:50:00.005-04:00</published><updated>2008-09-27T17:27:31.388-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='struts'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>Select list patterns with Struts</title><content type='html'>The &lt;a href="http://struts.apache.org/1.2.x/userGuide/struts-html.html"&gt;Struts User Guide&lt;/a&gt; is confusing when it comes to populating select lists. Here are some useful patterns I ended up with, after a lot of trial-and-error.

&lt;p&gt;&lt;b&gt;1) Application-level bean with a List of Strings which is used for both values and labels.&lt;/b&gt;&lt;br/&gt;
Here the globalQueries.getStateValues() method returns a List&lt;String&gt; with values like "AL", "AK", "AS", etc.

&lt;pre class="code"&gt;
&amp;lt;html:select property=&amp;quot;state&amp;quot;&amp;gt;
&amp;lt;html:option value=&amp;quot;&amp;quot;&amp;gt;Select...&amp;lt;/html:option&amp;gt;
&amp;lt;html:options name=&amp;quot;globalQueries&amp;quot; property=&amp;quot;stateValues&amp;quot;/&amp;gt;
&amp;lt;/html:select&amp;gt;
&lt;/pre&gt;
&lt;/p&gt;

&lt;p&gt;&lt;b&gt;2) Application-level bean with a List of beans.&lt;/b&gt;&lt;br/&gt;

Here the globalQueries.getStates() method returns a List&lt;State&gt;, where State defines getAbbreviation() and getName(). If the values are immutable, State could even be an enumeration and getStates() would simply return State.values().

&lt;pre class="code"&gt;
&amp;lt;html:select property=&amp;quot;state&amp;quot;&amp;gt;
&amp;lt;html:option value=&amp;quot;&amp;quot;&amp;gt;Select...&amp;lt;/html:option&amp;gt;
&amp;lt;html:optionsCollection name=&amp;quot;globalQueries&amp;quot; property=&amp;quot;states&amp;quot; value=&amp;quot;abbreviation&amp;quot; label=&amp;quot;name&amp;quot;/&amp;gt;
&amp;lt;/html:select&amp;gt;
&lt;/pre&gt;
&lt;/p&gt;

&lt;p&gt;&lt;b&gt;3) Action Form with List of beans.&lt;/b&gt;&lt;br/&gt;

Same as 2, but the getter is on the ActionForm.

&lt;pre class="code"&gt;
&amp;lt;html:select property=&amp;quot;state&amp;quot;&amp;gt;
&amp;lt;html:option value=&amp;quot;&amp;quot;&amp;gt;Select...&amp;lt;/html:option&amp;gt;
&amp;lt;html:optionsCollection property=&amp;quot;states&amp;quot; value=&amp;quot;abbreviation&amp;quot; label=&amp;quot;name&amp;quot;/&amp;gt;
&amp;lt;/html:select&amp;gt;
&lt;/pre&gt;
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5318719657729013964-6564174820632732912?l=krokicki.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://krokicki.blogspot.com/feeds/6564174820632732912/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5318719657729013964&amp;postID=6564174820632732912' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/6564174820632732912'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/6564174820632732912'/><link rel='alternate' type='text/html' href='http://krokicki.blogspot.com/2008/09/select-list-patterns-with-struts_23.html' title='Select list patterns with Struts'/><author><name>Konrad</name><uri>http://www.blogger.com/profile/12388023926322793474</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5318719657729013964.post-5841720808990624061</id><published>2008-09-22T16:33:00.004-04:00</published><updated>2008-09-28T10:57:19.384-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='meta'/><title type='text'>Sine scientia, ars nihil est</title><content type='html'>There are many times when I find myself rediscovering techniques that I researched and employed only months before. This notebook is an attempt to keep track of various tricks of the trade which I learn, use, and ultimately forget. It may be code snippets, commands, workarounds or bookmarks. They may deal with Java, Python, mobile devices, bioinformatics, Google Maps, Javascript, or just about anything else related to programming. Despite the title of this blog, I expect to also be posting about code from personal projects which may not relate to life sciences.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5318719657729013964-5841720808990624061?l=krokicki.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://krokicki.blogspot.com/feeds/5841720808990624061/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5318719657729013964&amp;postID=5841720808990624061' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/5841720808990624061'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5318719657729013964/posts/default/5841720808990624061'/><link rel='alternate' type='text/html' href='http://krokicki.blogspot.com/2008/09/sine-scientia-ars-nihil-est.html' title='Sine scientia, ars nihil est'/><author><name>Konrad</name><uri>http://www.blogger.com/profile/12388023926322793474</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
