Wednesday, December 17, 2008

Calling a Site Entry from a form

Updates
16th November, 2009: or use action=post.

In Content Server Explorer, you can create a JSP that is addressable through a Site Entry. For example, create a Content Server Element named my/site/FormResponse and a Site Entry whose RootElement is my/site/FormResponse.jsp - and you can reach that element with a URL such as: http://SERVER/servlet/ContentServer?pagename=my/site/FormResponse. Content Server is the controller (/servlet/ContentServer) that invokes other elements based on the GET arguments sent to it :?pagename=my/site/FormResponse for example.

That's good, but you cannot use that URL directly in a form's action attribute, such as below.

<FORM ACTION="http://SERVER/servlet/ContentServer?pagename=my/site/FormResponse">
<INPUT TYPE="Submit" VALUE="Submit"/>
</FORM>

That's because in a form's action attribute, any GET style arguments at the end of the URL are ignored. So the URL invoked from the FORM above would be http://SERVER/servlet/ContentServer. The fix is simple enough. Put the pagename argument in a hidden field, such as below.

<FORM ACTION="http://SERVER/servlet/ContentServer">
<INPUT TYPE="Submit" VALUE="Submit"/>
<INPUT TYPE="Hidden" NAME="pagename" VALUE="my/site/FormResponse"/>
</FORM>

Or (much simpler!) just explicitly specify action as POST.

<FORM ACTION="http://SERVER/servlet/ContentServer?pagename=my/site/FormResponse" action="POST">
<INPUT TYPE="Submit" VALUE="Submit"/>
</FORM>

Saturday, December 13, 2008

Cygwin Bash script: search through files and let user open results

I frequently use this Bash script in Cgywin to easily search for a file by name (using regex), see a list of results and choose which of those results I want to open up in my favourite text editor, like UltraEdit. The script in this post my own wrapper for grep and find that allows me to search through the contents of files, show the results of the search and call the previous script to show me a list of matching files, asking me which ones I want to open.

Purpose. Search through files, show user results and allow user to open any of the files that contained a match.

Input. File name search criteria, file contents search criteria, selection of what files to output.

Output. Lines of results, sorted by file, menu of result files to open.

Dependencies. Relies on this Bash script being present in the path - refers to it as u - because when I first wrote it, I would only use it to open files in UltraEdit!

Example 1.

  1. Search through all *.java files for the regular expression setco.*nager.
  2. Display results sorted by file.
  3. Allow user to see a list of result files and select which ones, if any, to open.
-bash3.2 - rbram@MCLL - Javascript TOC - /cygdrive/c/temp/DocPublisher
Sat Dec 13 - 08:13 PM > search --type j --term "setco.*nager"
Search for pattern "setco.*nager" in dir /cygdrive/c/temp/DocPublisher through file pattern "*.java"

====
./src/java/au/com/publisher/doc/PublisherServiceImpl.java
846:    public void setCollectionManager(

====
./test/java/other/au/com/publisher/admin/MockData.java
1005:           publisherServiceImpl.setCollectionManager(mockCollectionManager);

Do you want to view any of the matching files?
============
File  0: ./src/java/au/com/publisher/doc/PublisherServiceImpl.java
File  1: ./test/java/other/au/com/publisher/admin/MockData.java
Specify files to open. [A]ll, [N]one or [x y z] space separated indexes.

Code

#!/bin/bash

#-------------------------------------------------------------------------------
#  Search Command
#-------------------------------------------------------------------------------
# Search through files in any location and open any of them in an editor.
#
# Author: Robert Mark Bram
# v 1.2 - 1920/11/2007 1:37:05 PM. Included the ability to follow (or not) symbolic
#   links by hooking into the find command's capability for this. By default, do
#   not follow symbolic links.
# v 1.3 - 29/02/2008 4:03:53 PM. Don't look in files matching:
#   grep -v '[.]svn-base$\|zzbuild'

#-------------------------------------------------------------------------------
#  Dependencies
#-------------------------------------------------------------------------------
# - Relies on the 'u' command.

#-------------------------------------------------------------------------------
#  Variables for this script.
#-------------------------------------------------------------------------------
# Variables for this script.
# Pretty up the output to make it more readable. Yes or no.
pretty=yes
# Make the search case sensitive (-i) or not (no value).
case=-i
# Output results to console. Yes or no.
console=yes
# Pattern of files in which we shall search.
filePattern=
# Output names of files only.
fileNamesOnly=
# Number of lines around the search result to display.
numberOfLines=0
# Directory within which to search.
searchDir=.
# Was --type given to specify a file type? This var will have that value.
filePatternType=
# Should filePattern be treated as a regular expression as per find: -iregex pattern?
filePatternRegularExpression=N
# Follow symbolic links? -P (no) or -L (yes) - as per the find command.
symbolic=-P
# Temp dir.
tempDir=/cygdrive/C/Temp


# Help!
usage() {
cat << STOP_HELP
Usage: $0 --term searchTerm --file filePattern
Search set of files specified by filePattern for searchTerm.
          --file filePattern   Defines what file names to search for.
                               For example, "*.java" to search all Java files.
                               Can be a regex if you use -r.
                               This is not required if --type is used, but this will override
                               --type option if supplied.
           --term searchTerm   String to search for within files specified by file pattern.
  === Options that can be used. ===
                     [--c x]   x = number of lines to output on each side of each match (turns pretty off).
           [--dir searchDir]   Directory within which to look for files matching file pattern.
                               Defaults to current directory.
                    [--help]   Display help (this message) and exit.
               [--type type]   Specify type of files to search for. Shortcut for --file option.
                               Overridden by --file option. Available shortcut types:
                                  j for *.java files.
                                  p for *.jsp files.
                                  r for *.properties files.
                                  s for *.sql files.
                                  t for *.txt files.
                                  x for *.xml files.
                        [-f]   Output to a file which is then displayed in UltraEdit.
                        [-l]   Show matching file names only (turns pretty off).
                        [-L]   Follow symbolic links (as per find command).
                        [-n]   Avoid prettying up the output (automatic with -c and -l).
                        [-P]   Never follow  symbolic  links (as per find command). DEFAULT is this.
                        [-r]   --file filePattern is a regular expression, as per find: -iregex pattern
                        [-s]   Make search case sensitive.

Example usage 1.
    search -r --term 'folder' --file '.*.jsp?'
    Look for the term "folder" in js or jsp files.

STOP_HELP
}

# Process single hyphen arguments.
processBitArguments() {
   argument=$1
   while [ -n "${argument}" ]
   do
      nextArgument=${argument:0:1}
      argument="${argument:1}"
      case "${nextArgument}" in
         f) console=no;;
         l) fileNamesOnly=-l;;
         L) symbolic="-L";;
         n) pretty=no;;
         P) symbolic="-P";;
         r) filePatternRegularExpression=Y;;
         s) case=;;
         *) echo "*** Unknown arguments in -$1. ***"; usage; exit 3;;
      esac
   done
}

# Process all arguments.
if [ $# -lt 2 ] ; then
    echo "** Incorrect number of args specified **"; usage; exit 2
fi
while [ $# -gt 0 ]; do    # Until you run out of parameters . . .
   case "${1}" in
        "--c"   ) numberOfLines="$2"; shift;;
      "--dir"   ) if [ ! -d "${2}" ] ; then
                    echo "** Search directory ["${2}"] is not a dir or does not exist. **"; usage; exit 45
                  fi
                  searchDir="$2"; shift;;
      "--file"  ) filePattern="$2"; shift;;
      "--help"  ) usage; exit 0;;
      "--term"  ) searchTerm="$2"; shift;;
      "--type"  ) filePatternType="$2";shift;;
      -*        ) processBitArguments ${1/-/};;

   esac
   shift   # Check next set of parameters.
done

if [ -z "${filePattern}" -a -n "${filePatternType}" ] ; then
      case "${filePatternType}" in
         j) filePattern='*.java';;
         p) filePattern='*.jsp';;
         r) filePattern='*.properties';;
         s) filePattern='*.sql';;
         t) filePattern='*.txt';;
         x) filePattern='*.xml';;
         *) echo "*** Unknown --type: $filePatternType. ***"; usage; exit 3;;
      esac
fi


if [ -z "$filePattern" ]
then
   echo "*** File pattern not specified. ***"
   usage
   exit 4
elif [ -z "$searchTerm" ]
then
   echo "*** Search term not specified. ***"
   usage
   exit 5
fi


# Turn off pretty to show file names only.
if [ "$fileNamesOnly" = "-l" ]
then
   pretty=no
fi

# Turn off pretty to show number of lines
if [ "$numberOfLines" -ne 0 -o "$numberOfLines" -eq 0 ] 2> /dev/null
then
   # Don't pretty it up if number of lines is greater than 0
   if [ "$numberOfLines" -ne 0 ]
   then
      pretty=no
   fi
   numberOfLines="-C $numberOfLines"
else
   echo "[-c x] where x must be a positive integer, not: $numberOfLines"
   usage
   exit 21
fi


# Go to right dir and turn it into an absolute path.
oldDir=`pwd`
cd "$searchDir"
searchDir=`pwd`

# echo Search Term: "$searchTerm" > ${tempDir}/search.out.txt
# echo File Pattern: "$filePattern" >> ${tempDir}/search.out.txt
# echo Dir: "$3" >> ${tempDir}/search.out.txt
# echo "" >> ${tempDir}/search.out.txt

# if [ -f "$filePattern" ]
# then
#    files="$filePattern"
# else
#    files=`find . -type f -name "$filePattern"`
# fi
# if [ -z "$files" ]
# then
#    echo "No files found."
#    exit 2
# fi


if [ -f "$filePattern" ]
then
   files="$filePattern"
   grep ${case} "$numberOfLines" ${fileNamesOnly} -n "$searchTerm" $files > ${tempDir}/search.out.txt 2>&1
else
   if [ "$filePatternRegularExpression" = "Y" ]
   then
      find ${symbolic} . -type f -iregex "$filePattern" -print0 | xargs -0 grep ${case} "$numberOfLines" ${fileNamesOnly} -n "$searchTerm" | grep -v '[.]svn-base$\|zzbuild' > ${tempDir}/search.out.txt 2>/dev/null
   else
      find ${symbolic} . -type f -name "$filePattern" -print0 | xargs -0 grep ${case} "$numberOfLines" ${fileNamesOnly} -n "$searchTerm" | grep -v '[.]svn-base$\|zzbuild' > ${tempDir}/search.out.txt 2>/dev/null
   fi

fi

cd ${tempDir}/

# pretty it up?
if [ "$pretty" = "yes" ]
then
   # Replace first ":" with a new line..
   cat search.out.txt | sed -e 's/:/\
   /' -e 's/^[.]/\
   ./'  > /tmp/search.preprocessed.txt 2>&1

   echo Search for pattern \""$searchTerm"\" in dir $searchDir through file pattern \"$filePattern\"  2>&1 | tee search.processed.txt /tmp/search.processed.txt

   fileSet=
   fileIndex=0

   while read line
   do
      case $line in
          "") continue ;;
          ./*) # Is line a file name? (Will all filenames start with ./?)

              currentFile=$line
              if [ "$currentFile" != "$lastFile" ]
              then

                # store file name...
               fileSet[$fileIndex]="$line"
               let "fileIndex++"

               lastFile="$currentFile"
               printf "\n====\n%s\n" "$lastFile" >> search.processed.txt
               printf "\n====\n%s\n" "$lastFile"
              fi;;
           *) printf "%s\n" "$line" >> search.processed.txt
              printf "%s\n" "$line";;
      esac
   done < /tmp/search.preprocessed.txt

else
   cp search.out.txt search.processed.txt
   cat search.processed.txt
fi

if [ "$console" = "yes" ]
then
   if [ "${#fileSet[@]}" -gt 0 -a -n "${fileSet[0]}" ]
   then
      echo -e "\n\nDo you want to view any of the matching files?\n============"
       u -d "$searchDir" -i f ${fileSet[@]}
   fi
   echo
else
    # Open results up in favourite editor.
   U search.processed.txt
fi

cd "$oldDir"

Friday, December 12, 2008

Searching files within Vim and opening results with Vim

When I am programming, I often swap between using Eclipse or a Cywin + UltraEdit combination.

In Eclipse, Control+Shift+T is a wild card enabled search for Java files by file name and Control+Shift+R is a wild card enabled search for everything else (plus Java files) by file name - where you can double click on a result to open the file. You also have a file search (with regex) with Control+H.

In Cygwin, I use my own script to regex search for files by name, opening whichever file I need in UltraEdit. I also have a couple of scripts that grep through file contents and pass the result to the above script, which offers me a menu of the results and allows me to choose which of the results to open. I find this incredibly handy for many situations when I want to drop to a command line and search with a regular expression, or when I want a refreshing burst of geeky joy from using a command line.

I find it is easier to use my script on the command line to search through files and open the match I need than it is to use Eclipse's file search.

Recently, I have started to use Vim more and more - a particularly useful skill for times when I need to SSH to a remote *nix box and I don't have an UltraEdit to help me. Like any program, there is a learning curve, but text editors like Vim and Emacs have a steeper curve because there are no GUI menus to explore. However, the reward is arguably higher because as you learn how to use the app, you find so many ways to make your life easier.

I decided to try and match the functionality above using Vim. Here is the way I have found to allow searching for files by name and contents, seeing a list of results and easily opening one of the results.

Put the following in your .vimrc file.


" http://vim.wikia.com/wiki/Display_shell_commands'_output_on_Vim_window
" Run a shell command and open results in a horizontal split
command! -complete=file -nargs=+ Split call s:RunShellCommandInSplit(<q-args>)
function! s:RunShellCommandInSplit(cmdline)
  botright new
  setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap
  call setline(1,a:cmdline)
  call setline(2,substitute(a:cmdline,'.','=','g'))
  execute 'silent $read !'.escape(a:cmdline,'%#')
  setlocal nomodifiable
  1
endfunction

" Copy of the above that opens results in a new tab.
command! -complete=file -nargs=+ Tab call s:RunShellCommandInTab(<q-args>)
function! s:RunShellCommandInTab(cmdline)
  tabnew
  setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap
  call setline(1,a:cmdline)
  call setline(2,substitute(a:cmdline,'.','=','g'))
  execute 'silent $read !'.escape(a:cmdline,'%#')
  setlocal nomodifiable
  1
endfunction
" Issue a find command using regex and open results in a new tab.
command! -nargs=+ Find call s:RunShellCommandInTab('find . -iregex '.<q-args>)


With these two, I can now very easily run a shell command from within Vim and have the results appear in a new buffer - either a horizontal split or a new tab. For example here are three ways to run the shell command "ls -la" from within Vim:

:Split ls -la
:Tab ls -la
:! ls -la

:Split puts the results in a new horizontal split. :Tab puts the results in a new tab. The third technique is the built in way to run a shell command from within Vim. It hides Vim i.e. shows the command line, runs the command and shows the results in the command line and displays a prompt to go back to Vim: Press ENTER or type command to continue.

Now for the kicker. If I use :Split or :Tab, I can move the cursor to rest within whichever file I am interested in and press gf to open the file.

To find a file within a project's directory tree, ls -la isn't enough: I need the find command. The two examples below are equivalent.

:Find ".*\.css"
:Tab find . -iregex ".*\.css"

The first - much shorter - example uses the last short cut now in my .vimrc file. They both do a recursive regex search for a file through the entire directory tree, beginning from the directory I opened Vim from. Again, I put my cursor within whichever result I am interested in and press gf to open the file - which replaces the search results.

Always looking for shorter shortcuts for common searches, I put this small command in my bin dir: ~/bin/fjg ("fjg" for "find Java then grep").

#!/bin/bash
find . -name "*.java" | grep -i $@

It makes it much easier to search for Java source files quickly. Now I search with a shorter string:

:Tab fjg SomeClass

If I want to search through the contents of the files and select which result file to open, this is easy too:

:Find ".*\.css" | xargs grep -l span
:Tab find . -iregex ".*\.css" | xargs grep -l span

Here are some links that helped me write this.

Firefox plugin: It's All Text - make it use the same instance of GVim

One of my favourite Firefox plugins is It's All Text! which allows you to use an external editor to edit text areas in a browser page.

One of my favourite text editor (up there with UltraEdit) is vim and gvim. I wanted to use "It's All Text!" with gvim. It is easy enough to point the plugin at the gvim executable, but this meant each time I used the plugin, it would open a new gvim instance.

For Windows, this is the workaround I found to make sure "It's All Text!" would always open in the same gvim instance - opening a new tab each time. Create a cmd file:

@echo off
"C:\Program Files\Vim\vim72\gvim.exe" --remote-tab-silent %1

Then point "It's all Text" to that batch file instead, e.g. C:\Documents and Settings\rbram\My Documents\Tools\Windows\editFileInExistingVim.cmd

Tuesday, December 09, 2008

How to flush the cache with FatWire's ContentServer 5.5

Flush the cache with a URL like this:

http://DOMAIN/servlet/CacheServer?all=true&username=USERNAME&password=PASSWORD
If it worked, you should see a response like this: CacheServer flushing all pages.

Trouble shooting tips.

  • If flushing gives you this error: CacheServer : No access allowed for requested action.(Force page flush), check the following.
    • Are you sure your username and password are correct?
    • Does that user name have SiteGod ACL?
    • Are you logged in?
  • I made some changes and can't see them on refresh! Check your URL. If it is a satellite URL (http://DOMAIN/servlet/Satellite), change Satellite to ContentServer, i.e. http://DOMAIN/servlet/ContentServer and refresh again.
  • CSElements called by SiteEntry - to cache or not to cache.
    • If it is static (like some non changing content or maybe a Javascript file), cache it. In the SiteEntry, set "Pagelet Cache Criteria" to "true,*".
    • If the element is dynamic, i.e. it performs some processing and the output should be different each time, don't cache it. In the SiteEntry, set "Pagelet Cache Criteria" to "false".
    • See CacheInfo String Syntax section of the developer pdf for a detailed description of how to use this setting.
    • I said don't cache, but it is! I found (CS 5.5) that when I set my SiteEntry initially to "true,*" and changed it afterwards to "false", it was still being cached. To fix it, I deleted and re-created the SiteEntry.

Asset Descriptor Files in FatWire's Content Server 5.5

A few tips and things to watch for with Asset Descriptor Files (ADFs) under Content Server in the FatWire 5.5 Content Manaqement System (CMS). An ADF is the XML file used to define and create a basic asset using the AssetMaker utility. I am not sure how many of these issues are still relevant in newer versions.

General Tips

  • Keep PROPERTY NAME values all in lower case.
  • Keep INPUTFORM TYPE values all in upper case.

Date Fields

Here is a sample date field. Note that it allows searching for the date field.

<PROPERTY NAME="closedate" DESCRIPTION="Close Date">
   <STORAG TYPE="TIMESTAMP" LENGTH="20" />
   <INPUTFORM TYPE="TEXT" REQUIRED="NO" WIDTH="30"
         INSTRUCTION="Format the date as YYYY-MM-DD HH:MI:SS. Defines the date/time after which this is closed." />
   <SEARCHFORM DESCRIPTION="Close Date" TYPE="TEXT" WIDTH="30" MAXLENGTH="30" VERB="="/>
</PROPERTY>

Note the SEARCHFORM uses the = verb so that people search by exact date. Question: how to do date ranges?

Select Fields

Here is a sample select field. Note that it allows searching. Note that both search and input use string based values to populate the control as opposed to values drawn from the database.

<PROPERTY NAME="resultgraphtype" DESCRIPTION="Result Graph Type">
   <STORAGE TYPE="VARCHAR" LENGTH="10" />
   <INPUTFORM TYPE="SELECT" REQUIRED="NO" DEFAULT="hor bar"
         INSTRUCTION="Defines what graph type (if any) will be used for display."
         SOURCETYPE="STRING"
         OPTIONDESCRIPTIONS="Horizontal Bar Graph,Vertical Bar Graph,Pie Graph,Ranking Chart"
         OPTIONVALUES="hor bar,ver bar,pie,rank"
   <SEARCHFORM TYPE="SELECT" DESCRIPTION="Result Graph Type"
         SOURCETYPE="STRING"
         OPTIONDESCRIPTIONS="Horizontal Bar Graph,Vertical Bar Graph,Pie Graph,Ranking Chart"
         OPTIONVALUES="hor bar,ver bar,pie,rank" />
</PROPERTY>

Element Property Types

On DEV (CS 5.5) this worked:

<PROPERTY NAME="options" DESCRIPTION="Options">
   <STORAGE TYPE="VARCHAR" LENGTH="1"/>
   <INPUTFORM TYPE="ELEMENT" REQUIRED="NO"
         INSTRUCTION="Fill out potential options."/>
</PROPERTY>

Trouble Shooting

First steps.

  • Open the XML ADF in a browser like Firefox, which will parse the XML and give you a bit more information about malformed XML.
  • If a browser like Firefox opens the ADF ok, it should be properly formed XML, which still means it might be breaking a FatWire specific parsing rule i.e. it is valid XML, but not not valid ADF XML. Look at the FatWire documentation on the error numbers for some further clues.
  • If you are getting a problem with your ADF and you are unable to nail down which property it relates to, start removing properties from your ADF and re-loading it until you find a point at which the error no longer occurs. This means: delete the asset and table from Content Server, modify the ADF to remove one property, re-make the asset, register asset elements and create the table.

A Short FAQ on Common Problems

  • -2002 processing asset descriptor file. AssetMaker: Descriptor parse failed.
    • There were some problems with my INPUTFORM elements. If you find a different solution, please let me know!
      • I was missing some closing "/>"s.
      • My INSTRUCTION attributes too long - they have a limit of 80 characters.
  • AssetMaker: An error occurred creating the asset table. Please check the descriptor file for invalid STORAGE tags. (AssetMaker error number=-2005, error number=-100)
    • There was an error with a STORAGE element. If you find a diferent solution, please let me know!
      • <STORAGE TYPE="varchar" needed to be <STORAGE TYPE="VARCHAR" --- just capitalize VARCHAR!!!
  • Do you find that your text fields get pre-populated with contents such as Variables.ContentDetails:mandatoryMessage?
      Make sure your property names are all lower case and have no underscores. For example, do this:
      <PROPERTY NAME="mandatorymessage" DESCRIPTION="Mandatory Message">
      instead of this
      <PROPERTY NAME="mandatoryMessage" DESCRIPTION="Mandatory Message">
      or this
      <PROPERTY NAME="mandatory_message" DESCRIPTION="Mandatory Message">
      Source of this answer from the FatWire forum.
  • Could not delete a basic asset.
    • The error was: This attempt to delete the asset failed with error -105. The most likely cause is a system configuration error. Please exit the browser, login, and then try the operation again. If the problem persists, please contact your System Administrator.
    • The issue was a STORAGE TYPE that had to be changed from CHAR to VARCHAR. Here is the changed property.
      <PROPERTY NAME="multivoting" DESCRIPTION="Multiple Voting">
         <STORAGE TYPE="VARCHAR" LENGTH="3"/>
         <INPUTFORM REQUIRED="NO" TYPE="RADIO" DEFAULT="No"
               SOURCETYPE="STRING" RBDESCRIPTIONS="Yes,No" RBVALUES="Yes,No"
               INSTRUCTION="Define whether users are able to vote more than once."/>
         <SEARCHFORM TYPE="RADIO" DESCRIPTION="Multiple Voting"
               SOURCETYPE="STRING" RBDESCRIPTIONS="Yes,No" RBVALUES="Yes,No" />
      </PROPERTY>

Wednesday, November 12, 2008

CVS diff on labels

Find out what files have changed, been added or removed between two labels in CVS.

  1. Install wincvs (WinCvs 2.0.2.4-4.zip).
  2. Make sure you have C:\Program Files\cvsnt" in your Window's PATH environment variable.
  3. Open DOS prompt/Cygwin shell.
  4. set CVSROOT=:pserver:USERNAME@SERVER:PORT:/PATH/TO/CVSROOT
  5. cvs login
  6. Then enter your password.
  7. cvs -d:USERNAME@SERVER:PORT:/PATH/TO/CVSROOT -q rdiff -s -r LABEL1 -r LABEL2 PATH/TO/PROJECT

Thanks to Lars

Saturday, November 01, 2008

Eclipse: bookmarks, multiple clip-board buffers, column mode editing

Some simply amazing Eclipse plugins that I am using with Ganymede (3.4).

Eclipse Bookmarks plugin - for making and navigating around quick bookmarks with simple keyboard shortcuts. At last something that makes the Bookmarks view actually useful.

http://etc.to/eclipse_bookmarks_plugin

Multi-clipboard plugin - gives you multiple clipboard buffers so that you can copy and paste multiple pieces of separately. Uses some very simple keyboard shortcuts to make this very easy to use.

http://tkilla.ch/column_mode/

Hallelujah! Column mode comes to Eclipse! http://tkilla.ch/column_mode/

These plugins give Eclipse some extremely powerful and easy to use functionality that I wished were present from day one, all of which I have gotten used to from UltraEdit. In fact, until now I would frequently switch to UltrEdit just to do some column mode editing.

Monday, September 29, 2008

Most Used UltraEdit Macros

There are some truly excellent macros on the UltraEdit extras page as well, but I am putting these up mostly for my own reference whenever I swap machines.

Updates:
24th May, 2009 - Added macro to close a html/xml tag, copied from UltraEdit forum post.
Monday 14 March 2011, 01:01:46 AM - reference to two other frequently used macros; for xml/html tag completion.

I use copy, cut and paste frequently. Most often I am cutting, copying and pasting just a single word (or token), so I wrote these macros to select a token and then copy or cup or paste. It saves me just that little bit of extra energy, since I don't have to select the token first. I added a bit of logic into each as well so that if I already have text selected, the macro will use that text instead of selecting the token under the cursor.

Copy token under cursor, or currently selected text. I map this to Alt+Z.

InsertMode
ColumnModeOff
HexOff
UnixReOff
IfSel
Copy
StartSelect
Else
SelectWord
Copy
StartSelect
EndIf

Cut token under cursor, or currently selected text. I map this to Alt+X.

InsertMode
ColumnModeOff
HexOff
UnixReOff
IfSel
StartSelect
Cut
Else
SelectWord
StartSelect
Cut
EndIf

Paste, replacing token under cursor, or currently selected text. I map this to Alt+Q.

InsertMode
ColumnModeOff
HexOff
UnixReOff
IfSel
StartSelect
Paste
EndSelect
Else
SelectWord
StartSelect
Paste
EndSelect
EndIf

This one isn't mine, but I don't remember where I got it from. Probably one of the truly excellent macros on the UltraEdit extras page.

Duplicate currently selected text or current line. I map this to Control+D.

ColumnModeOff
HexOff
Clipboard 9
ClearClipboard
IfSel
Cut
Paste
Paste
Find Up Select "^c"
Else
SelectLine
Cut
Paste
Key UP ARROW
Paste
PlayMacro 1 ""
EndIf
Clipboard 0

This one also isn't mine. I got it from an UltraEdit forum post: Automatically close tags.

Close most recent tag. I map this to Control+Shift+>.

InsertMode
ColumnModeOff
HexOff
UnixReOn
">"
Find Up "<"
IfFound
Clipboard 9
Key RIGHT ARROW
SelectWord 
Copy 
Find "<"
Find "<"
Key LEFT ARROW
"</"
Paste 
Find Up "<"
Key LEFT ARROW
Clipboard 0
Find ">"
Key RIGHT ARROW
EndIf
See my post UltraEdit macro for HTML/XML - auto-create closing tags for two other frequently used macros - these for completing HTML/XML tags.

Virtual Desktop Managers for XP

Linux has had desktop managers for ages - it was one of the biggest things that impressed me right off when I started using Linux years ago.

On Windows XP, I have been using Virtual Dimension 0.94 for quite some time and enjoyed it a lot. The things I like best about it are: 1) switching between desktops with different wall papers, 2) having only those apps in the desktop shown in the task bar.

Unfortunately, Virtual Dimension hasn't been updated for some time and is occasionally flaky.

  • Microsoft Outlook 2003 using Winword as the editor can often freeze Virtual Dimension, but I can usually fix that by killing Winword or Outlook in the Task Manager.
  • However, sometimes it freezes for other reasons, and if I kill it - the other desktops, and the programs stored therein - are lost to me and I need to re-boot.
  • Virtual Dimension doesn't have shortcuts for arbitrarily moving programs around desktops: up, down, left and right. You can only set short cuts for moving programs to the next/previous desktop or brining up a context menu to switch it to a selected desktop.
  • It does now always remember my settings between re-boots, particularly crashy re-boots.
  • The most annoying thing is that often a window goes nuts if you switch desktops while it is still doing something i.e. has the CPU. When that happens, the program appears on all desktops by mistake - and the application's icon doesn't appear in all desktops in the desktop tray either. You have to close and re-open the program, which is awkward.

None of these are huge errors in an of themselves, but since there is no movement in the project, I am willing to try something else.

As of this morning I am trying Vista/XP Virtual Desktop Manager, the port of the Vista Windows Desktop Manager. So far it seems a match in functionality - plus it has shortcuts to move applications up, down, left and right.

Virtual Dimension used about 8.2 MB for four desktops. Virtual Desktops right now is using 28.1 MB for four desktops. I will post more as I get to know it better.

Other desktop managers exist.

  • Another desktop manager for XP that I have not tried is VirtuaWin. It didn't win me over after reading Dennis O'Reilly's article Add virtual desktops to Windows XP, Vista.
  • XP has had a Virtual Desktop Manager power toy for some time, but it is very limited. 1) It has no shortcuts for moving applications to different desktops. 2) It doesn't hide applications. 3) And (most annoying) is the UI sits in the start menu taking up space rather than having just an icon in the tray.

Setting name attribute on an anchor via DOM Javascript in IE

This Javascript code creates a DOM ANCHOR element that I want to insert into my document somewhere.

var anchor = document.createElement("a");
anchor.name = "top";     // NOTE: set the anchor name.  
anchor.appendChild(document.createTextNode("Top"));

It should create the equivalent of this:

<a name="top">Top</a>

It works in Firefox (3.0.3), Google Chrome (0.2.149.30) and Safari (3.1.2). It doesn't work in Internet Explorer (7.0.5730.13).

Neither does this work in IE:

anchor.setAttribute("name", "top");

The only way I can get it to work in IE is by using ID instead of NAME. Either of these will work.

anchor.setAttribute("id", "top");
anchor.id = "top";

Thankfully, it works in Firefox, Google Chrome and Safari too.

Sunday, September 28, 2008

Can't get Firebug Lite working on IE?

Update, 12/08/2010 6:55:19 PM. This page is defunct. Try this one instead: A better way to handle debug logging in IE.

Update, 23/12/2008 11:44:05 PM. Added code from my own comments.

For those times when I cannot get Firebug Lite working in I.E. (or Chrome.. haven't tried Safari or Opera), I re-route my debug messages in I.E. to a text area by always calling this debug() method for my messages.

function debug(message) {
   if (typeof(console) != "undefined" ||
         typeof(console.debug) != "undefined") {
      console.debug(message);
   } else {
      if (document.getElementById("debugConsole") == null) {
         var textConsole = document.createElement("textarea");
         textConsole.id = "debugConsole";
         textConsole.rows = 10;
         textConsole.cols = 100;
         textConsole.readOnly = true;
         document.body.appendChild(textConsole);
         document.body.appendChild(document.createElement("br"));
         document.body.appendChild(document.createElement("br"));
      }
      document.getElementById("debugConsole").value += "\n" + message
   }
}

(From my comments.) Here is another alternative that provides proxies for some of the standard logging calls.

if (!window.console) {
 var textConsole = document.createElement("textarea");
 textConsole.id = "debugConsole";
 textConsole.rows = 10;
 textConsole.cols = 100;
 textConsole.readOnly = true;
 document.body.insertBefore(textConsole, document.body.firstChild);
 window.console = {
  log: function(message) {
   document.getElementById("debugConsole").value += "\n" + message;
  },
  debug: function(message) {
   document.getElementById("debugConsole").value += "\n" + message;
  },
  info: function(message) {
   document.getElementById("debugConsole").value += "\n" + message;
  },
  warn: function(message) {
   document.getElementById("debugConsole").value += "\n" + message;
  },
  error: function(message) {
   document.getElementById("debugConsole").value += "\n" + message;
  }
 };
}

Thursday, September 25, 2008

How to insert a date

Update: 21 February 2011, 11:57:26 AM. Stop the press: forget these shortcuts.. just use an AutoHotkey script that will work for ALL programs! Insert a date into any program using Autohotkey on Windows.


Here is how to insert a date for the different programs that I have had to do this for so far. Feel free to tell me how to do it for programs you use that I haven't listed here. :)

  • Eclipse: edit a template to put the time stamp in a comment for example, as per this newsgroup post: Insert Date/Timestamp in comment. Or make a new template with just a time stamp in it, as per this alternate reply to the previous post.
  • JEdit: you need a script. Here is a script by Robert Schwenn to insert a date.
  • Microsoft Excel: press control+;.
  • Microsoft Word 2003 and earlier: Alt, I, T or from the menu bar, select Insert > Date and Time. I like being able to select the date format here. Note that if you check the "Update Automatically" box, the date will be "dynamic" i.e. it will update to the current date whenever someone selects the date (or does a select all in the document) and presses F9 - in Windows.
  • Notepad: F5
  • UltraEdit: press F7 and get a date like this: 25/09/2008 3:36:32 PM.
  • Vim: roll your own function. I use this one in my .vimrc: :inoremap <F5> <C-R>=strftime("%c")<CR>. Check out the vim wikia page Insert current date or time for more info.

Excel: Displaying Autofilter Criteria

Edit: 19/3/2009. Updated with instructions for adding autofilter with Excel Office 2007 and before.

I love AutoFilter in Excel: it allows you to very easily sort or hide data in various ways.

Excel Office Pre 2007: Data > Filter > AutoFilter.

Excel Office 2007: Home (on Ribbon) > click Sort & Filter > Filter. See Tables Part 4: AutoFilter improvements: much more than just multi-select for more info.

The problem is that for complicated spreadsheets, it isn't always easy to tell what filters you have applied.

The above image indicates that the Numbers column is filtered by having that downward pointing triangle shaded blue. With a spreadsheet of 5, 10 or more columns, it is really hard to work out which of those small arrows are shaded. Worse, I can never remember exactly what filter I applied to each column.

What I do now is put a macro into the spreadsheet and a formula into the row above my headings that will show what filters have been applied i.e. display the AutoFilter criteria.

  1. Open your spreadsheet and press Alt+F11 to open the Microsoft Visual Basic Editor.
  2. Right click on the VBAProject entry for your spreadsheet (the spreadsheet file name is in brackets after VBAProject) > select Insert > Module.
  3. In the new pane that opens to the right, make sure that (General) is selected in the drop-down at top left. Then copy and paste Stephen Bullen's macro into the VB Editor, like so (the code is below the image).

    Here is the macro code.

    Function FilterCriteria(Rng As Range) As String
        'By Stephen Bullen
        Dim Filter As String
        Filter = ""
        On Error GoTo Finish
        With Rng.Parent.AutoFilter
            If Intersect(Rng, .Range) Is Nothing Then GoTo Finish
            With .Filters(Rng.Column - .Range.Column + 1)
                If Not .On Then GoTo Finish
                Filter = .Criteria1
                Select Case .Operator
                    Case xlAnd
                        Filter = Filter & " AND " & .Criteria2
                    Case xlOr
                        Filter = Filter & " OR " & .Criteria2
                End Select
            End With
        End With
    Finish:
        FilterCriteria = Filter
    End Function
    
  4. Close the VB Editor and go back to the spreadsheet.
  5. Insert a new row above the headings by putting the cursor into any heading cell and selecting from the menu bar: Insert > Rows.
  6. For each column that you wish to display the filter criteria for, go to the now empty row above the heading, press F2 in the cell and paste in this formula:
    =FilterCriteria(A2)&LEFT(SUBTOTAL(9,A5:A200),0)
    Correct A2 so that it correctly refers to the heading cell underneath the formula.

    You could put in just the function call by itself: =FilterCriteria(A2), but this doesn't automatically update the cell every time you change a filter. To make the formula update every time you change a filter, we add a dummy function LEFT(SUBTOTAL(9,A5:A200),0) that doesn't harm anything, just makes the spreadsheet re-evaluate the filter display whenever the filter is changed i.e. the calculation performed won't slow anything down and won't change anything displayed in the spreadsheet.

    Now, I can easily tell what AutoFilter criteria apply to my spreadsheet!

    Of course, there is an important disadvantage to this approach: you are putting a macro into your document, and Excel security settings apply. If the document is intended for wide distribution, consider carefully just how necessary this functionality is. Check your security settings (Tools > Macro > select Security Level tab in MS Excel 2003 and earlier). If the setting is Medium, the user will be asked whether they want to enable macros every single time they open the file. If the setting is above Medium, the macro will simply be ignored and the formula may display an error in each cell, since the function is calling a macro that isn't loaded. If the document is going to people you don't know, they may be very suspicious of a new document opening with a security warning! You can sign the macro, but that is not a trivial thing to do, at least the first time - and still requires the user to trust your signature.

  7. Freeze your panes! But wait! There's more! If I have a spreadsheet complicated enough to need AutoFilter, it will probably have more than enough data to fill a single screen, which means that scrolling down makes the headings (and AutoFilter criteria display) disappear. Before you actually apply any filtering, put the cursor into the left-most cell just below the headings i.e. cell A3 in those images above. Then select on the menu bar Window > Freeze Panes. Now you can scroll down as far as you like and the first two rows will remain visible at the top.

Pages that helped me make this entry.

Copying and Pasting Formatted Code in Blogger

I often see some nice HTML code on a web page and want to use it in Blogger.

<span><a href="http://www.blogger.com">Get your own blog!</a></span>

If you copy the above text, you will also copy the formatting that comes with it. To use it - as HTML - paste it into the Edit Html tab of a new Blogger post.

Or, paste it into something like Notepad or UltraEdit first, then copy and paste it from there - Notepad and UltraEdit are examples of text editors that ignore formatting information when you paste text into them. You can do the same with Word too: in Word 2003 and before, you select   Edit | Paste Special | Unformatted Text.

If you copy and paste it straight from the web page into the Compose tab of a new Blogger post, it will look as it did on the web page (or close to - some more complicated formatting might be ignored or not copied in its entirety).

But if you then click on the Edit Html tab, you will see that what you actually have is the code you wanted - formatted to look as it did on the page you copied it from. I.e. it might work to display the formatted code, but it won't work as HTML code itself.

As an author of blog posts, this is generally unavoidable even if you use no extra formatting! This is because the software e.g. Blogger, will automatically wrap text in formatting HTML of its own. For example, consider the below very plain piece of html.

<span><a href="http://www.blogger.com">Get your own blog!</a></span>

Copy that in compose mode (not HTML), and the line above becomes this:

&lt;span&gt;&lt;a href="http://www.blogger.com"&gt;Get your own blog!&lt;/a&gt;&lt;/span&gt;

No formatting at all: I used the HTML entities for < and > to avoid the code being evaluated as HTML, but there is no PRE, B I tags etc. Copy and paste this into the Compose tab of a new Blogger post and then look at the Edit Html tab - you will still see extra SPANs and other things, because Blogger is formatting all of my text.

Also see my other post, Bogger: Convert Line Breaks Setting for an idea about some other difficulties with posting code in Blogger - and a few alternatives for how to post code.

Thanks to Nithin for asking posting code in Blogger.

Wednesday, September 24, 2008

UltraEdit macro: act on selected text or whole document

Updates
15/06/2009 12:28:28 PM. Fixed up the example macro, which was broken (bad c&p plus forgot "GotoLine 1 1"). Added some additional explanation.

I often want my UltraEdit macros to act on the text I have selected or the whole document if I have no text selected. Here is a small macro I use often that demonstrates how to do this.

InsertMode
ColumnModeOff
HexOff
UnixReOff
IfSel
Find SelectText "<"
Replace All "&lt;"
Find SelectText ">"
Replace All "&gt;"
Find SelectText """
Replace All "&quot;"
Find SelectText "^t"
Replace All "  "
Else
GotoLine 1 1
Find "<"
Replace All "&lt;"
Find ">"
Replace All "&gt;"
Find """
Replace All "&quot;"
Find "^t"
Replace All "  "
EndIf

The red text is the guts of macro, i.e. what it does. It replaces:

  • the less than symbol "<" with "&lt;";
  • the greater than symbol ">" with "&gt;";
  • the double quote symbol """ with "&quot;"; and
  • tab characters with two spaces " ".

The blue text is the control logic. If you have text selected (IfSel), the actions will be performed only upon that selected text. Otherwise (Else), the logic will be applied accross the entire document (GotoLine 1 1 ... Find ... Replace All).

UltraEdit macros are purely procedural, so there isn't any way around repeating the slightly modified code in the IF and ELSE.

I think UltraEdit is very cool because it supports many ways to automate various things: look up tags, templates, macros and (JavaScript) scripts.

Friday, September 19, 2008

FatWire - dump all ICS variables

Often I get stuck in some element code or whatever, where the ICS variable I think is there, turns out not to be there. Rather than guess, it is often useful just to output all of the variables first and see if your variable is there but perhaps under a different name. Here is the code I use to dump out all the ICS variables for inspection.

In JSP, output the ICS variable names.

<div style="color: red">List ICS var names.</div>
<ul>
<%
java.util.Enumeration varNames = ics.GetVars();
while (varNames.hasMoreElements()) {
   %>
   <li><%= varNames.nextElement().toString() %></li>
   <%
}
%>
</ul>

In JSP, output the ICS variable names and values.

<div style="color: red">List ICS var names and values.</div>
<table border="1">
    <tr><th>Name</th><th>Value</th></tr>
    <%
    java.util.Enumeration varNames = ics.GetVars();
    while (varNames.hasMoreElements()) {
        String nextName = varNames.nextElement().toString();
        %><tr>
            <td><%= nextName %></td>
            <td><%= ics.GetVar(nextName) %></td>
        </tr><%
    }
    %>
</table>

In Java, output the ICS variable names and values.

java.util.Enumeration varNames = ics.GetVars();
while (varNames.hasMoreElements()) {
    String elementName = varNames.nextElement().toString();
    System.out.println("Element name  [" + elementName + "] with value  [" +
            ics.GetVar(elementName) + "].");
}

Sunday, August 03, 2008

Cygwin Command Prompt

Linux shells use the PS1 variable to determine what the command prompt should look like. This is the command prompt I use in my shell:

export PS1="\[\e]2;\u@\H - ${workSpaceName} - \w\a\e[32;1m\n\s\v - \u@\H - ${workSpaceName} - \w\n\d - \@ > \e[0m"

It gives me a prompt like this:

-bash3.2 - RobertMarkBram@orson - SCJP - /cygdrive/d/WorkSpaces/scjp
Sun Aug 03 - 11:52 PM >
And gives my shell this window title (also used in the alt+tab display). RobertMarkBram@orson - SCJP - /cygdrive/d/WorkSpaces/scjp

Edit 7/08/2008 12:35:39 PM: see my comments below.

IBM's page, Tip: Prompt magic helped me build it.

You can also use the PROMPT_COMMAND variable. The contents of this variable are executed as a regular Bash command just before Bash displays a prompt. It isn't the same as the PS1 variable, because the PROMPT_COMMAND string is actually executed, whereas PS1 is parsed to form a string.

For example, this:

export PROMPT_COMMAND='echo -ne "\nnice prompt"'

Gives me this (in combination with my PS1):

nice prompt
-bash3.2 - RobertMarkBram@orson - SCJP - /cygdrive/d/WorkSpaces/scjp
Sun Aug 03 - 11:58 PM >

The Linux BASH syntax : prompt variable can give you a bit more detail about what you can put there.

Inputrc for bash history completion using up/down arrows

The bashrc file stores key mappings. Use your own bashrc by putting export INPUTRC=~/.inputrc in your .bash_profile or .bashrc

This page, Creating the /etc/inputrc File and this page, Super-useful inputrc give some useful advice about things you can put in those files.

One of things I most often find myself doing is searching my command line history. I frequently use the cursor up and down to scroll through my most recent commands. Often, I want to re-use a particular grep or find that I used recently, but I don't remember the specifics of it. With the text below in your .inputrc, you can type in the first few letters, say gr or f and press the cursor keys and it will scroll through your command history, showing the commands that began with those characters. Nice. :)

# By default up/down are bound to previous-history
# and next-history respectively. The following does the
# same but gives the extra functionality where if you
# type any text (or more accurately, if there is any text
# between the start of the line and the cursor),
# the subset of the history starting with that text
# is searched (like 4dos for e.g.).
# Note to get rid of a line just Ctrl-C
"\e[B": history-search-forward
"\e[A": history-search-backward

$if Bash
  # F10 toggles mc on and off
  # Note Ctrl-o toggles panes on and off in mc
  "\e[21~": "mc\C-M"

  #do history expansion when space entered
  Space: magic-space
$endif

# Include system wide settings which are ignored
# by default if one has their own .inputrc
$include /etc/inputrc

Source: Inputrc for bash history completion using up/down arrows

Cygwin and Putty - a wonderful combination!

Updates
16/07/2009 12:33:04 PM - added reference from David Jones' blog entry: PuttyCyg on how to set up PuttyCyg.

I have been using Cygwin for many years now and I find it extremely useful. It is a Linux on Windows tool that lets me do amazing things with a few shell scripts. Putty is a terminal that is also very powerful, and a pleasure to use. :)

Today I found PuttyCyg which allows me to use PuTTY as a local Cygwin terminal.

It is easy to setup, just extract the Zip and create a shortcut with putty.exe as a target and "-cygterm -" as a parameter.
-- David Jones' blog entry: PuttyCyg.

Wednesday, June 18, 2008

Blogger: Convert Line Breaks Setting

This post has a few tips/warnings for posting code for display and code for use (i.e. Javascript) in a Blogger post when you have "convert line breaks" on.

Updates
30/01/2009 5:49:32 PM. I have since turned this setting off and am now reformatting all of my posts to contain valid HTML.
22/07/2009 10:21:16 AM. Updated text in this article to account for my previously turning off this setting. Added brief section on Syntax Highlighter alternative (that I would like to re-visit in the future in another post with more detail).

The Problem with Convert Line Breaks

Check this setting: Settings > Formatting > Convert line breaks. If it is set to yes, then each line break you insert into your post will be interpreted as a <br/>. In general, this good for WYSIWYG writing i.e. it is good for authors who don't want to write HTML by hand. I thought I was one of these recently, until I tried to do two things in particular:

  • Insert some Javascript into my blog post. This was defeated because after Blogger replaced all line breaks with <br/>, the Javascript didn't work!
  • Post some code into a text area. This was defeated because my carefully laid out code was mangled by Blogger replacing all line breaks with <br/>

Here are my work-arounds for these two issues.

Inserting Javascript

  1. Make sure you use semi-colons to properly end Javascript statements. I am a Java programmer, so this comes naturally to me.
  2. Remove all comments from the Javascript. You can leave in block comments if you wish (/* .. **/) because they are self enclosed, but you definitely need to get rid of single line comments (//) or the next step will break your code.
  3. Remove all line breaks from the code. Replace them with a single space if you have kept in block comments. I use UltraEdit, so this is an easy find/replace (find all instances of ^p, replace with nothing). You could remove all the leading space too, though in my usage so far I have left leading space as is - so that if I need to put back the line breaks I still have easily readable code.

Post some code

Previously, I was posting code with HTML PRE elements, sometimes with colour. For example: <pre style="color: blue">Formatted code goes here..</pre>. The problem with this approach is that large amounts of code don't look good. The flow of an article is easily broken if the reader has to scroll past a large code excerpt to continue reading the text. If the code excerpt is large, it is generally easier for a reader to skip it first and come back later to read it through or c&p it.

To help with this, I wanted to try putting the code into a scrollable text area. It didn't work though.

The above is from my previous Blogger post, Dynamic text field prompts with Javascript

My current work-around (See the Syntax Highlighter section below.) One approach I tried is to use a combination of the DIV and PRE HTML elements. For example: <div style="width: 500px; height: 200px; background-color: a0ffff; color: 000000; font-family: arial; font-size: 12px; text-align: left; border: 3px solid 00000; overflow: auto; padding: 4px;"><pre>Formatted code goes here..</pre></div>.

Here is an example:

<script language="javascript" type="text/javascript">
<!--
  // Clear prompt from username field, if required.
  function clearUsernamePrompt() {
    var username = document.getElementById("username2");
    if (username.value == "Username") {
      username.value = "";
    }
  }

  // Set the prompt for the username field, if required.
  function setUsernamePrompt() {
    var username = document.getElementById("username2");
    if (username.value == "") {
      username.value = "Username";
    }
  }

  // Clear prompt from password field, if required.
  function clearPasswordPrompt() {
    var password = document.getElementById("password2");
    if (password.value == "Password") {
      password.type = "password";
      password.value = "";
    }
  }

  // Set the prompt for the password field, if required.
  function setPasswordPrompt() {
    var password = document.getElementById("password2");
    if (password.value == "") {
      password.type = "text";
      password.value = "Password";
    }
  }
// -->
</script>

<input type="input" value="Username" id="username2"
onFocus="clearUsernamePrompt();" onBlur="setUsernamePrompt();"/> <input
type="input" value="Password" id="password2" value="Password"
onBlur="setPasswordPrompt();" onFocus="clearPasswordPrompt();"/>

I can now post large code excerpts with a basic level of formatting such that the reader can easily scroll past the code and refer back to it when they are ready. If you look at the source If I still had Convert line breaks turned on, you would see that Blogger has still gone ahead and replaced my line breaks with <br/> elements, but that's cool - the HTML PRE element will render them just the same as line breaks.

This approach has one major draw-back though. With a real text area, you can put your cursor inside it and do a "select all" (control+a in windows/linux browsers, use that funky apple button if you are on a Jobs machine). With a DIV, you end up selecting all text on the page.

Tables

Problems also occur with this setting turned on when you insert TABLE HTML code in your blogger post, which is very nicely illustrated by The Real Blogger Status in Tables Are Very Sensitive To Gratuitous Line Breaks. Line breaks convert to <br/> elements which will do a very good job of screwing up your carefully adjusted table layout! The work-around suggested is simply to remove line breaks in your code.

MLA Wire's Blogger post Table Formatting in Blogger suggests another neat work-around: use a CSS class to hide <br/> elements. Insert the following code in your post: <style type="text/css">.nobr br { display: none }</style> then give your HTML TABLE element the nobr class name.

Convert Line Breaks?

In future, I plan to turn that setting off I have now turned that setting off and "code" my blog posts with HTML. Not a big handicap for me, since I am comfortable doing this. There are various WYSIWYG editors that can help too, letting you type in preview mode and copy and paste the resulting HTML when you are done. Unfortunately, I will have to go over all of my old blog posts and change them!

Also check out what The Real Blogger Status has to say about this setting in the following blog entry: Separate Your Paragraphs.

Syntax Highlighter

Added 22/07/2009 10:23:06 AM.

Now I am using an amazing Javascript solution by Alex Gorbatchev: SyntaxHighlighter. I love this solution for the following reasons.

  1. It uses language specific syntax highlighting to best display my code - just see the example below.
  2. It provides buttons to let readers "view source" i.e. plain text and print the code.
  3. It works equally in IE (8.0.6001.18702), Firefox 93.5.1), Chrome (2.0.172.37), Safari (4.0.2) and Opera (9.64) - tested on Windows XP.
  4. Look at the code below - I specified "js" as the language, but it has handled the HTML components properly as well.
  5. I don't need to replace < and > with &lt; and &gt;.


 

There are some disadvantages though. In my opinion these are minor compared to what it gives me in return, but they should be considered.

  1. Setup. You have to edit your Blogger template to import the Javascript and CSS files. Sometimes this can be tricky to manage and get the paths right.
  2. Hosting. You have to find somewhere to host the Javascript, CSS and image files because Blogger won't let you upload the Javascript/CSS files. I host them on my Google App Engine site, but (just for Blogger) Alex allows you to refer to the files directly on his own site.
  3. Screen space. I mentioned above that the advantage of using a scrollable DIV means that long code fragments take up small sections of the screen. SyntaxHighlighter code is relatively compact but big sections of code will still take up big sections of screen real estate. I have decided the cost is worth it. If required, I will use SyntaxHighlighter to demonstrate excerpts of code and provide links to more complete examples.
  4. Processing time. Because SyntaxHighlighter is a Javscript API, all the processing is done on the client side every time they open/refresh a page. It means that the more (and longer) pieces of SyntaxHighlighter code I have on a page, the longer and longer it will take for the SyntaxHighlighter sections to be processed. At least the processing is done after the page has loaded - so the user will still see the code in "plain" PRE format until the Javascript has finished and re-renders it.

Tuesday, June 17, 2008

Dynamic text field prompts with Javascript

Edit: (2 Sep 2008), explained why IE doesn't like the first example.

The problem: the client wants a username and password field, but space is too tight to allow for labels. Instead, they want the labels inside the fields. The labels should disappear once the user puts their cursor inside the field. Let's call them prompts from here on in because they are not permanent.

Here is my first attempt.

It works ok in Firefox 2, but not in IE 6. :(

The password field is the problem. The Javascript dynamically toggles the password field's type between text and password via password.type = "text" and password.type = "password". IE doesn't allow this action. No error is raised, it just ignores it.

Here is the code.

<script language="javascript" type="text/javascript">
<!--
  // Clear prompt from username field, if required.
  function clearUsernamePrompt() {
    var username = document.getElementById("username2");
    if (username.value == "Username") {
      username.value = "";
    }
  }

  // Set the prompt for the username field, if required.
  function setUsernamePrompt() {
    var username = document.getElementById("username2");
    if (username.value == "") {
      username.value = "Username";
    }
  }

  // Clear prompt from password field, if required.
  function clearPasswordPrompt() {
    var password = document.getElementById("password2");
    if (password.value == "Password") {
      password.type = "password";
      password.value = "";
    }
  }

  // Set the prompt for the password field, if required.
  function setPasswordPrompt() {
    var password = document.getElementById("password2");
    if (password.value == "") {
      password.type = "text";
      password.value = "Password";
    }
  }
// -->
</script>

<input type="input" value="Username" id="username2"
    onFocus="clearUsernamePrompt();" onBlur="setUsernamePrompt();"/>
<input type="input" value="Password" id="password2" value="Password"
    onBlur="setPasswordPrompt();" onFocus="clearPasswordPrompt();"/>

An alternative approach is to use two fields for the password: one is a text input and the other is a password. Now we use the handlers to change which one is visible. Here is the second attempt.

And here is the code.

<script language="javascript" type="text/javascript">
<!--
  // Clear prompt from username field, if required.
  function clearUsernamePrompt() {
    var username = document.getElementById("username");
    if (username.value == "Username") {
      username.value = "";
    }
  }

  // Set the prompt for the username field, if required.
  function setUsernamePrompt() {
    var username = document.getElementById("username");
    if (username.value == "") {
      username.value = "Username";
    }
  }

  // Clear prompt from password field, if required.
  function clearPasswordPrompt() {
    var password = document.getElementById("password");
    password.style.display = "inline";
    password.focus();
    var passwordPrompt = document.getElementById("passwordPrompt");
    passwordPrompt.style.display = "none";
  }

  // Set the prompt for the password field, if required.
  function setPasswordPrompt() {
    var password = document.getElementById("password");
    if (password.value == "") {
      password.style.display = "none";
      var passwordPrompt = document.getElementById("passwordPrompt");
      passwordPrompt.style.display = "inline";
    }
  }
// -->
</script>
<input type="input" value="Username" id="username"
    onFocus="clearUsernamePrompt();" onBlur="setUsernamePrompt();"/>
<input type="password" id="password" style="display:none"
    onBlur="setPasswordPrompt();" /><input type="text" value="Password"
    id="passwordPrompt" onFocus="clearPasswordPrompt();"/>

Note 1. Make sure there is no space in between the password and passwordPrompt fields. If you leave any space there, the transition will look odd - the password field will look like it is jumping.

Note 2. As always, you must take careful consideration of your target audience. Specifically relevant to this example: will they always have Javascript enabled? If they don't, this page won't work - they will never be able enter a password! For the majority of websites nowadays, it is an assumption that Javascript will always be enabled, especially for the "Web 2.0" sites, but you need to be sure before you use this.

Another alternative would be to use DOM within the Javascript to swap elements in and out. This would work and might even make the HTML look simpler, but has a major complication: swapping fields in and out will disturb the tab order. Unless you explicitly set tab indexes (for all the fields on the form), DOM manipulation can screw that up.

Friday, May 23, 2008

UltraEdit macro: replace all is from top of file

Edit: 24/05/2008 12:18:08 AM. Thanks to the UltraEdit support team who showed me that bookmarks can store cursor position within a line.

UltraEdit is by far my favourite text editor. Over time, I have built up a small collection of macros suited to my needs. Many of these macros perform a find/replace over the entire document. The macro would act over the entire document because I always have the "Replace all is from top of file" option checked in the find/replace dialog. I also expect the cursor to be in the same place after the macro is completed - this wasn't a problem because "find/replace all" actions wouldn't move the cursor.

As of version 13.20, UltraEdit has changed the macro behaviour: now a macro is always executed from the current cursor position, irrespective of the "Replace all is from top of file" option. This means that for my "replace all in document" macros I need two things: a) in order to execute over the entire document, a macro needs the Top command to move the cursor to the start of the document before the find/replace processing and b) I need a workaround to get the cursor back to the starting position at the macro's completion.

One workaround for the cursor position is to set a bookmark at the start of the macro and go back to the bookmark at the end of the macro. The below example shows this.

InsertMode
ColumnModeOff
HexOff
PerlReOn
ToggleBookmark 1
Top
Find "bad text"
Replace All "good text"
GotoBookMark 1
ToggleBookmark

A couple of notes are relevant to this macro.

  1. By default, a bookmark stores only references a particular line in a file, not the cursor position within that line, but this can be controlled with a Configuration setting. Go to Advanced > Configuration > Editor > Bookmarks and make sure "Bookmark column with line" is checked. From now on, a bookmark will remember the particular cursor position within a line, as well as the line.
  2. Note the index after these two book mark commands: ToggleBookmark 1 and GotoBookMark 1. Use the index to make sure you return to the same bookmark you created. If the macro logic needs to work with bookmarks, make sure they use other indexes.

If you don't want to rely on bookmarks, there is another way. Insert some unique text at the start of the macro and execute a find/replace for that text at the end of the macro. The below example shows this.

InsertMode
ColumnModeOff
HexOff
PerlReOn
"xxxcxxx"
Top
Find "bad text"
Replace All "good text"
Find "xxxcxxx"
Key DEL

There are two flaws with this technique.

  1. The text must be unique!
  2. This technique puts the unique text insertion and deletion actions into the 'undo' queue i.e. undo a few times after the completion of this macro and you will see the unique text get un-deleted. This isn't a big issue, but it is not neat: the purpose of my macro is to replace "bad text" with "good text" - I shouldn't see artifacts of a workaround appear in the action queue.

Personally I prefer the former technique, and I have refactored my macros to use this.

Monday, February 25, 2008

Nested Property Placeholders in Spring Configuration

  1. Using Java Properties in Spring Configuration - the PropertyPlaceholderConfigurer
  2. Using Nested Java Properties in Spring Configuration - nested Place Holders
  3. More Nested Java Properties in Spring Configuration
  4. Caveat for using PropertyPlaceholderConfigurer
  5. Data Source vs JNDI
  6. Appendix

Using Java Properties in Spring Configuration - the PropertyPlaceholderConfigurer

In your Spring configuration (Spring 2.5), it is possible to use property place-holders that get replaced with values from a properties file. For example, consider the Spring configuration below in which I define a POJO (plain old Java object) called testBean and a PropertyPlaceholderConfigurer called placeholderConfig.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
  <bean id="placeholderConfig"
      class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location"
      value="classpath:application.properties" />
  </bean>
  <bean name="testBean" class="my.Test">
    <property name="environmentName">
      <value>${environmentSpecificName}</value>
    </property>
  </bean>
</beans>

Note that the placeholderConfig bean has a location property whose value is the name of a properties file (application.properties) that is expected to appear on the class path. PropertyPlaceholderConfigurer's job is to go through your Spring configuration and replace all property place-holders (a string enclosed by a dollar-curly-brace pair: ${}) with a value from a property file. The property place-holder value is the key to retrieving the value from property file. See below for an important caveat with this behaviour.

Below is the properties file that my Spring configuration references.

# Environment: "local environment", "development environment",
# "test environment", "stress and volume testing environment"
# or "production environment".
environmentSpecificName=local environment

This means that when I retrieve testBean, its environmentName property will be "local environment". Whenever I want to change the value of testBean.environmentName in my code, I change the environmentSpecificName property and re-run my application.

TOP

Using Nested Java Properties in Spring Configuration - nested Place Holders

As you might guess from the comment I placed above the environmentSpecificName property, I want it to vary depending on which environment my code is running within. I.e. I want a different value for each of the environments my code might run within: local, development, testing, SVT and production.

An alternative way I can do this is to set up different properties for each possibility (environment), and use a nested property place-holder in the property value to determine which value should actually be used. My Spring configuration does not change, but my properties file does. Here is my new properties file.

# Environment: local, dev, test, svt or prod.
environmentSpecificName=${local.environmentSpecificName}
local.environmentSpecificName=local environment
dev.environmentSpecificName=development environment
test.environmentSpecificName=test environment
svt.environmentSpecificName=stress and volume testing environment
prod.environmentSpecificName=production environment

Now what happens is that the PropertyPlaceholderConfigurer looks up the value for environmentSpecificName and finds another property place-holder: ${local.environmentSpecificName}. It looks up the value for ${local.environmentSpecificName} and finds local environment.

The advantage here is that I can express each of the possible values as properties and switch between them as needed. It is a bit easier to edit a properties file than a Spring configuration, which tends to get complicated very quickly i.e. arguably, a properties file is more readable than a Spring configuration.

The disadvantage is that I still have to edit a file, which means a re-deploy for a web-app. An Ant build script can help with this, by outputting a different deployable artifact for each environment, using a string replacement (the replace task) to make sure environmentSpecificName is correct.

Why go to all this trouble just to vary one value in the Spring configuration? Forget the place-holders altogether and edit the value directly in the Spring configuration - like you said, an Ant build script can replace the value for us too. This is correct. Everything in this blog entry can be done with an Ant script. The only potential advantage is if you consider it easier to manage options in a properties file than in a build script, or Spring configuration. See the next section for an example of when I prefer to use this approach.

TOP

More Nested Java Properties in Spring Configuration

One situation in which I prefer this approach is when I have to manage multiple values that depend on one condition. Perhaps I have a different database for each environment, or a different set of web service URLs for each environment. I can set up each set of values in a properties file and use a single nested property to switch between sets. This is as close to conditional properties you can get with Spring.

Below is an example Spring configuration in which I define a JdbcTemplate that will come loaded with a data source specific for the environment my code is operating within.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
  <bean id="placeholderConfig"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="ignoreUnresolvablePlaceholders" value="false" />
    <property name="location"
      value="classpath:application.properties" />
  </bean>
  <bean name="jdbcTemplate" singleton="true"
    class="org.springframework.jdbc.core.JdbcTemplate">
    <constructor-arg index="1">
      <ref bean="dataSource" />
    </constructor-arg>
  </bean>
  <bean name="dataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource"
    destroy-method="close">
    <property name="driverClassName">
      <value>${dataSource.driverClassName}</value>
    </property>
    <property name="url">
      <value>${dataSource.url}</value>
    </property>
    <property name="username">
      <value>${dataSource.username}</value>
    </property>
    <property name="password">
      <value>${dataSource.password}</value>
    </property>
  </bean>
</beans>

Here is what my application.properties file might look like.

# Environment: local, dev, test, svt or prod.
environment=local

###############################################################################
## DATABASE PROPERTIES
###############################################################################

dataSource.driverClassName=${${environment}.dataSource.driverClassName}
dataSource.url=${${environment}.dataSource.url}
dataSource.username=${${environment}.dataSource.username}
dataSource.password=${${environment}.dataSource.password}

local.dataSource.url=jdbc:oracle:thin:@oralocal:1521:oralocal
local.dataSource.username=localuser
local.dataSource.password=localpwd
local.dataSource.driverClassName=oracle.jdbc.driver.OracleDriver

dev.dataSource.url=jdbc:oracle:thin:@oradev:1521:oradev
dev.dataSource.username=devuser
dev.dataSource.password=devpwd
dev.dataSource.driverClassName=oracle.jdbc.driver.OracleDriver

test.dataSource.url=jdbc:oracle:thin:@oratest:1521:oratest
test.dataSource.username=testuser
test.dataSource.password=testpwd
test.dataSource.driverClassName=oracle.jdbc.driver.OracleDriver

svt.dataSource.url=jdbc:oracle:thin:@orasvt:1521:orasvt
svt.dataSource.username=svtuser
svt.dataSource.password=svtpwd
svt.dataSource.driverClassName=oracle.jdbc.driver.OracleDriver

prod.dataSource.url=jdbc:oracle:thin:@oraprod:1521:oraprod
prod.dataSource.username=produser
prod.dataSource.password=prodpwd
prod.dataSource.driverClassName=oracle.jdbc.driver.OracleDriver

In this version, I only need to change one property (environment) to have four other properties change in response. Here is what the PropertyPlaceholderConfigurer is doing for ${dataSource.url} and the other place-holders in the data source Spring configured bean.

  1. The property place holder ${dataSource.url} key resolves to the value ${${environment}.dataSource.url}, which is itself a property place holder with another property place holder nested within it.
  2. The property place holder ${environment} key resolves to the value local.
  3. The property place holder ${${environment}.dataSource.url} key is now ${local.dataSource.url}
  4. The property place holder ${local.dataSource.url} resolves to the value jdbc:oracle:thin:@oralocal:1521:oralocal.

TOP

Caveat for using PropertyPlaceholderConfigurer

If you are using an XmlBeanFactory, you have to explicitly reference the PropertyPlaceholderConfigurer and invoke it upon your bean factory. For example.

XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml"));
PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer();
configurer.setLocation(new FileSystemResource("application.properties"));
configurer.postProcessBeanFactory(factory);

Or you could retrieve the bean from the context and then invoke it.

XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml"));
PropertyPlaceholderConfigurer configurer = factory.getBean("placeholderConfig");
configurer.postProcessBeanFactory(factory);

Either way, you have explicitly invoke the configurer to do its work upon your Spring configuration. For this reason alone, I prefer to use ClassPathXmlApplicationContext as my factory, because it will automatically invoke any PropertyPlaceholderConfigurer on the context, just by having it in the Spring configuration. Also, you generally need a ApplicationContext in web apps.

TOP

Data Source vs JNDI

If you need to manage data sources across environments, and you have access to JNDI in each environment, use that instead. No need to hard code passwords in property files that way. But you will probably still a properties file approach for local unit testing - that you should be able to run without a server.

TOP

Appendix

Links that helped me with this.

  • From the Spring - Java/J2EE Application Framework Reference Documentation is Chapter 3. Beans, BeanFactory and the ApplicationContext.
  • I posted a question about this on the Spring forum, where I learned how to use this functionality. Further, I learned that Spring V3 might allow nested property place-holders in the property keys, not just values.