Build A Better ColdFusion App: Simple Conditionals

In really large apps, every line of processing can become critical. Every small process can add up, in terms of overall overhead. Especially in high traffic apps. Sometimes, the little things can really make a difference. Even your simple code might be...simpler. Take the following example:

view plain print about
1<cfset mySelections = "1">
2<cfif #mine# eq "0">
3    <cfset mySelections = "0">

I saw something like this in code this morning. Right off, the first thing you see is that all of the variables are unscoped, forcing the server to do a scopeCheck() to find out which scope each variable is a part of. Then there's the unnecessary bits to go along with it, like setting a variable, then checking another to reset the variable based on a condition. There's also the bit where the one variable is unnecessarily hashed. On top of all of that, variable assignments are one of those things it just makes more sense to write in CFScript. Let's take a look at how this might be improved:

view plain print about
1REQUEST.mySelections = (URL.mine) ? 1 : 0;

One line of code. Less keystrokes. Clearer references, and logic.

The "mySelections" variable, in this instance, is a part of the REQUEST scope (I upper case all variable scopes, to make them easier to spot when editing code).

In this situation, "mine" was a URL variable, param'd further up the page. We've used short-circuit boolean logic in our conditional statement. For those that don't understand that, any numeric value other than "0" is evaluated to true. If the value is "0", then it is false.

And speaking of numeric values, you don't need to quote them. Even though ColdFusion would figure it out, a quoted value is a string. Don't quote numeric, and don't quote Booleans.

We use the URL conditional inside of a ternary operator. Ternary operators were introduced in ColdFusion 9. The basics of this are, if the conditional is true, then use the value after the question mark. If the conditional is false, then use the value after the colon.

Finally, there's no need to hash the URL variable. You only need to hash variable references as part of output, or when they are used inside of quotation marks in code.

Now comes the really fun part. There really isn't any need for REQUEST.mySelections at all. Since the value or URL.mine was param'd, further up in the code, then the variable is always available here. Rather than copying that value to another variable (which takes up more memory), we can just reference URL.mine anywhere that REQUEST.mySelections was being used.

As you maintain your applications, it's always good to take some time and read through what's been written before. Refactoring little nuggets like this one, and a little testing, can eventually go a long way in preserving the life of your app and your server.

Why I Like, and Use, CFScript

Many, many moons ago, I began a journey to learn web development. Actually it was a re-acquaintance, as I had gotten side tracked for a few years in a poor business partnership, having to catch up on how much things had changed. During that time, HTML had moved for v2 to 4, CSS was the crazy new thing replacing font and center tags, Dynamic HTML was making JavaScript hot again, and the browser wars were making life impossible.

Back then I was on the LA Tech Edu JavaScript mailing list (sadly retired, years ago). This guy named Peter Paul Koch was a major contributor to the list, and just ramping up this little site called QuirksMode. Scripting, though somewhat nightmarish with the browser wars, was actually a lot of fun. My first shopping cart was completely JS based.

I have always pushed for full parity between cfscript and tags, because scripting is more natural to me, in terms of writing programmatic logic. Sure, cfoutput and cfloop make a lot of sense in the middle of html display code, but CFC's don't (typically) contain display code, being data access and model objects.

I have issues with the implementation of CFScript. I find that there are a lot of functions of similar actions with completely different naming conventions, and argument requirements, and more. I think these things need to be addressed. That said, if I have to call the function anyway, I prefer script to tags when it's outside of display. It's a more natural approach, to me. 99 times out of 100 I end up with less lines of code, and find the logic more defined, clear cut, and easier to understand.

It's not for everyone. There are many that just plain hate cfscript, and that's their prerogative. But I will continue to use cfscript, and my examples will show that, because I personally find it a better way to write code. If it's not your preference, that's fine, you're welcome to translate my examples to tags if you need to, but for me this is how I do it. (For the record, many of the newer functions are not identical, under the hood [base Java code], as their tag counterparts, and some scripted functions have been proven to have better performance than their tag counterparts. There are benchmarks for this out there, somewhere, if you feel strongly enough to go look for them. I just take it on my experience with using it.)

VFS and Zip Files...Kinda

There are several posts, out there in the wild, about using ColdFusion's built-in VFS support for working with zip files. I posted the other day (and fairly quickly removed it), about using the FileMove() function to rename a zip file and all of it's contents. What I discovered, though, was that this wasn't possible. Even though the documentation states that FileMove() and FileCopy() support the in-memory file system, I found that zip files are not included in that support. Luckily there was the CFScript Community Components project, for working with zip files within script. And, though zip files were unsupported, "ram://" was still a viable option:

   *  Rename a zip file, and the internal files as well
   *  @access private
   *  @returntype boolean
   *  @output false
  function RenameZipAndContents (required string srcPath, string destPath=ARGUMENTS.srcPath, required string srcFileRoot, required string newFileRoot) {
    // Create the zip object
    var zip = CreateObject("component", "").Init();
    // Create a temp directory in RAM
    var vfsDir = "ram:///" & ARGUMENTS.newFileRoot & "/";
    DirectoryCreate(LOCAL.vfsDir & "zip");
    // Unzip the source file to the RAM directory
    zip.unzip(destination=LOCAL.vfsDir & "zip", file=ARGUMENTS.srcPath & ARGUMENTS.srcFileRoot & ".zip");
    // Get a list of the files
    var files = DirectoryList(LOCAL.vfsDir & "zip", false, "query");
    // Loop the file list
    for (LOCAL.file in LOCAL.files) {
      // Get the file extension
      var ext = Right(, 4);
      // Rename the file
      FileMove(LOCAL.vfsDir & "zip/" &, LOCAL.vfsDir & "zip/" & ARGUMENTS.newFileRoot & LOCAL.ext);
      // Zip up renamed file in new zip & ARGUMENTS.srcFileRoot & ".zip", source=LOCAL.vfsDir & "zip/" & ARGUMENTS.newFileRoot & LOCAL.ext);
      // Delete original file from zip
      zip.delete(file=ARGUMENTS.srcPath & ARGUMENTS.srcFileRoot & ".zip",;
    // Delete the temp RAM directory
    DirectoryDelete(LOCAL.vfsDir, true);
    // Rename the original zip file
    FileMove(ARGUMENTS.srcPath & ARGUMENTS.srcFileRoot & ".zip", ARGUMENTS.destPath & ARGUMENTS.newFileRoot & ".zip");

return true; }

Yes, that's a lot of jumping through hoops, and the multiple zip operations really drag out something that should be pretty simple. I'm not sure why zip isn't supported by these operations. Maybe some future revision will correct that.

2011 In Review, and the View for 2012

My, how time flies when you're having fun! It seems like only yesterday that I was welcoming in 2011, and now we're here a year later. So many things have happened in the last year, and rereading that post I see that I missed some things I should've done, but let's take a look in retrospect.

I wrote 27 blog posts in 2011. This is nothing, compared to guys like Ray Camden or Ben Nadel, but for me it was quite a bit, especially when you consider that between March and August I released only one post. Very early in the year, I began a series on creatingmany sites with one codebase. In the process, the series has evolved to contain a fairly detailed primer in ColdFusion application architecture (because of it's importance to this process), has currently spanned 8 separate posts, and was even referenced by Sean Corfield in his great presentations on the same topic. 2012 will see the completion of that CF app discussion, and gradually move it back to the MSOC topic itself, as there is still a ton to talk about there, and a lot of interest in the topic. I also began a series on the jqGrid JQuery plugin. jqGrid is another Data Grid visualization tool (I have now written about three, including Ext JS and DataTables), and is a clear choice for those who must use JQuery. (To be fair, JQueryUI is working on a grid component, but they are still behind the curve, and way behind Sencha.) Finally, one common thread seen in the majority of my posts, is how much I've embraced cfscript. I wrote a lot of things, on a variety of topics, but most of my code examples were pure scripted examples.

Now let's talk about some other departures from the norm for Cutter.

You did not see a lot of content around Ext JS. In fact, I stopped writing Ext JS books. This is not, in any way, a reflection on my feelings for Ext JS. I still believe that Sencha has built one of the best client-side libraries for web application development. In evaluating the overall ROI, I realized that I was writing more for the community than the money, and that my reach was greater through my blog, while giving me flexibility on when and what I deliver from a content standpoint. That said, I didn't have a single project this year that used Ext JS, so had very little time to experiment and write about it. This year, I'm going to expand on a personal project, and get back to some great Ext JS content for my readers.

You, also, did not see me speak at any conferences this past year. Nor at any user group meetings. This wasn't because I didn't want to, but because of some more personal reasons. I'm not going to go in depth here, other than to say that I've had some long standing health issues that required me to have some surgery done on my mouth. (Mark Drew is making a joke right now...) Aside from the fact that this has been very costly (chewing up any conference/travel budget), it also meant that my speech has been affected for a good part of the year. Thankfully this experience is (mostly) over now, and I hope to get back to presenting sometime this year. Any user group looking for a speaker this year, please contact me through the Contact link on this blog.

One group I am hoping to speak to this year is the Northeast Florida CFUG. I have to call Mike back, but he's looking to get things kicked off again, and I want to help it be successful. If you're in or around the Jacksonville area, make sure to keep an eye on the site for upcoming events.

One other thing I'm looking to do is to migrate all of my projects into GitHub. I've been using Git at work, and I am loving it, and I think combining GitHub with RIAForge is a great way to promote the terrific technologies we work with every day. I will make the time, I promise.

This comes to the final discussion of this post, Adobe. I again had the pleasure of being an Adobe Community Professional this past year. Due to my health issues, I didn't get to do everything I would've wanted to this year, but I've tried to be a good supporter. There are some fabulous things coming in ColdFusion Zeus and, by extension, to ColdFusion Builder as well. There has been a lot of hub-bub over Adobe's communications flubs regarding Flash, mobile, and Flex. I've avoided much of the discussion, other than to say "be patient and watch". Flash isn't going away, and neither is Flex. HTML 5 is a beautiful thing, if you aren't developing desktop browser applications (i.e. You're only writing for mobile/tablet development). There, that is my whole contribution to that discussion. Give it a rest.

2012 will be a fantastic year. Set yourself some clear, definable goals. Break them down, step by step, and write the steps down on paper. Each successive step, print out in large letters and place it somewhere where you will see it each and every day. Set yourself up to succeed, and you will. Have a great year, everyone, and I can't wait to hear what you have planned for 2012.

Why I Like

With the position I'm in now, I've had the joy of finally being able to write (full time) for ColdFusion 9. There were many great improvements and updates within CF 9, but two of mine were the increased parity to tags, and the ability to write fully scripted components. I was one of the first to post a fully scripted Application.cfc. I've pushed most of my code examples, since then, using cfscript. I wrote a brief primer on extending ColdFusion, by writing custom components for use server wide. I use cfscript constantly in my day to day development, and used to use as much as I could before CF 9. And yet three times, in as many weeks, people have asked me "Why?"

I've gotten this question before. Most, who've mentioned it, don't really mind it one way or another, they just wanted to know why it's my preference. Others were adamantly opposed to it, because it is so different in style from other scripted languages. There have been efforts to improve cfscript, and even calls to deprecate cfscript, in it's current form, and reinvent it (or switch wholly to server side ActionScript). Last month, Adam Tuttle created the CFCommunity, on GitHub, for open source contributions in filling in the few remaining gaps in cfscript through custom component objects. But, why do I like it?

I've been a ColdFusion developer now for over a decade. Like every other programming language I've worked with I was self taught, and came to CF after learning Visual Basic (6), some C++, and InstallScript (which is what Install Shield used to use for custom installers). Aside from progressively improving my knowledge and usage of HTML/XHTML and CSS, I was primarily a 'server side' developer for much of the beginning of my CF career. I'm a programmer, not a designer, so I wrote (hopefully) good logic. The browser wars had soured me on JavaScript, so I avoided it when I could. Luckily libraries like ExtJS and JQuery came on the scene. By this point my grasp of ColdFusion (and programming, in general) was much more advanced, so putting effort into interface development and usability was a good step. This also meant heavily re-aquainting myself with JavaScript, and remembering what a joy scripting was.

After a while, I found that I was writing more JavaScript than ColdFusion, and focusing more time on client-side development than on the server. Complex server-side code had become second nature, and came rather quickly, whereas client-side code was still relatively new and challenging. I was writing two to three lines of client-side code for every line of ColdFusion (at least). In the process, I've also had to adjust how I approach some of my ColdFusion development, to accomodate ajax interface development. It's been a lot of fun.

While I love ColdFusion, writing complex logic in JavaScript again had shown me how much more rapid scripting was. While ColdFusion's tag syntax is fantastic for transitioning html developer's, and great for generating client-side code (cfoutput and cfloop stuff), straight scripting syntax for logic is much faster, trimmer, and concise. I will agree with those who say that cfscript needs an overhaul. Function naming is inconsistent, as is treatment of arguments and argument naming. All of that aside, it's still quick, trim, and powerful, and the Java behind the functions is (sometimes) more current, and better performant, than the Java behind their tag counterparts.

I wish Adam luck with his CFCommunity project on GitHub. I think it will be a great thing for the ColdFusion community, as a whole, if it can gain steam, and hope to make some time to help contribute to it myself. I also hope that, in some future version of ColdFusion, that the Adobe engineering team will do a thorough review of cfscript and work to create a scripted ColdFusion that the community, as a whole, can agree upon as a new standard. In the meantime, I'm happy to use what we have, as it's still very performant, and a lot of fun!

A Scripted Query Param & Whitespace Gotcha

I discovered this one a while back, but forgot to write a post on it. Did you realize that formatting queries could affect the execution of scripted queries? Consider the following function:

view plain print about
2 *    FUNCTION login
3 *    A function to validate a user login, and return a struct of user details
4 *
5 *    @access public
6 *    @returnType struct
7 *    @output true
8 */

9function login(required struct formScope) {
10    var retVal = {"success"=true,"message"="","data"=""};
11    var sql = "SELECT     u.userID,
12                        u.username,
13                        u.password,
14                        u.dateCreated,
15                        u.lastUpdated
16                FROM    users u
17                WHERE     u.username = :username
18                AND        u.password = :password
19                AND        u.isActive = 1";
20    var q = new Query(datasource = VARIABLES.instance.dsn,sql = sql);
21    q.addParam(name = "username", value = ARGUMENTS.formScope.username, cfsqltype = "cf_sql_varchar");
22    q.addParam(name = "password", value = ARGUMENTS.formScope.password, cfsqltype = "cf_sql_varchar");
24    try {
25 = LOCAL.q.execute().getResult();
26        // Check for no recordCount, and throw a 'no records' exception
27        if(!{
28            throw(type="MH-Custom",errorCode="001",message="The user " & ARGUMENTS.formScope.username & " could not be authenticated. Please check your credentials and try again.");
29        }
30    } catch (any excpt) {
31        retVal.success = false;
32        if(excpt.type eq "MH-Custom"){
33            retVal.message = excpt.message;
34        } else {
35            // TODO: Add admin notification in here somewhere
36            retVal.message = "There was a problem executing this request, and our administrators have been notified";
37            WriteDump(var=VARIABLES.instance,label="instance");
38        }
39        if(StructKeyExists(excpt,"errorCode") AND Len(excpt.errorCode)){
40         retVal["errorCode"] = excpt.errorCode;
41        }
42    }
43    return retVal;

It's a pretty basic function, with a query to check submitted form fields against the database. Right? So, why would it error? "Error? What error?" Yes, it errors. Here's the code for a basic call, along with a dump to output that to the page:

view plain print about
2    REQUEST.testObj = CreateObject("component","com.multihome.core.Security").init(DSN='multihome');
3    REQUEST.test = REQUEST.testObj.login({username='admin',password='admin'});
4    WriteDump(var=REQUEST.test);

Dumping that result shows you the error coming through:

CFDump 1

So, to get at the root of this I had to comment out all of my try/catch work:

CFDump 2

Whoops! Forgot my onError handler. OK, I'll comment that out. Here we go! Now we get to the meat of it (the dump was the same, but sometimes you just want to see the raw error):

view plain print about
1Error Executing Database Query
3Parameter 'username AND u.password' not found in the list of parameters specified
5SQL: SELECT u.userID, u.username, u.password, u.dateCreated, u.lastUpdated FROM users u WHERE u.username = :username AND u.password = :password AND u.isActive = 1
7The error occurred in C:\JRun4\servers\cfusion\cfusion-ear\cfusion-war\WEB-INF\cfusion\CustomTags\com\adobe\coldfusion\query.cfc: line 108
8Called from C:\JRun4\servers\cfusion\cfusion-ear\cfusion-war\WEB-INF\cfusion\CustomTags\com\adobe\coldfusion\query.cfc: line 137
9Called from C:\JRun4\servers\cfusion\cfusion-ear\cfusion-war\WEB-INF\cfusion\CustomTags\com\adobe\coldfusion\query.cfc: line 472
10Called from C:\JRun4\servers\cfusion\cfusion-ear\cfusion-war\WEB-INF\cfusion\CustomTags\com\adobe\coldfusion\query.cfc: line 605
11Called from C:\Inetpub\com\multihome\core\Security.cfc: line 68
12Called from C:\Inetpub\wwwroot\multihome\index.cfm: line 21
13Called from C:\Inetpub\wwwroot\multihome\application.cfc: line 228

Did you get anything out of that? Neither did I. The first thing I did was go review the scripted 'new Query()' documentation on the Adobe site. That didn't help. According to the documentation, everything appears to be fine. The next thing I did was look at other examples out there. Yep, still good. Next, I started comparing to other instances of code that I know to work. Wait a minute....Look at this:

SQL code with whitespace characters

See anything odd? Yes, I show whitespace characters in my editor (ColdFusion Builder). Pretty easy to do. Just go to your preferences and change it: go to Window | Preferences | General | Editors | Text Editors, and select Show whitespace characters. What you see in this picture are tabs, spaces, and End of Line markers. "OK, so what?" Well, here's where it gets strange. Let's take our original query, and put it all on a single line:

view plain print about
1var sql = "SELECT u.userID, u.username, u.password, u.dateCreated, u.lastUpdated FROM users u WHERE u.username = :username AND u.password = :password AND u.isActive = 1 ";
2var q = new Query(datasource = VARIABLES.instance.dsn,sql = sql);
3q.addParam(name = "username", value = ARGUMENTS.formScope.username, cfsqltype = "cf_sql_varchar");
4q.addParam(name = "password", value = ARGUMENTS.formScope.password, cfsqltype = "cf_sql_varchar");

If you run this, all is well:

CFDump 3

As you can see, everything works fine now. But, when I format my SQL for readability again, I again get the error. When I went back, and looked at examples that worked (in my editor) I discovered that lines following lines with params were directly preceded with one or more spaces. So, just to test, I added a single space right before those lines that followed lines referencing params:

view plain print about
1var sql = "SELECT     u.userID,
2                    u.username,
3                    u.password,
4                    u.dateCreated,
5                    u.lastUpdated
6            FROM    users u
7            WHERE    u.username = :username
8             AND    u.password = :password
9             AND    u.isActive = 1";
Code with adjust whitespace

This took care of it. My error went away, and my query executed properly, and my query was still formatted for readability. After another round of the great Tabs vs Spaces debate at work I had to change my editor's default preferences back, which is what caused/highlighted this issue. (I told you guys we needed to stick with 4 spaces ;) Maybe it's a bug in the SQL parser, or there's a method to the madness, but adding that single space before those lines is all that's required to get back on track.

Scripted Noob: Queries (and Issues)

OK, I'm not a noob. Not even with cfscript. In fact, I love cfscript, and prefer to script as much as I can. ColdFusion 9 created a much greater degree of parity between cftags and cfscript. Unfortunately, the events of the past year and a half have left me with few opportunities to work on ColdFusion 9, so I'm playing catch-up on some of these great new enhancements. I did script the application.cfc back in October of 2009, but aside from that I was buried in writing a book, new job responsibilities, and more. I'm working to write all future ColdFusion examples in as much script as possible, but I still hit the occassional hurdle and ask for help.

So, while writing my examples for my last post, I kept hitting a snag while scripting a query. After banging my head on the wall for a while, I finally pinged the ColdFusionJedi himself for assistance. Ray probably though I was off my nutter, having never scripted a query, but we did run into something worth talking about.

First, I've gotten into a habit of scoping querynames. Why? If you didn't (in ColdFusion 8 or earlier) they were part of the VARIABLES scope. This can give you unintentional results, if you aren't careful, so I'd gotten into scoping querynames.

view plain print about
1<cffunction name="getSiteId" output="false" access="public" returntype="struct">
2     <cfargument name="cgiScope" required="true" type="struct" />
3 <cfset var LOCAL = StructNew() />
4 <cfset LOCAL.retVal = StructNew() />
5 <cfset LOCAL.retVal['success'] = true />
6 <cftry>
7     <cfquery name="LOCAL.qSiteId" datasource="#VARIABLES.instance.dsn#">
8     SELECT    siteId
9 FROM    sites
10 WHERE    urlAddress = <cfqueryparam cfsqltype="cf_sql_varchar" value="#ARGUMENTS.cgiScope.server_name#" />
11 </cfquery>
12 <cfif LOCAL.qSiteId.recordCount>
13     <cfset LOCAL.retVal['result'] = LOCAL.qSiteId />
14 <cfelse>
15     <cfthrow type="My_Custom" errorcode="001" message="No siteId was found for this domain." />
16 </cfif>
17 <cfcatch type="any">
18     <cfset LOCAL.retVal['success'] = false />
19 <cfset LOCAL.retVal['message'] = CFCATCH.message />
20 <!--- Any other error handling --->
21 </cfcatch>
22 </cftry>
23 <cfreturn LOCAL.retVal />
24 </cffunction>

We'll come back to that in just a minute. I also like to pass argumentCollections into functions. Maybe it's just me, but it's something I do. So, I created a collection to pass into the constructor of a new Query.

view plain print about
3 /**
4 * FUNCTION getSiteIdByUrl
5 * @access public
6 * @returnType struct
7 * @output false
8 */

9 function getSiteIdByUrl(required struct cgiScope) {
10 LOCAL.retVal = {};
11 LOCAL.retVal['success'] = true;
12 LOCAL.qPrms = {};
13 = "LOCAL.qSiteId";
14 LOCAL.qPrms.datasource = VARIABLES.instance.dsn;
15 LOCAL.qPrms.sql = "SELECT siteId
16 FROM sites
17 WHERE UrlAddress = :urlAddress";
18 LOCAL.q = new Query(argumentCollection = LOCAL.qPrms);
19 LOCAL.q.addParam(name = "urlAddress", value = ARGUMENTS.cgiScope.urlAddress,
20 cfsqltype = "cf_sql_varchar");
21         try {
22     LOCAL.retVal['queryResult'] = LOCAL.q.execute();
23 if (!LOCAL.retVal.queryResult.recordCount) {
24     throw (type = "My_Custom",errorcode = "001",message = "No siteId was found for this domain.");
25 }
26 } catch (any excpt) {
27     LOCAL.retVal['success'] = false;
28 LOCAL.retVal['message'] = excpt.message;
29 // other error handling here
30 }
31 return LOCAL.retVal;
32 }

Alright, it seems to look OK, right? So why is it erroring? Well, Ray first told me to break it down some. Make it simple, remove the param, stuff like that. No dice. Then he said use getResult() after the execute statement. Uh huh. Then I decided to take the LOCAL scope out (it's local anyway, right?) Still no dice. Finally Ray said "Don't use argumentCollection." Use the set methods instead. Bam! It worked! I thanked Ray for the assist, and went back to recreating the full function.

Whoops! Not working again. Could not find retVal.qSiteId in LOCAL (or something like that). Now what? But then I saw I had put the LOCAL scope back on the query name. Took it off, and it worked like a charm.

view plain print about
3 /**
4 * FUNCTION getSiteIdByUrl
5 * @access public
6 * @returnType struct
7 * @output false
8 */

9 function getSiteIdByUrl(required struct cgiScope) {
10 var retVal = {};
11 retVal['success'] = true;
12 var sql = "";
13 var q = new Query();
14 q.setName("qSiteId");
15 q.setDatasource(VARIABLES.instance.dsn);
16 sql = "SELECT siteId
17 FROM sites
18 WHERE UrlAddress = :urlAddress";
19 q.setSQL(sql);
20 q.addParam(name = "urlAddress", value = ARGUMENTS.cgiScope.urlAddress,
21 cfsqltype = "cf_sql_varchar");
22         try {
23     retVal['queryResult'] = q.execute().getResult();
24 if (!retVal.queryResult.recordCount) {
25     throw(type = "My_Custom",errorcode = "001",message = "No siteId was found for this domain.");
26 }
27 } catch (any excpt) {
28     retVal['success'] = false;
29 retVal['message'] = excpt.message;
30 // other error handling here
31 }
32 return retVal;
33 }

Now, I should go back and test argumentCollection without the LOCAL scope on the query name, but for now I'm just happy that it works. I really do love scripting, and see where this really brings ColdFusion (once again) to another level as a language choice.