Skip to content

Developing Applications Manual

sebduggan edited this page Aug 19, 2010 · 26 revisions

Developing Applications with FW/1

FW/1 is intended to allow you to quickly build applications with the minimum of overhead and interference from the framework itself. The convention-based approach means that you can quickly put together an outline of your site or application merely by creating folders and files in the views folder. As you are ready to start adding business logic to the application, you can add controllers and/or services as needed to implement the validation and data processing.

Basic Application Structure

FW/1 applications must have an Application.cfc that extends org.corfield.framework and an empty index.cfm as well as at least one view (under the views folder). Typical applications will also have folders for controllers, services and layouts. The folders may be in the same directory as Application.cfc / index.cfm or may be in a directory accessible via a mapping (or some other path under the webroot). If the folders are not in the same directory as Application.cfc / index.cfm, then variables.framework.base must be set in Application.cfc to identify the location of those folders.

Note: because Application.cfc must extend the FW/1 framework.cfc, you need a mapping in the CF Administrator. An alternative approach is to simply copy framework.cfc to your application’s folder (containing Application.cfc and index.cfm) and then have Application.cfc extend framework instead. This requires no mapping – but means that you have multiple copies of the framework instead of a single, central copy.

The views folder contains a subfolder for each section of the site, each section’s subfolder containing individual view files (pages or page fragments) used within that section. All view folders and filenames must be all lowercase.

The layouts folder may contain subfolders for sections within the site, which in turn contain layouts for specific views or a default layout for a section. The layouts folder may also contain general layouts for each section and/or a default layout for the entire site. All layout folders and filenames must be all lowercase.

The controllers folder contains a CFC for each section of the site (that needs a controller!). Each CFC contains a method for each requested item in that section (where control logic is needed). Controller CFC filenames must be all lowercase.

Similarly, the services folder contains a CFC for each section of the site, with methods for each requested item – where service logic is needed. Service CFC filenames must be all lowercase.

An application may have additional web-accessible assets such as CSS, images and so on.

Views and Layouts

Views and layouts are simple CFML pages. Both views and layouts are passed a variable called rc which is the request context (containing the URL and form variables merged together). Layouts are also passed a variable called body which is the current rendered view. Both views and layouts have direct access to the full FW/1 API (see below).

The general principle behind views and layouts in FW/1 is that each request will yield a unique page that contains a core view, optionally wrapped in a section-specific layout, wrapped in a general layout for the site. In fact, layouts are more flexible than that, allowing for item-specific layouts as well as section-specific layouts. See below for more detail about layouts.

Both views and layouts may invoke other views by name, using the view() method in the FW/1 API. For example, the home page of a site might be a portal style view that aggregates the company mission with the latest news. views/home/default.cfm might therefore look like this:

<cfoutput>
  <div>#view('company/mission')#</div>
  <div>#view('news/list')#</div>
</cfoutput>

This would render the company.mission view and the news.list view.

Note: The view() method behaves like a smart include, automatically handling subsystems and providing a local scope that is private to each view, as well as the rc request context variable (through which views can communicate, if necessary). No controllers or services are executed as part of a view() call. In 1.2, Additional data may be passed to the view() method in an optional second argument, as elements of a struct that is added to the local scope.

Views and Layouts in more depth

As hinted at above, layouts may nest, with a view-specific layout, a section-specific layout and a site-wide layout. When FW/1 is asked for section.item, it looks for layouts in the following places:

  • layouts/section/item.cfm – The view-specific layout
  • layouts/section.cfm – The section-specific layout
  • layouts/default.cfm – The site-wide layout

For a given item up to three layouts may be found and executed, so the view may be wrapped in a view-specific layout, which may be wrapped in a section-specific layout, which may be wrapped in a site-wide layout. To stop the cascade, set request.layout = false; in your view-specific (or section-specific) layout. This allows you to have a view which returns plain XML or JSON, by using a view-specific layout that looks like this:

<cfoutput>#body#</cfoutput>
<cfset request.layout = false>

(although you’d probably want to set the content type header to ensure XML was handled correctly!).

By default, FW/1 selects views (and layouts) based on the action initiated, subsystem:section.item but that can be overridden in a controller by calling the setView() method to specify a new action to use for the view and layout lookup. This can be useful when several actions need to result in the same view, such as redisplaying a form when errors are present.

The view and layout CFML pages are actually executed by being included directly into the framework CFC. That’s how the view() method is made available to them. In fact, all methods in the framework CFC are directly available inside views and layouts so you can access the bean factory (if present), execute layouts and so on. It also means you need to be a little bit careful about unscoped variables inside views and layouts: a struct called local is available for you to use inside views and layouts for temporary data, such as loop variables and so on.

It would be hard to give a comprehensive list of variables available inside a view or layout but here are the important ones:

  • path – The path of the view or layout being invoked. For a given view (or layout), it will always be the same so I can’t imagine this being very useful. Strictly speaking, this is arguments.path, the value passed into the view() or layout() method.
  • body – The generated output passed into a layout that the layout should wrap up. Strictly speaking, this is arguments.body, the value passed into the layout() method.
  • rc – A shorthand for the request context (the request.context struct). If you write to the rc struct, layouts will be able to read those values so this can be a useful way to set a page title, for example (set in the view, rendered in the layout where appears).
  • local – An empty struct, created as a local scope for the view or layout.
  • framework – The FW/1 configuration structure (variables.framework in the framework CFC) which includes a number of useful values including framework.action, the name of the URL parameter that holds the action (if you’re building links, you should use the buildURL() API method which knows how to handle subsystems as well as regular section and item values in the action value). You can also write SES URLs without this variable, e.g., /index.cfm/section/item as long as your application server supports such URLs.

In addition, FW/1 uses a number of request scope variables to pass data between its various methods so it is advisable not to write to the request scope inside a view or layout. See the ReferenceManual for complete details of request scope variables used by FW/1.

It is strongly recommended to use the local struct for any variables you need to create yourself in a view or layout!

Designing Controllers

Controllers are the pounding heart of an MVC application and FW/1 provides quite a bit of flexibility in this area. The most basic convention is that when FW/1 is asked for section.item it will look for controllers/section.cfc and attempt to call the item() method on it, passing in the request context as a single argument called rc, then going on to call a service method and then render a view. This allows you to put input data validation in a controller method and massage the request context so that the service method can be called automatically.

Controllers are cached in FW/1’s application cache so controller methods need to be written with thread safety in mind (i.e., use var to declare variables properly!). If you are using a bean factory, any setXxx() methods on a controller CFC may be used by FW/1 to autowire beans from the factory into the controller when it is created. Alternatively, you can let the bean factory take care of the controller CFC’s lifecycle completely: just name the bean with a suffix of Controller and when FW/1 is asked for section.item it will ask the bean factory for sectionController . See the note about Controllers and the FW/1 API below for a caveat on this.

In addition, if you need certain actions to take place before all items in a particular section, you can define a before() method in your controller and FW/1 will automatically call it for you, before calling the item() method. This might be a good place to put a security check, to ensure a user is logged in before they can execute other actions in that section. The variable request.item contains the name of the controller method that will be called, in case you need to have exceptions on the security check (such as for a main.doLogin action that attempts to log a user in).

Similarly, if you need certain actions to take place after all items in a particular section, you can define an after() method in your controller and FW/1 will automatically call it for you, after calling the item() method and after invoking any matching service method.

Finally, a controller method can be split around a service method call by using the start and end prefixes. Since the default service method result is placed in the data element of the request context (by default), it can be useful to post-process that data and update the request context accordingly for use by the views (perhaps to filter data returned by a service or handle status returned by the service). Additional services may be queued for automatic execution using the service() method – see below.

Here is the full list of methods called automatically when FW/1 is asked for section.item :

  • controllers/section.cfc : before()
  • controllers/section.cfc : startItem()
  • controllers/section.cfc : item()
  • services/section.cfc : item()
  • (any additional service calls that were added via the service() API call)
  • controllers/section.cfc : endItem()
  • controllers/section.cfc : after()

Methods that do not exist are not called.

Using onMissingMethod() to Implement Items

FW/1 supports onMissingMethod(), i.e., if a desired method is not present but onMissingMethod() is declared, FW/1 will call the method anyway. That applies to all five potential controller methods: before, startItem , item , endItem and after. That means you must be a little careful if you implement onMissingMethod() since it will be called whenever FW/1 needs a method that isn’t already defined. I would recommend always defining before and after methods, even if they are empty, and inside onMissingMethod() decide whether you need special behavior for the start/end aspects of the item methods.

Using onMissingView() to Handle Missing Views

FW/1 provides a default onMissingView() method for Application.cfc that throws an exception (view not found). This allows you to provide your own handler for when a view is not present for a specific request. The most common usage for this is likely to be for Ajax requests, to return a JSON-encoded data value without needing an explicit view to be present. Other use cases are possible since whatever onMissingView() returns is used as the core view and, unless layouts are disabled, it will be wrapped in layouts and then displayed to the user.

Be aware that onMissingView() will be called if your application throws an exception and you have not provided a view for the default error handler (main.error – if your defaultSection is main). This can lead to exceptions being masked and instead appearing as if you have a missing view!

Taking Actions on Every Request

FW/1 provides direct support for handling a specific request’s lifecycle based on an action (either supplied explicitly or implicitly) but relies on your Application.cfc for general lifecycle events. That’s why FW/1 expects you to write per-request logic in setupRequest(), per-session logic in setupSession() and application initialization logic in setupApplication().

If you have some logic that is meant to be run on every request, the FW/1 way to implement this is to implement setupRequest() in your Application.cfc and have it retrieve the desired controller by name and run the appropriate event method, like this:

function setupRequest() {
  controller( 'security.checkAuthorization' );
}

This queues up a call to that controller at the start of the request processing, calling before(), startCheckAuthorization(), checkAuthorization(), endCheckAuthorization() and after() as appropriate, if those methods are present.

Designing Services

The convention to invoke service methods is intended to reduce the number of controller methods that simply delegate to a service method and store the result in the request context. The default service method is invoked as if you had written:

request.context.data = service.item( argumentCollection=request.context );

In other words, any declared arguments on the service method will be matched against values passed in through the URL or form scope, and the result of the call will be placed in the data element of the request context for use by the view in this request. Additional service method calls queued up by the controller will place their results in the designated elements of the request context.

Services should not know anything about the framework. When a service CFC is created by FW/1, its init() method is invoked (if present) and no arguments are passed in, unlike a controller’s init() method. Service methods should not “reach out” into the request scope to interact with FW/1, they should simply have some declared arguments, perform some operation and return some data.

Like controllers, services are cached in FW/1’s application cache so service methods need to be written with thread safety in mind (i.e., use var to declare variables properly!). If you are using a bean factory, any setXxx() methods on a service CFC may be used by FW/1 to autowire beans from the factory into the service when it is created. Alternatively, you can let the bean factory take care of the service CFC’s lifecycle completely: just name the bean with a suffix of Service and when FW/1 is asked for section.item, it will ask the bean factory for sectionService.

Using onMissingMethod() to Implement Items

FW/1 supports onMissingMethod(), i.e., if a desired method is not present but onMissingMethod() is declared, FW/1 will call the method anyway. Unlike controllers, only one method call attempt is made per request (per service) and therefore there is less risk of onMissingMethod() being called unexpectedly if you use it to implement service items.

Using Bean Factories

FW/1 supports your favorite bean factory (aka IoC container or DI factory). As long as you have a CFC that supports the following API, FW/1 will accept it as a bean factory:

  • boolean containsBean(string name) – returns true if the factory knows of the named bean
  • any getBean(string name) – returns a fully initialized bean identified by name

Telling FW/1 about your bean factory is as simple as calling setBeanFactory(myFactory) inside your Application.cfc’s setupApplication() method. The following example uses ColdSpring:

bf = createObject('component','coldspring.beans.DefaultXmlBeanFactory').init();
bf.loadBeans( expandPath('config/coldspring.xml') );
setBeanFactory(bf);

Note: If you are using subsystems, please read UsingSubsystems for details about setting up bean factories for subsystems.

When you tell FW/1 to use a bean factory, it does several things behind the scenes:

  • If the factory knows about a sectionController bean, FW/1 will use that instead of controllers/section.cfc
  • Else FW/1 will create an instance of controllers/section.cfc and attempt to call any setters that match beans from the factory (autowiring beans into the controller)
  • If the factory knows about a sectionService bean, FW/1 will use that instead of services/section.cfc
  • Else FW/1 will create an instance of services/section.cfc and attempt to call any setters that match beans from the factory (autowiring beans into the service)

The FW/1 API includes the following methods related to bean factory support:

  • hasBeanFactory() – returns true if FW/1 knows about a bean factory
  • getBeanFactory() – returns the bean factory you told FW/1 about

Since an application should know whether it is designed to use a bean factory, it is expected that only the latter method will actually be used and even then only rarely (since controllers and services are autowired and views should not need beans to do their work).

Error Handling

By default, if an exception occurs, FW/1 will attempt to run the main.error action (as if you had asked for ?action=main.error), assuming your defaultSection is main. If you change the defaultSection, that implicitly changes the default error handler to be the error item in that section. If FW/1 was processing an action when the exception occurred, the name of that action is available as request.failedAction. The default error handling action can be overridden in your Application.cfc by specifying variables.framework.error to be the name of the action to invoke when an exception occurs.

If the specified error handler does not exist or another exception occurs during execution of the error handler, FW/1 provides a very basic fallback error handler that simply displays the exception. If you want to change this behavior, you can either override the fail() method or the onError() method but I don’t intend to “support” that so the only documentation will be in the code!

Note: If you override onMissingView() and forget to define a view for the error handler, FW/1 will call onMissingView() and that will hide the original exception.

Configuring FW/1 Applications

All of the configuration for FW/1 is done through a simple structure in Application.cfc. The default behavior for the application is as if you specified this structure:

variables.framework = {
  action = 'action',
  usingSubsystems = false,
  defaultSubsystem = 'home',
  defaultSection = 'main',
  defaultItem = 'default',
  subsystemDelimiter = ':'.
  siteWideLayoutSubsystem = 'common',
  home = 'main.default', // defaultSection & '.' & defaultItem
  // or: defaultSubsystem & subsystemDelimiter & defaultSection & '.' & defaultItem
  error = 'main.error', // defaultSection & '.error'
  // or: defaultSubsystem & subsystemDelimiter & defaultSection & '.error'
  reload = 'reload',
  password = 'true',
  reloadApplicationOnEveryRequest = false,
  generateSES = false,
  SESOmitIndex = false,
  // base = omitted so that the framework calculates a sensible default
  baseURL = 'useCgiScriptName',
  // cfcbase = omitted so that the framework calculates a sensible default
  suppressImplicitService = false,
  unhandledExtensions = 'cfc',
  unhandledPaths = '/flex2gateway',
  preserveKeyURLKey = 'fw1pk',
  maxNumContextsPreserved = 10,
  cacheFileExists = false,
  applicationKey = 'org.corfield.framework'
};

The keys in the structure have the following meanings:

  • action – The URL or form variable used to specify the desired action (section.item).
  • usingSubsystems – Whether or not to use subsystems – see Using Subsystems below.
  • defaultSubsystem – If subsystems are enabled, this is the default subsystem when no action is specified in the URL or form post.
  • defaultSection – If subsystems are enabled, this is the default section within a subsystem when either no action is specified at all or just the subsystem is specified in the action. If subsystems are not enabled, this is the default section when no action is specified in the URL or form post.
  • defaultItem – The default item within a section when either no action is specified at all or just the section is specified in the action.
  • subsystemDelimiter – When subsystems are enabled, this specifies the delimiter between the subsystem name and the action in a URL or form post.
  • siteWideLayoutSubsystem – When subsystems are enabled, this specifies the subsystem that is used for the (optional) site-wide default layout.
  • home – The default action when it is not specified in the URL or form post. By default, this is defaultSection.defaultItem. If you specify home, you are overriding (and hiding) defaultSection but not defaultItem. If usingSubsystem is true, the default for home is “home:main.default”, i.e., defaultSubsystem & subsystemDelimiter & defaultSection & ‘.’ & defaultItem.
  • error – The action to use if an exception occurs. By default this is defaultSection.error.
  • reload – The URL variable used to force FW/1 to reload its application cache and re-execute setupApplication().
  • password – The value of the reload URL variable that must be specified, e.g., ?reload=true is the default but you could specify reload = ‘refresh’, password = ‘fw1’ and then specifying ?refresh=fw1 would cause a reload.
  • reloadApplicationOnEveryRequest – If this is set to true then FW/1 behaves as if you specified the reload URL variable on every request, i.e., at the start of each request, the controller/service cache is cleared and setupApplication() is executed.
  • generateSES – If true, causes redirect() and buildURL() to generate SES-style URLs with items separated by / (and the path info in the URL will begin /section/item rather than ?action=section.item – see the Reference Manual for more details).
  • SESOmitIndex – If SES URLs are enabled and this is true, will attempt to omit the base filename in the path when constructing URLs in buildURL() and redirect() which will generally omit /index.cfm from the start of the URL. Again, see the Reference Manual for more details.
  • base – Provide this if the application itself is not in the same directory as Application.cfc and index.cfm. It should be the relative path to the application from the Application.cfc file.
  • baseURL – Normally, redirect() and buildURL() default to using CGI.SCRIPT_NAME as the basis for the URL they construct. This is the right choice for most applications but there are times when the base URL used for your application could be different. In FW/1 1.2, you can also specify baseURL = “useRequestURI” and instead of CGI.SCRIPT_NAME, the result of getPageContext().getRequest().getRequestURI() will be used to construct URLs. This is the right choice for FW/1 applications embedded inside Mura.
  • cfcbase – Provide this if the controllers and services folders are not in the same folder as the application. It is used as the dotted-path prefix for controller and service CFCs, e.g., if cfcbase = ‘com.myapp’ then a controller would be com.myapp.controllers.MyController.
  • suppressImplicitService – Use this option to stop FW/1 automatically invoking a service that matches section.item in the request. New in 1.2
  • unhandledExtensions – A list of file extensions that FW/1 should not handle. By default, just requests for some.cfc are not handled by FW/1.
  • unhandledPaths – A list of file paths that FW/1 should not handle. By default, just requests for /flex2gateway are not handled by FW/1. If you specify a directory path, requests for any files in that directory are then not handled by FW/1. For example, unhandledPaths = ‘/flex2gateway,/404.cfm,/api’ will cause FW/1 to not handle requests from Flex, requests for the 404.cfm page and any requests for files in the /api folder.
  • preserveKeyURLKey – In order to support multiple, concurrent flash scope uses – across redirects – for a single user, such as when they have multiple browser windows open, this value is used as a URL key that identifies which flash context should be restored for that browser window. If that doesn’t make sense, don’t worry about it – it’s magic! This value just needs to be something unique that won’t clash with any of your own URL variables. As of FW/1 1.2 this will be ignored if you set maxNumContextsPreserved to 1 because with only one context, FW/1 will not use a URL variable to track flash scope across redirects.
  • maxNumContextsPreserved – If you expect users to have more than 10 browser windows open at the same time, you’ll want to set this value higher. I know, Ryan was very thorough when he implemented multiple flash contexts! As of FW/1 1.2 setting maxNumContextsPreserved to 1 will prevent the URL key from being used for redirects (since FW/1 will not need to track multiple flash contexts).
  • cacheFileExists – If you are running on a system where disk access is slow – or you simply want to avoid several calls to fileExists() during requests for performance – you can set this to true and FW/1 will cache all its calls to fileExists(). Be aware that if the result of fileExists() is cached and you add a new layout or a new view, it won’t be noticed until you reload the framework. This setting was added in FW/1 1.2.
  • applicationKey – A unique value for each FW/1 application that shares a common ColdFusion application name.

At runtime, this structure also contains the following key (from release 0.4 onward):

  • version – The release number (version) of the framework.

This is set automatically by the framework and cannot be overridden (well, it shouldn’t be overridden!).

In addition you can override the base directory for the application, which is necessary when the controllers, services, views and layouts are not in the same directory as the application’s index.cfm file. variables.framework.base should specify the path to the directory containing the layouts and views folders, either as a mapped path or a webroot-relative path (i.e., it must start with / and expand to a full file system path). If the controllers and services folders are in that same directory, FW/1 will find them automatically. If you decide to put your controllers and services folders somewhere else, you can also specify variables.framework.cfcbase as a dotted-path to those components, e.g., com.myapp.cfcs assuming that com.myapp.cfcs.controllers.Controller maps to your Controller.cfc and com.myapp.cfcs.services.Service maps to your Service.cfc .

Setting up application, session and request variables

The easiest way to setup application variables is to define a setupApplication() method in your Application.cfc and put the initialization in there. This method is automatically called by FW/1 when the application starts up and when the FW/1 application is reloaded.

The easiest way to setup session variables is to define a setupSession() method in your Application.cfc and put the initialization in there. This method is automatically called by FW/1 as part of onSessionStart().

The easiest way to setup request variables (or even global variables scope) is to define a setupRequest() method in your Application.cfc and put the initialization in there. Note that if you set variables scope data, it will be accessible inside your views and layouts but not inside your controllers or services.

Using Subsystems

The subsystems feature allows you to combine existing FW/1 applications as modules of a larger FW/1 application. The subsystems feature was contributed by Ryan Cogswell and the documentation was written by Dutch Rapley. Read about Using Subsystems to combine FW/1 applications.

Accessing the FW/1 API

FW/1 uses the request scope for some of its temporary data so that it can communicate between Application.cfc lifecycle methods without relying on variables scope (and potentially interfering with user data in variables scope). The ReferenceManual specifies which request scope variables are used and what you may and may not do with them.

In addition, the API of FW/1 is exposed to controllers, views and layouts in a particular way as documented below.

Controllers and the FW/1 API

Each controller method is passed the request context as a single argument called rc, of type struct. If access to the FW/1 API is required inside a controller, you can define an init() method (constructor) which takes a single argument, of type any, and when FW/1 creates the controller CFC, it passes itself in as the argument to init(). Your init() method should save that argument in the variables scope for use within the controller methods:

function init(fw) {
  variables.fw = fw;
}

Then you could call any framework method:

var user = createObject('component','model.user').init();
variables.fw.populate(user);

This will call setXxx() methods on the user bean, passing in matching elements from the request context. An optional second argument may be provided that specifies the keys to populate (the default is to attempt to match against every setXxx() method on the bean):

variables.fw.populate(user,'firstName, lastName, email');

This will call setFirstName(), setLastName() and setEmail() on the user bean, passing in matching elements from the request context.

The other framework methods that are useful for controllers are:

variables.fw.redirect( action, preserve, append, path, queryString );

where action is the action to redirect to, preserve is a list of request context keys that should be preserved across the redirect (using session scope) and append is a list of request context keys that should be appended to the redirect URL. preserve and append can both be omitted and default to none, i.e., no values preserved or appended. The optional path argument allows you to force a new base URL to be used (instead of the default variables.framework.baseURL which is normally CGI.SCRIPT_NAME). queryString allows you to specify additional URL parameters and/or anchors to be added to the generated URL. See the Reference Manual for more details.

variables.fw.service( action, key );

where action represents the service method to queue up and key is where the result should be stored in the request context.

I cannot imagine other FW/1 API methods being called from controllers at this point but the option is there if you need it.

Note: if you let your controllers be managed by a bean factory, in FW/1 1.1 and earlier you lose the ability to pass the framework in at construction time and therefore the ability to call API methods. My recommendation is to use a bean factory to manage your services but let FW/1 create your controllers, pass itself into their constructors and autowire dependencies from the bean factory. In FW/1 1.2 you can use a setFramework( any framework ) method on your controller to have FW/1 injected into your controller.

Specifying Additional Services

By convention, when section.item is requested, a default service method will be invoked: item() on the services/section.cfc . In 1.2, you can suppress this behavior with the new suppressImplicitService option. The result is stored in request.context.data by default (more commonly referred to as rc.data). Prior to execution of any service call, a controller may add additional services to be called, and specify where they should store their result, using the service() API call:

variables.fw.service( "myservice.myaction", "myresult" );

This will cause services/myservice.cfc:myaction() to be invoked (after any default service method call) and the result to be stored in rc.myresult. If the key is an empty string, FW/1 will simply call the method and ignore the result.

Behind the scenes, FW/1 uses this method to queue up the default action:

service( "section.item", "data" );

Technically, that call is actually:

service( request.action, getServiceKey( request.action ), false );

The getServiceKey() method returns “data” but can be overridden, in your Application.cfc, to return a value of your choosing, possibly computed from the requested action. A suggested use case is to have this method return the section name so that, for example, actions in the user section of a site would store the results of service calls in rc.user (i.e., request.context).

This allows additional service calls to be made to set up data the views may need, without needing explicit calls from the controller or creating an actual dependency between the controller and the service. Additional service calls are deliberately placed after the default service call to emphasize this separation. Since services are executed after the main controller method, and before any endItem() or after() controller method, it is not legal to call the service() method in those controller methods (you will get an exception). If you could call service() in those methods, the services wouldn’t be called anyway – a programming error that FW/1 prevents.

Note: whereas the default service invocation does not require the item() method to exist in the selected service, any additional services queued for execution in the controller must have item() methods that exist. Since a service CFC may exist for a section, it does not need to have a method matching every item requested (otherwise you’d get an error – or have to write lots of dummy methods). On the other hand, if a controller queues up a specific service item, it is considered a programming error if that method does not exist because it is an explicit request under your control (and having the framework silently ignore requests to non-existent service method is more likely to hide errors than be the correct behavior!).

Views/Layouts and the FW/1 API

As indicated above under the “in depth” paragraph about views and layouts, the entire FW/1 API is available to views and layouts directly (effectively in the variables scope) because of the way views and layouts are executed. This allows views and layouts to access utility beans from the bean factory, such as formatting services, as well as render views and, if necessary, other layouts. Views and layouts also have access to the framework structure which contains the action key – which could be used for building links:

<a href="?#framework.action#=section.item">Go to section.item</a>

But you’re better off using the buildURL() API method:

<a href="#buildURL( 'section.item' )#">Go to section.item</a>

You can provide additional query string values to buildURL():

<a href="#buildURL( 'section.item?arg=val' )#">Go to section.item with arg=val in URL</a>
<a href="#buildURL( action = 'section.item', queryString = 'arg=val' )#">Go to section.item with arg=val in URL</a>

I cannot imagine a view or layout needing full access to the FW/1 API methods beyond view(), layout() and getBeanFactory() but the option is there if you need it.

Convenience Methods in the FW/1 API

FW/1 provides a number of convenience methods for manipulating the action value to extract parts of the action (the action argument is optional in all these methods and defaults to the currently requested action):

  • getSubsystem( action ) – If subsystems are enabled, this returns either the subsystem portion of the action or the default subsystem. If subsystems are not enabled, returns an empty string.
  • getSection( action ) – Returns the section portion of the action. If subsystems are enabled but no section is specified, returns the default section.
  • getItem( action ) – Returns the item portion of the action. If no item is specified, returns the default item.
  • getSectionAndItem( action ) – Returns the section.item portion of the action, including default values if either part is not specified.
  • getFullyQualifiedAction( action ) – If subsystems are enabled, returns the fully qualified subsystem:section.item version of the action, including defaults where appropriate. If subsystems are not enabled, returns getSectionAndItem( action ).