Friday, October 29, 2010

Why do I see output when I call put() on a HashMap in Velocity?

Consider the following Velocity code:
#set($myMap = $contents.getEmptyMap())
$myMap.put("first", "first value")
$myMap.put("second", "second value")
$myMap.put("third", "third value")

<p>$myMap.first</p>
<p>$myMap.second</p>
<p>$myMap.third</p>
The output of that is as below.

$myMap.put("first", "first value") $myMap.put("second", "second value") $myMap.put("third", "third value")

first value

second value

third value
The problem is that put() returns something (it's return type is not void) and Velocity is trying to evaluate it somehow. The fix is easy enough though. Use a dummy variable to catch the return value.

#set($myMap = $contents.getEmptyMap())
#set($dontOutput = $myMap.put("first", "first value"))
#set($dontOutput = $myMap.put("second", "second value"))
#set($dontOutput = $myMap.put("third", "third value"))

Thursday, October 28, 2010

Don't confuse jQuery and Velocity Variable Names

Update. 6/02/2011 1:12:38 PM. Putting Javascript in a separate file is the best way to make sure jQuery and Velocity variables are not confused.

In Velocity, variables are denoted by the dollar sign ($), so when using normal notation, a velocity variable will appear as $someVar. This is tricky if you are writing jQuery code in the same file, because by convention you use the dollar sign to denote jQuery variables as well. For example, see below.

## Refers to a Velocity variable.
$emailAddress = "noreply@example.com"

// Refers to the jQuery object (that wraps a dom object).
var $email = $("#email");
// Refers to a dom object itself
var email_field = $("#email").get(0);

Most often, this is not an issue. The server (and thus Velocity) evaluates a script first and then passes the HTML (including jQuery code) to the browser. In the first stage, Velocity will pick up and evaluate all Velocity variable names i.e. things beginning with a dolllar sign, such as $email. If it finds no value (empty string or null) for that variable, it echos out the name as is, leaving the token $email intact for Javascript to evaluate.

If you do find names are clashing, you can fix this using any of these ways, listed in order of my personal preference.

  1. Put the Javascript into an external .js file. This way the Velocity engine will never even look at the file, and you get the benefit of separation of concerns (HTML being separate from Javascript) and caching (browsers caches Javascript file so it doesn't have to downloaded again every time the page is accessed).
  2. Use different names! The example above will not create an issue because the names are all different: $emailAddress vs $email vs email_field.
  3. Use different characters to denote a jQuery object, e.g. _email = $("#email") as per this excellent article on Authentic Society (The Learning Website): JavaScript Dollar Sign ($) - What is it for?
  4. Use silent notation for Velocity variables, or least those Velocity variables you know or think will be confused with jQuery variables. E.g. $!email. When using silent notation, Velocity will output nothing in place of the variable name if it finds no value (empty string or null) for that variable - leaving nothing to confuse Javascript.
  5. Use formal notation for Velocity variables, or least those Velocity variables you know or think will be confused with jQuery variables. E.g. ${email}. Just as in Bash scripting, the curly braces help the Velocity script engine to discriminate Velocity variables from other text. When using formal notation, Velocity will output tha name as is, leaving the token (e.g. ${email}) intact - which will not confuse Javascript because you will be using $email for the jQuery variable.

It is important to note that in jQuery code, the dollar sign is not just used to denote jQuery variables, but it is also used as an alias for the jQuery class itself, and that in these instances you can use the token jQuery instead. For example, var $email = $("#email") and var $email = jQuery("#email") are the same.

Wednesday, October 27, 2010

Optimising jQuery Selectors

Is your jQuery slow? Is there a noticeable delay when your jQuery effect runs? Check your selectors. jQuery has made Javascript so much easier to write, but easier to get some things wrong too - particularly through over-use of selectors whereby a piece of code spends too much time scanning the DOM. Here are some techniques to help optimise your use of selectors in your jQuery code.

  1. Select by ID wherever possible - use the speed hierarchy.
  2. Don't repeat yourself (DRY). Store results of a selector in a variable if you need to refer to it again.

Select by ID

When you select by ID ($("#some-id")) jQuery will use the "native Javascript" call getElementById() which is very fast and will stop scanning after finding the first result. [3]

If you cannot select an element by ID (perhaps because you are looking for multiple elements), can you narrow down the search by selecting a parent element by ID? jQuery will have to scan for the parent but has a smaller search to execute for the child. For example, $("#some-form-id .mandatory") will be faster than $(".mandatory"). Why? Because the $(".mandatory") selector forces jQuery to visit every single element in the DOM to find all elements with the class "password", whereas the $("#some-form-id .mandatory") narrows down that search to children of the element with ID "some-form-id".

Don't Repeat Yourself

If you find yourself referring to the same element mutliple times using the same jQuery selector, try to turn it into a variable instead. Keep in mind that each time you use a jQuery selector, you are running a search command. So, instead of having multiple instances of $("#some-id") in your code, have one $someElement = $("#some-id") and multiple instances of $someElement.

On a side note, what is the significance of the dollar sign in the variable name $someElement? Is it some supercool jQuery magic? No! $someElement and someElement are both valid JavaScript variable names, and the dollar sign isn't some special operator - it is denoting that the object being referred to was created by jQuery. This is important because the jQuery object you get back from $("#some-id") is different to the DOM element you get back from document.getElementById("some-id"): the former has a "jQuery wrapper".

The Speed Hierarchy

In general, everything revolves around the fact that Javascript/jQuery scans through the DOM every time you ask it to look for something, so you want to choose selectors that make that scanning job fast and small.

The "speed hierarchy" goes like this, in order of fastest to slowest [1]:

  1. ID: $("#some-id")
  2. Element: $("div")
  3. Class: $(".some-class")

As mentioned before, when you select by ID ($("#some-id")) jQuery will use the "native Javascript" call getElementById() which is very fast and will stop scanning after finding the first result. [3]

Selecting by element (tag name) is next fastest because it uses the native call getElementsByTagName() to narrow down the search. For example, searching for $("div .password") will limit the search for element with class "password" to all DIV elements (retrieved by getElementsByTagName()).

Selecting by class is slowest because, as mentioned above, a search like that ($(".mandatory")) forces jQuery to visit every single element in the DOM to find all elements with the class "password".

$= means "ends with". But... even faster than $("#some-id") is $("div[id$=some-id]"). It is faster because div in $("div[id$=some-id]") is telling jQuery to use ignore elements that aren't DIVs and $= in $("div[id$=some-id]") is the "single selector" operator, telling jQuery to stop looking after it's found the first match.

Resources

Sites that helped me with this page.

Friday, October 22, 2010

Multi-line strings in Velocity

Just noticed you can use multi-line strings in Velocity strings to maintain maximum readability. For example:

#getSQLResults("select distinct(t.child) as identifier, c.text1 as title, c.text_area1 as summary
      from tree t, inode i, contentlet c
      where c.live = true
          and t.child = i.identifier
          and i.inode = c.inode
          and (relation_type = 'Gen_Prod_Overview_Tab-Overview_Tab_Articles'
          or t.relation_type = 'Gen_Prod_Specif_Tab-Specif_Tab_Articles'
          or t.relation_type = 'App_Ov_Tab-Ov_Tab_Articles'
          or t.relation_type = 'App_Specif_Tab-Specif_Tab_Articles'
          or t.relation_type = 'App_Key_Cons_Tab-Key_Consideration_Articles'
          or t.relation_type = 'App_Install_Tab-Install_Tab_Articles'
          or t.relation_type = 'App_Finishes_Tab-Finishes_Tab_Articles'
          or t.relation_type = 'App_Supp_Tab-Supp_Tab_Articles')
      order by c.text1
      offset $offset
      limit $resultsPerPage;" )

My dotCMS notes.

Monday, October 18, 2010

Left side (xx) of 'y' operation has null value. Operation not possible

I have been seeing a lot of messages like this in my dotCMS logs:
[18/10/10 02:19:45:045 EST] ERROR app.VelocityEngine: Left side ($queryType) of '==' operation has null value. Operation not possible. /usr/local/dotcms/webap
ps/../dotCMS/assets/1/4/14777.vtl [line 5, column 25]

This results from code such as:
#if ($queryType == "$expectedValue" )

I thought this was strange because the macro this code belonged to wasn't even being executed - so the variable never gets a value anyway. It turns out the fix is to quote the variable, as per below.
#if ("$queryType" == "$expectedValue" )

That's fine for string values, but won't stop an error on anything involved in a numerical operation.
#if ($totalResults > 0 )

The above code results in this error:

[18/10/10 02:46:43:043 EST] ERROR app.VelocityEngine: Left side ($totalResults) of '>' operation has null value. Operation not possible. /usr/local/dotcms/webap
ps/../dotCMS/assets/1/4/14777.vtl [line 1, column 24]

The fix is to make sure it is a number under any circumstances.

#if (!$UtilMethods.isSet($totalResults))
   #set($totalResults = 0)
#end
#if ($totalResults > 0 )


It seems Velocity is a bit like bash or DOS shell scripts in this respect; unless you quote a variable name, it will see null as the value and output an error.

My dotCMS notes.

Wednesday, October 13, 2010

Remove duplicates in a list using Velocity code only

Updates: 25/10/2010. Thanks to Christopher F. Falzone in the post Compound relationship query for a better way to create an empty map.

In dotCMS, I faced a situation where I had a list in Velocity code that contained duplicates. It contained duplicates because I had to traverse a transitive relationship to build a list of objects related to some source object through two separate relationships.

Here is how I removed the duplicates - via a map.

## Remove duplicates.
#set($publishersMap = $contents.getEmptyMap()) ## Make a map to remove dupes.
#foreach($publisher in $publishers)
   #set($dontOutput = $publishersMap.put("$publisher.name", $publisher))
#end
#set($dontOutput = $publishers.clear())
#set($dontOutput = $publishers.addAll($publishersMap.values()))

## Sort.
#set($list = $sorter.sort($publishers, "name"))

Essentially, I am adding all elements from the list to a map (because a map ignores duplicates). Then I clear the original list and add all the elements (values, not keys or entries) back to the original list.

My dotCMS notes.

Friday, October 08, 2010

Simplest dotCMS Detail Page using Velocity

Update: 27/10/2010 Used $webapi.getIdentifierInode($id) to ensure that we have identifier, irrespective of whether our request parameter was an identifier or inode.

Simplest way to create a dotCMS detail page in Velocity. All you need is the item's identifier or inode. Assume identifier is stored as id in the request.

#if(!$UtilMethods.isSet($request.getParameter("id")))
   <p> Nothing to fetch </p>
#else
   #set($id = $request.getParameter("id"))
   #set($id = $webapi.getIdentifierInode($id)) ## Get identifier from inode or identifier
   #getContentMapDetailByIdentifier($id)
   #foreach($entry in $content.entrySet())
      <p><b>$entry.key</b>: $entry.value</p>
   #end
#end

This produces a non-sorted list of attribute name/value pairs. For a more complicated detail page (which is sorted), try out this code sample by Chris Falzone, which he referred me to in this dotCMS Yahoo post: loop through all fields in a node?

You can use #getContentMapDetailByIdentifier() or #getContentMapDetail() - they seem to do pretty much the same thing. It is most important to use a method that relies on identifier rather than inode (like #pullContent()) for lookup because users might bookmark the detail page and inode changes with each revision. The #getContent() and #pullContent() macros can use either the identifier or any of the inode values - they are smart enough to always look up the latest content (but they don't return a handy map though).

One more thing: right now, I can find no concrete documentation on either #getContentMapDetailByIdentifier() or #getContentMapDetail(), just a couple of posts that reference those methods. I had to dig into dotCMS vm files (dotCMS 1.7). Both of these macros are defined in $DOTCMS_HOME/dotCMS/WEB-INF/velocity/VM_global_library.vm.

My dotCMS notes.

Don't swallow IOException from OutputStream.close().

If OutputStream throws IOException on close then probably the whole write failed and left the data in a buffer i.e., nothing happened. So don't swallow this exception when you close an OutputStream.

From the Java Posse #299 Devoxx 09 - Project Lombok Interview, 18 mins 10 seconds in.

This is a good reason to support Joshua Bloch's ARM Block proposal.

Sunday, September 26, 2010

Using webDAV and Cadaver with dotCMS

As a developer, one of the most annoying things about dotCMS is how many darn *clicks* it takes to edit anything. There must be an easier way to access files in dotCMS. There is: webDAV and Cadaver. Both of these allow limited access to dotCMS's files: the former through Windows Explorer, the latter through a command line.

Connect to the dotCMS Using webDAV

Look at the Connect to the dotCMS Using webDAV page (for Windows, Mac and Linux). This lets you see webDAV files through your file explorer (Windows Explorer on Windows). But be warned: you can't edit these files in place. Instead, you have to make copies of them, edit them locally, then click and drag the files back to the webDAV directory, replacing the originals. You can bookmark the directories though. Also, you don't see everything - you can't see .dot files for example.

Using autopub vs nonpub 

The URL you use to connect with webDAV can control whether changes you make are automatically published or not. See the below examples.

  • http://example.com/webdav/autopub means files are published as they are uploaded.
  • http://example.com/webdav/nonpub means files uploaded as "working" copies.

Cadaver

Macabre name, but a great tool for command line access to webDAV collections such as dotCMS. It is a *nux tool, but there is Cadaver for Cygwin too! Use it like you use the BSD ftp command line tool - when connected, type ? for a list of the commands you can use.

First thing to do is set up a .netrc file that contains your webDAV credentials.

machine     example.com
login       your-username
password    you-password

Then you can connect to it with the following command:

cadaver -t http://example.com/webdav/autopub/dir/you/want

Replace /dir/you/want with whatever directory path you want to start in. Also note the differences between using /webdav/autopub and /webdav/nonpub as noted above in the Using autopub vs nonpub section.

Transferring files via Cadaver script

Of course, what is life without a script? Here is a script that will make transferring files a lot easier.

#! /bin/bash
# webDavTransfer.sh
if [ "$1" != "-d" ] ; then
   echo Usage $0 -d dest-path files ...
   echo Transfer files from current directory to dest-path in webDAV.
   exit 2
fi
destPath=$2
shift; shift

cadaver -t <<EOF
open http://example.com/webdav/autopub/base-path
cd $destPath
mput $@
quit
EOF

The pre-requisite to using this script is that you have ~.netrc file set up as described above, also copied below.

machine     example.com
login       your-username
password    you-password

This is how to use the script:

cd path/to/local/files
webDavTransfer.sh -d tests/rob *.vtl

The result should be what looks like FTP output, and your files should be transferred.

Don't forget that aliases can make it even easier: 

alias myTransfer='webDavTransfer.sh -d tests/rob'

Then all you need to run is:

myTransfer *.vtl

So you can make an alias for each path you transfer to.

Pages that helped me write this post.

My dotCMS notes.

Using cdargs in Cygwin

The cdargs utility is a very powerful directory bookmarking in *nix shells, including Cygwin. There are many wonderful pages on how to use and extend cdargs, some of which I reference at the end of this post.

Unfortunately, these pages talk about some utility functions (mark, cdb, cv, ca) that you get from sourcing cdargs-bash.sh - which I don't have in my copy of Cygwin. After reading man cdargs though, I found that the two most important functions were easy to create for myself: mark to create a cdargs bookmark and cdb to cd into the directory referenced by the bookmark.

Put these in your .bashrc or .bash_profile.

# cd to a cdargs bookmark.
function mark() {
   if [ $# -ne 1 ] ; then
     echo Usage: mark cdargs-bookmark-name
     return 2
   fi
   cdargs --add=:$1:` cygpath -u -a .`
}

# Create a bookmarks in cdargs for the current directory.
function cdb() {
   if [ $# -ne 1 ] ; then
     echo Usage: cdb cdargs-bookmark-name
     return 2
   fi
   cdargs "$1" && cd "`cat "$HOME/.cdargsresult"`" ;
}

Using them is easy enough too. Use mark to create a cdargs entry (stored in the plain text file ~/.cdargs - which you can even edit with a text editor). Then use mark to go to (cd) a bookmarked directory. If you ever need to be reminded what your bookmarks are, look at the ~/.cdargs file or just run cdargs.

Example usage.

cd /c/really/long/path/that/is/hard/to/type/and/remember
mark bigPath

Now you have bookmarked the directory against the key bigPath. To get back to that directory easily, use the command cdb bigPath.

The following pages have helped me learn more about how to use cdargs.

Search for dotCMS users by group or role

You interact with dotCMS users in Velocity via the $cmsuser object, which is an instance of the com.dotmarketing.viewtools.CMSUsersWebAPI class and provides various methods for looking up user objects.

The searchUsersAndUsersProxy method lets you search for users by name, group name or role. Below is the code I use to search for all users who have the Administrator role and then output Name (email address) for each of them. I have only tested this on dotCMS 1.7.

#set($found = $cmsuser.searchUsersAndUsersProxy(null, null, null, [], false, ["Administrator"], true, null, 1, 1000))
<p>Found $found.get("users").size() Administrator users.</p>
#set($theUsers = $found.get("users"))
#foreach ($user in $theUsers)
   $user.fullName ($user.emailAddress)<br>
#end

Below are some important notes about this code.

  • This method is the only one in the class designed for pagination i.e. for displaying results one page at a time. The last two parameters are page - as in what "what page of results do you want to return?" - and pageSize - as in "how many results should be on this page?"
  • The limit argument for this method (pageSize) has slightly different semantics to the limit argument in other search techniques such as # results in the pullContent macro. In the latter, 0 means no limit on the results, but in searchUsersAndUsersProxy it really means 0! If you intend on pulling back all results in one page, use a number so big that you will never get that many users.

    At the time of writing, Facebook has more than 500,000,000 users, Twitter has more than 75,000,000 users, Hotmail has more than 360,000,000 users and Gmail has more than 170,000 users. I think 1,000 is well and truly more than the total number of Administrator users the site I am working on will ever have. A dotCMS JIRA bug was raised for this discrepancy and I posted a question to StackOverflow when I was trying to work why my original code wasn't working.
  • The fourth parameter, groupNames should be empty square brackets [] - which is an empty array/ArrayList for Velocity; which does not support the keyword null (my first guess as a Java programmer).
  • The fifth parameter, showUserGroups should be false. If true, you will get lots of duplicates (as you would if there was a full join between selecting users matching role X and any group).

I found this useful when I was working on a dotCMS form handler whose intention was to allow users of the site to submit feedback for review by the site administrators. I used the above technique to extract all of the administrator email addresses as recipients for the email (addresses which you will find are not displayed in the HTML source, if you read the post I linked to). I came across the issues mentioned above and tried to solve them in this post to the dotCMS Yahoo group: Getting email addresses of all users with the Administrator role.

For testing purposes, here is the SQL that will do the same job (get email addresses for every user with an Administrator role).

select user_.emailaddress
from user_
INNER JOIN users_roles
ON users_roles.userid = user_.userid
INNER JOIN role_
ON users_roles.roleid = role_.roleid
where role_.name = 'Administrator';

Pages that helped me write this post.

My dotCMS notes.

Thursday, September 23, 2010

dotCMS form handler as a jQuery dialog with encrypted email

dotCMS 1.7 has fairly straight-forward form handling that makes it easy to write (in HTML) a form that will submit to dotCMS (action="/dotCMS/submitWebForm") and email the form contents to an email address you nominate. The purpose of this posting is to show the following.

  1. How to create a simple dotCMS form using this mechanism that emails the results to someone.
  2. How to encrypt the email addresses so that they do not appear in plain text within your HTML
  3. How to use some simple jQuery to make the form a "floating dialog" that can be triggered by clicking on a button.

Since I don't have a dotCMS server of my own, here are screen shots of the results.

This is what the form looks like.

Click the button and the form is displayed. It isn't a pop-up (those are evil); it's a hidden element that gets shown (less evil) when you click "Give Feedback". After the user has filled out the form and clicks "Submit", they are redirected back to the same page.

And this is the email you get from dotCMS containing details of the submitted form.

Next I will show the code in chunks, with a bit of discussion for each chunk.

The first things you need are imports for the jQuery theme, jQuery API itself and then jQuery UI.

<link href='http://robertmarkbram.appspot.com/content/global/css/jQueryUi/customThemeGreen/jquery-ui-1.8.4.custom.css' rel='stylesheet' type='text/css'/>
<script type="text/javascript" src="http://www.google.com/jsapi?key=ABQIAAAAoDEIY_vXge_LQOEVgHyheBSvIISGg2D4cAMKlpvPZkPgQSL0sRRGyBerqkXeyllTDvkGdlqzeYPWKA" ></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.5/jquery-ui.min.js"></script>

What's with the jsapi?key and the imports from ajax.googleapis.com? Google (being the thoroughly non-evil company that they are) offer several well known Javascript APIs for public use on websites other than their own i.e. they host them so you don't have to. They ask that you register for (free) and use an API key specific to your site so they know ... whatever it is they want to know. Read more about this on the Google Libraries API - Developer's Guide. This is perfect for things like Blogger i.e. services for which you cannot upload your own scripts or other arbitrary files.

Here is the HTML for the visible portion of the page.

<div style="border:1px solid;padding:10px;margin:10px;width:200px;">
   <p>My very interesting page.. </p>
   <button id="opener">Give Feedback</button>
</div>

This is the HTML for the dialog that will appear when the user clicks the button.

<form method="post" id="feedbackForm" name="feedbackForm" action="/dotCMS/submitWebForm" style="display:none;">
   <input type="hidden" name="from" id="from" value="noreply@example.com">
   #encrypt('robertmarkbram@gmail.com') #set ($to = $encryptedStr)
   <input type="hidden" name="to" id="to" value="$to"/>
   <input type="hidden" name="subject" id="subject" value="Feedback on your cool site"/>
   <input type="hidden" name="returnUrl" id="returnUrl" value=""/>
   <input type="hidden" name="errorURL" id="errorURL" value="/error/page" />
   <input type="hidden" name="html" id="html" value="1" />
   <input type="hidden" name="order" id="order" value="referrer,reason,feedback" />
   <input type="hidden" name="formType" id="formType" value="feedbackForm" />
   <fieldset>
      <h1>Feedback Form</h1>
      <p>Please use this form to provide feeback.</p>
      <input type="hidden" id="referrer" name="Referrer URL" value="" />
      <p>Reason for feeback</p>
      <select id="reason" name="Reason for Feedback">
         <option value="Your site is awesome!">Your site is awesome!</option>
         <option value="Are you my father?">Are you my father?</option>
      </select>
      <p>Comments</p>
      <textarea id="feedback" name="Feedback"></textarea>
      <p><input type="submit" value="Submit" /></p>
   </fieldset>
</form>

Notes about this code.

This form submits to /dotCMS/submitWebForm - which is the dotCMS code that will process the data and email it out to whoever is specified in the hidden to field.

It is very easy to encypt the email address that will appear in the hidden to field. Do it with the code below.

#encrypt('robertmarkbram@gmail.com') #set ($to = $encryptedStr)
<input type="hidden" name="to" id="to" value="$to"/>

This is how the HTML looks on the browser:

<input type="hidden" value="aJ8pVupCRaDYMRzM8qXtCS4IGXAInnAtgKPynQZNDe4=" id="to" name="to">

dotCMS's /dotCMS/submitWebForm automagically decrypts it to get the actual email address(es).

The hidden returnUrl field specifies what URL the browser will go to after the form is submitted (shown below). The jQuery discussed further down sets this value.

<input type="hidden" name="returnUrl" id="returnUrl" value=""/>

This jQuery Javascript sets up the dialog and sets up a button to open it. Plus it initialises data for the form - specifically what URL this form is hosted on.

<script language="javascript">
  $(document).ready(function() {
     // Creates a dialog from element with ID "feedbackForm"
    var $dialog = $("#feedbackForm").dialog({
      autoOpen: false,
      title: 'Feedback Form',
      height: "auto",
      width: "auto"
    });
      // If you click the button (ID "opener") it will open the form dialog.
    $('#opener').click(function() {
      $dialog.dialog('open');
      // prevent the default action, e.g., following a link
      return false;
    });
    // Redirect back to the same page after the form is submitted.
      $("#returnUrl").val(window.location.href);
    // To inform email recipient of the URL the form was from.
      $("#referrer").val(window.location.href);
  });
</script>

Note that the hidden returnUrl field is given the value of window.location.href - so after clicking the button to submit the form, the browser will go back to the same page (the dialog form will not be visible again though; just like what happens if you refresh the page).

For a more detailed code example showing what options you can specify in the form, see the dotCMS Wiki page on Sample Form Code. See the dotCMS documentation page, Form Handling for an explanation of the different options. For example, you can even provide an alternate email template.

Current Form Handling. In dotCMS 1.9, there is a new way to build forms that means you don't need knowledge of HTML and makes the forms more re-usable as dotCMS Form entities. See the new Form Handling documentation for instructions on this.

I am not sure if the approach outlined in this post still works in 1.9. I am also not sure if the new approach hides emails. Or how easy it is to apply the jQuery touch.

My dotCMS notes.

Sunday, September 19, 2010

jQuery Slideshow

Presented below is a jQuery slideshow, with the code discussed below that. Originally, this was based on Rodrigo Silveira's Javascript Prototype Slideshow, re-written for jQuery. But in Chrome I was witnessing a disturbing flicker (that not all Chrome users were seeing) and alternative code was suggested by Peter Ajtai in my StackOverflow post, Why is this slideshow flickering? This post now uses and presents Peter's adaptation of my re-write of Rodrigo's code. And they say there is nothing new in the world?

Here is a standalone version without the chaff from before the write, and here is a standalone version without the chaff showing the current code.


Here is the code.

First up, you need to import the jQuery API.

<script type="text/javascript" src="http://www.google.com/jsapi?key=ABQIAAAAoDEIY_vXge_LQOEVgHyheBSvIISGg2D4cAMKlpvPZkPgQSL0sRRGyBerqkXeyllTDvkGdlqzeYPWKA" ></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>

What's with the jsapi?key and the imports from ajax.googleapis.com? Google (being the thoroughly non-evil company that they are) offer several well known Javascript APIs for public use on websites other than their own i.e. they host them so you don't have to. They ask that you register for (free) and use an API key specific to your site so they know ... whatever it is they want to know. Read more about this on the Google Libraries API - Developer's Guide. This is perfect for things like Blogger i.e. services for which you cannot upload your own scripts or other arbitrary files.

Next is the Javascript, written against jQuery.

<script type="text/javascript">
   // Contain all your functionality in a self calling anonymous
   // function, so that you don't clutter the global namespace.
   (function() {
      // ------
      // ###### Edit these.
      // Assumes you have images in path named 1.jpg, 2.jpg etc.
      var imagePath = "images";
      var lastImage = 5;         // How many images do you have?
      var fadeTime = 4000;       // Time between image fadeouts.

      // ------
      // ###### Don't edit beyond this point.
      // No need for outer index var
      function slideShow(index) {
         var url = imagePath + "/" + index + ".jpg";
         // Add new image behind current image
         $("#slideShow").prepend($("<img/>").attr("src",url));
         // Fade the current image, then in the call back
         // remove the image and call the next image.
         $("#slideShow img:last").fadeOut("slow", function() {
            $(this).remove();
            setTimeout(function() {
               slideShow((index % lastImage) + 1)
            }, fadeTime);
         });
      }
      $(document).ready(function() {
         // Img 1 is already showing, so we call 2
         setTimeout(function() { slideShow(2); }, fadeTime);
      });
   })();
</script>

Note that we are creating the second image within this Javascript ($("#slideShow").prepend($("").attr("src",url));), so the HTML only needs to define one image at the start.

Here is the CSS needed to render two images on top of each other.

<style>
   #slideShow {
      position:relative;
      width: 500px;
      height: 500px;
   }
   #slideShow IMG {
      position:absolute;
   }
</style>

And lastly, the HTML - you need to fill in the first image yourself.

<div id="slideShow">
   <img id="slideShowBack"  src="images/1.jpg" />
</div>

One last important note (at least to this post). Part of the reason for the flickering I witnessed in Chrome was attributed to the re-rendering of some fancy border related CSS on the IMG elements. The fix was to remove the CSS from the IMG and put it on a surrounding DIV instead. Since the CSS is coming from my Blogger template, I had to first "reverse" the template's CSS, and then add it to the surrounding DIV. Below is the CSS I used to do just that.

/* Overriding Blog styles to put IMG border on the surrounding DIV. */
.post-body IMG {
   padding: 0px;
   background: white;
   border: none;
   -moz-box-shadow: none;
   -webkit-box-shadow: none;
   box-shadow: none;
   -moz-border-radius: 0;
   -webkit-border-radius: 0;
   border-radius: 0;
}
.post-body #slideShow {
   padding: 8px;
   background: #ffffff;
   border: 1px solid #cccccc;
   -moz-box-shadow: 0 0 20px rgba(0, 0, 0, .2);
   -webkit-box-shadow: 0 0 20px rgba(0, 0, 0, .2);
   box-shadow: 0 0 20px rgba(0, 0, 0, .2);
   -moz-border-radius: 0;
   -webkit-border-radius: 0;
   border-radius: 0;
}

Saturday, September 18, 2010

Event Handling with the jQuery Autocomplete Combobox

Updates.
Tuesday 5 April 2011, 02:18:59 PM - added code that copies TITLE across from the original SELECT to the new INPUT.

First, the exciting bit. Have a look at the list of Star Trek characters below. See if you can guess which actor plays your favourite character - before you make a selection. Each time you make a selection, you will see the actor's name appear in the text box beside the combobox.


Now for the really cool bit: try typing TNG into the combobox. See what happens? The list is shortened to include only those options that begin with "TNG". Now try typing Spock into the combobox. The list is shortened to include only those options that contain "Spock" (not just begin with).

This is a demonstration of jQuery's combobox UI widget (links to demo page) - currently described as a prototype - which is built on top of the autocomplete UI widget.

The autocomplete feature has an overwhelming appeal, meaning that I couldn't bear to use any other jQuery combobox after I saw this one in action; but there is a cost. This widget replaces a SELECT control with a text field, a button and a floating list, thus it is a complicated task to make them all work as one; and it isn't done perfectly (yet).

  • Event handling is incomplete and not compliant to jQuery naming standards (though the latter is minor and will be easy to fix).
  • The floating list needs adjustment to be made scrollable (or large lists will be unmanageable).
  • The combobox doesn't adjust to fit the width of its contents like a HTML SELECT control should.
  • You need a whole jQuery theme just to get the autocomplete control (the floating list coming down from the text field) to render decently - plus extra CSS fixes specifically for the combobox, i.e. it's a lot of effort and CSS if all you want is one (very cool) combobox control. Granted, if you know your CSS well enough you don't need a whole jQuery theme; but after looking at what goes into a theme, I didn't want to spend the time to pick out what pieces are needed - plus I wanted to be ready for when I might use other jQuery themed controls.

Below is my adaptation of jQuery's combobox UI widget. I present the code in chunks and explain what I have changed from the original. There is also a standalone version of this example that shows the control without the chaff.

The first things you need are imports for the jQuery theme, jQuery API itself and then jQuery UI.

<link href='http://robertmarkbram.appspot.com/content/global/css/jQueryUi/customThemeGreen/jquery-ui-1.8.4.custom.css' rel='stylesheet' type='text/css'/>
<script type="text/javascript" src="http://www.google.com/jsapi?key=ABQIAAAAoDEIY_vXge_LQOEVgHyheBSvIISGg2D4cAMKlpvPZkPgQSL0sRRGyBerqkXeyllTDvkGdlqzeYPWKA" ></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.5/jquery-ui.min.js"></script>

What's with the jsapi?key and the imports from ajax.googleapis.com? Google (being the thoroughly non-evil company that they are) offer several well known Javascript APIs for public use on websites other than their own i.e. they host them so you don't have to. They ask that you register for (free) and use an API key specific to your site so they know ... whatever it is they want to know. Read more about this on the Google Libraries API - Developer's Guide. This is perfect for things like Blogger i.e. services for which you cannot upload your own scripts or other arbitrary files.

Next is the CSS specifically for the combobox.

<style type="text/css">
   .ui-button { margin-left: -1px; }
   .ui-button-icon-only .ui-button-text { padding: 0.35em; }
   .ui-autocomplete-input { margin: 0; padding: 0.48em 0 0.47em 0.45em; }
   .ui-autocomplete { height: 200px; overflow-y: scroll; overflow-x: hidden;}
</style>

I added the last line to make sure the autocomplete portion would scroll on long lists - which would otherwise be unmanageable. I copied the code from this jQuery forum post: Autocomplete with vertical scrollbar. I am still not entirely happy with some aspects of the above CSS. It renders nicely on Firefox 3.6.8, Internet Explorer 8 and Chrome 6 - but the edges didn't line up correctly in Chrome 7 (Dev build): the text field seemed just a pixel or two taller than the button. Worse, zooming in and out in all of those browsers could also make the heights mis-match by just a pixel or so.

Below is the actual code for the jQuery autocomplete combobox widget. Of course, this belongs in its own javascript file unless you plan to use it one page only.

<script type="text/javascript">
   (function( $ ) {
      $.widget( "ui.combobox", {
         _create: function() {
            var self = this;
            var select = this.element,
               theWidth = select.width(),
               selected = select.children( ":selected" ),
               theTitle = select.attr("title"),
               value = selected.val() ? selected.text() : "";
            select.hide();
            var input = $( "<input style=\"width:" + theWidth + "px\">" )
               .val( value )
               .attr('title', '' + theTitle + '')
               .autocomplete({
                  delay: 0,
                  minLength: 0,
                  source: function( request, response ) {
                     var matcher = new RegExp( $.ui.autocomplete.escapeRegex(request.term), "i" );
                     response( select.children( "option" ).map(function() {
                        var text = $( this ).text();
                        if ( this.value && ( !request.term || matcher.test(text) ) )
                           return {
                              label: text.replace(
                                 new RegExp(
                                    "(?![^&;]+;)(?!<[^<>]*)(" +
                                    $.ui.autocomplete.escapeRegex(request.term) +
                                    ")(?![^<>]*>)(?![^&;]+;)", "gi"
                                 ), "<strong>$1</strong>" ),
                              value: text,
                              option: this
                           };
                     }) );
                  },
                  select: function( event, ui ) {
                     ui.item.option.selected = true;
                     //select.val( ui.item.option.value );
                     self._trigger( "selected", event, {
                        item: ui.item.option
                     });
                  },
                  change: function( event, ui ) {
                     if ( !ui.item ) {
                        var matcher = new RegExp( "^" + $.ui.autocomplete.escapeRegex( $(this).val() ) + "$", "i" ),
                           valid = false;
                        select.children( "option" ).each(function() {
                           if ( this.value.match( matcher ) ) {
                              this.selected = valid = true;
                              return false;
                           }
                        });
                        if ( !valid ) {
                           // remove invalid value, as it didn't match anything
                           $( this ).val( "" );
                           select.val( "" );
                           return false;
                        }
                     }
                  }
               })
               .addClass( "ui-widget ui-widget-content ui-corner-left" );
            var span = $("<span style=\" white-space: nowrap;\"></span>")
                  .append(input).insertAfter( select );
            input.data( "autocomplete" )._renderItem = function( ul, item ) {
               return $( "<li></li>" )
                  .data( "item.autocomplete", item )
                  .append( "<a>" + item.label + "</a>" )
                  .appendTo( ul );
            };

            $( "<button> </button>" )
               .attr( "tabIndex", -1 )
               .attr( "title", "Show All Items" )
               .insertAfter( input )
               .button({
                  icons: {
                     primary: "ui-icon-triangle-1-s"
                  },
                  text: false
               })
               .removeClass( "ui-corner-all" )
               .addClass( "ui-corner-right ui-button-icon" )
               .click(function() {
                  // close if already visible
                  if ( input.autocomplete( "widget" ).is( ":visible" ) ) {
                     input.autocomplete( "close" );
                     return;
                  }

                  // pass empty string as value to search for, displaying all results
                  input.autocomplete( "search", "" );
                  input.focus();
               });
         }
      });
   })(jQuery);
</script>

I changed parts of the original to make the combobox re-adjust to match the size of the underlying SELECT control. The original and modified versions are shown below for comparison.

// Original
var self = this;
var select = this.element.hide(),
 selected = select.children( ":selected" ),
 theTitle = select.attr("title"),
 value = selected.val() ? selected.text() : "";
var input = $( "" )
.attr('title', '' + theTitle + '')

// Modified. Can you see Wally?
var self = this;
var select = this.element,
   theWidth = select.width(),
   selected = select.children( ":selected" ),
   value = selected.val() ? selected.text() : "";
select.hide();
var input = $( "<input style=\"width:" + theWidth +
.val( value )
.attr('title', '' + theTitle + '')

Although the width of the combobox should now match the width of the underlying SELECT, the font used in the jQuery theme can still screw up the effect. E.g. a comparatively large (or small) font will still make the combobox appear to be the wrong width (the HTML SELECT would just re-size itself). I also made sure to copy over TITLE from the SELECT over to the INPUT that is being made here.

Another change I made from the original was to ensure that the two elements (an INPUT and a BUTTON) are enclosed by a SPAN with white-space: nowrap to ensure that the controls always stay together and never wrap

// Original
var input = $( "<input>" )
.insertAfter( select )
.val( value )

// Modified, Part 1. Removed insertAfter.
var input = $( "<input>" )
.val( value )

// Modified, Part 2, added two lines after .addClass( "ui-widget...
.addClass( "ui-widget ui-widget-content ui-corner-left" );
var span = $("<span style=\" white-space: nowrap;\"></span>")
      .append(input).insertAfter( select );

That's the end of the "infrastructure" code. Below is an abridged version of the underlying SELECT, an input that will display the actor's name when a selection is made (to demonstrate event handling), and a button that will toggle display of the underlying SELECT control.

<select id="starTrekCharacters">
   <option value="William Shatner">TOS - Captain James T. Kirk</option>
   <!-- et etc -->
 </select>
<input type="text" id="starTrekActor" length="30"/>
<button id="toggle">Show underlying select</button>

For your own combobox, you only need the SELECT. The INPUT and BUTTON are just there to help demonstrate event handling and to toggle showing the underlying SELECT control.

Also note that the jQuery combobox will drop any OPTIONs that don't have a value; so if you have a blank OTPION at the start (like my -- Choose a Star Trek character. --) you must give it a value and make sure that any back-end system knows how to deal with that value too.

The jQuery code that turns a SELECT into a combobox is also very easy, as seen below.

<script language="javascript" type="text/javascript">
   // This is run when the document has loaded.
   $(document).ready(function() {
      // Create a combobox
      $("#starTrekCharacters").combobox({
         // And supply the "selected" event handler at the same time.
         selected: function(event, ui) {
            $("#starTrekActor").val($("#starTrekCharacters").val());
         }
      });
      // NOT NEEDED for combobox; included just to let you see underlying SELECT.
      $("#toggle").click(function() {
         $("#starTrekCharacters").toggle();
      });
   });
</script>

As you can see, the code to create a combobox from a SELECT and provide an event handler is almost trivial.

$("#starTrekCharacters").combobox({
   selected: function(event, ui) {
      // ...
   }
});

Unfortunately, the documentation for the base autocomplete widget doesn't mention combobox and I had some difficulty working out how I was meant to adapt the SELECT event handler that is documented (see below).

$( ".selector" ).autocomplete({
   select: function(event, ui) { ... }
});

The main problem is that the jQuery standard event name for something being selected is select, but combobox calls it selected. Also, the documentation for autocomplete mentions six events - but combobox only handles one. This is all understandable since combobox is still a prototype. It's just hard to work out if you don't know what to look for - as described in this jQuery forum post Using Events in Combobox, in this StackOverflow post, Hooking event handlers to jQuery Autocomplete Combobox, in this comment on Jörn Zaefferer's blog post A jQuery UI Combobox: Under the hood and this rejected bug report: Autocomplete fails to call event handler for combobox.

So that's it for my long winded introduction to jQuery's autocomplete combobox widget. Despite the time I have spent working out how to use this - and the fact that it is still a prototype - the autocomplete functionality has me hooked and I will continue to use it wherever I can.

Wednesday, September 15, 2010

UltraEdit Function List shows function names missing last letter

Sometimes the UltraEdit Function List shows the function names missing the last letter - of every function. I have noticed this a few times. The fix is to make sure the file is in Unicode format: File > Conversions > ASCII to Unicode.

Saturday, September 11, 2010

UltraEdit macro for outputting anchor tags in HTML

One of the most common (and therefore painful) HTML elements to write repeatedly is an anchor tag. There is so much detail to fill in. First you have to write the A tag itself, opening and closing it. Then you have to copy and paste the URL from your browser into an HREF attribute, then you have to cursor into the middle of it and think of something descriptive to write for the display text. It's just so much hard work!

<a href="http://robertmarkbramprogrammer.blogspot.com">my blog</a>

I do most of my coding "by hand" in the wonderful and amazing UltraEdit which is extensible through, among other means, a macro language. Here is my macro for outputting anchor tags. I promise that this macro will make you truly enjoy coding HTML again. In fact, I had two weeks annual leave coming up which I was going to spend on the beach with my partner: but I cancelled it just so that I could spend the time hand coding the HTML for more blog posts! That's right - this macro really is that good.

Use it by first copying the URL you need; then type your display text, select it and run this macro: it will correctly output <a href="copied url">selected display text</a>| and leave the cursor where the pipe character is.

InsertMode
ColumnModeOff
HexOff
IfSel
Clipboard 9
Cut
"<a href=""
Clipboard 0
Paste
"">"
Clipboard 9
Paste
Clipboard 0
"</a>"
Else
"<a href=""
Paste
""></a>"
Key LEFT ARROW
Key LEFT ARROW
Key LEFT ARROW
Key LEFT ARROW
EndIf

Alternatively, you can use it without typing and selecting the display text: copy the URL you need and then run this macro: it will output <a href="copied url">|</a> and leave the cursor where the pipe character is.

How to escape quotes in Velocity

Jan 14, 2011. Thanks to Michael Fienen's comment below, quoting Strings in Velocity is not so hard; just use ${esc.q}.

#set($searchTerm = "red house" )
#pullContent("+structureInode:6333 +live:true +text1:${esc.q}$searchTerm${esc.q}" '0' 'random')

You can ignore the rest of this post - I leave it here purely for the sake of posterity. :)


Short story: skip to the bit about how to escape quotes in Velocity.

In dotCMS, I use Velocity to perform various Lucene searches through macros such as #pullContent, for which there is a well defined Lucene Query Parser Syntax. A simple example: find all content belonging to some structure called "Historic Buildings" whose name includes the token "house". Let's say it has an inode value of 6333 and and it's name field (a.k.a. the "variable" column in a Structure) is text1 (a.k.a. "index name" column in a Structure). Below is a query to find all Historic Buildings whose name includes the token "house" - in random order.

#set($searchTerm = "house" )
#pullContent("+structureInode:6333 +live:true +text1:$searchTerm" '0' 'random')

That's easy enough, and it will find all values that have "house" anywhere in their value (as a whole word). A phrase search is required when you want to search for an unbroken string, for example you want to search for "red house" and match "the big red house" but not match "red (recently painted) house". In Lucene, a phrase search is easy: just contain the string in double quotes: "red house"

How do you escape quotes in a Velocity string? My first thought was to use backslash as an escape character - which is common to many languages.

#set($searchTerm = "\"red house\"" )
#pullContent("+structureInode:6333 +live:true +text1:$searchTerm" '0' 'random')

Unfortunately Velocity doesn't recognise the backslash as an escape character and you will get a parser error from this. As I learned from this Apache mailing list response: escape double quotes it seems that escaping quotes in Velocity is very ugly. Here is what seems to me to be the most robust method. 

#set($searchTerm = "red house" )
#set($Q = '"')
#set($quotedTerm = "${Q}$searchTerm${Q}")
#pullContent("+structureInode:6333 +live:true +text1:$quotedTerm" '0' 'random')

Btw, a very good way to build your Lucene query strings is to use the dotCMS Admin interface. When you use it to search for content, click the "Show Query" button at the bottom left (below the search results and other buttons) and you will see the query string for the search just completed.

My dotCMS notes.

When to use single or double quotes in Velocity

Did you see that episode of the Simpsons where Bart writes on the blackboard: "I shall not use single quotes around variable names in Velocity." No? *Sigh.* Neither did I, which is why I write this post. It is my penance given in the hope that I will not make this mistake a fourth time, or at least recognise the mistake immediately by, say, the tenth time.

In Velocity, single and double quotes are similar to Bash scripting: variable references will be evaluated in double quotes, but not single quotes. For example, if you have #set( $name = "Rob" ) then "My name is $name" will output My name is Rob but 'My name is $name' will output My name is $name.

I think the best practice is to use double quotes by default and use single quotes only when you specifically want to avoid evaluating the contents of the string.

"I shall not use single quotes around variable names in Velocity."
"I shall not use single quotes around variable names in Velocity."
"I shall not use single quotes around variable names in Velocity."
"I shall not use single quotes around variable names in Velocity."
"I shall not use single quotes around variable names in Velocity."
"I shall not use single quotes around variable names in Velocity."
"I shall not use single quotes around variable names in Velocity."
"I shall not use single quotes around variable names in Velocity."
"I shall not use single quotes around variable names in Velocity."
"I shall not use single quotes around variable names in Velocity."

Read more about the quoting rules and more in Apache's User Guide for Velocity.

My dotCMS notes.

Thursday, September 09, 2010

dotCMS combo-box from pullContent macro

In dotCMS, it's very easy to build a combo-box (a.k.a. SELECT or drop-down) from content drawn from the #pullContent() macro. Below is how to do it.

## Build a combo-box from content of a given Structure.
## $limit - Max number of results (0 for all results)
## $structure - inode of the structure
## $sortField - Results will be sorted by this field.
#macro( writeSelectAnyStructure $structure $sortField $limit)
   #pullContent("+type:content +live:true +structureInode:$structure" "$limit" "$sortField")
   <select id="qt$structureInode" name="qt$structureInode">
      <option value=""></option>
      #foreach($content in $list)
         #if($UtilMethods.isSet($!content.title))
            <option value="$!content.inode">$!content.title</option>
         #else
            <option value="$!content.inode">$!content.name</option>
         #end
      #end
   </select>
#end

My dotCMS notes.

dotCMS pull related content for transitive relationships

In dotCMS, it's easy enough to use the #pullRelatedContent() macro traverse a relationship to get a list of, say, all books related to author. But what if you wanted to traverse a chain of relationships? Let's say you have the following pair of transitive relationships: Author-Book and Book-Publisher, you have an Author and need to find what Publishers are related to her?

Here is how you do it. First, you pull related content from one relationship. Then you iterate through the results, pulling related content from the second relationship - merging the results into a separate list. You should then sort the list at the end.

## Author has books. Book has publisher.
#set($publishers = [])
#pullRelatedContent("Author-Book" "$authorInode" "0" "name")
#set($bookList = $list)
#foreach($book in $bookList)
   #pullRelatedContent("Book-Publisher" "$book.inode" "0" "title")
   $publishers.addAll($list)
#end
#set($list = $publishers)
#set($list = $sorter.sort($publishers, "name"))

After this, you might need to remove the duplicates.

Here is the post I made to the dotCMS Yahoo group about this: Compound relationship query.

My dotCMS notes.

dotCMS: paginate results from pulling related contents

Pull Related Content

In dotCMS, the #pullRelatedContent() macro is used to create a list of all content related to some base content through a named relationship.

Syntax for the call is as below. The nice thing about it is that the call doesn't care if your content inode refers to a child or parent to the relationship. For example, let's say you have a bunch of books and a bunch of authors linked through a relationship called book_authors. In the call below, content inode could the inode of a book or an author.

#pullRelatedContent("relationship name" "content inode" "limit" "sort by")

After the call, you will have a $list variable available that references a java.util.ArrayList with your results.

Paginating Content

When you have a query that you know will return a large set of results, you should paginate the results. This means showing only a portion of the results at a time with controls that let user go to the next/previous page or even click on a specific page number within the results. In dotCMS, the #pageContent() does this.

#pageContent("query" "sort by" "# per page" "# current page")

Actually, all it does is process your query and leave you with the following variables: list, totalResults and totalPages. You then output the actual pagination controls for yourself based on this information.

Paginating Related Content

Both #pageContent() and #pullRelatedContent() take query as a parameter and leave you with a $list - which means you cannot call #pageContent() on the results of #pullRelatedContent() (e.g. #pageContent(#pullRelatedContent(...), ...).

So how do you paginate related content?

The answer is that you need to use a relationship query in #pageContent(), providing the identity (not inode of the content whose related items you wish to show. I do this with my own macro, as below.

#macro ( paginateRelatedContent $relationshipName $baseInode )
   #set($x = 0)
   #set($x = $webapi.getIdentifierInode($baseInode))
   #if($x < 1)
      #set($x = $inode)
   #end
   #set($uQuery = "+type:content +live:true +deleted:false +${relationshipName}:${x}")
   #pageContent("$uQuery" "text1" "3" "$page")
#end

Syntax is below.

#paginateRelatedContent("name of a relationship" "content inode")

Here is my post about this on the dotCMS Yahoo group: Paginate related content?

My dotCMS notes.

Wednesday, August 18, 2010

A long shadow over Java's future

Martin Paulo and Ben Hutchison are closing the VJUG mailing list.



Hi All

Ben and I feel that Oracle choosing to sue Google has cast a very long shadow over Java's future, potentially affecting all of us who use Java in our day to day work. We thus no longer have any desire to direct our energies toward VJUG activities going forward.

Unless anyone has strong feelings to the contrary, we propose that the VJUG formally be closed at the end of this week - that the newsgroup and web site be shuttered, and finally, the jug place marker be removed.

Any thoughts or screams to the contrary before we start the process? We will take silence as a vote for closure!

Martin




While the Victorian Java community has never been strong, and I can fully understand the reasons for closing the group, I haz a sad.

Despite the considered opinions of others a lot smarter than me concluding that it's far from being the end of Java, Oracle has delivered a great big kick in the slats to a thriving community of Java developers. In my opinion, the biggest effect of this law suit is to bring down the collective morale of Java developers everywhere; it feels like a show of contempt rather than a shroud business move.

It is certainly a sore strain on my good will towards to the new care-taker of Java - the technology my professional career is built on. I cannot understand what positive effects Oracle hopes will come from this suit - either for them as company or the Java community. Does Android hurt Java or Oracle? Will Java or Oracle grow stronger if Google moves away from Java?

This story is far from over, but it is not a good beginning.

Thursday, August 12, 2010

A better way to handle debug logging in IE

When it comes to debugging Javascript in Internet Explorer, the Internet Explorer Developer Toolbar and Firebug Lite are both very useful tools. For a long time, Firebug Lite offered functionality not otherwise available to Internet Explorer developers. Recently, the Developer Toolbar has began incorporating some of the functionality found in Firebug Lite, such as console debugging, They both allow you to execute arbitrary Javascript code to inspect and manipulate elements and state on your page (in IE).

Firebug Lite is installed by dragging a bookmarklet to your Favourites bar. The bookmarklet contains Javascript that opens up a Firebug instance for whatever page you are on. Open the Developer Tools via Tools menu > Developer Tools or F12. Once you have either open, you can run arbitrary Javascript statements and see console logging displayed nicely.

The problem is that often I have Javascript console logging that executes while the document is loading (such as during onLoad). This means IE will always show an error when the page loads because the console object doesn't exist until you open the Developer Tools or Firebug Lite. Usually I will develop in Firefox or Chrome (which both have a console object by default), use a lot of console logging to debug etc and get a nasty surprise when I eventually switch to IE and find a bunch of console is not defined errors. Worse, what happens if you forget to remove those logging statements and the code hits production with those errors?

I have attempted to deal with the development phase of this issue before by opening a text area and dumping console logging to it, but that solution was ugly, didn't play nicely in frame documents and needed to be tied into onLoad. Since those days, I have been playing around with jQuery and Firebug and found a better way to do it.

The code below checks for the existence of the console object. If it is not present, make one up with all the expected log methods: log, debug, info, warn, error. Instead of outputting the messages, it will store them in an array that can be referenced later. When Firebug Lite or the Developer Tools are opened, they override the console object with their own. The old messages can still be accessed though - through the console using the jQuery syntax $(oldConsole.history). That code is below.

/**
 * Catch logging to console in browsers that don't have the console
 * object e.g. IE. Even though you have Firebug Lite, you can only turn
 * that on after the page has loaded - too late for console logging
 * made before you load Firebug Lite.
 *
 * NOTE: This code needs to be loaded before any logging statements.
 */
if (!window.console) {
   window.console = {
      logHistory: function () {
         // Store logs to an array for reference. Thanks to
         // paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
         console.history = console.history || [];
         console.history.push(arguments);
      },
      log: function() {
         this.logHistory(arguments);
      },
      debug: function() {
         this.logHistory(arguments);
      },
      info: function() {
         this.logHistory(arguments);
      },
      warn: function() {
         this.logHistory(arguments);
      },
      error: function() {
         this.logHistory(arguments);
      }
   };

   // Once Firebug is opened up, can still access old messages here.
   // E.g. jQuery syntax: $(oldConsole.history).
   window.oldConsole = window.console;
}

This code needs to be placed before any logging statements happen.

Here is a nice way to re-print those old statements after Firebug has loaded (using jQuery syntax).

$(oldConsole.history).each(function(index) {
    console.debug($(oldConsole.history)[index]);
});

Additionally, I have found the following two snippets to be very useful. The first provides a shortcut (log) instead of console.log. The second allows you to use use log as part of a jQuery builder chain.

/**
 * Allows logging to console.log shortcut "log" object. E.g.:
 *    log('inside coolFunc',this,arguments);
 * paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
 */
window.log = function(){
  if(this.console){
    console.log( Array.prototype.slice.call(arguments) );
  }
};


// Allow inline jQuery logging following builder pattern: E.g.
//    $("p.someClass").log("inline logging").css("background-color", "red");
// http://ajaxian.com/archives/jquery-logging
jQuery.fn.log = function (msg) {
   console.log("%s: %o", msg, this);
   return this;
};

Pages that helped me with this code.

Wednesday, August 04, 2010

Check your DOCTYPE

Hit this issue again: some CSS works fine in Chrome and Firefox, but *gasp* doesn't in Internet Explorer. The fix: make sure you have a correct DOCTYPE to ensure IE renders in a "standards compliant" mode. Read more about DOCTYPEs in this informative A List Apart page: Fix Your Site With the Right DOCTYPE! by JEFFREY ZELDMAN.


Monday, August 02, 2010

Autohotkey mapping for a find with "Highlight All Items Found" option in UltraEdit

One of the relatively new features I like in UltraEdit is the "Highlight All Items Found" option in the Find dialog - it will yellow highlight all instances of the thing you want to find in the current document. Unfortunately, this is not quite as good as the same thing in Notepad++ and Eclipse. In Notepad++ and Eclipse, you activate this just by double clicking on a token i.e. double click on a word and all instances of it in the same document will be highlighted.

Worse, Ultraedit doesn't have any way to make a keyboard shortcut for a find with the "Highlight All Items Found" option, nor can it be recorded/used in a macro. So, I have created a rough work-around using Autohotkey. The script below binds control+h to do a find against whatever text you currently have selected and uses the "Highlight All Items Found" option to do so. Also note that the control+h only does this when you are actually in UltraEdit i.e. doesn't affect control+h in any other programs.

; - UltraEdit macros
#IfWinActive, UltraEdit ; Mappings defined here work only for UltraEdit
   ; - Ultraedit: Find using "Highlight All Items Found" option.
   ^h::
      WinWait, Find,
      IfWinNotActive, Find, , WinActivate, Find,
      WinWaitActive, Find,
      Send, {ALTDOWN}i{ALTUP}{ENTER}
   return
#IfWinActive ; Subsequent mappings now affect all windows.

This is just one AutoHokey function I have defined in a single Autohotkey utilities script that gets loaded through my Windows Startup directory so that it is always available. For others, check out this SuperUser post: Most useful AutoHotkey scripts.


Wednesday, June 30, 2010

Use cases for Alfresco.

This is a description of the general use cases for Alfresco as I see them. The audience for this document consists of IT/technical experts in a group focused on Java development. This is relevant to Alfresco version 3.

This is how I think about Alfresco: web client vs share interface vs web project vs external project.

Thank you to Jeff Potts - author of the Alfresco Developer Guide for reviewing this description and adding detail.

  1. Alfresco Web Client is a highly customizable administration front end for Alfresco. The focus of this interface is document management (Alfresco objects are documents with added meta-data) and administration (of users/groups/directories/rules/workflows etc).
  2. Alfresco Share interface is a much more customizable and user friendly Portal-like interface composed of Pages and Dashboards: we can create custom Dashlets (similar to portlets) that show up on the Dashboards. The user can add or remove dashlets. Site owners can add or remove pages. The focus of the Share interface is primarily team collaboration using tools such as a Document Library, Blogs, Wiki, Bookmarks, and a Team Calendar. With Share, users can provision their own team - or project - focused sites without the need for IT involvement.
  3. Alfresco Web Projects are specialized folders with the Alfresco Web Client used to implement Web Content Management (WCM) functionality. Web Projects can contain any type of web assets. For example, we could create a Java web app of our own using any Java framework we want (Spring, Seam, Surf etc) and manage it via Alfresco WCM. The focus of this mechanism is developer created front ends (web sites) that are managed by Alfresco: we write web apps that do whatever we want and look however we want them to look and are versioned with and deployed by Alfresco tools. The web apps can optionally manipulate Alfresco objects in the back end.
  4. An external project is any other sort of web application that makes use of the same remote APIs that the first 3 do, but is not managed by Alfresco. The focus of this mechanism is to allow any sort of front end (including Joomla) to use Alfresco as a back-end repository. Think of Alfresco as a database for rich or semi-structured content.

The real power of Alfresco is two fold: it allows it's own interfaces (point 1 and 2) to be customised using XML and Java and; it provides a number of remote APIs (such as Web Services, both SOAP and RESTful) so that any sort of project can be a client to Alfresco (point 3 and 4). Additionally, Alfresco provides functionality specific to managing rich content, such as:

  • Full-text and metadata search (powered by Lucene).
  • Extensible content model.
  • Check-in/check-out.
  • Workflow (powered by JBoss jBPM).
  • Numerous options for getting content into the repository (CIFS, WebDAV, FTP, SMTP, IMAP).

Recommended use of Alfresco for an IT development company with Java developers skilled in Alfresco: keep it Java, but be aware that other technologies can be an Alfresco client too. Use the Web Client (point 1) and Share (point 2) interfaces for inward facing administration and collaboration respectively and a Web Project (point 3) for any web site that could benefit from WCM-specific functionality such as Sandboxes, Deployment, or (light) Structured XML Authoring. Use 4) when we come across a client crawling across the desert in search of a document management tool and literally cannot move off away from the rest of their stack or when the amount of customization necessary to the out-of-the-box Alfresco clients is such that building a custom UI on top of Alfresco would be faster, easier to maintain, or a better fit.