I’m going to document this because it took me ages to work out, but in the end it turned out to be quite easy. In Rails there is a useful thing called ‘flash’. Flash is a component which is automatically available in every view. Controller classes can insert a message into the flash object, and when it renders the view it can display the message. The great thing is that by default the message exists in the flash for two requests. This is so that the current request can be terminated with a browser redirect, and when the browser follows the redirect the flash message will be there waiting for it. The way it works in ruby is quite simple. In a controller’s action method, you just put a message into the flash object:
flash[:notice] = 'Logged in successfully'.t
Then in the view, usually in a common part of the layout on every page, you put:
<% if self.flash[:error] -%>
<div class="error" id="error"><%= self.flash[:error]%></div>
<% end -%>
<% if self.flash[:notice] -%> 
<div class="notice" id="notice"><%= self.flash[:notice] %></div>
<script type="text/javascript">new Effect.Highlight('notice', {duration: 2.0});</script>
<% end -%>
One of the reasons the flash object is so smart is that the messages you add to it persist for two requests by default - the current request, and the next request. This is so that when you set a flash message then issue a redirect, the message is still there to be rendered when your browser follows the redirect. In order to implement this in Java, I’ve had to change things slightly. In the expression language that Spring uses, you can only access bean properties, you can’t call functions. So I can’t call flash.get(‘message’). In the application I’m porting to Spring, I have message types ‘notice’ and ‘error’, so I’ve hard-coded methods for them in my flash object. Here’s the code for the Flash object:
package info.evansweb.spring.examples.flash;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * Copyright 2007 Jon Evans, jon@springyweb.com
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * 
 * This class implements a Flash object for Spring MVC, inspired by the flash
 * object available to views in Rails (rubyonrails.org)
 *
 * @author Jon Evans, jon@springyweb.com
 *
 */
public class Flash {
  private static final String NOTICE = "notice";
  private static final String ERROR = "error";
  
  private Map<String, FlashEntry> data = new HashMap<String, FlashEntry>();
  
  public void clear() {
    this.data.clear();
  }
  
  public void decr() {
    Iterator<FlashEntry> it = data.values().iterator();
    while (it.hasNext()) {
      FlashEntry entry = it.next();
      int ttl = entry.decr();
      if (ttl <= 0) {
        it.remove();
      }
    }
  }

  /** Set an arbitrary key / value / ttl message */
  public void set(String key, String value, int ttl) {
    data.put(key, new FlashEntry(value, ttl));
  }
  /** Set an key / value message with default ttl message */
  public void set(String key, String value) {
    data.put(key, new FlashEntry(value));
  }
  /** Set a NOTICE message */
  public void set(String value) {
    data.put(NOTICE, new FlashEntry(value));
  }

  /** Set a message for the current request only */
  public void setCurrent(String key, String value) {
    data.put(key, new FlashEntry(value, 1));
  }
  /** Set a NOTICE message for the current request only */
  public void setCurrent(String value) {
    data.put(NOTICE, new FlashEntry(value, 1));
  }

  /** Set a NOTICE message with a ttl */
  public void setNotice(String value, int ttl) {
    data.put(NOTICE, new FlashEntry(value, ttl));
  }
  /** Set a NOTICE message with default ttl */
  public void setNotice(String value) {
    data.put(NOTICE, new FlashEntry(value));
  }
  /** Set an ERROR message with a ttl */
  public void setError(String value, int ttl) {
    data.put(ERROR, new FlashEntry(value, ttl));
  }
  /** Set an ERROR message with default ttl */
  public void setError(String value) {
    data.put(ERROR, new FlashEntry(value));
  }

  public String getValue(String key) {
    if (data.containsKey(key)) {
      return data.get(key).getValue();
    } else {
      return null;
    }
  }
  public String getNotice() {
    return getValue(NOTICE);
  }
  public String getError() {
    return getValue(ERROR);
  }

  public class FlashEntry {
    private String value;
    
    // Default ttl is 2, this request, and the following request
    private int ttl = 2;
    
    public FlashEntry(String value) {
      this.value = value;
    }
    
    public FlashEntry(String value, int ttl) {
      this(value);
      this.ttl = ttl;
    }
    
    public int getTTL() {
      return ttl;
    }
    public void setTTL(int ttl) {
      this.ttl = ttl;
    }

    public int decr() {
      return --ttl;
    }
    
    public String getValue() {
      return value;
    }
  }
  
  public String toString() {
    return "Flash [" + data.size() + " message(s)]";
  }
}
Continue to part 2

I fixed the cistern in our toilet this evening. For the last few months it has been making a noise like a tuba every time it fills up. Turns out the valve just needed dismantling and cleaning. While I was in there I found an alternative valve insert which the manufacturers had provided in case you have low water pressure. Well, our water pressure is really high, but I thought I’d give it a try anyway. I connected of all up and flushed, and it was an amazing spectacle. It filled the cistern with a deluge of water in about 20 seconds. I decided that the valve probably couldn’t cope with that long term, so I put the old one back. At least I silenced the toilet tuba.

I bought an iPhone last Friday. I arrived at the O2 store in Banbury at about 18:10. There was quite a party atmosphere with about 15 members of staff available, but there were only about 2 customers there apart from me. Nobody else bought anything while I was there, although I passed someone on the way to the shop posing for a photograph, beaming broadly and holding his “Got One!” bag for the camera to see.

Here’s the Mac OS X Leopard version of the Airport menu shown in the previous post: (Update 2019-02-28: Skitch is long gone, so unfortunately that includes this image, which was hosted there) The lock icons are presumably quite useful for some people, although I only ever connect to either my home or office network so I never really even look at the menu.

I can see loads of WiFi routers from my house these days. A couple of years ago, mine was the only one. Oh yes, this post is also a test of the new Skitch Beta. (2019-02-28: removed dead link to an image that was hosted on Skitch which has now been lost forever)