I had some issues with how event.buildlink() in Coldbox is generating a URL. To understand what’s my problem let me introduce the old-fashioned way to hit a coldbox application

http://mysite.ext/index.cfm?event=user&age=30
or
http://mysite.ext/index.cfm?event=user.index&age=30

With some rewrite magic this last form can be written as

http://mysite.ext/user/index&age=30
or even
http://mysite.ext/user/index/age/30

So all four forms are behaving the same, if you apply the correct rewrite rules in your webserver and the default rules in the Coldbox router. The coldbox manual has some info on rewrites for several webservers. So in most cases the web is rewriting your url’s in such a format that coldbox will receive this:

http://mysite.ext/index.cfm?user/index/age/30

Coldbox itself is smart enough to hand this over to the router, which has some default rules enabled which translate the user/index/age/30 to:

  • event = user.index
  • age=30

So when the router detects multiple value pairs after the handler (user) and action (index) it will create variables with values for these pairs.

But what happens if we have default actions (such as index)

http://mysite.ext/user/

This will happily execute the event user/index. But now we are adding the parameters in this way

http://mysite.ext/user/age/30

Ok, this fails. Because the coldbox router is parsing it in such a way that the nonexisting user/age event is executed. Of course you can add all kind of non-default route patterns, but that’s not what this post is about. It’s about the event.buildLink function. But before we continue, take note that this will generate a valid response:

http://mysite.ext/user?age=30

So as long as I explicitly specify my query parameters, no problems at all. Now on to the event.buildLink method in coldbox, which is a very handy method to generate links. Let’s see some examples:

event.buildLink("user");
event.buildLink("user.index");
event.buildLink( to="user.index", querystring="age=30&something=else");
event.buildLink( to="user", querystring="age=30&something=else");
// results
// http://mysite.ext/user
// http://mysite.ext/user/index
// http://mysite.ext/user/index/age/30/something/else
// THIS WILL GENERATE AN ERROR
// http://mysite.ext/user/age/30/something/else

As you can see we should be very careful with generating url’s in this way. Event.buildLink will not add a default action (it is just string handling) and because the querystring params are now added in an incorrect order it will fail.

And there’s more. Let’s see this perfectly valid sample:

param rc.from_date ="";
param rc.to_date = "";
event.buildLink( 
  to="user.index", 
  querystring= {
    from_date=#rc.from_date,
    to_date=#rc.to_date#
  }
)
// totally confused now....
// http://mysite.ext/user/index/from_date//to_date/

So now the wirebox router is totally confused. It thinks from_date has a value of to_date, because it is ignoring the empty space between the two slashes //

This makes me wonder, why is event.buildLink generating a path instead of appending the query string to the rest of the url. If this would have been the result

http://mysite.ext/user/index?from_date=&to_date=

it would be totally valid, and immediately clear which are query parameters instead of part of the path. I know putting all variables in a path can be very useful for REST style interfaces, but even there there’s some kind of a rule: make everything which leads to your resource part of the path and leave all other stuff for filtering and selecting in the querystring.

In my case I solved it by creating my own buildMyLink function which is just removing the queryString argument from the original call, and appending it after the result. Please have a look at the condition where queryString can be a struct. This is part of coldbox 6.1 and if I want to use that I have to add it to my own function as well.

function buildMyLink(){
  var args = arguments;
  //process queryString param in a different way
  var queryString = arguments.queryString ?: "";
  // Query String Struct to String
  if( isStruct( queryString ) ){
    queryString = queryString.reduce( function( result, key, value ){
      result.append( "#encodeForURL( key )#=#encodeForURL( value )#" );
      return result;
    }, [] )
    .toList( "&" );
  }
  args.delete("queryString");
  return 
    "#getRequestContext().buildLink(argumentCollection=args)#?#queryString#";
}

And yes, so sorry for messing up the ampersand character in my code. I think I should fix my blog software sometime soon.