Framework One AJAX Method (FW/1)

NOTE: This is now obsolete. Use the method renderData() instead.

I really haven’t found a method I like that returns JSON via FW/1 until I saw a presentation by Steven Neiland the other day. Basically you have a few choices:

  1. Call a service directly: This leaves you w/o any FW/1 features and the service is not initialized.
  2. Create views that return JSON: This can become tedious and seems unnecessary, but FW/1 features are available
  3. Have onMissingView handle it: Views do not need to be created and FW/1 features are available

My approach uses #3.

Example jQuery AJAX call:

$.ajax({
	url: 'index.cfm?action=products.attributesAJAX',
	type: 'GET',
	dataType: 'json',
	data: {parentPartNumber: $('#productForm').data( 'parentpartnumber' )}
});

You’ll notice it requests the section “products” and the action “attributesAJAX”. We will not create the view for the action, but we will create the controller method for it.

component {

	function init( FW ) {
		variables.FW = FW;
		return this;
	}

	// AJAX
	void function attributesAJAX( required struct RC ) {
		FW.service( 'productService.getProductAttributes', 'data', { parentPartNumber = RC.parentPartNumber } );
	}

}

To return JSON data w/o creating a view, we let the Application.cfc’s onMissingView method handle the return:

function onMissingView( required struct RC) {
	// if a data key exists, assume this is for AJAX and render as JSON
	if ( structKeyExists( RC, "data" ) ) {
		request.layout = false; // turn off default layout
		new services.utility().showDebugOutput( false );
		getPageContext().getResponse().getResponse().setContentType('application/json');
		return serializeJSON( RC.data ); // convert data to JSON
	} else {
		return view( 'main/pageNotFound' ); // set view to the missing page message
	}
}

You’ll notice that if the view’s missing it looks for the RC.data value. If it exists it assumes you want to return JSON back. Otherwise it will call the “pageNotFound” view (which you need to create). Note how the layout is turned off, it calls a method to turn off debugging (for development), and sets the content type to JSON.

This is the utility I created to turn off debugging:

<cfcomponent output="false">

	<cffunction name="showDebugOutput" output="false" returntype="void">
		<cfargument name="switch" default="false" type="boolean">
		<cfsetting showdebugoutput="#arguments.switch#">
	</cffunction>

</cfcomponent>

Returning Distinct ColdFusion Entities

Take this code example:

return ORMExecuteQuery(
	'FROM user U
	JOIN U.stateLicenses
	WHERE U.company.id IN (:companyIDs)'
	,
	{
		companyIDs = arguments.companyIDs
	},
	false,
	{ ignorecase = true }
);

Each user can have multiple state licenses, thus what we get back here is an array of array of entities that have duplicated users.

Lets take a couple of steps to fix this.

1. Change “JOIN U.stateLicenses” to “JOIN FETCH U.stateLicenses”. This will give us an array to work with instead of an array of an array. It also brings back the licenses right away. I didn’t investigate why this is though.
2. Add “SELECT DISTINCT U” to the beginning of the HQL. This will return distinct user records with an array of licenses. This fixes our duplicate users situation.

The modified working code is:

return ORMExecuteQuery(
	'SELECT DISTINCT U
	FROM user U
	JOIN FETCH U.stateLicenses
	WHERE U.company.id IN (:companyIDs)'
	,
	{
		companyIDs = arguments.companyIDs
	},
	false,
	{ ignorecase = true }
);

#hql

FW/1 ColdSpring Services That Rely On The Prior

In FW/1 you have the variables.fw.service method to work with in your controllers. This method will run your service’s method and return the results to a key inside the RC scope. Keep in mind, however, these service calls are queued up and not run until the view is called. Why this is done I have no idea, but I’m sure there’s a reason.

Because they are queued, something like this would return a variable not defined error:

variables.fw.service ( 'users.get', 'userRecord' );
variables.fw.service ( 'geographic.getStatesByCountry', 'states', { userID = userRecord.getUserID() );

So after spending some time reading through threads on Google Groups, I determined the old fashioned way is the best way. However there’s a nice feature if you have a bean service defined such as ColdSpring. This feature implicitly sets a property named the same as a defined bean in your XML configuration. For example:

component accessors = true {
  property usersService;

  function init ( FW ) {
    variables.fw = arguments.fw;
    return this;
  }

  void function default ( RC ) {
    RC.performerRecord = getUsersService().get ( ID = 1 );
    variables.fw.service ( 'geographic.getStatesByCountry', 'states', { userID = userRecord.getUserID() );
  }
}

You don’t need use the fw.service on the last call, but I left it in there … just because.

What happens is FW/1 will look to see if each property defined matches a bean definition. If found it automatically sets the property to the bean. Otherwise you’d normally put something like this into your init:

setUsersService ( application.beanfactory.getBean ( "users" ) );

FW/1 and Service Beans in ColdSpring

Recently I started switching from a Model-Glue implementation with ColdSpring to FW/1 (Framework One) with ColdSpring on a project of mine.

An example service bean definition looks like this:

<bean id="geographic" class="geographicService" />

With the new FW/1 code, I tried calling a method in this service by using:

variables.fw.service ( 'geographic.getStatesByCountry', 'states' );

But ended up with this error:

Service ‘geographic.getStatesByCountry’ does not exist. To have the execution of this service be conditional based upon its existence, pass in a third parameter of ‘false’.

After hours of debugging, my team member finally found a solution; change the bean id from geographic to geographicService. Apparently FW/1 automatically appends “Service” to the bean ID it references so that it can cache services and controllers that way. This appears to be lacking in the FW/1 documentation, or at least clearly.

So the fix is:

<bean id="geographicService" class="geographicService" />

#beans, #coldfusion-2, #coldspring, #framework, #fw1

Console Logging of ColdFusion ORM SQL

If you read my post on getting the ColdFusion Builder Console working and use ORM, you may have run into some further questions.

When generating ORM, it’s wise to monitor what SQL queries Hibernate is generating for you, both for performance and debugging reasons. (Wasn’t ORM supposed to make my life easier?).

To start logging ORM’s SQL set this property in your application.cfc:

<cfset this.ormsettings.logsql="true">

You’ll may notice however that the default configuration will not show DDL queries used for creating or updating tables nor will it show the parametrized values (just a ?).

To enable these things look at <cf_home>\lib\log4j.properties (in my case it’s C:\ColdFusion9\lib\log4j.properties).

To enable logging of parametrized values uncomment and change the value for log4j.logger.org.hibernate.type to look like this:

log4j.logger.org.hibernate.type=DEBUG

It seems like a little overkill on what this ends up returning because not only will it return your parametrized values but also what each column returns. I wish I could disable the latter.

To enable logging of exports and updates (DDL) uncomment and change the value for log4j.logger.org.hibernate.tool.hbm2ddl to look like this:

log4j.logger.org.hibernate.tool.hbm2ddl=DEBUG, HIBERNATECONSOLE

I placed an example snippet below. Thanks to Rupesh Kumar for providing this information.

#sql-queries

SQL to ColdFusion ORMType Reference

I have not been able to find a good reference chart out there that maps SQL Data Types to ColdFusion ORM Data Types. It’s always really been my best guess. So I’m going to start a reference chart here that as I figure it out I’ll update. If you have any input on this please comment and I will update. Thanks!

ORMType SQL MySQL
big_decimal DECIMAL, MONEY DECIMAL
binary BINARY, VARBINARY TINYBLOB
blob TINYBLOB
Boolean [SMALLINT], BIT BIT
clob LONGTEXT
date DATE DATE
double DOUBLE, MONEY, NUMERIC DOUBLE
character, char CHAR
float REAL, FLOAT FLOAT
integer, int INT INT
long BIGINT BIGINT
serializable TINYBLOB
short SMALLINT SMALLINT
string CHAR, NCHAR, VARCHAR, NVARCHAR VARCHAR
text TEXT, NTEXT LONGTEXT
timestamp DATETIME, SMALLDATETIME, TIMESTAMP DATETIME
true_false CHAR
yes_no CHAR

Notes on Installing Local ColdFusion 10 Beta, ColdFusion Builder 2.0.1 Beta and IIS 7

I recently did a clean install with ColdFusion 10 Beta and ColdFusion Builder 2.0.1 Beta on a Windows 7 SP1 64-bit machine. (This is for a local development environment)

IIS

After installing IIS 7 with defaults and turning on ISASPI filters, I noticed ColdFusion would not initialize. After reading through some notes be sure to turn on these options for IIS 7:

  • .NET Extensibility
  • ASP.NET
  • CGI
  • ISAPI Extensions
  • ISAPI Filters

Web Root

I noticed when installing ColdFusion, the option for where the web root is located has been removed. It’s kind of complicated, so I’ll give you an example of how I changed mine to c:\wwwroot.

  1. I pointed my default IIS site to c:\wwwroot. This is probably not necessary as long as you have another site setup with virtual directory for /CFIDE (C:\wwwroot\CFIDE) and /jakarta (C:\ColdFusion10\config\wsconfig\1). Notice the new jakarta requirement in addition to the standard CFIDE alias.
  2. Copy (or perhaps move) the contents of “C:\ColdFusion10\cfusion\wwwroot” to “C:\wwwroot”. This should include the folders CFIDE and WEB-INF.
  3. Edit the file “C:\ColdFusion10\cfusion\runtime\conf\server.xml”
  4. Copy the “<Context…” open and close element that is currently commented out to the next line uncommented.
  5. You will need to change the docBase to your new webroot, the WorkDir to the absolute path, and the aliases for CFIDE and WEB-INF. Why the aliases are needed in both IIS and this config you’ve got me. But if you leave them out you’ll end up with an error from Apache. Not sure what this is about yet.
    <Context path=”/” docBase=”C:\wwwroot” WorkDir=”C:\ColdFusion10\cfusion\runtime\conf\Catalina\localhost\tmp” aliases=”/CFIDE=C:\wwwroot\CFIDE,/WEB-INF=C:\wwwroot\WEB-INF” ></Context>
  6. Restart your ColdFusion server service.
Thanks to Ryan Anklam’s Blog for providing me with a starting point on this.

ColdFusion Builder 2.0.1 Beta

After I installed ColdFusion Builder 2.0.1 Beta (running as Administrator), I attempted to add the server to the server view. However I ran into the issue where only ColdFusion version 9 was available. After a post to the discussion groups I learned:

  • The Application Server setting should be “CF+Tomcat Bundle” instead of Jrun.
Also there is a difference for enabling the console view on ColdFusion Builder.
  • Be sure to install the ColdFusion Jetty Service during the ColdFusion 10 install to be able to start and stop your ColdFusion service.
  • You no longer need to set the ColdFusion Application Server service to manual.
  • ColdFusion Builder 2.0.1 will now control the service instead of its own instance.
  • Console view works with the service started instead of its own instance.

I still have a lot of playing around to do with this combination, but I hope this helps a few of you out in the mean time.

#coldfusion-application-server, #jetty, #jrun, #server-service, #server-view, #web-root

jQuery Select All Checkboxes

I needed to come up with a generic way of checking all checkboxes that could be reused using jQuery. At first I thought about using the data- attribute to define which class of checkboxes to check. But then found a great way to just check all checkboxes that are located in their element container. (The .on method required jQuery 1.7)

HTML Example Code:

<div class="control-group">
<input type="checkbox" class="selAllChksInGroup"> All
<input type="checkbox" value="NE"> Nebraska
<input type="checkbox" value="FL"> Florida
</div>

JavaScript Code:

$(document).ready(function(){

$("input[type=checkbox].selAllChksInGroup").on("click.chkAll", function( event ){
    $(this).parents('.control-group:eq(0)').find(':checkbox').prop('checked', this.checked);
});

});

Camel Case for Two Letter Acronyms

Here’s another “for later reference”…

Microsoft’s rules for .NET Framework 1.1 Abbreviations consist of:

  • Do not use abbreviations or contractions as parts of identifier names. For example, use GetWindow instead of GetWin.
  • Do not use acronyms that are not generally accepted in the computing field.
  • Where appropriate, use well-known acronyms to replace lengthy phrase names. For example, use UI for User Interface and OLAP for On-line Analytical Processing.
  • When using acronyms, use Pascal case or camel case for acronyms more than two characters long. For example, use HtmlButton or htmlButton. However, you should capitalize acronyms that consist of only two characters, such as System.IO instead of System.Io.
  • Do not use abbreviations in identifiers or parameter names. If you must use abbreviations, use camel case for abbreviations that consist of more than two characters, even if this contradicts the standard abbreviation of the word.

These appear to be good guidelines to follow for ColdFusion as well. I was going after casing for two letter acronyms in this case.

REF: http://msdn.microsoft.com/en-us/library/141e06ef(v=vs.71).aspx

Looping Over Arrays in ColdFusion 9.01 (CFScript)

This is pretty much a “note to self”… but in ColdFusion 9.01 you can now loop over arrays using the  for – in loop while in cfscript.

Syntax is:

for(item in array) {
    doSomething(item);
}

#array, #arrays, #coldfusion-2