Skip to content

Instantly share code, notes, and snippets.

@atadams
Last active August 29, 2015 14:02
Show Gist options
  • Save atadams/125aafeec54354d11474 to your computer and use it in GitHub Desktop.
Save atadams/125aafeec54354d11474 to your computer and use it in GitHub Desktop.
FW/1 Wiki

Using Subsystems

Subsystems give you a way of dropping in one FW/1 application into an existing one. Subsystems can be used to create a module that has no dependencies on the parent application or you can use subsystems to group common functionality together.

Enabling Subsystems

If you want to enable subsystems, you must set the following in the parent application’s Application.cfc:

variables.framework.usingSubsystems = true;

Once you enable subsystems, you’ll need to follow a couple of additional conventions:

  1. Your site’s default application must be implemented as a subsystem. By default, the framework will look in the sub-directory home.
  2. A sitewide layout can be specified at common/layouts/default.cfm. If this file exists, it will be applied.

The locations for your default subsystem and sitewide layout can be overridden in Application.cfc with:

variables.framework.defaultSubsystem = 'home';
variables.framework.siteWideLayoutSubsystem = 'common';

Accessing Subsystems

To access a subsystem in the browser, you’ll have to specify it in the action:

index.cfm?action=subsystem:section.item

If you leave off the subsystem in the url, the section and item will reference the default subsystem.

When creating links in your views and layouts, it’s recommended that you use buildUrl(). You do not have to specify the current subsystem inside buildUrl(), it will automatically be prepended. This method is preferred. If you change the name of a subsystem, all of your links inside the subsystem will represent the change.

buildUrl('section.item') - action=currentSubsystem:section.item

You can also link to other subsystems:

buildUrl('otherSubsystem:section.item') - action=otherSubsystem:section.item

Configuring Subsystems

There is an optional method that can be declared in Application.cfc for configuring subsystems:

function setupSubsystem(subsystem) {}

setupSubsystem() is called once for each subsystem, when a subsystem is initialized. When an application is reloaded, the initialized subsystems are cleared and setupSubsystem() will be called on the next request to a subsystem.

Framework Configuration

The following options relate to subsystems:

  • usingSubsystems – This will be true if you are using subsystems.
  • defaultSubsystem – This is the default subsystem when none is specified in the URL or form post.
  • subsystemDelimiter – This specifies the delimiter between the subsystem name and the action in a URL or form post.
  • siteWideLayoutSubsystem – This specifies the subsystem that is used for the (optional) site-wide default layout.

Controllers

Subsystem controllers are located in subsystem/controllers (e.g., admin/controllers).

Services

Subsystem services are located in subsystem/services (e.g., admin/services).

Views

Subsystem views are located in subsystem/views (e.g., admin/views).

Layouts

Subsystem layouts are looked up in the same order as before, but with the additional inclusion of a sitewide layout, if it exists. The default sitewide layout folder is common. Subsystem specific layouts are located in subsystem/layouts (e.g., admin/layouts).

  • subsystem/layouts/section/item.cfm
  • subsystem/layouts/section.cfm
  • subsystem/layouts/default.cfm
  • common/layouts/default.cfm

Using Bean Factories

The introduction of subsystems introduces the ability to have subsystem specific bean factories.

function setupApplication() {
    var bf = createObject('component','coldspring.beans.DefaultXmlBeanFactory').init();
    var homeBf = createObject('component','coldspring.beans.DefaultXmlBeanFactory').init();
    var adminBf = createObject('component','coldspring.beans.DefaultXmlBeanFactory').init();
    // load the main bean factory:
    bf.loadBeans('config/coldspring.xml.cfm');
    setBeanFactory(bf);
    // load a bean factory for the home subsystem:
    homeBf.loadBeans('home/config/coldspring.xml.cfm');
    homeBf.setParent(getDefaultBeanFactory());
    setSubsystemBeanFactory('home', homeBf);    
    // load a bean factory for the admin subsystem:
    adminBf.loadBeans('admin/config/coldspring.xml.cfm');
    adminBf.setParent(getDefaultBeanFactory());
    setSubsystemBeanFactory('admin', adminBf);
}

The following methods are now at your disposal:

  • setSubsystemBeanFactory(subsystem, beanFactory) – sets up a subsystem specific bean factory
  • hasSubsystemBeanFactory(subsystem) – returns true if a subsystem specific bean factory exists
  • getBeanFactory() – returns the bean factory for the current subsystem. Alternately, it can be used to retrieve the bean factory for another subsystem by passing the name of the subsystem (e.g., getBeanFactory(subsystem) ).
  • getDefaultBeanFactory() – returns the default bean factory that was passed to setBeanFactory()

Services

If you specified a subsystem specific bean factory and it knows about a bean named sectionService, it will attempt to use that as the service. If a bean named sectionService doesn’t exist, the subsystem will attempt to use subsystem/services/section.cfc, if it exists.

Controllers

If you specified a subsystem specific bean factory and it knows about a bean named sectionController, it will attempt to use that as the controller. If a bean named sectionController doesn’t exist, the subsysem will attempt to use subsystem/controllers/section.cfc, if it exists. Remember, if you use a bean factory to manage your controllers, the framework will not be passed to the init() method. It’s recommended that you use subsystem/controllers/section.cfc as the convention for declaring controllers.

Auto Wiring

If you did not declare a subsystem specific bean factory, the framework will attempt to auto wire beans from the default bean factory into subsystem specific controllers and services.

If you have declared a subsystem specific bean factory, the framework will attempt to auto wire only the beans in the subsystem specific bean factory into your subsystem controllers and services. If a subsystem specific bean factory exists, beans from the default bean factory will NOT be autowired into your subsystem controllers, even if the bean doesn’t exist in the subsystem specific bean factory.

If you are using a subsystem specific bean factory and wish to auto wire beans from the default bean factory, consider using an IoC framework that supports setParent().

Setting Bean Factories With setupSubsystem()

With setupSubsystem(), it’s possible that you can use your own convention to load subsystem specific bean factories, instead of explicitly declaring each one in setupApplication(). The following example makes the assumption that each subsystem has a bean factory config file in a common subsystem specific folder. If the config file is found, it then loads the subsystem bean factory.

function setupSubsystem(subsystem) {
    var bf = createObject('component','coldspring.beans.DefaultXmlBeanFactory').init();
    var bfConfigFilePath = subsystem & '/config/coldspring.xml.cfm';
    // conditionally load the bean factory for this subsystem by convention:
    if ( fileExists(expandPath('./') & bfConfigFilePath) ) {
        bf.loadBeans( bfConfigFilePath );
        setSubsystemBeanFactory( subsystem, bf );           
    }         
}

Accessing Other Bean Factories From A Subsystem

If you have a default bean factory, you can access it in your controllers and views from any subsystem with getDefaultBeanFactory().

While it’s not considered a best practice, there may be a chance when you will need to access a bean factory from another subsystem. You can do this by calling getBeanFactory(subsystem) (i.e. getBeanFactory(‘user’)).

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 general layouts for each section and/or a default layout for the entire site. The layouts folder may also contain subfolders for sections within the site, which in turn contain layouts for specific views. 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.

You would typically also have a model folder containing CFCs for your services and
your domain objects – the business logic of your application. Prior to Release 2.5, you
would have had a service folder for service CFCs but there would have no consistent
structure for the rest of your business logic.

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
are executed as part of a view() call. As of 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() and setLayout() methods to specify a new action to use for the view and layout lookup respectively. setLayout() is new in 2.0. 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).</li> <li><strong>local</strong> – An empty struct, created as a local scope for the view or layout.</li> <li><strong>framework</strong> – The FW/1 configuration structure (variables.framework in the framework <span class="caps">CFC</span>) which includes a number of useful values including <strong>framework.action</strong>, the name of the <span class="caps">URL</span> parameter that holds the action (if you’re building links, you should use the <strong>buildURL()</strong> <span class="caps">API</span> method which knows how to handle subsystems as well as regular section and item values in the action value). You can also write <span class="caps">SES</span> URLs without this variable, e.g., <em>/index.cfm/section/item</em> as long as your application server supports such URLs.</li> </ul> <p>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.</p> <p>It is strongly recommended to use the <strong>local</strong> struct for any variables you need to create yourself in a view or layout!</p> <p>If you have data that is needed by all of your views, it may be convenient to set that up in your <strong>setupView()</strong> method in <strong>Application.cfc</strong> – see <strong>Taking Actions on Every Request</strong> below. <em>Added in 2.0.</em></p> <h3>Rendering Data to the Caller</h3> <p>Prior to 2.2, if you wanted to create an <span class="caps">API</span> that returned <span class="caps">JSON</span> or <span class="caps">XML</span> to a caller, you had to format data in a special view and set the content type correctly and suppressed layouts and so on. It was possible, but ugly.</p> <p>As of 2.2, you can use the <strong>renderData()</strong> framework <span class="caps">API</span> to bypass views and layouts completely and automatically return <span class="caps">JSON</span>, <span class="caps">XML</span>, or plain text to your caller, with the correct content type automatically set. See below for more details.</p> <h2>Designing Controllers</h2> <p>Controllers are the pounding heart of an <span class="caps">MVC</span> application and FW/1 provides quite a bit<br /> of flexibility in this area. The most basic convention is that when FW/1 is asked for <br /> <em>section.item</em> it will look for controllers/<em>section.cfc</em> and attempt to call the <br /> <em>item()</em> method on it, passing in the request context as a single argument called <br /> <strong>rc</strong> and controllers may call into the application model as needed, then render a view.</p> <p>Controllers are cached in FW/1’s application cache so controller methods need to be written with thread safety in mind (i.e., use <strong>var</strong> to declare variables properly!). If you are using a bean factory, any <em>setXxx()</em> methods on a controller <span class="caps">CFC</span> may be used by FW/1 to autowire beans from the factory into the controller when it is created. <em>Release 2.0 supports property-based autowiring as well as explicit setters.</em> Alternatively, you can let the bean factory take care of the controller CFC’s lifecycle completely: just name the bean with a suffix of <strong>Controller</strong> and when FW/1 is asked for <em>section.item</em> it will ask the bean factory for <em>sectionController</em> . See the note about Controllers and the FW/1 <span class="caps">API</span> below for a caveat on this.</p> <p>In addition, if you need certain actions to take place before all items in a particular section, you can define a <strong>before</strong>() method in your controller and FW/1 will automatically call it for you, before calling the <em>item()</em> 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 <strong>request.item</strong> 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 <em>main.doLogin</em> action that attempts to log a user in).</p> <p>Similarly, if you need certain actions to take place after all items in a particular section, you can define an <strong>after</strong>() method in your controller and FW/1 will automatically call it for you, after calling the <em>item()</em> method and after invoking any service method.</p> <p>Note that your <strong>Application.cfc</strong> is also viewed as a controller and if it defines <strong>before()</strong> and <strong>after()</strong> methods, those are called as part of the lifecycle, around any regular controller methods. Unlike other controllers, it does not need an <strong>init()</strong> method and instead of referring to the FW/1 <span class="caps">API</span> methods via <strong>variables.fw…</strong> you can just use the <span class="caps">API</span> methods directly – unqualified – since <strong>Application.cfc</strong> extends the framework and all those methods are available implicitly. <em>Added in 2.0.</em></p> <p>Here is the full list of methods called automatically when FW/1 is asked for <em>section.item</em> :</p> <ul> <li><strong>Application.cfc</strong> : <strong>before()</strong></li> <li><strong>controllers/</strong><em>section.cfc</em> : <strong>before()</strong></li> <li>Deprecated: <strong>controllers/</strong><em>section.cfc</em> : <em>startItem()</em></li> <li><strong>controllers/</strong><em>section.cfc</em> : <em>item()</em></li> <li>Deprecated: (any service calls that were queued via the <strong>service()</strong> <span class="caps">API</span> call)</li> <li>Deprecated: <strong>controllers/</strong><em>section.cfc</em> : <em>endItem()</em></li> <li><strong>controllers/</strong><em>section.cfc</em> : <strong>after()</strong></li> <li><strong>Application.cfc</strong> : <strong>after()</strong></li> </ul> <p>Methods that do not exist are not called. <em>Application.cfc methods were not called in FW/1 1.×.</em></p> <h3>Using onMissingMethod() to Implement Items</h3> <p>FW/1 supports <strong>onMissingMethod()</strong>, i.e., if a desired method is not present but <br /> <strong>onMissingMethod()</strong> is declared, FW/1 will call the method anyway. That applies to <br /> all five potential controller methods: <strong>before</strong>, <em>startItem</em> , <em>item</em> , <em>endItem</em> and <br /> <strong>after</strong>. As of Release 2.5, <em>startItem</em> and <em>endItem</em> are deprecated and will not be<br /> called by default (unless you set <strong>suppressServiceQueue</strong> false).<br /> That means you must be a little careful if you implement <strong>onMissingMethod()</strong> since it <br /> will be called whenever FW/1 needs a method that isn’t already defined. If you are going<br /> to use <strong>onMissingMethod()</strong>, I would <br /> recommend always defining <strong>before</strong> and <strong>after</strong> methods, even if they are empty;<br /> and if you are still using <em>start</em> and <em>end</em> items, be extra careful!<br /> As of 2.1, <strong>onMissingMethod()</strong> calls will <br /> receive two arguments in <strong>missingMethodArguments</strong>: <strong>rc</strong> (as in earlier versions) and <br /> <strong>method</strong> which is the type of the method being invoked (“before”, “start”, “item”, <br /> “end”, “after”).</p> <h3>Using onMissingView() to Handle Missing Views</h3> <p>FW/1 provides a default <strong>onMissingView()</strong> method for <strong>Application.cfc</strong> 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 <span class="caps">JSON</span>-encoded data value without needing an explicit view to be present. Other use cases are possible since whatever <strong>onMissingView()</strong> returns is used as the core view and, unless layouts are disabled, it will be wrapped in layouts and then displayed to the user.</p> <p>Be aware that <strong>onMissingView()</strong> 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!</p> <h3>Taking Actions on Every Request</h3> <p>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 <strong>Application.cfc</strong> for general lifecycle events. That’s why FW/1 expects you to write per-request logic in <strong>setupRequest()</strong>, per-session logic in <strong>setupSession()</strong> and application initialization logic in <strong>setupApplication()</strong>. FW/1 2.0 adds <strong>setupView()</strong> which is called just before view rendering begins to allow you to set up data for your views that needs to be globally available, but may depend on the results of running controllers or services.</p> <p>If you have some logic that is meant to be run on every request, the FW/1 way to implement this is to implement <strong>setupRequest()</strong> in your <strong>Application.cfc</strong> and have it retrieve the desired controller by name and run the appropriate event method, like this:</p> <pre>function setupRequest() { controller( 'security.checkAuthorization' ); }</pre> <p>This queues up a call to that controller at the start of the request processing, calling <strong>before()</strong>, <br /> <strong>checkAuthorization()</strong> and <strong>after()</strong> as appropriate, if those methods are present.</p> <p>If you need to perform some actions after controllers and services have completed but before any views are rendered, you can implement <strong>setupView()</strong> in your <strong>Application.cfc</strong> and FW/1 will call it after setting up the view and layout queue but before any rendering takes place:</p> <pre>function setupView() { // pre-rendering logic }</pre> <p>You have access to <strong>rc</strong> in <strong>variables</strong> scope at this point. You cannot call controllers here – this lifecycle method is intended for common data setup that is needed by most (or all) of your views and layouts. If services from your model have been autowired into <strong>Application.cfc</strong>, you can call those. Added in FW/1 2.0.</p> <p>Finally, there is a lifecycle method that FW/1 calls at the end of every request – including redirects – where you can implement <strong>setupResponse()</strong> in your <strong>Application.cfc</strong>:</p> <pre>function setupResponse() { // end of request processing }</pre> <p>This is called after all views and layouts have been rendered in a regular request or immediately before the redirect actually occurs when <strong>redirect()</strong> has been called. Again, you have access to <strong>rc</strong> in <strong>variables</strong> scope and you cannot call controllers here. If services from your model have been autowired into <strong>Application.cfc</strong>, you can call those. Added in FW/1 2.0.</p> <h3>Short-Circuiting the Controller / Services Lifecycle</h3> <p>If you need to immediately halt execution of a controller and prevent any further controllers or services from being called, use the <strong>abortController()</strong> method. See the [[Reference Manual]] for more details of <strong>abortController()</strong>, in particular how it interacts with exception-handling code in your controllers. <em>Added in 2.0.</em></p> <h3>Controllers for <span class="caps">REST</span> APIs</h3> <p>As of 2.2, you can return data directly, bypassing views and layouts, using the new <strong>renderData()</strong> function.</p> <pre>variables.fw.renderData( contentType, resultData );</pre> <p>Calling this function does not exit from your controller, but tells FW/1 that instead of looking for a view to render, the <strong>resultData</strong> value should be converted to the specified <strong>contentType</strong> and that should be the result of the complete <span class="caps">HTTP</span> request.</p> <p><strong>contentType</strong> may be <strong>“json”</strong>, <strong>“xml”</strong>, or <strong>“text”</strong>. The <strong>Content-Type</strong> <span class="caps">HTTP</span> header is automatically set to:</p> <ul> <li><strong>application/json; charset=utf-8</strong></li> <li><strong>text/xml; charset=utf-8</strong></li> <li><strong>text/plain; charset=utf-8</strong></li> </ul> <p>respectively. For <span class="caps">JSON</span>, the <strong>resultData</strong> value is converted to a string by calling <strong>serializeJSON()</strong>; for <span class="caps">XML</span>, the <strong>resultData</strong> value is expected to be either a valid <span class="caps">XML</span> string or an <span class="caps">XML</span> object (constructed via CFML’s various <strong>xml…()</strong> functions); for plain text, the <strong>resultData</strong> value must be a string.</p> <p>You can also specify an <span class="caps">HTTP</span> status code as a third argument. The default is 200.</p> <p>When you use <strong>renderData()</strong>, no matching view is required for the action being executed.</p> <h2>Designing Services and Domain Objects</h2> <p>Services – and domain objects -<br /> should encapsulate all of the business logic in your application. Where possible, most<br /> of the application logic should be in the domain objects, making them smart objects,<br /> and services can take care of orchestrating work that reaches across multiple domain<br /> objects.</p> <p>Controllers should call methods on domain objects and services to do all the heavy <br /> lifting in your application, passing specific elements of the request<br /> context as arguments. FW/1’s <strong>populate()</strong> <span class="caps">API</span> is designed to allow you to store<br /> arbitrary elements of the request context in domain objects.</p> <p>If you’re using a bean factory (recommended – and covered in the next section), <br /> your services can be autowired into your<br /> controllers, making it easier to call them directly, without having to worry about how<br /> and where to construct those CFCs. Bean factories can also be used to manage your domain<br /> objects, autowiring services into them as necessary, so your controllers can simply<br /> ask the bean factory for a new domain object, <strong>populate()</strong> it from the request context,<br /> call methods on the domain object, or pass them to services as necessary.</p> <p>Services should not know anything about the framework. <br /> Service methods should not “reach out” into the request <br /> scope to interact with FW/1 – or any other scopes! – they should simply have some <br /> declared arguments, perform some operation and return some data.</p> <h3>Services, Domain Objects and Persistence</h3> <p>There are many ways to organize how you save and load data. You could use the <span class="caps">ORM</span> that<br /> comes with ColdFusion or Railo, you could write your own data mapping service, you could<br /> write custom <span class="caps">SQL</span> for every domain object. Regardless of how you choose to handle your<br /> persistence, encapsulating it in a service <span class="caps">CFC</span> is probably a good idea. For convenience<br /> it is often worth injecting your persistence service into your domain object so you can<br /> have a convenient <strong>domainObject.save()</strong> method to use from your controller, even if it<br /> just delegates to the persistence service internally:</p> <pre>function save() { variables.dataService.save( this ); }</pre> <h3>History of Services and FW/1</h3> <p>The convention introduced in FW/1 1.x to invoke service methods was intended to reduce the number <br /> of controller methods that simply delegate to a service method and store the result in <br /> the request context. The default service method was invoked as if you had written:</p> <pre>request.context.data = section.item( argumentCollection=request.context );</pre> <p>In other words, any declared arguments on the service method were matched against <br /> values passed in through the <span class="caps">URL</span> or form scope, and the result of the call <br /> placed in the <strong>data</strong> element of the request context for use by the view in that request. <br /> Service method calls queued up by the controller placed their results in the designated <br /> elements of the request context.</p> <p>FW/1 expected to find service CFCs in the <strong>services</strong> folder and when it created them, <br /> it invoked their <strong>init()</strong> method, if present, passing no arguments, and then cached the<br /> service instance in application scope, just as controllers were cached. That meant that<br /> service methods needed to be written with thread safety in mind.</p> <p>If you used a bean factory but let FW/1 create and manage service CFCs, <br /> any setXxx() methods on a service <span class="caps">CFC</span> could <br /> be used by FW/1 to autowire beans from the factory into the service when it is created. <br /> <em>Release 2.0 added support for property-based autowiring as well as explicit setters.</em> <br /> Alternatively, you could let the bean factory take care of the service CFC’s lifecycle <br /> completely: just name the bean with a suffix of <strong>Service</strong> and when FW/1 is asked for <br /> <em>section.item</em>, it will ask the bean factory for <em>sectionService</em>.</p> <p>The implicit management of services and queuing up of their method calls was deprecated<br /> in Release 2.5 and will be removed in 3.0. The recommended approach has always been to<br /> use a bean factory to manage services and autowiring.</p> <h2>Using Bean Factories</h2> <p>FW/1 supports your favorite bean factory (aka IoC container or DI factory). As long as you have a <span class="caps">CFC</span> that supports the following <span class="caps">API</span>, FW/1 will accept it as a bean factory:</p> <ul> <li>boolean containsBean(string name) – returns true if the factory knows of the named bean</li> <li>any getBean(string name) – returns a fully initialized bean identified by name</li> </ul> <p>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 DI/1:</p> <pre>var bf = new framework.ioc('/controllers,/model'); setBeanFactory(bf);</pre> <p>or WireBox:</p> <pre>var bf = new framework.WireBoxAdapter(); bf.getBinder().scanLocations('/controllers,/model'); setBeanFactory(bf);</pre> <p>or ColdSpring:</p> <pre>var bf = createObject('component','coldspring.beans.DefaultXmlBeanFactory').init(); bf.loadBeans( expandPath('config/coldspring.xml') ); setBeanFactory(bf);</pre> <p>Note: If you are using subsystems, please read UsingSubsystems for details about setting up bean factories for subsystems.</p> <p>When you tell FW/1 to use a bean factory, it does several things behind the scenes:</p> <ul> <li>If the factory knows about a sectionController bean, FW/1 will use that instead of controllers/section.cfc</li> <li>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)</li> </ul> <p>The FW/1 <span class="caps">API</span> includes the following methods related to bean factory support:</p> <ul> <li>hasBeanFactory() – returns true if FW/1 knows about a bean factory</li> <li>getBeanFactory() – returns the bean factory you told FW/1 about</li> </ul> <p>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).</p> <h2>Error Handling</h2> <p>By default, if an exception occurs, FW/1 will attempt to run the <strong>main.error</strong> action (as if you had asked for <strong>?action=main.error</strong>), assuming your <strong>defaultSection</strong> is <strong>main</strong>. If you change the <strong>defaultSection</strong>, that implicitly changes the default error handler to be the <strong>error</strong> item in that section. The exception thrown is stored directly in the request scope as <strong>request.exception</strong>. If FW/1 was processing an action when the exception occurred, the name of that action is available as <strong>request.failedAction</strong>. The default error handling action can be overridden in your <strong>Application.cfc</strong> by specifying <strong>variables.framework.error</strong> to be the name of the action to invoke when an exception occurs.</p> <p>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 <strong>fail()</strong> method or the <strong>onError()</strong> method but I don’t intend to “support” that so the only documentation will be in the code!</p> <p>Note: If you override <strong>onMissingView()</strong> and forget to define a view for the error handler, FW/1 will call <strong>onMissingView()</strong> and that will hide the original exception.</p> <h1>Configuring FW/1 Applications</h1> <p>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:</p> <pre>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 = true, // this used to be false in FW/1 1.x suppressServiceQueue = true, // false restores the FW/1 2.2 behavior unhandledExtensions = 'cfc', unhandledPaths = '/flex2gateway', unhandledErrorCaught = false, preserveKeyURLKey = 'fw1pk', maxNumContextsPreserved = 10, cacheFileExists = false, applicationKey = 'org.corfield.framework', trace = false };</pre> <p>The keys in the structure have the following meanings:</p> <ul> <li><strong>action</strong> – The <span class="caps">URL</span> or form variable used to specify the desired action (<em>section.item</em>).</li> <li><strong>usingSubsystems</strong> – Whether or not to use subsystems – see <strong>Using Subsystems</strong> below.</li> <li><strong>defaultSubsystem</strong> – If subsystems are enabled, this is the default subsystem when no action is specified in the <span class="caps">URL</span> or form post.</li> <li><strong>defaultSection</strong> – 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 <span class="caps">URL</span> or form post.</li> <li><strong>defaultItem</strong> – The default item within a section when either no action is specified at all or just the section is specified in the action.</li> <li><strong>subsystemDelimiter</strong> – When subsystems are enabled, this specifies the delimiter between the subsystem name and the action in a <span class="caps">URL</span> or form post.</li> <li><strong>siteWideLayoutSubsystem</strong> – When subsystems are enabled, this specifies the subsystem that is used for the (optional) site-wide default layout.</li> <li><strong>home</strong> – The default action when it is not specified in the <span class="caps">URL</span> or form post. By default, this is <strong>defaultSection</strong>.<strong>defaultItem</strong>. If you specify <strong>home</strong>, you are overriding (and hiding) <strong>defaultSection</strong> but not <strong>defaultItem</strong>. If <strong>usingSubsystem</strong> is true, the default for <strong>home</strong> is “home:main.default”, i.e., <strong>defaultSubsystem</strong> & <strong>subsystemDelimiter</strong> & <strong>defaultSection</strong> & ‘.’ & <strong>defaultItem</strong>.</li> <li><strong>error</strong> – The action to use if an exception occurs. By default this is <strong>defaultSection</strong>.error.</li> <li><strong>reload</strong> – The <span class="caps">URL</span> variable used to force FW/1 to reload its application cache and re-execute <strong>setupApplication()</strong>.</li> <li><strong>password</strong> – The value of the reload <span class="caps">URL</span> variable that must be specified, e.g., <strong>?reload=true</strong> is the default but you could specify reload = ‘refresh’, password = ‘fw1’ and then specifying ?refresh=fw1 would cause a reload.</li> <li><strong>reloadApplicationOnEveryRequest</strong> – If this is set to <strong>true</strong> then FW/1 behaves as if you specified the <strong>reload</strong> <span class="caps">URL</span> variable on every request, i.e., at the start of each request, the controller/service cache is cleared and <strong>setupApplication()</strong> is executed.</li> <li><strong>generateSES</strong> – If true, causes <strong>redirect()</strong> and <strong>buildURL()</strong> to generate <span class="caps">SES</span>-style URLs with items separated by <strong>/</strong> (and the path info in the <span class="caps">URL</span> will begin <em>/section/item</em> rather than <em>?action=section.item</em> – see the [[Reference Manual]] for more details).</li> <li><strong>SESOmitIndex</strong> – If <span class="caps">SES</span> URLs are enabled and this is true, will attempt to omit the base filename in the path when constructing URLs in <strong>buildURL()</strong> and <strong>redirect()</strong> which will generally omit <strong>/index.cfm</strong> from the start of the <span class="caps">URL</span>. Again, see the [[Reference Manual]] for more details.</li> <li><strong>base</strong> – Provide this if the application itself is not in the same directory as <strong>Application.cfc</strong> and <strong>index.cfm</strong>. It should be the relative path to the application from the <strong>Application.cfc</strong> file.</li> <li><strong>baseURL</strong> – Normally, <strong>redirect()</strong> and <strong>buildURL()</strong> default to using <strong><span class="caps">CGI</span>.SCRIPT_NAME</strong> as the basis for the <span class="caps">URL</span> they construct. This is the right choice for most applications but there are times when the base <span class="caps">URL</span> used for your application could be different. As of 1.2, you can also specify <strong>baseURL</strong> = “useRequestURI” and instead of <strong><span class="caps">CGI</span>.SCRIPT_NAME</strong>, the result of <strong>getPageContext().getRequest().getRequestURI()</strong> will be used to construct URLs. This is the right choice for FW/1 applications embedded inside Mura.</li> <li><strong>cfcbase</strong> – Provide this if the <strong>controllers</strong> and <strong>services</strong> 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.</li> <li><strong>suppressImplicitService</strong> – Use this option to tell FW/1 to automatically invoke a service that matches section.item in the request. Added in 1.2. In 2.0, the default of this option is <strong>true</strong> no implicit service call is queued. In FW/1 1.x, the default was <strong>false</strong> and FW/1 called a service method automatically. Experience has shown that was not a good idea (sorry!).</li> <li><strong>suppressServiceQueue</strong> – Use this option to tell FW/1 to allow <strong>service()</strong> calls and <strong>start</strong>/<strong>end</strong> controller handlers. <br /> Release 2.5 deprecated the service queue and introduced this option to allow the release<br /> 2.2 behavior to continue until users can migrate away from the service queue.<br /> Release 3.0 will remove all of the service queue machinery. <em>New in Release 2.5</em></li> <li><strong>unhandledExtensions</strong> – A list of file extensions that FW/1 should not handle. By default, just requests for <em>some.cfc</em> are not handled by FW/1.</li> <li><strong>unhandledPaths</strong> – A list of file paths that FW/1 should not handle. By default, just requests for <strong>/flex2gateway</strong> 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, <strong>unhandledPaths = ‘/flex2gateway,/404.cfm,/api’</strong> will cause FW/1 to not handle requests from Flex, requests for the <strong>404.cfm</strong> page and any requests for files in the <strong>/api</strong> folder.</li> <li><strong>unhandledErrorCaught</strong> – By default the framework does not attempt to catch errors raised by unhandled requests but sometimes when you are migrating from a legacy application it is useful to route error handling of legacy (unhandled) requests through FW/1. The default for this option is <strong>false</strong>. Set it <strong>true</strong> to have FW/1’s error handling apply to unhandled requests. <em>Added in 2.1</em></li> <li><strong>preserveKeyURLKey</strong> – 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 <span class="caps">URL</span> 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 <span class="caps">URL</span> variables. As of 1.2, this will be ignored if you set maxNumContextsPreserved to 1 because with only one context, FW/1 will not use a <span class="caps">URL</span> variable to track flash scope across redirects.</li> <li><strong>maxNumContextsPreserved</strong> – 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 1.2, setting maxNumContextsPreserved to 1 will prevent the <span class="caps">URL</span> key from being used for redirects (since FW/1 will not need to track multiple flash contexts).</li> <li><strong>cacheFileExists</strong> – 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. Added in FW/1 1.2.</li> <li><strong>applicationKey</strong> – A unique value for each FW/1 application that shares a common ColdFusion application name.</li> <li><strong>noLowerCase</strong> – If true, FW/1 will not force actions to lowercase so subsystem, section and item names will be case sensitive (in particular, filenames for controllers, views and layouts may therefore be mixed case on a case-sensitive operating system). The default is <strong>false</strong>. Use of this option is <em>not</em> recommended and is not considered good practice. Added in FW/1 2.0.</li> <li><strong>subsystems</strong> – An optional struct of structs containing per-subsystem configuration data. Each key in the top-level struct is named for a subsystem. The contents of the nested structs can be anything you want for your subsystems. Retrieved by calling <strong>getSubsystemConfig()</strong>. Currently the only key used by FW/1 is <strong>baseURL</strong> which can be used to configure per-subsystem values. Added in FW/1 2.0.</li> <li><strong>trace</strong> – If true, FW/1 will print out debugging / tracing information at the bottom of each page. This can be very useful for debugging your application! Note that you must enable session management in your application if you use this feature. Added in FW/1 2.1.</li> </ul> <p>At runtime, this structure also contains the following key (from release 0.4 onward):</p> <ul> <li><strong>version</strong> – The release number (version) of the framework.</li> </ul> <p>This is set automatically by the framework and cannot be overridden (well, it shouldn’t be overridden!).</p> <p>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 <strong>index.cfm</strong> file. <strong>variables.framework.base</strong> 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 <strong>variables.framework.cfcbase</strong> as a dotted-path to those components, e.g., <em>com.myapp.cfcs</em> assuming that <em>com.myapp.cfcs.controllers.Controller</em> maps to your <em>Controller.cfc</em> and <em>com.myapp.cfcs.services.Service</em> maps to your <em>Service.cfc</em> .</p> <h2><span class="caps">URL</span> Routes</h2> <p>In addition to the standard /section/item URLs that FW/1 supports, you can also specify “routes” that are <span class="caps">URL</span> patterns, optionally containing variables, that map to standard /section/item URLs.</p> <p>To use routes, specify <strong>variables.framework.routes</strong> as an array of structures, where each structure specifies mappings from routes to standard URLs. The array is searched in order and the first matching route is the one selected (and any subsequent match is ignored). This allows you to control which route should be used when several possibilities match.</p> <p>Placeholder variables in the route are identified by a leading colon and can appear in the <span class="caps">URL</span> as well, for example <strong>{ “/product/:id” = “/product/view/id/:id” }</strong> specifies a match for /product/something which will be treated as if the <span class="caps">URL</span> was /product/view/id/something – section: product, item: view, query string id=something.</p> <p>Routes can also be restricted to specific <span class="caps">HTTP</span> methods by prefixing them with <strong>$</strong> and the <em>method</em>, for example <strong>{ “$<span class="caps">POST</span>/search” = “/main/search” }</strong> specifies a match for a <span class="caps">POST</span> on /search which will be treated as if the <span class="caps">URL</span> was /main/search – section: main, item: search. A <span class="caps">GET</span> operation will not match this route.</p> <p>Routes can also specify a redirect instead of a substitute <span class="caps">URL</span> by prefixing the <span class="caps">URL</span> with an <span class="caps">HTTP</span> status code and a colon, for example <strong>{ “/thankyou” = “302:/main/thankyou” }</strong> specifies a match for /thankyou which will cause a redirect to /main/thankyou.</p> <p>A route of “*” is a wildcard that will match any request and therefore must be the last route in the array. A wildcard route may be restricted to a specific method, e.g., “$POST*” will match a <span class="caps">POST</span> to any <span class="caps">URL</span>.</p> <p>The keyword <strong>“$<span class="caps">RESOURCES</span>”</strong> can be used as a shorthand way of specifying resource routes: <strong>{ “$<span class="caps">RESOURCES</span>” = “dogs,cats,hamsters,gerbils” }</strong> (<em>Added in FW/1 2.2</em>). FW/1 will interpret this as if you had specified a standard set of routes for each of the listed resources. For example, for the resource “dogs”, FW/1 will parse the following routes:</p> <pre>{ "$GET/dogs/$" = "/dogs/default" }, { "$GET/dogs/new/$" = "/dogs/new" }, { "$POST/dogs/$" = "/dogs/create" }, { "$GET/dogs/:id/$" = "/dogs/show/id/:id" }, { "$PATCH/dogs/:id/$" = "/dogs/update/id/:id", "$PUT/dogs/:id/$" = "/dogs/update/id/:id" }, { "$DELETE/dogs/:id/$" = "/dogs/destroy/id/:id" }</pre> <p>There are also some additional resource route settings that can be specified. First you should note that the following three lines are equivalent:</p> <pre>{ "$RESOURCES" = "dogs,cats,hamsters,gerbils" }, { "$RESOURCES" = [ "dogs","cats","hamsters","gerbils" ] }, { "$RESOURCES" = { resources = "dogs,cats,hamsters,gerbils" } }</pre> <p>The first two lines are shorthand ways of specifying the full configuration struct given in the third line. An example of a full configuration struct would be the following:</p> <pre>{ resources = "dogs", methods = "default,create,show", pathRoot = "/animals", nested = "..."/[...]/{...} }</pre> <p>The key <strong>“methods”</strong>, if specified, limits the generated routes to the method names listed.</p> <p>The key <strong>“pathRoot”</strong>, if specified, is prepended to the generated route paths, so, given the above configuration struct, you get routes such as <strong>{ “$<span class="caps">GET</span>/animals/dogs/:id” = “/dogs/show/id/:id” }</strong>.</p> <p>Alternatively (or in addition), you can specify a subsystem: <strong>subsystem = “animals”</strong>, which generates routes such as <strong>{ “$<span class="caps">GET</span>/animals/dogs/:id” = “/animals:dogs/show/id/:id” }</strong>.</p> <p>The key <strong>“nested”</strong> is used to indicate resources which should be nested under another resource, and again can be specified as a string list, an array, or a struct. For example: <strong>{ “$<span class="caps">RESOURCES</span>” = { resources = “posts”, nested = “comments” } }</strong> results in all of the standard routes for “posts”, and in addition generates nested routes for “comments” such as <strong>{ “$<span class="caps">GET</span>/posts/:posts_id/comments” = “/comments/default/posts_id/:posts_id” }</strong>. Here it should be noted that the convention is to map the parent resource key to the variable name <strong>“#resource#_id”</strong>. Also, you cannot specify a path root or subsystem for a nested resource as it inherits these from its parent resource.</p> <p>The specific routes that FW/1 generates are determined by the <strong>variables.framework.resourceRouteTemplates</strong> array. By default it looks like the following:</p> <pre>variables.framework.resourceRouteTemplates = [ { method = 'default', httpMethods = [ '$GET' ] }, { method = 'new', httpMethods = [ '$GET' ], routeSuffix = '/new' }, { method = 'create', httpMethods = [ '$POST' ] }, { method = 'show', httpMethods = [ '$GET' ], includeId = true }, { method = 'update', httpMethods = [ '$PUT','$PATCH' ], includeId = true }, { method = 'destroy', httpMethods = [ '$DELETE' ], includeId = true } ];</pre> <p>If you wish to change the controller methods the routes are mapped to, for instance, you can specify this array in your Application.cfc and then change the default method names. For example, if you want “$<span class="caps">GET</span>/dogs/$” to map to “/dogs/index”, you would change <strong>method = ‘default’</strong> to <strong>method = ‘index’</strong> in the first template struct.</p> <p>A route structure may also have documentation by specifying a hint: <strong>{ “/product/:id” = “/product/view/id/:id”, hint = “Display a product” }</strong>.</p> <p>Here’s an example showing all the features together:</p> <pre>variables.framework.routes = [ { "/product/:id" = "/product/view/id/:id", "/user/:id" = "/user/view/id/:id", hint = "Display a specific product or user" }, { "/products" = "/product/list", "/users" = "/user/list" }, { "/old/url" = "302:/new/url" }, { "$GET/login" = "/not/authorized", "$POST/login" = "/auth/login" }, { "$RESOURCES" = { resources = "posts", subsystem = "blog", nested = "comments,tags" }, { "*" = "/not/found" } ];</pre> <h2>Environment Control</h2> <p>As of version 2.1, FW/1 supports <em>environment control</em> – the ability to automatically detect your application environment (development, production, etc) and adjust the framework configuration accordingly. There are three components to environment control:</p> <ul> <li><strong>variables.framework.environments</strong> – An optional structure containing groups of framework options for each environment.</li> <li><strong>getEnvironment()</strong> – A function that you override in <strong>Application.cfc</strong> that returns a string indicating the application environment.</li> <li><strong>setupEnvironment( string env )</strong> – A function that may optionally override in <strong>Application.cfc</strong> to provide more programmatic configuration for your application environment.</li> </ul> <p>Environment control is based on the concept of <strong>tier</strong> – development, staging, production etc – and optionally a <strong>server</strong> specifier. This two-part string determines how elements of <strong>variables.framework.environments</strong> are selected and merged into the base framework configuration. A string of the format <em>“tier”</em> or <em>“tier-server”</em> should be returned from <strong>getEnvironment()</strong>. FW/1 first looks for a group of options matching just <em>tier</em> and, if found, appends those to the base configuration. FW/1 then looks for a group of options matching <em>tier-server</em> and, if found, appends those to the configuration. After merging configuration options, FW/1 calls <strong>setupEnvironment()</strong> passing the tier/server string so your application may perform additional customization. This process is executed on every request (so be aware of performance considerations) which allows a single application to serve multiple domains and behave accordingly for each domain, for example.</p> <p>Your <strong>getEnvironment()</strong> function can use any means to determine which environment is active. Common methods are examining <strong><span class="caps">CGI</span>.SERVER_NAME</strong> or using the server’s actual hostname (accessible thru the new <strong>getHostname()</strong> <span class="caps">API</span> method). Here’s an example setup:</p> <pre>public function getEnvironment() { if ( findNoCase( "www", CGI.SERVER_NAME ) ) return "prod"; if ( findNoCase( "dev", CGI.SERVER_NAME ) ) return "dev"; else return "dev-qa"; }</pre> <pre>variables.framework.environments = { dev = { reloadApplicationOnEveryRequest = true, error = "main.detailederror" }, dev-qa = { reloadApplicationOnEveryRequest = false }, prod = { password = "supersecret" } }</pre> <p>With this setup, if the <span class="caps">URL</span> contains www, e.g., <a href="http://www.company.com">www.company.com</a>, the tier will be production (“prod”) and the reload password will be changed to “supersecret”. If the <span class="caps">URL</span> contains dev, e.g., dev.company.com, the tier will be development (“dev”) and the application will reload on every request. In addition, a detailed error page will be used instead of the default. Otherwise, the tier will still be development (“dev”) but the environment will be treated as a QA server: the development error setting will still be in effect but the framework will no longer reload on every request.</p> <p>Here is another example:</p> <pre>public function getEnvironment() { var hostname = getHostname(); if ( findNoCase( "local", hostname ) ) return "dev-" & listFirst( hostname, "-" ); var svrname = listFirst( hostname, "." ); // drop domain name etc switch ( svrname ) { case "proserver14a": return "prod-1"; case "proserver15c": return "prod-2"; case "proserver22x": return "prod-3"; case "proserver03b": return "qa"; default: return "dev-unknown"; } }</pre> <p>This maps local hostnames to specific developers machines (e.g., my development machine is called Sean-Corfields-iMac.local and my team’s machines follow similar naming). It specifically identifies the three servers in the production cluster and the QA server. All other environments are treated as development with an unknown server specifier.</p> <p>If you want to ensure that all environments are known configurations, your <strong>setupEnvironment()</strong> function can halt the application if a default environment is detected.</p> <h2>Setting up application, session and request variables</h2> <p>The easiest way to setup application variables is to define a <strong>setupApplication()</strong> method in your <strong>Application.cfc</strong> 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.</p> <p>The easiest way to setup session variables is to define a <strong>setupSession()</strong> method in your <strong>Application.cfc</strong> and put the initialization in there. This method is automatically called by FW/1 as part of <strong>onSessionStart()</strong>.</p> <p>The easiest way to setup request variables (or even global variables scope) is to define a <strong>setupRequest()</strong> method in your <strong>Application.cfc</strong> 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.</p> <h1>Using Subsystems</h1> <p>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.</p> <h1>Accessing the FW/1 <span class="caps">API</span></h1> <p>FW/1 uses the request scope for some of its temporary data so that it can communicate between <strong>Application.cfc</strong> 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.</p> <p>In addition, the <span class="caps">API</span> of FW/1 is exposed to controllers, views and layouts in a particular way as documented below.</p> <h2>Controllers and the FW/1 <span class="caps">API</span></h2> <p>Each controller method is passed the request context as a single argument called <strong>rc</strong>, of type <strong>struct</strong>. If access to the FW/1 <span class="caps">API</span> is required inside a controller, you can define an <strong>init</strong>() method (constructor) which takes a single argument, of type <strong>any</strong>, and when FW/1 creates the controller <span class="caps">CFC</span>, it passes itself in as the argument to <strong>init</strong>(). Your <strong>init</strong>() method should save that argument in the variables scope for use within the controller methods:</p> <pre>function init(fw) { variables.fw = fw; }</pre> <p>Then you could call any framework method:</p> <pre>var user = createObject('component','model.user').init(); variables.fw.populate(user);</pre> <p>This will call <em>setXxx()</em> 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 <em>setXxx()</em> method on the bean):</p> <pre>variables.fw.populate(user,'firstName, lastName, email');</pre> <p>This will call setFirstName(), setLastName() and setEmail() on the user bean, passing in matching elements from the request context.</p> <p>The other framework methods that are useful for controllers are:</p> <pre>variables.fw.redirect( action, preserve, append, path, queryString );</pre> <p>where <em>action</em> is the action to redirect to, <em>preserve</em> is a list of request context keys that should be preserved across the redirect (using session scope) and <em>append</em> is a list of request context keys that should be appended to the redirect <span class="caps">URL</span>. <em>preserve</em> and <em>append</em> can both be omitted and default to none, i.e., no values preserved or appended. The optional <em>path</em> argument allows you to force a new base <span class="caps">URL</span> to be used (instead of the default <strong>variables.framework.baseURL</strong> which is normally <span class="caps">CGI</span>.SCRIPT_NAME). <em>queryString</em> allows you to specify additional <span class="caps">URL</span> parameters and/or anchors to be added to the generated <span class="caps">URL</span>. See the [[Reference Manual]] for more details.</p> <p>I cannot imagine other FW/1 <span class="caps">API</span> methods being called from controllers at this point but the option is there if you need it.</p> <p>Note: if you let your controllers be managed by a bean factory, <em>in FW/1 1.1 and earlier</em> you lose the ability to pass the framework in at construction time and therefore the ability to call <span class="caps">API</span> 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. As of 1.2, you can use a setFramework( any framework ) method on your controller to have FW/1 injected into your controller.</p> <h2>Views/Layouts and the FW/1 <span class="caps">API</span></h2> <p>As indicated above under the “in depth” paragraph about views and layouts, the entire FW/1 <span class="caps">API</span> 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 <strong>framework</strong> structure which contains the <strong>action</strong> key – which could be used for building links:</p> <pre><a href="?#framework.action#=section.item">Go to section.item</a></pre> <p>But you’re better off using the <strong>buildURL()</strong> <span class="caps">API</span> method:</p> <pre><a href="#buildURL( 'section.item' )#">Go to section.item</a></pre> <p>You can provide additional query string values to <strong>buildURL()</strong>:</p> <pre><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></pre> <p>I cannot imagine a view or layout needing full access to the FW/1 <span class="caps">API</span> methods beyond view(), layout() and getBeanFactory() but the option is there if you need it.</p> <h2>Convenience Methods in the FW/1 <span class="caps">API</span></h2> <p>FW/1 provides a number of convenience methods for manipulating the action value to extract parts of the action (the <strong>action</strong> argument is optional in all these methods and defaults to the currently requested action):</p> <ul> <li><strong>getSubsystem( action )</strong> – 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.</li> <li><strong>getSection( action )</strong> – Returns the section portion of the action. If subsystems are enabled but no section is specified, returns the default section.</li> <li><strong>getItem( action )</strong> – Returns the item portion of the action. If no item is specified, returns the default item.</li> <li><strong>getSectionAndItem( action )</strong> – Returns the section.item portion of the action, including default values if either part is not specified.</li> <li><strong>getFullyQualifiedAction( action )</strong> – 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 <strong>getSectionAndItem( action )</strong>.</li> </ul>

Introducing FW/1 – Framework One

Why FW/1? Read the introductory blog post about the framework.

Once you’ve read this Getting Started guide, you’ll want to move on to the [[Developing Applications Manual]] and when you need to look things up, use the [[Reference Manual]]. You may also want to learn about [[Using Subsystems]] which allows FW/1 applications to be combined as modules of a larger FW/1 application.

You probably also want to join the FW/1 mailing list on Google Groups or follow the #FW1 twitter stream; you may also find help and inspiration in the [[FW/1 Site Showcase]], a directory of sites created with FW/1. To read about what’s coming in the future, take a look at the [[Roadmap]].

Getting Started

FW/1 consists of a single CFC: org.corfield.framework

If you check out FW/1 from git, it’s a complete web application. The org folder should be in your webroot (or accessible via a mapping).

Note: do not install FW/1 into a subfolder that contains . in the name as this will prevent CFC resolution from working!

The simplest FW/1 application comprises:

  • Application.cfc which extends org.corfield.framework
  • Empty index.cfm
  • views folder containing a main subfolder containing default.cfm – your initial application view

Pages are accessed using ?action=section.item in the URL which will display the views/section/item.cfm file. The default action is main.default, as you might have guessed from the simplest FW/1 example above! If you specify just the section name – ?action=section then the item has the default of default, in other words, ?action=section is equivalent to ?action=section.default.

If your application server supports it, so-called SES URLs can be used with FW/1:

  • index.cfm/section – equivalent to ?action=section
  • index.cfm/section/item – equivalent to ?action=section.item
  • index.cfm/section/item/name/value – equivalent to ?action=section.item&name=value

To use name/value pairs in SES URLs, you must specify both the section and item parts of the action. A trailing name with no value is treated as &name= in a normal URL.

Create Application.cfc containing:

<cfcomponent extends="org.corfield.framework">
</cfcomponent>

Create an empty index.cfm file.

Create views/main/default.cfm containing:

Hello FW/1!

When you access the application, it should say Hello FW/1!

Adding a Controller

When you ask for action=section.item FW/1 looks for section.cfc in a controllers folder and, if present, invokes the item() method on it (and then displays the matching view). Since the default action is main.default, here’s what we need to do to add our default controller:

Change views/main/default.cfm to contain:

<cfoutput>Hello #rc.name#!</cfoutput>

Add controllers/main.cfc with a method, default(), that takes a single struct argument called rc (for request context) and then add:

<cfparam name="rc.name" default="anonymous">

to that function. Note that controller CFC names must be all lowercase.

When you access the application now, it should say Hello anonymous! but if you put ?name=Sean on the URL, it should say Hello Sean! The request context passed to the controller contains all the URL and form variables from the browser and is also made available to the view directly.

Controllers are cached. Add ?reload=true to the URL to reload your controllers.

Adding a Service

Whilst you can keep adding functionality to your controllers, a well-structured MVC application tries to keep the controllers lightweight and delegate all the business logic to the “Model” of your application. The Model is generally exposed to your controllers through a service layer.

Although it is recommended that you control your own service invocation in controllers FW/1 has a convention for services: CFCs in a folder called services which can be invoked through a method in the framework. In order to do that, your controller needs a constructor which takes FW/1 as an argument and stores it in variables scope, like this:

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

Now, inside your default() controller method, remove the cfparam and put in a call to a service (NOTE: this method of invoking services has been generally abandoned by the FW/1 community in favor of manually calling methods on your services, often by using DI/1, ColdSpring, or other similar dependency injection packages):

variables.fw.service( "name.default", "name" );

This tells FW/1 to queue up a call to the default() method in the name.cfc service. When FW/1 calls the service, after the controller method has completed, it will put the result in rc.name. FW/1 passes the request context to the service as a set of named arguments (so the service accessing named elements of the request, not the request context itself). Now we’ll add that service. Create a services folder and put a name.cfc file in it containing this:

component {
    function default( string name = "anonymous" ) {
        return "so-called " & name;
    }
}

Your views/main/default.cfm file should already contain this:

<cfoutput>Hello #rc.name#!</cfoutput>

When you access the application now with ?name=Sean on the URL, it should say Hello so-called Sean!

Services are cached. Add ?reload=true to the URL to reload your services.

Prior to FW/1 2.0, a service method was automatically called, with a name that matched the action and the result was placed in rc.data. FW/1 1.2 introduced a configuration variable to control this behavior and that variable is still present in 2.0, but the default behavior has changed so that service methods are not called automatically.

Adding a Layout

When you ask for action=section.item FW/1 looks for layouts/section/item.cfm to find a specific layout (it also knows how to look for default layouts for sections and for applications, I’ll cover that later). The basic view is passed in as a variable called body. Let’s try this for our default action, main.default:

Create layouts/main/default.cfm containing:

<h1>Welcome to FW/1!</h1>
<cfoutput>#body#</cfoutput>

Layout filenames, like view filenames, must be all lowercase.

When you access the application now, it should have Welcome to FW/1! as a heading above the previous output.

Next Steps

Read the Developing Applications with FW/1 and Using Subsystems with FW/1 sections below. The Reference Manual is a work in progress.

Developing Applications with FW/1

For an example-based approach to building applications with FW/1, read the [[Developing Applications Manual]].

Using Subsystems with FW/1

FW/1 allows you to combine applications in a modular fashion to create a larger application. This feature was primarily contributed by Ryan Cogswell with documentation by Dutch Rapley. Read about [[Using Subsystems]] to combine your FW/1 applications.

Reference Manual

For a detailed description of the framework’s API, read the [[Reference Manual]].

Reference Manual

This page provides a description of all the APIs and components involved in a FW/1 application. Please also read the [[Roadmap]] to see how things may change in the future.

FW/1 Controllers

A controller in a FW/1 application does not need to extend any base component.

A controller method is passed a single struct called rc (request context). This structure initially contains all the URL and form scope variables passed into the request. Form scope takes precedence (i.e., when the same key appears in both URL and form scope, the value for that key in the request context is taken from form scope).

A controller communicates data to other parts of a FW/1 application by adding data to rc.

The following public methods are significant in a controller (and are all optional):

  • void before(struct rc) – called at the start of each request to this section
  • Deprecated: void startItem(struct rc) – called next, for section.item
  • void item(struct rc) – called next, for section.item
  • Deprecated: void endItem(struct rc) – called after services have executed, for section.item
  • void after(struct rc) – called at the end of each request to this section before views are rendered.

As of FW/1 2.0, your Application.cfc is also considered a controller and if it defines before() or after() methods, those will be called at the start and end of the controller lifecycle. Unlike other controllers, it does not need an init() method and instead of referring to the FW/1 API methods via variables.fw… you can just use the API methods directly – unqualified – since Application.cfc extends the framework and all those methods are available implicitly.

If the controller needs to invoke FW/1 API methods (see org.corfield.framework below), the controller must have a constructor (a method called init()) that accepts an instance of the FW/1 CFC and saves it for use in other methods. The following is the recommended way to write your controller’s constructor:

<cfscript>
function init(fw) {
  variables.fw = fw;
}
</cfscript>

Within other controller methods, you can then invoke FW/1 API methods using variables.fw.apimethod(args)

A controller is instantiated on the first request to an item in that section and is cached in application scope. You can ask FW/1 to reload the cache at any point (by default, you add ?reload=true to the URL).

As of release 2.0, you can abort processing of controllers and services by calling variables.fw.abortController() which throws an exception that is caught by the framework. Execution of the current controller is immediately aborted and execution continues with setupView(). If your controller code catches and swallows the exception, execution of your controller code will proceed from your catch statement until it returns but at that point the framework will not execute any further controllers and execution continues with setupView().

FW/1 Views

All views execute as included files within the context of a FW/1 request, i.e., inside the current request’s instance of Application.cfc. That means that all methods inside FW/1 and inside your Application.cfc are available directly inside your views. See the public and private API documentation below for what you can and should use of those methods! This means that you can have view helper methods in your Application.cfc, either directly or via an include of a library file.

This process also means that you are mostly free of thread safety issues (because each request automatically has its own context). That said, you need to cognizant of two things when storing data in the variables scope of a view:

  • The variables scope of a view is the variables scope of FW/1 / Application.cfc – so don’t overwrite anything the framework might need!
  • Data stored in variables scope by one view is accessible to other views executed in the same request such as those views rendered by calls to view() in layouts.

You can avoid those concerns by using local scope for any new variables introduced inside the view. This is actually a struct created by the view() method (so it works on all versions of all CFML engines).

Each view has the following two important variables available to it:

  • local – A struct that can be safely used for new variables created by the view. See above.
  • rc – The request context struct. This is the recommended way for views to communicate if they have a need to do so (e.g., setting a page title in a view so that a layout or later view can render it). rc contains whatever data the controller and service(s) have provided, as well as what was originally in the URL and/or form scopes.

The following variables are also available but should not be relied upon as they are implementation details that may change in the future:

  • path – The path to the view (as passed to the view() method).
  • pathInfo – A struct containing the base directory (containing the views/ folder) and the path to the view (below the views/ folder).
  • response – Another local variable that will be overwritten by the output of the rendered view.

Any other variables assigned to by a view (without a scope qualifier – or explicitly with variables scope) are part of the FW/1 instance.

If no matching view file exists for a request, onMissingView() is called and whatever is returned is used as the text of the view, and layouts are applied (unless they are suppressed). The default implementation is to throw an exception but by overriding this method you can create any behavior you want for requests that have no specific view, e.g., you can return a JSON-encoded version of certain data or a default view or pretty much anything you want.

As noted in the [[Developing Applications Manual]], 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 defaultSection is main). This can lead to exceptions being masked and instead appearing as if you have a missing view!

FW/1 Layouts

Everything that applies to views above also applies to layouts. The variables that are available to layouts are the same as for views with just one addition:

  • body – This contains the rendered views / layouts so far (as the layouts cascade).

Layouts have exactly the same access to the FW/1 API as views and all the same considerations apply.

FW/1 and Bean Factories

FW/1 can be used without a bean factory but, well, it’s really not recommended since
DI/1 is so easy to use (and you can also use WireBox or ColdSpring). It does not
depend on ColdSpring or any other sort of object factory. One of the examples that
ships with FW/1 (2.2 and earlier) uses a simple object factory to manage its CFCs as
an example of how FW/1 can leverage a bean factory. In Release 2.5, the examples were
converted to use DI/1 as the easiest way to manage the CFCs in your application’s
business model.

By default, i.e., without a bean factory, FW/1 will create your controller and,
in Release 2.2 and earlier, service CFCs and cache them automatically (in application
scope). If you want to access / manage other CFCs, you can do so manually in the
init() method of your controller and/or service CFC. Storing references to those
other CFCs into the variables scope of your controller and/or service CFC will
effectively cache those other CFCs in application scope. When FW/1 is reloaded, the
cache is cleared and the controller, service and those other CFCs will all be recreated
on demand as new requests come in.

You may choose to leverage a bean factory in order to automate the management of those
other CFCs and, if you wish, your controller and service CFCs. FW/1 accepts anything
as a bean factory as long as it implements these two methods:

  • public boolean function containsBean( string name ) – returns true if the factory contains that named bean, otherwise false
  • public any getBean( string name ) – returns the named bean

Default Bean Factory

This applies to a top-level FW/1 application, i.e., in the absence of subsystems or for the home application that controls a set of subsystems.

You tell FW/1 about your bean factory by calling the setBeanFactory( factory ) API method, passing in an instance of your bean factory. If you call getBeanFactory(), you’ll get back that same instance – and this is how FW/1 accesses the bean factory internally.

Once FW/1 has been told about a bean factory, it uses it in three situations:

  • When a controller CFC is needed, it will look for sectionController as a bean first, and if that does not exist it will attempt to create the controllers.section CFC (as would be the case when no bean factory is present).
  • In Release 2.2 and earlier, when a service CFC is needed, it will look for sectionService as a bean first, and if that does not exist it will attempt to create the services.section CFC (as would be the case when no bean factory is present).
  • When a controller (or service) CFC instance has been obtained (from the bean factory or by creating it directly), it uses the bean factory to autowire dependencies into the controller
    (or service) CFC instance.

Auto Wiring

FW/1 looks at all the public methods of a controller (or service) CFC for methods that begin with set (aka setters) and if there is a matching bean in the factory, invokes that method passing the bean as an argument. Release 2.0 supports property-based autowiring as well as explicit setters. For example:

If your controller has a public method called setUserManager(), the framework will call containsBean(“UserManager”) on the bean factory and if that returns true, it will call getBean(“UserManager”) on the bean factory to get the bean, and then setUserManager(bean) on controller. With FW/1 2.0, you could omit the setter and use property-based injection instead: declare a property called userManager and specify accessors=“true” on your controller CFC.

Note that FW/1 does not check the arguments on the setters nor the type (or even presence) of the value returned from the bean factory – it’s just follows the conventions and it expects you to follow them too!

Subsystem Bean Factories

If you are using subsystems, you can tell FW/1 about a separate bean factory for each subsystem using the setSubsystemBeanFactory( subsystem, factory ) method. See the UsingSubsystems chapter for details on ways to call this method conveniently to set up your subsystem-specific bean factories.

When you are using subsystems, FW/1 will only look in the subsystem-specific bean factory for sectionController or sectionService beans. This ensures that a subsystem can only ‘see’ the controller and/or service beans that it is expecting and it cannot accidentally be slipped a controller and/or service from another subsystem in the overall application.

Autowiring is handled slightly differently. If a subsystem-specific bean factory is present, autowiring is done using only that bean factory. If no subsystem-specific bean factory is present, autowiring will be done using the default, top-level bean factory if one is present. This allows for FW/1 subsystems to have beans plugged into them by convention when they are used as part of a large application but also ensures that if a subsystem-specific bean factory is present, no unexpected beans will be autowired. This lets you use conventions to locate controllers and services while still relying on a single bean factory for the entire application for autowiring.

One thing to be aware of here is that the complete application also needs to use a subsystem-specific bean factory – there is no parent application as such, just a default subsystem. When you are using subsystems, FW/1 does not look in the default bean factory for controllers or services. This makes sense because the overall application itself relies on a specific subsystem as its default. The default convention is that the overall application is the home subsystem and therefore controllers, services and autowiring will be done against the home bean factory. You can still have a default bean factory but it is a separate bean factory that can be retrieved explicitly using the getDefaultBeanFactory() API method (and will only be used for autowiring if no subsystem-specific bean factory is present).

Parent Bean Factories

A useful pattern with subsystems that use bean factories may be to set the default bean factory as the parent bean factory for all of the subsystem-specific bean factories. This is only possible with a bean factory that supports the concept of a parent,
which the popular bean factories do, such as DI/1, WireBox, or ColdSpring. This allows beans to be common across multiple subsystems so that a single instance can be shared, rather than each subsystem having its own instance.

Since parent bean factories are only checked when a child factory does not contain the requested bean, the parent factory cannot override the child factory, but if you are refactoring subsystems as you assemble them, it is an opportunity to remove beans from the subsystem factory configurations and place them in the default, parent factory instead.

This should encourage pluggable subsystems to be created, where the subsystem is not a fully-functional standalone application but instead expects certain beans to be provided by the overall application. For example, a security subsystem could be written that relies on “userManager” and “roleManager” beans, for example, provided by the overall application as a standard way to access user information and permissions.

Request Scope

FW/1 uses the request scope fairly extensively to pass data between parts of the application in order to keep things simple and decoupled (from a developer’s point of view). This section documents all of the request scope variables used by the framework. In general, you should not reference any of these request scope variables: there are supported API methods that you should use instead for the data that you might want to access (and those are documented in the following list).

Request variables:

  • request._fw1 – An opaque structure introduced in version 2.1 to hide most of the internal request scope variables that FW/1 uses.
  • request.action – The action specified in the URL or form (after expansion to the fully qualified form). This can be obtained by calling getFullyQualifiedAction().
  • request.base – The canonical path to the application directory (where the views/ and layouts/ folders are). There is no API method to access this.
  • request.cfcbase – The dot-separated path prefix for the controller and service CFCs. There is no API method to access this.
  • request.context – The request context, the main “data bus” through the application. This is available in controller methods as the argument rc and in the views and layouts as the (local) variable rc. It is also available in lifecycle extension point methods as rc in variables scope.
  • request.event – When an error action is executed, this holds the Application.cfc event in which the exception occurred (it is the argument to the onError() method). An API method may be added in future to access this.
  • request.exception – When an error action is executed, this holds the original exception that occurred (it is the argument to the onError() method). An API method may be added in future to access this.
  • request.failedAction – When an error action is executed, this holds the original action being processed when the exception occurred (it is the argument to the onError() method). If the exception occurred before the point at which the action is determined, request.failedAction will not be defined. An API method may be added in future to access this.
  • request.failedCfcName – If an exception occurs during execution of a controller or service, this holds the name of the failed controller or service CFC. This can be accessed in the error action to provide more details of where the exception occurred. An API method may be added in the future to access this.
  • request.failedMethod – If an exception occurs during execution of a controller or service, this holds the name of the failed method (on the controller or service CFC). This can be accessed in the error action to provide more details of where the exception occurred. An API method may be added in the future to access this.
  • request.item – The item portion of the action. This can be obtained by calling getItem() (with no argument).
  • request.layout – This is a boolean that indicates whether layouts should be rendered. It can be set in a view or layout to prevent any further layouts from being processed. This is the only request scope variable which a developer should be updating directly. An API method may be added in future for this.
  • request.missingView – When onMissingView() is triggered, this hold the name of the view that was not found. An API method may be added in future to access this.
  • request.section – The section portion of the action. This can be obtained by calling getSection() (with no argument).
  • request.subsystem – The subsystem portion of the action, if appropriate. This can be obtained by calling getSubsystem() (with no argument).
  • request.subsystembase – The path from the main application directory to the current subsystem’s directory (where the views/ and layouts/ folders are). This can be obtained by calling getSubsystemBase().

The following request variables have been removed as of version 2.1:

  • request.controllerExecutionComplete – This is present (and true) when the controllers have all been executed in a request. This is used to prevent subsequent calls to the layout() and view() API methods. There is no API method to access this.
  • request.controllers – This holds the sequence of controllers that have been requested. There is no API method to access this (and you shouldn’t touch it!).
  • request.generateSES – If an SES URL was used for the requested action, this is true and causes FW/1 to attempt to generate SES URLs automatically. See also variables.framework.generateSES which overrides this.
  • request.layouts – This holds the cascade of layouts that have been discovered by convention, to be rendered in order (from most specific to site-wide). There is no API method to access this (and you shouldn’t touch it!).
  • request.overrideLayoutAction – This holds the action specified by setLayout() to override the default convention for locating the layouts. There is no API method to access this (and you shouldn’t touch it!).
  • request.overrideViewAction – This holds the action specified by setView() to override the default convention for locating the view and layouts. There is no API method to access this (and you shouldn’t touch it!).
  • request.serviceExecutionComplete – This is present (and true) when the services have all been executed in a request. This is used to prevent subsequent calls to the service() API method. There is no API method to access this.
  • request.services – This holds the sequence of services that have been requested (the initial default service and those added via the service() API call). There is no API method to access this (and you shouldn’t touch it!).
  • request.view – The path to the view (relative to the views/ folder). There is no API method to access this (and you shouldn’t touch it!).

Where there is no API method, there is generally an underlying assumption that a developer should not need access to that data. If it turns out that developers are commonly referring to the underlying request scope variables due to various needs, the addition of API methods might be considered.

org.corfield.framework

This section documents every method in FW/1’s core file. Public methods are listed first, then private methods.

Public API Methods

These methods are callable from outside the framework and are intended to be used with controllers, layouts and views or may be intended to be overridden in you Application.cfc. Technically, methods in FW/1 do not need to be public to be called from within layouts, views or Application.cfc but for the most part, the convention is: if it’s public, it’s documented and guaranteed to remain part of the API; if it’s private, it may be documented (or it may not) and it may be called within layouts, views and Application.cfc but it is not recommended.

public void function abortController()

Attempts to immediately abort execution of the current controller by throwing an exception (which is caught by the framework). If your controller catches this exception, execution will continue until your controller returns. No further controller or service methods will be called. Execution will continue with the setupView() lifecycle method and views and layouts will then be rendered. Added in 2.0.

public boolean function actionSpecifiesSubsystem( string action )

Returns true if the application is using subsystems and the action contains a colon : (default – framework.subsystemDelimiter). Returns false if the application is not using subsystems or the action does not contain a colon : (default – framework.subsystemDelimiter).

public string function buildCustomURL( string uri )

Used in views and layouts to generate route-based links.
Generates a URL prefix, the same way buildURL() does, and appends uri.

public string function buildURL( string action = ‘.’, string path = see below, any queryString = ’’ )

Used in views and layouts to generate links to FW/1 actions. Produces “traditional” links if variables.framework.generateSES is false and the current request used a “traditional” URL. Produces “SES” links if variables.framework.generateSES is true or the current request used an “SESURL. The optional path argument allows you to override the default base URL used for links in the same way that redirect() allows below. Note that specifying path will disable generation of “SES” links! As of FW/1 2.0, path will default to the result of calling getBaseURL() which in turn defaults to variables.framework.baseURL, unless overridden by a subsystem-specific configuration.

The optional queryString argument allows to specify URL variables (and values) that should be added to the generated URL. In general, variable / value pairs should be specified with an equals = and separated with an ampersand & and they will be appended either as-is, for “traditional” link generation, or converted to “SES” format as appropriate. The “SES” conversion can be overridden by preceding a sequence of variable / value pairs with ? in which case such arguments will be appended as-is for both forms of generated link. Finally, an HTML anchor may be specified, preceded by # and that will be appended to the final URL.

As a shortcut, the action may include the queryString value, separated by ? like this: ‘section.item?arg=val’ (with all the same considerations for embedded & and ? and # characters).

As of release 2.0:

  • queryString also accepts a structure, which is converted to an HTML escaped query string.
  • action can be ‘.’, in which case a link to the current section and item is returned.

Here are some examples:

buildURL( 'product.list' )

Will generate:

  • /index.cfm?action=product.list – in “traditional” mode
  • /index.cfm/product/list – in “SES” mode
  • /product/list – in “SES” mode with SESOmitIndex set to true
buildURL( action = 'product.detail', queryString = 'id=42?img=large##overview' )

Will generate:

  • /index.cfm?action=product.detail&id=42&img=large#overview – in “traditional” mode
  • /index.cfm/product/detail/id/42?img=large#overview – in “SES” mode
  • /product/detail/id/42?img=large#overview – in “SES” mode with SESOmitIndex set to true
buildURL( 'product.detail?id=42?img=large##overview' )

Will also generate:

  • /index.cfm?action=product.detail&id=42&img=large#overview – in “traditional” mode
  • /index.cfm/product/detail/id/42?img=large#overview – in “SES” mode
  • /product/detail/id/42?img=large#overview – in “SES” mode with SESOmitIndex set to true

As of release 2.0:

buildURL( '.list' )

Will generate a URL based on the current section – a section-relative link to the current section’s list item.

buildURL( action = 'product.detail', queryString = { id = 76, img = 'small' } )

Will generate:

  • /index.cfm?action=product.detail&id=76&img=small – in “traditional” mode
  • /index.cfm/product/detail/id/76/img/small – in “SES” mode
  • /product/detail/id/76/img/small – in “SES” mode with SESOmitIndex set to true

public void function controller( string action )

Call this from your Application.cfc methods to add to the queue of controllers that will be called by the framework. The action is used to identify the controller that should be called, e.g., “app1:section.item”, “section.item” or “section” (which will call the default item with that section).

A typical example is to trigger a security controller method invoked from setupRequest(), e.g.,

controller( 'security.checkAuthorization' );

You may only queue up additional controller prior to the start of controller execution (in onRequest()). If you attempt to queue up additional controllers in controller methods (or later in the request), you will get an exception because at that point all controllers have been queued up and/or executed.

Just like the implicit controller invocation, before(), startItem(), item(), endtItem() and after() are all invoked appropriately if present. If multiple methods are queued up from a single controller, before() and after() are executed just once (for each controller).

public string customizeViewOrLayoutPath( struct pathInfo, string type, string fullPath )

By default, this simply returns fullPath.

It can be overridden to provide customized handling of view and layout locations. Everywhere that FW/1 needs to figure out the location of a view or layout based on conventions, it calls this method. See the skinning example in the FW/1 distribution that shows how this method can be used to provide automatic overrides of the default conventions.

The arguments are as follows:

  • pathInfo – This struct contains two keys: base which is the application-relative path to the location of the (default) views and layouts folders; path which is the relative path below those folders. For example, for a request of “sub:section.item”, this structure will contain: base=“sub/”, path=“section/item”. The base will have already been adjusted to wherever the views / layouts are supposed to be if you have framework.base set.
  • type – Either “view” or “layout”.
  • fullPath – The default location of the view or layout: “#pathInfo.base##type#s/#pathInfo.path#.cfm”. For the example above, this would be “sub/views/section/item.cfm” for a view.

Additional documentation will be provided for this feature in due course. Probably an entire section on skinning applications with FW/1.

public void function disableFrameworkTrace()

Disable framework tracing for this request.

public void fuction enableFrameworkTrace()

Enable framework tracing for this request.

public void function frameworkTrace( string message [, any value ] )

Adds the message and optional value to the framework trace data, for rendering at the end of the request.
New in Release 2.5

public string function getAction()

Returns the name of the action variable in the URL or form post (variables.framework.action).

public string function getBaseURL()

Returns the configured variables.framework.baseURL value. Can be overridden in Application.cfc to provide a customized value, e.g., per request. Added in FW/1 2.0.

public any function getBeanFactory( string subsystem = "" )

Returns whatever the framework has been told is a bean factory. If you are using subsystems, this will return a subsystem-specific bean factory if one exists for the specified subsystem, or for the subsystem of the current request if no subsystem is specified in the call. Otherwise it will return the default bean factory.

More specifically, if you are not using subsystems:

  • getBeanFactory() – returns the bean factory for the application.
  • getBeanFactory(subsystem)subsystem is ignored and this returns the bean factory for the application.
    If you are using subsystems:
  • getBeanFactory() – returns the subsystem bean factory for the subsystem of the current request if one exists, otherwise the default bean factory for the application.
  • getBeanFactory(subsystem) – returns the subsystem bean factory for the named subsystem if one exists, otherwise the default bean factory for the application.

If no application bean factory can be found, this will throw an exception. Use hasBeanFactory() and/or hasSubsystemBeanFactory( subsystem ) to determine whether this call will successfully return a bean factory.

public struct function getConfig()

Returns a copy of the framework structure containing the configuration settings specified in Application.cfc. This allows controllers to inspect the FW/1 configuration settings if necessary.

public any function getDefaultBeanFactory()

Returns whatever the framework has been told is a bean factory. This will return the default, top-level bean factory for the application. If no such bean factory exists, this will throw an exception. Use hasDefaultBeanFactory() to determine whether the default, top-level bean factory exists for the application.

public string function getDefaultSubsystem()

If the application is not using subsystems, this returns an empty string. If the current request’s action specifies a subsystem, return that. Otherwise return the default subsystem configured for the application.

If the application is using subsystems and the current request’s action does not specify a subsystem and there is no default subsystem configured, this will throw an exception.

public string function getEnvironment()

Returns an empty string by default. If you want to use the Environment Control feature, you should override this in Application.cfc and have it return the “tier” or “tier-server” string. See Environment Control in the [[Developing Applications Manual]] for more detail. Added in 2.1

public array function getFrameworkTrace()

Returns an array of the current request’s framework trace data. See setupTraceRender() for one reasonable use.

public string function getFullyQualifiedAction( string action = request.action )

If the application is not using subsystems, this behaves the same as getSectionAndItem( action ).

If the application is using subsystems, this returns the specified action formatted as subsystem:section.item.

public string function getHostname()

Returns the server’s local hostname (via Java’s InetAddress class). Intended to be used with Environment Control. Added in 2.1

public string function getItem( string action = request.action )

Returns the item portion of the specified action – or of the current request’s action if no action is specified. Returns the default item if no item is present in the specified action.

public string function getSection( string action = request.action )

Returns the section portion of the specified action – or of the current request’s action if no action is specified. Returns the default section if no section is present in the specified action.

public string function getSectionAndItem( string action = request.action )

Returns the specified action formatted as section.item. Automatically strips the subsystem from the action, if present. Automatically adds the default section if not present. Automatically adds the default item if not present.

public string function getServiceKey( string action )

Effectively deprecated in Release 2.5 since this is only used for the implicit service queue call.

Returns the request context key to be used for the default service call’s result – if suppressImplicitService is false. This returns “data”. You can override this in Application.cfc to customize how that default service call behaves (i.e., where that result is stored). For example, you might return getSection( action ) so that service calls in the user section of the site are stored in rc.user and service calls in the product section of the site are stored in rc.product.

public string function getSubsystem( string action = request.action )

Returns the subsystem portion of the specified action – or of the current request’s action if no action is specified. Returns the default subsystem if no subsystem is present in the specified action. If the application is not using subsystems, this returns the default subsystem name (which is an empty string by default).

public string function getSubsystemBase()

Returns the path to the folder where the current request’s subsystem views/ and layouts/ subfolders can be found. This can be useful for customer versions of buildURL() etc. Added in version 2.1

public any function getSubsystemBeanFactory( string subsystem )

Returns whatever the framework has been told is a bean factory. This will return the bean factory for the named subsystem. If no such bean factory exists, this will throw an exception. Use hasSubsystemBeanFactory( subsystem ) to determine whether a bean factory exists for the named subsystem. If this is the first reference to this subsystem, the subsystem will be initialized.

public struct function getSubsystemConfig( string subsystem )

Returns the configuration for the named subsystem, as a copy of variables.framework.subsystems[subsystem]. If no configuration exists for the named subsystem, an empty struct is returned. FW/1 uses this to retrieve the per-subsystem baseURL value, if any, as part of buildURL() and redirect(). Added in FW/1 2.0.

public boolean function hasBeanFactory()

Returns true if a default, top-level bean factory exists. If using subsystems, returns true if a bean factory exists for the subsystem of the current request. Otherwise returns false. If hasBeanFactory() returns true, calling getBeanFactory() will return a bean factory.

public boolean function hasDefaultBeanFactory()

Returns true if a default, top-level bean factory exists. Otherwise returns false. If hasDefaultBeanFactory() returns true, calling getDefaultBeanFactory() will return a bean factory.

public boolean function hasSubsystemBeanFactory( string subsystem )

Returns true if a bean factory exists for the named subsystem. Otherwise returns false. If hasSubsystemBeanFactory( subsystem ) returns true, calling getBeanFactory( subsystem ) and getSubsystemBeanFactory( subsystem ) will both return a bean factory (for the named subsystem).

public boolean function isCurrentAction( string action )

Returns true if the action passed in matches the currently executing action.
This can be useful to figure out which tab to highlight in navigation or make other
choices based on actions.

public string function layout( string path, string body )

This function renders a layout and could be called inside a view or a layout, although it is recommended to rely on the conventions for layouts where possible. If you decide you need to render a layout directly, you can invoke it like this:

writeOutput( layout( 'main/nav-template', nav_menu ) );

Rendering views, using the view() method, is supported, documented and the recommended way to build composite pages. Layouts should simply wrap views, in a cascade from item to section to site.

public function onApplicationStart()

Part of the standard CFML lifecycle, this method is called automatically by the CFML engine at application startup. You should not override this (even tho’ it is public). Use setupApplication() to perform application-specific initialization.

If you override this method, you must call super.onApplicationStart().

public function onError( any exception, string event )

The standard CFML error handler, this method is called automatically by the CFML engine in the event of an uncaught exception. By default, FW/1 will try to execute the action specified by variables.framework.error. The action that caused the exception, if known, will be available in request.failedAction. The exception and event are available as request.exception and request.event respectively. If the error action fails, FW/1 tries to display the original exception and event in a simple error view (using the failure() method in the private API below).

You may override this method in Application.cfc if you wish to provide different error handling behavior. You may call super.onError( exception, event ) if appropriate. You may also consider overriding the failure() method.

public string function onMissingView( struct rc )

Called when a view is not found for a request. The default behavior is to call viewNotFound() which throws an exception.

You may override this method to provide alternative behavior when a view is not found. You should either throw an exception or return a string that represents the view that should be rendered instead. For example:

function onMissingView( rc ) {
  return view( 'page/notFound' );
}

public void function onPopulateError( any cfc, string property, struct rc )

Called when a exception occurs during an attempt to populate the named property of the specified cfc if no keys were specified for populate() and those keys were trusted. This method does nothing, effectively causing the exception to be ignored.

If you intend to call populate() with no keys specified and you tell it to trust what it finds in the request context, you may wish to override onPopulateError() and do something like log such failed attempts. See populate() below.

public function onRequest( string targetPage )

Part of the standard CFML lifecycle, this method is called automatically by the CFML engine to handle each request. You should not override this (even tho’ it is public).

If you override this method, you must call super.onRequest( targetPage ).

public function onRequestStart( string targetPage )

Part of the standard CFML lifecycle, this method is called automatically by the CFML engine at the beginning of each request. You should not override this (even tho’ it is public). Use setupRequest() to perform request-specific initialization.

If you override this method, you must call super.onRequestStart( targetPage ).

public function onSessionStart()

Part of the standard CFML lifecycle, this method is called automatically by the CFML engine when each new session is created. You should not override this (even tho’ it is public). Use setupSession() to perform session-specific initialization.

If you override this method, you must call super.onSessionStart().

public any function populate( any cfc, string keys = "", boolean trustKeys = false, boolean trim = false )

Automatically populates an object with data from the request context. For any public method setKey() on the object, if rc.key exists, call the method with that value. If the optional list of keys is provided, only attempt to call setters for the specified keys in the request context. Mainly useful for populating beans from form posts. Whitespace is permitted in the list of keys for clarity, e.g., “firstname, lastname, email”. This approach relies on setter methods in the object. It won’t detect and use onMissingMethod(). In FW/1 1.x, it won’t detect property-based setter methods. FW/1 2.0 does detect property-based setters.

As of 1.2, you can specify trim = true and FW/1 will call trim() on each item before calling the setter. In 1.1 and earlier, the trim argument was not present.

For populate() to work with onMissingMethod() (or property-based setters in FW/1 1.x), you need to specify trustKeys = true. If you specify the list of keys, populate() will not test whether the setter exists, it will just call it – which means that onMissingMethod() and property-based setters will be invoked automatically. If you omit the keys, be careful because populate() will cycle through the entire request context and attempt to set properties on the object for everything! A try/catch is used to suppress any exceptions in this case but you need to be aware that this may be a little dangerous if you have a lot of data in your request context that does not match the properties of the object! If an exception is caught, onPopulateError() is called with the object, property name and the request context structure as its three arguments. The default behavior is to simply ignore the exception but you can override that if you want – see onPopulateError() above.

As of 1.2, populate() returns the cfc passed in. In 1.1 and earlier, the return type was void.

Populating Child Objects (Added In Version 2.1)

If trustKeys = true or if deep = true the populate method will try to populate data on child objects of the cfc argument. In order to populate a child component “dot” notation is used in the request context’s properties. For example, to set the firstName property on a Contact component that is a child of the cfc that is being passed into populate the following key would be added to the request context:

contact.firstname

This would cause populate to traverse the cfc argument like so:

cfc
|-- getContact
|    |-- setFirstName

Child properties can also be nested many levels deep. The following key would populate the line 1 property of an address cfc that is a child of the contact cfc that is a child of the cfc argument passed into the populate method:

contact.address.line1

This would cause populate to traverse the cfc argument like so:

cfc
|-- getContact
|    |-- getAddress
|    |    |-- setLine1

public void function redirect( string action, string preserve = “none”, string append = “none”, string path = see below, string queryString = ‘’, string statusCode = ’302’ )

This constructs a URL based on the action and optional path and redirects to it. If preserve is “all”, the entire contents of the request context are saved to session scope across the redirect (and restored back to the request context automatically after the redirect). If preserve is a list of keys, just those elements of the request context are preserved. If append is “all”, all simple values in the request context are appended to the constructed URL as a query string before the redirect. If append is a list of keys, just those elements of the request context are appended (if they are simple values). statusCode was added in version 2.1.

If path is specified, that base URL is used instead of the default, as per buildURL() above.

The queryString argument may be used to append additional URL variables / values to the constructed URL, as explained in buildURL() above. Or the query string value may be combined with the action also as explained in buildURL() above.

For example:

variables.fw.redirect( action = 'blog.entry', append = 'id', queryString = '##comment' )

Will generate:

  • /index.cfm?action=blog.entry&id={rc.id}#comment – in “traditional” mode
  • /index.cfm/blog/entry/id/{rc.id}#comment – in “SES” mode
  • /blog/entry/id/{rc.id}#comment – in “SES” mode with SESOmitIndex set to true

public void function renderData( string contentType, any resultData, numeric statusCode = 200 )

Call this from your controller to tell FW/1 to skip views and layouts and instead render resultData in the specified contentType format. contentType may be “json”, “xml”, or “text”.

For “json”, FW/1 calls serializeJSON( resultData ) to generate the result of the HTTP request and sets the Content-Type header to application/javascript; charset=utf-8.

For “xml”, the resultData value must be either a valid XML string or an XML object (constructed via CFML’s various xml…() functions). If resultData is an XML object, FW/1 calls toString( resultData ) to generate the result of the HTTP request, otherwise the XML string is used as the result of the request. In both cases, FW/1 sets the Content-Type header to text/xml; charset=utf-8.

For “text”, the resultData value must be a string and that is the result of the HTTP request. FW/1 sets the Content-Type header to text/plain; charset=utf-8.

When you call renderData(), processing continues in your controller (so use return; if you want processing to stop at that point), and subsequent calls to setView() or setLayout() will have no effect (since FW/1 will ignore views and layouts for this request).

Added in FW/1 2.2.

public void function service( string action, string key, struct args = { } )

Deprecated in Release 2.5. Will be removed in Release 3.0.

If you wish to use this API method with Release 2.5, you must set suppressServiceQueue to false,
but this is only intended as a migration aid so you can prepare for Release 3.0.

Call this from your controller to add to the queue of services that will be called by the framework. The action is used to identify the service that should be called, e.g., “app1:section.item”, “section.item” or “section” (which will call the default item with that section). The key specifies which request context variable should receive the result of the service call. If the key is an empty string, the result of the service call, if any, will be ignored. The optional args struct specifies additional values that will be supplied as arguments to the service call without being part of the request context, e.g.,

service( 'product.get', 'product', { id = rc.productid } );

You may only queue up services in the before(), startItem() and item() methods in your controller. If you attempt to queue up services in other controller methods (endtItem() or after()), you will get an exception because at that point all services have been called.

There is an optional fourth argument, enforceExistence, which defaults to true. The intent is that when you explicitly queue up a service call, it should be an error if the named service does not exist. When FW/1 1.x used to queue up the initial convention-based service call, it used service( action, getServiceKey( action ), { }, false ) so that the service was allowed not to exist. If you want to provide optional, pluggable services, you might specify false as a third argument to allow for this.

public void function setBeanFactory( any factory )

Call this from your setupApplication() method to tell the framework about your primary (default) bean factory. Any bean factory will work as long as it implements the following two methods:

  • public boolean function containsBean( string name ) – returns true if the factory contains that named bean, otherwise false
  • public any getBean( string name ) – returns the named bean

public void function setLayout( string action )

Call this to tell the framework to use a new action subsystem:section.item as the basis of the lookup process for the layouts for the current request. This allows you to override the default convention for choosing the layouts. Added in version 2.0.

public void function setSubsystemBeanFactory( string subsystem, any factory )

Call this to tell the framework about a subsystem-specific bean factory. Again, the bean factory must support containsBean( name ) and getBean( name ). You would typically call this method from your setupSubsystem() method.

public void function setupApplication()

Override this in Application.cfc to provide application-specific initialization. If you want the framework to use a bean factory and autowire controllers and services, this is where you should call setBeanFactory( factory ). You do not need to call super.setupApplication().

public void function setupEnvironment( string env )

Override this in Application.cfc to provide environment-specific initialization. See Environment Control in the [[Developing Applications Manual]] for more detail. Added in 2.1

public void function setupRequest()

Override this in Application.cfc to provide request-specific initialization. You do not need to call super.setupRequest().

public void function setupResponse()

Override this in Application.cfc to provide request-specific finallization. This is called after all views and layouts have been rendered or immediately before a redirect. You do not need to call super.setupResponse(). Added in FW/1 2.0.

public void function setupSession()

Override this in Application.cfc to provide session-specific initialization. You do not need to call super.setupSession().

public void function setupSubsystem( string subsystem )

Override this in Application.cfc to provide subsystem-specific initialization. If you want the framework to use subsystem-specific bean factories and autowire controllers and services for each subsystem, this is where you should call setSubsystemBeanFactory( subsystem, factory ). See the example in UsingSubsystems for more details. You do not need to call super.setupSubsystem().

public void function setupTraceRender()

This is called when the framework trace is about to be rendered at the end of a request.
You can override it to take control of the rendering process yourself (for whatever reason
such as saving the data to a database perhaps or providing a fancier rendering?). You can
call getFrameworkTrace() to obtain the framework’s trace data (note that this will be a
copy on Adobe ColdFusion but just a reference on Railo), and do whatever you want with it.
It’s probably a good idea to call disableFrameworkTrace() to prevent any further
additions to the framework trace data.

public void function setupView()

Override this in Application.cfc to provide pre-rendering logic, e.g., putting globally available data into the request context so it is available to all views. You do not need to call super.setupView(). Added in FW/1 2.0.

public void function setView( string action )

Call this to tell the framework to use a new action subsystem:section.item as the basis of the lookup process for the view and layouts for the current request. This allows you to override the default convention for choosing the view and layouts. A common use for this is expected to be when redisplaying a form view when errors are present.

For example:

  • user.form would display a form to allow a user to be created / edited
  • user.save would be the submit action which does validation and can call setView( ‘user.form’ ) to redisplay the user form with errors or call redirect() to a confirmation page if there are no errors and the user has been saved.

public boolean function usingSubsystems()

Returns true if the application is using subsystems, i.e., variables.framework.usingSubsystems is true. Otherwise returns false.

public string function view( string path, struct args = { }, any missingView = { } )

This renders a view and returns the output of that view as a string. It is intended to be used primarily inside layouts to render fragments of a page such as menus and other common elements. As of 1.2, elements of the args structure are appended to the local scope accessible inside the view file. For example:

<cfoutput>
<div>#view( 'common:site/header' )#</div>
<div>#view( 'nav/menu', { selected = 'home' } )#</div>
<div>#body#</div>
<div>#view( 'common:site/footer' )#</div>
</cfoutput>

This renders the header and footer items (views) from the common subsystem’s site section and the menu item (view) from the current subsystem’s nav section. Inside menu, local.selected would be available containing the string ‘home’.

As of 2.0, a controller may call view() after service execution has completed (i.e., if no services are queued up or from the *end*item controller method).

As of 2.2, missingView was added to the arguments and if not specified, and the specified view does not exist, onMissingView() will be called. If a string is passed as missingView and the specified view does not exist, the value of that argument will be returned. This allows for programmatically calculated views to be silently rendered as empty strings if they are not present. This can be useful for programmatic skins with optional elements.

Private API Methods

These methods are not intended to be called (except in special circumstances detailed under each method), nor should they be overridden in Application.cfc. The private API of FW/1 is generally only loosely documented and is subject to change without notice.

private void function autowire( any cfc, any beanfactory )

This internal method is used by the framework to wire dependencies into controllers and services using the specified bean factory.

This method should not be called, nor overridden. It may be renamed in the future.

private void function buildLayoutQueue()

This internal method is used by the framework to setup the layout queue for the request.

This method should not be called, nor overridden. It may be renamed in the future.

private void function buildViewQueue()

This internal method is used by the framework to setup the view queue for the request.

This method should not be called, nor overridden. It may be renamed in the future.

private string function cfcFilePath( string dottedPath )

This internal method is used by the framework to convert a dot-separated CFC path into a filesystem path.

This method should not be called, nor overridden. It may be renamed in the future.

private void function doController( struct tuple, string method )

This method is used to execute the specified method of a controller CFC instance.

This method should not be called, nor overridden. It may be renamed in the future.

If you want to call methods on a specific controller, such as for a security controller method invoked from setupRequest(), use the controller() method, e.g.,

controller( 'security.checkAuthorization' );

private any function doService( struct tuple, string method, struct args, boolean enforceExistence )

This method is used to execute the specified method of a service CFC instance.

This method should not be called, nor overridden. It may be renamed in the future.

private void function dumpException( any exception )

This is a wrapper for the tag and is used by the failure() method to display an exception. If you want exceptions to be displayed in a nicer format, you could override this method although a cleaner, supported way to handle uncaught exceptions may be provider in the future.

This method should not be called. It may be renamed in the future.

private void function ensureNewFrameworkStructExists()

This was introduced to provide a smooth upgrade path when subsystems were introduced. That upgrade introduced new structures inside FW/1. This method ensures that if you have an earlier version of FW/1 running and you replace the framework CFC, those structures are automatically created in application scope so you don’t get an exception.

At present, this method is actually public but it should not be called, nor overridden. It may become private or be renamed in the future.

private void function failure( any exception, string event )

This is called if an exception occurs and FW/1 is unable to execute the error action successfully. This outputs a simple view of the exception that occurred and includes a stacktrace. The HTTP response status is set to 500 (error) instead of 200 as of version 2.1 If you wish to change the output of an exception, you could override this method in Application.cfc. See also onError() above.

At present, this method is actually public but it should not be called. It may become private in the future.

private void frameworkTraceRender()

This is called at the end of the request to render the trace information, if variables.framework.trace is true.

private any function getCachedComponent( string type, string subsystem, string section )

This is the core convention-based creation routine for controllers and services.

This method should not be called, nor overridden. It may be renamed in the future.

private any function getController( string section, string subsystem = getDefaultSystem() )

This returns the controller CFC instance for the specified section (and subsystem, if specified).

This method should not be called, nor overridden. It may be renamed in the future.

private void function getNextPreserveKeyAndPurgeOld()

This internal method is part of the machinery to preserve the request context across redirects.

This method should not be called, nor overridden. It may be renamed in the future.

private string function getPreserveKeySessionKey( string preserveKey )

This internal method is part of the machinery to preserve the request context across redirects.

This method should not be called, nor overridden. It may be renamed in the future.

private any function getService( string section, string subsystem = getDefaultSystem() )

This returns the service CFC instance for the specified section (and subsystem, if specified). Since controllers may call the public, documented service() method to queue up service invocations, this method should never be called.

At present, this method is actually public but it should not be called, nor overridden. It may become private or be renamed in the future.

private string function getSubsystemDirPrefix( string subsystem )

This is an internal utility (that returns a directory prefix for subsystems).

At present, this method is actually public but it should not be called, nor overridden. It may become private or be renamed in the future.

private void function injectFramework( any cfc )

This is called by the framework to inject itself into controllers that are managed by bean factories.

This method should not be called, nor overridden. It may be renamed in the future.

private void function internalFrameworkTrace( string message, string subsystem = ‘’, string section = ’’, string item = ’’ )

This is called by the framework at various points to trace the lifecycle of a request.

private string function internalLayout( string layoutPath, string body )

This is used to render a layout. It is called by the layout() method and internally by FW/1.

This method should not be called, nor overridden. It may be renamed in the future.

private string function internalView( string viewPath, struct args = { } )

This is used to render a view. It is called by the view() method and internally by FW/1.

This method should not be called, nor overridden. It may be renamed in the future.

private boolean function isFrameworkInitialized()

This returns true if the framework has been initialized. Otherwise it returns false.

At present, this method is actually public but it should not be called, nor overridden. It may become private or be renamed in the future.

private boolean function isFrameworkReloadRequest()

If the framework is configured to reload on every request, this returns true. If the current request includes the URL parameters to reload the framework, this returns true. Otherwise it returns false.

At present, this method is actually public but it should not be called, nor overridden. It may become private or be renamed in the future.

private boolean function isSubsystemInitialized( string subsystem )

If the specified subsystem has been initialized, this returns true. Otherwise it returns false.

At present, this method is actually public but it should not be called, nor overridden. It may become private or be renamed in the future.

private struct function parseViewOrLayoutPath( string path )

This utility method allows the framework to automatically adjust layout and view paths to real paths, whether subsystems are in use or not.

At present, this method is actually public but it should not be called, nor overridden. It may become private or be renamed in the future.

private void function raiseException( string type, string message, string detail = "" )

This is a wrapper for the tag and is used in several places within the framework to throw custom exceptions.

This method should not be called, nor overridden. It may be renamed in the future.

private string renderDataWithContentType()

This is used to set the appropriate Content-Type header and to perform the actual rendering of the data that was provided in an earlier call to renderData().

This method should not be called, nor overridden. It may be renamed in the future.

private void function restoreFlashContext()

This internal method is part of the machinery to preserve the request context across redirects. It restores data from session scope back into the request context.

This method should not be called, nor overridden. It may be renamed in the future.

private string function saveFlashContext( string keys )

This internal method is part of the machinery to preserve the request context across redirects. It saves data from the request context into session scope. If keys is “all”, every element of the request context is saved. If keys is a list of keys, only those elements are saved.

This method should not be called, nor overridden. It may be renamed in the future.

private void function setupApplicationWrapper()

This performs framework-specific initialization when an application starts up or the framework is reloaded. It calls setupApplication() (which you can override).

At present, this method is actually public but it should not be called, nor overridden. It may become private or be renamed in the future.

private void function setupFrameworkDefaults()

This sets up default values for the framework conventions. You define the framework conventions by setting key/value in the variables.frameworks struct in the pseudo-constructor of your Application.cfc. This method fills in defaults for any conventions you did not specify. It also sets the version key in that struct.

At present, this method is actually public but it should not be called, nor overridden. It may become private or be renamed in the future. It is called from a number of places within the framework to ensure that the conventions are setup correctly.

private void function setupRequestWrapper()

This performs framework-specific initialization when a request starts. It calls setupRequest() (which you can override).

At present, this method is actually public but it should not be called, nor overridden. It may become private or be renamed in the future.

private void function setupSessionWrapper()

This performs framework-specific initialization when a new session starts. It calls setupSession() (which you can override).

At present, this method is actually public but it should not be called, nor overridden. It may become private or be renamed in the future.

private void function setupSubsystemWrapper( string subsystem )

This performs framework-specific initialization when a subsystem is initialized. It calls setupSubsystem( string subsystem ) (which you can override).

At present, this method is actually public but it should not be called, nor overridden. It may become private or be renamed in the future.

private void function viewNotFound()

This is a utility method to throw an exception when a view cannot be found. You could override this method to handle a missing view. You could output a view of your choice, for example, with:

writeOutput( view( 'missing/template' ))

At present, this method is actually public but it should not be called. It may become private in the future. A better way to handle missing views may be provided in future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment