Vandaag is het exact 1 jaar geleden dat ik voor het eerst in mijn nieuwe woning heb geslapen. Ter ere van dat heugelijke feit heb ik ter vergelijking een paar nieuwe foto’s gemaakt van foto’s die ik op 6 maart 2009 heb gemaakt toen mijn appartement nog ‘under construction was’. Nieuw huis, nieuwe inboedel, nieuwe start
Whale sharks
I just saw this video on Facebook about the world’s second largest fish tank where you see numerous whale shark and manta rays swimming by… amazing!
Creating dependent selects in Grails
![]()
In my previous post I explained how to use Ajaxify Grails Webflows, and this time I will address how to add two select boxes where the latter is depending on the input of the first. Let’s say we would like to add a select containing species, and a select containing genotypes of a particular species. As the latter is depending on the first, we would like the latter to automatically update whenever another species is selected.
First, we will create the two select elements in the view. The genotype select can be empty as it will be dynamically filled by the species select. The species select, however, is a little bit more complicated as it uses a list (speciesList) as input and contains an onChange action:
...
<tr>
<td class="title">Species</td>
<td>
<g:select
name="species_id"
value="${species?.id}"
optionKey="id"
optionValue="name"
from="${speciesList}"
noSelection="['':'-Choose species-']"
onChange="${remoteFunction( action:'ajaxGetGenotypeBySpecies',
params: '\'id=\'+escape(this.value)',
onSuccess:'updateSelect(\'genotype_id\',data,false,false,\'default\')')}" />
</td>
</tr>
<tr>
<td class="title">Genotype</td>
<td><g:select name="genotype_id" optionKey="id" optionValue="name" from="[]" /></td>
</tr>
...
Looking at the two selects, you will see the species select will use Grails’ remoteFunction to remotely call ajaxGetGenotypeBySpecies in the controller. That particular action just fetches all genotypes for a particular species, and renders the list as a JSON object which, in turn, is given back to the calling JavaScript.
// use Grails' converters to be able to render JSON
import grails.converters.*
...
/**
* Fetch all genotypes for a species and render them as JSON
* @param int species id
* @return string JSON response
*/
def ajaxGetGenotypeBySpecies = {
// find species by id
def species = Species.get(params.id)
// fetch genotypes of a particular species
def genotypes = Genotype.findAllBySpecies(species, [sort:'name',order:'asc'])
// render as JSON
render genotypes as JSON
}
When the call has been a success, the updateSelect(\’genotype_id\’…) JavaScript function is executed, stating we would like to update the select element with genotype_id as id.
/**
* Update one select based on another select
*
* @author you
* @see http://www.grails.org/Tag+-+remoteFunction
* @param string select (form) name
* @param string JSON data
* @param boolean keep the first option
* @param int selected option
* @param string if null, show this as option instead
* @void
*/
function updateSelect(name,data,keepFirstOption,selected,presentNullAsThis) {
var rselect = $('#'+name).get(0);
if (data) {
// remove old options
var start = (keepFirstOption) ? 0 : -1;
var i = rselect.length;
while (i > start) {
rselect.remove(i);
i--;
}
// add new options
$.each(items,function() {
var i = rselect.options.length
rselect.options[i] = new Option(
(presentNullAsThis && this.name == null) ? presentNullAsThis : this.name,
this.id
);
if (this.id == selected) rselect.options[i].selected = true;
});
}
}
Now that the whole chain is working, we have one last thing to do. When the user enters the page the first time, the species list is populated and the genotype list is empty. However, when he has already previously selected a species, we would like the genotype list to be populated and properly selected as well. In order to do this we will call do the following when a species is available:
<g:if test="${species}"><script><g:remoteFunction action="ajaxGetGenotypeBySpecies" params="'id=${species.id}'" onSuccess="updateSelect('genotype_id',data,false,${genotype?.id},'default')" /></script></g:if>
And we’re done!
ps. Note that this is only one way to accomplish creating dependent selects. There are several other ways to accomplish the same result
24 Season 8
Ajaxifying a Grails Webflow
![]()
The Grails framework is a very powerful MVC web framework based on technology that has proven itself (Spring, Hibernate, Java, etc). For a while now Grails has supported Spring webflows which is a great way to develop wizard like user interfaces and underlying logic. While Grails webflows work very well for full page refreshes, they do not really work in an Ajax manner to allow rendering of fractions of pages. This would be useful for a number of reasons:
- separation of templates and template elements
- limit page rendering to the things that change instead of the complete page
- reduce bandwidth and only download require JavaScript and CSS once when the complete page is loaded.
- bookmarking the wizard would only bookmark the initial wizard page
- etc
In order for a webflow to work with Ajax the webflow should be able to render and refresh only portions of a front-end. While Grails does provide a submitToRemote tag to perform an Ajax submit, the tag itself does not work with webflows. That is because a Grails webflow links form buttons to flow actions. So, if you have a submit button named ‘delete’, the delete action will be executed in the particular flow execution element. This is done by adding ‘_eventId_delete=1′ to the POST data. However, the Grails tag submitToRemote does not do that, so the webflow is not executed properly.
So, to solve this particular issue we have to create a new tag library to extend / modify the default submitToRemote tag behaviour. In the example below I have created a new tag library called myTagLib (grails create-tag-lib com.example.myTagLib) in which I extend the default tag library. It currently contains one method (‘tag’) called ajaxButton which wraps around the submitToRemote tag to add the webflow variable (_eventId_buttonName) to the Ajax call (note that I use jQuery as Ajax provider):
package com.example
import org.codehaus.groovy.grails.plugins.web.taglib.JavascriptTagLib
class MyTagLib extends JavascriptTagLib {
// define the tag namespace (e.g.: <my:action ... />
static namespace = "my"
/**
* ajaxButton tag, this is a modified version of the default
* grails submitToRemote tag to work with grails webflows.
* Usage is identical to submitToRemote with the only exception
* that a 'name' form element attribute is required. E.g.
* <my:ajaxButton name="myAction" value="myButton ... />
*
* @see http://www.grails.org/WebFlow
* @see http://www.grails.org/Tag+-+submitToRemote
* @param Map attributes
* @param Closure body
*/
def ajaxButton = {attrs, body ->
// get the jQuery version
def jQueryVersion = grailsApplication.getMetadata()['plugins.jquery']
// fetch the element name from the attributes
def elementName = attrs['name'].replaceAll(/ /, "_")
// generate a normal submitToRemote button
def button = submitToRemote(attrs, body)
/**
* as of now (grails 1.2.0 and jQuery 1.3.2.4) the grails webflow does
* not properly work with AJAX as the submitToRemote button does not
* handle and submit the form properly. In order to support webflows
* this method modifies two parts of a 'normal' submitToRemote button:
*
* 1) replace 'this' with 'this.form' as the 'this' selector in a button
* action refers to the button and / or the action upon that button.
* However, it should point to the form the button is part of as the
* the button should submit the form data.
* 2) prepend the button name to the serialized data. The default behaviour
* of submitToRemote is to remove the element name altogether, while
* the grails webflow expects a parameter _eventId_BUTTONNAME to execute
* the appropriate webflow action. Hence, we are going to prepend the
* serialized formdata with an _eventId_BUTTONNAME parameter.
*/
if (jQueryVersion =~ /^1.([1|2|3]).(.*)/) {
// fix for older jQuery plugin versions
button = button.replaceFirst(/data\:jQuery\(this\)\.serialize\(\)/, "data:\'_eventId_${elementName}=1&\'+jQuery(this.form).serialize()")
} else {
// as of jQuery plugin version 1.4.0.1 submitToRemote has been modified and the
// this.form part has been fixed. Consequently, our wrapper has changed as well...
button = button.replaceFirst(/data\:jQuery/, "data:\'_eventId_${elementName}=1&\'+jQuery")
}
// render button
out << button
}
}
Now that we have this tag library in place we can add a webflow compatible submitToRemote button by adding the following tag:
<my:ajaxButton name="next" value="next »" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" class="prevnext" />
As the new ajaxButton tag is a wrapper of the submitToRemote tag it supports all arguments the submitToRemote tag supports, but in contrast it does not ignore the name argument. In fact, the name argument is required as it is responsible for executing webflow actions. The only thing which remains is to make sure the webflow only renders portions, and not complete pages.
Observe the following WizardController code:
package com.example
class WizardController {
def index = {
redirect(action:'pages')
}
def pagesFlow = {
// render the main wizard page
mainPage {
render(view:"/wizard/index")
on("next") {
println "next page!"
}.to "pageTwo"
}
// render page one
pageOne {
render(view:"_one")
on("next") {
println "next page!"
}.to "pageTwo"
}
// render page two
pageTwo {
render(view:"_two")
on("next") {
println "next page!"
}.to "pageThree"
on("previous") {
println "previous page!"
}.to "pageOne"
}
}
}
As seen in the webflow we have a two paged wizard. The mainpage renders the initial wizard page (index.gsp), while the pageOne and pageTwo actions only render portions of a page (respectively: pages/_one.gsp and pages/_two.gsp). We could split this up into the following four templates. Note that you can seperate templates even more, but for this example I will stick to these four:
index.gsp
<html> <head> <meta name="layout" content="main" /> </head> <body> <g:render template="common/wizard"/> </body> </html>
common/_wizard.gsp
<div id="wizard" class="wizard">
<h1>Proof of concept AJAXified Webflow Wizard</h1>
<g:form action="pages" name="_wizard" >
<div id="wizardPage">
<g:render template="pages/one"/>
</div>
<div id="wizardError" class="error"/>
</g:form>
</div>
pages/_one.gsp
...my form content for page 1...<br /> <my:ajaxButton name="next" value="next »" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" class="prevnext" />
pages/_two.gsp
...my form content for page 2...<br /> <my:ajaxButton name="previous" value="« prev" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" class="prevnext" />
As you can see, index.gsp will render default layout (add your CSS here) and renders common/_wizard.gsp. Wizard.gsp will, on it’s turn, render the first web flow template (pages/_one.gsp) which includes a form and the ajaxButton we created in the tag library above. This button will now use and Ajax call to submit to our webflow and execute the on(“next”) action in the pageOne part of our webflow, which will make the logic jump to the pageTwo part. Upon entry pageTwo will render pages/_two.gsp and return the rendered content to the Ajax call.
The ajaxButton contains update=”[success:'wizardPage'...]” which makes the Ajax call refresh the wizardPage div with the result of the Ajax call. Hence, the wizardPage div will now contain the rendered content of pages/_two.gsp with it’s own form.
Hence, we have Ajaxified our Grails webflow
What's that sound, I like that sound…
My New MacBook Pro
De nieuwe unibody MacBook Pro 3.06Ghz die ik 18 augustus heb besteld is in circa 2 dagen van Shanghai, via Polen en Duitsland vandaag bezorgd.
Track n Trace
Wat een mooi apparaat, hij voelt veel robuuster dan mijn oude MacBook Pro. Kan ook haast niet anders aangezien de kast gefreesd is uit één blok aluminium. Ook is dit de eerste keer dat ik toch een glanzend scherm heb genomen, en ook dat is een prima keuze geweest. Nu nog even wachten op Snow Leopard voordat ik hem volledig ga installeren…

Unibody MacBook Pro 3.06Ghz
Apple (doesn't) Care – deel 2
Naar aanleiding van mijn aangetekende brief werd ik vandaag teruggebeld door Katia Vanhauwaert van customer relations (Cork, Ireland). Katia, die zelf geregeld problemen heeft gehad met Apple’s WebObjects, vertelde me nogmaals dat het Apple niets voor me kan betekenen, ook al schrijft Apple zelf dat de NVIDIA 8600M GT spontaan kan overlijden en daar 3 jaar garantie op geeft.
Omdat ik sowieso twijfels had bij de kwaliteiten van het A Mac servicecenter (ze hebben alléén de Apple diagnostic software gedraaid en niet eens de moeite genomen om via screensharing mijn MBP nader te onderzoeken) adviseerde ze me mijn MacBook Pro aan te bieden bij een ander Apple Service center. Ik vertelde haar dat ik er weinig voor voelde om nogmaals € 85,- te gaan betalen om wederom te horen dat Apple niet thuis geeft.
Ze probeerde mijn negatieve arvaring af te kopen door een € 30,- voucher te sturen (die nota bene slechts 1 maand geldig is bij een aankoop van tenminste € 100,-). Wat denk je zelf Katia, dat ik nu opeens een positieve AppleCare ervaring heb? Los van ‘t feit dat ik dan nog steeds € 58,- in de min sta vind ik het een uitermate beroerde ervaring dat zowel Apple als NVIDIA erkennen dat het inderdaad een probleem is, dat het in de media redelijk breek is uitgemeten dat alle 8600M chips problemen vertonen, dat Apple wereldkundig maakt dat het een probleem is maar dat ze het voor je oplossen als je er binnen 3 jaar na aanschaf last van krijgt, en áls je het dan daadwerkelijk hebt, dan geeft Apple niet thuis?!
Echt een ontzettend belabberde service Katia! Als geen problemen waren met deze chips en de nvidia chip houdt er mee op dan had ik er ‘vrede’ mee (ook al vind ik dat je redelijkerwijs mag verwachten dat laptop van € 2400,- waar je behoorlijk voorzichtig mee omgaat er niet spontaan mee ophoud na net 2 jaar), maar nu het zowel door NVIDIA als Apple is erkend vind ik ook dat het moet worden opgelost. Vanwege deze beroerde service heb ik besloten deze slechte apple service ervaringen op te schrijven en op diverse fora te gaan plaatsen…

Apple (Doesn't) Care
Pukkelpop 2009
De afgelopen dagen even geen Apple en defecte MacBook Pro’s maar muziek in de brandende zon bij Pukkelpop 2009! Hoogtepunten: Faith No More, Them Crooked Vultures en de overvloed aan Drum ‘n Bass grootheden. Een kleine impressie:

Nog schoon fris en fruitig bij Bert en Bobje

Friet mét

Radio Melinda was er ook
The video cannot be shown at the moment. Please try again later.

Marten en zijn dnb moves
The video cannot be shown at the moment. Please try again later.

Stephke!

Bart, Jeroen en Sjoerd hebben heel andere dnb moves

Jurrasic Park

The Boiler Room
Apple (doesn't) Care
Nadat ik gisteren werd gebeld door het A Mac servicecenter dat mijn defecte MacBook Pro inderdaad een defecte videokaart had maar dat een programmatje van Apple zei dat mijn MacBook Pro níét onder de verlengde Apple garantie van 3 jaar viel (quote: If the NVIDIA graphics processor in your MacBook Pro has failed, or fails within three years of the original date of purchase, a repair will be done free of charge, even if your MacBook Pro is out of warranty) kon ik hem vandaag -na het betalen van € 85,- nutteloze onderzoekskosten- weer ophalen bij A Mac op de Mariaplaats in Utrecht. Bij mijn MacBook Pro zat -per ongeluk- een printout van de Apple test software:
GPT Test Log
Serial Number: W872xxxxxxG
Eligible Graphics Processor Not Found – FAILED Error Code: 2
vc: NVQ-fba5e521-02
Hoezo eligible graphics processor not found?! Ik heb toch zeker een MacBook Pro uit de problematische serie mét de NVIDIA 8600M GT? Ik besloot mijn MacBook Pro dus zelf nog eens nader te onderzoeken en heb via Voice Over (fn-cmd-F5) screen sharing en ssh aangezet. Via screen sharing kon ik mijn (voor de rest perfect werkende) MacBook Pro benaderen en in System Profiler zag ik het volgende:
Volgens de system zit er geen NVIDIA 8600M GT meer in mijn MacBook Pro, maar een Intel integrated GMA X3100? Vind je het gek dat hun diagnostische software de nvidia niet kan vinden? Nogmaals uitgebreid met Apple Care aan de telefoon gehangen, evenals A Mac, maar dat leverde wederom helemaal niets op, en dat terwijl zowel Apple’s eigen support fora vol staan met honderden (en bij macrumors zelfs een kleine twee duizend) identieke ervaringen… Erg slechte service van Apple!
Omdat ik nu niet kan werken, Apple niet thuis geeft en ik tijdelijk de Powerbook G4 (traaaag) van mijn ouders heb geleend heb ik uiteindelijk maar besloten een nieuwe MacBook Pro te bestellen (ik heb nu dan wel een erg slechte Apple service ervaring, maar ben toch Apple hooked) en een aangetekende brief naar Apple te sturen waarbij ik ze in gebreke stel. Je moet wat…






