You are viewing documentation for v1.1.x. Change

Responding with Multiple Formats

Wheels controllers provide some powerful mechanisms for responding to requests for content in XML, JSON, and other formats. Build an API with ease with these functions.

If you've ever needed to create an XML or JSON API for your Wheels application, you may have needed to go down the path of creating a separate controller or separate actions for the new format. This introduces the need to duplicate model calls or even break them out into a super long list of before filters. With this, your controllers can get pretty hairy pretty fast.

Using a few Wheels functions, you can easily respond to requests for HTML, XML, JSON, and PDF formats without adding unnecessary bloat to your controllers.

Requesting Different Formats

With Wheels Provides functionality in place, you can request different formats using the following methods:

  1. URL Variable
  2. URL Extension
  3. Request Header

Which formats you can request is determined by what you configure in the controller. See the section below on Responding to Different Formats in the Controller for more details.

1. URL Variable

Wheels will accept a URL variable called format. If you wanted to request the XML version of an action, for example, your URL call would look something like this:

http://www.example.com/products?format=xml

The same would go for JSON:

http://www.example.com/products?format=json

2. URL Extension

Perhaps a cleaner way is to request the format as a "file" extension. Here are the XML and JSON examples, respectively:

http://www.example.com/products.xml
http://www.example.com/products.json

This works similarly to the URL variable approach mentioned above in that there will now be a key in the params struct set to the format requested. With the XML example, there will be a variable at params.format with a value of xml.

3. Request Header

If you are calling the Wheels application as a web service, you can also request a given format via the HTTP Accept header.

If you are consuming the service with another Wheels application, your <cfhttp> call would look something like this:

<cfhttp url="http://www.example.com/products">
<cfhttpparam type="header" name="Accept" value="application/octet-stream">
</cfhttp>

In this example, we are sending an Accept header with the value for the xml format.

Here is a list of values that you can grab from mimeTypes() with Wheels out of the box.

  1. html
  2. xml
  3. json
  4. csv
  5. pdf
  6. xls

You can use addFormat() to set more types to the appropriate MIME type for reference. For example, we could set a Microsoft Word MIME type in config/settings.cfm like so:

<cfset addFormat(extension="doc", mimeType="application/msword")>

Responding to Different Formats in the Controller

The fastest way to get going with creating your new API and formats is to call provides() from within your controller's init() method.

Take a look at this example:

<cfcomponent extends="Controller">

<cffunction name="init">
<cfset provides("html,json,xml")>
</cffunction>

<cffunction name="index">
<cfset products = model("product").findAll(order="title")>
<cfset renderWith(products)>
</cffunction>

</cfcomponent>

By calling the provides() function in init(), you are instructing the Wheels controller to be ready to provide content in a number of formats. Possible choices to add to the list are html (which runs by default), xml, json, csv, pdf, and xls.

This is coupled with a call to renderWith() in the following actions. In the example above, we are setting a query result of products and passing it to renderWith(). By passing our data to this function, Wheels gives us the ability to respond to requests for different formats, and it even gives us the option to just let Wheels handle the generation of certain formats automatically.

When Wheels handles this response, it will set the appropriate MIME type in the Content-Type HTTP header as well.

Providing the HTML Format

Responding to requests for the HTML version is the same as you're already used to with Rendering Content. renderWith() will accept the same arguments as renderPage(), and you create just a view template in the views folder like normal.

Automatic Generation of XML and JSON Formats

If the requested format is xml or json, the renderWith() function will automatically transform the data that you provide it. If you're fine with what the function produces, then you're done!

Providing Your Own Custom Responses

If you need to provide content for another type than xml or json, or if you need to customize what your Wheels application generates, you have that option.

In your controller's corresponding folder in views, all you need to do is implement a view file like so:

Type Example
html views/products/index.cfm
xml views/products/index.xml.cfm
json views/products/index.json.cfm
csv views/products/index.csv.cfm
pdf views/products/index.pdf.cfm
xls views/products/index.xls.cfm

If you need to implement your own XML- or JSON-based output, the presence of your new custom view file will override the automatic generation that Wheels normally performs.

Example: PDF Generation

If you need to provide a PDF version of the product catalog, the view file at views/products/index.pdf.cfm may look something like this:

<cfdocument format="pdf">
<h1>Products</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Price</th>
</tr>
</thead>
<tbody>
#includePartial(products)#
</tbody>
</table>
</cfdocument>
^ Top
Table of Contents

Comments

Read and submit questions, clarifications, and corrections about this chapter.

[Add Comment]

  1. Jay's Gravatar Jay says:

    The example code-block under "Responding to Different Formats in the Controller" includes a call to "respondWith(products)" .  Shouldn't that be "renderWith(products)" ?  The details after the example code block correctly reference the renderWith() function, but I can't find anything else on a "respondWith()" function.

    Probably just a typo...

  2. Max's Gravatar Max says:

    Quick question. I have a scenario in which I would like to create a new record via ajax, but also through an HTML fallback option for certain devices that need to work without javascript. Basically the user would visit the "student/new" view containing a form that calls the "student/create" action, which is responsible for creating the record in the database. In the ajax scenario, the "student/create" action would respond by sending the newly created record back to the client in JSON format. In the HTML scenario, the "student/create" action needs to use something like redirectTo() to send the user back to the "student/new" view instead of a "student/create" view.

    I understand using renderWith() to return a json response, but it doesn't seem like this would work for handling both scenarios, since the html response would be to render the view for the action of the same name. Can you please describe the proper way to handle these two scenarios within the same action?

    Thanks! Having lots of fun with CFWheels so far!

  3. Bernie's Gravatar Bernie says:

    My Survey.cfc controller code
    <cffunction name="init">
    <cfset provides("html,json,pdf")>
    </cffunction>
    <cffunction name="test">
    <cfset questions = model("question").findAll(order="displayOrder")>
    <cfset renderWith(questions)>
    </cffunction>
    my /view/Survey/test.pdf.cfm code
    <cfdocument format="pdf">
    <h1>Survey</h1>
    <cfoutput>
    #startFormTag(action="create")#
    </cfoutput>
    <h1>Questions</h1>
    <table>      
    <thead>            
    <tr>                
    <th>Question</th>            
    </tr>        
    </thead>
    <tbody>
    <cfoutput query="questions">
    <tr>
    <td>#question#</td>
    </tr>
    </cfoutput>
    </tbody>    
    </table>
    <cfoutput>
    #endFormTag()#
    </cfoutput>
    </cfdocument>

    I get a blank pdf with this code however when I put the questions query code directly into the view file it outputs the questions properly. It seems the query is not passed from the controller.

  4. Tom's Gravatar Tom says:

    Is it a bug that addFormat() adds a mime-type to the application.wheels.format struct but mimeTypes() reads from the application.wheels.mimetypes struct?

    It doesn't make sense, there is no way to use these two functions to interact with each other like explained above. Should I modify my core files?

    Cheers

  5. Bernie's Gravatar Bernie says:

    A link to a pdf version of my content would not work via the "url extension" method #2. It throws an error that the query needed to generate the content is invalid. It seems it would bypass the controller where the query is defined. However, using a url variable, method #1, it works fine.

  6. Kristof Polleunis's Gravatar Kristof Polleunis says:

    In order to have the nice urls working

    http://www.example.com/products.xml
    http://www.example.com/products.json

    I had to change my tuckey urlrewrite filter that I use with Tomcat or Jetty

    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE urlrewrite PUBLIC "-//tuckey.org//DTD UrlRewrite 3.2//EN" "http://tuckey.org/res/dtds/urlrewrite3.2.dtd">;
    <!--
        Configuration file for UrlRewriteFilter
        http://tuckey.org/urlrewrite/
    -->
    <urlrewrite>
        <rule>
            <note>
                Allow TomCat to have SES links.
            </note>
            <!--
                If our URI ends with one of these extentions we will not perform
                a URL rewrite.
            -->
            <condition type="request-uri" operator="notequal">\.(bmp|gif|jpe?g|png|css|js|txt|pdf|doc|xls|cfc|ico|php|asp)$</condition>
            <!--
                If any URI begins with one of these directories, we will not
                perform a rewrite. Helpful for running your unit tests or
                "blocking" rewrite for some directories.
            -->
           <condition type="request-uri" operator="notequal">^/$</condition>
           <condition type="request-uri" operator="notequal">^$</condition>
           <condition type="request-uri" operator="notequal">^(.*)/railo-context(.*)$</condition>
           <condition type="request-uri" operator="notequal">^(.*)/flex2gateway(.*)$</condition>
           <condition type="request-uri" operator="notequal">^(.*)/jrunscripts(.*)$</condition>
           <condition type="request-uri" operator="notequal">^(.*)/cfide(.*)$</condition>
           <condition type="request-uri" operator="notequal">^(.*)/cfformgateway(.*)$</condition>
           <condition type="request-uri" operator="notequal">^(.*)/files(.*)$</condition>
           <condition type="request-uri" operator="notequal">^(.*)/images(.*)$</condition>
           <condition type="request-uri" operator="notequal">^(.*)/javascripts(.*)$</condition>
           <condition type="request-uri" operator="notequal">^(.*)/miscellaneous(.*)$</condition>
           <condition type="request-uri" operator="notequal">^(.*)/stylesheets(.*)$</condition>
           <condition type="request-uri" operator="notequal">^(.*)/robots.txt(.*)$</condition>
           <condition type="request-uri" operator="notequal">^(.*)/sitemap.xml(.*)$</condition>
           <condition type="request-uri" operator="notequal">^(.*)/rewrite.cfm(.*)$</condition>
            
             <from>^/(.*?)(?:\.([a-z]{3,4}))?$</from>
            <to>/rewrite.cfm/$1?format=$2</to>
        </rule>
    </urlrewrite>

  7. Kristof Polleunis's Gravatar Kristof Polleunis says:

    Update urlrewrite code above

    https://gist.github.com/2858505

  8. Christopher's Gravatar Christopher says:

    Had issues tracking down why our AJAX data was passing over JSON with black diamonds with white question marks in the text.  We ensured that our view.json.cfm was passing over content as utf-8 by adding <cfcontent type="application/json; charset=utf-8"> to the view file that responds with JSON.

    We were able to resolve the issue by editing the \wheels\events\onapplicationstart\settings.cfm file and replacing this line:
    application.wheels.formats.json = "application/json";

    with this line:
    application.wheels.formats.json = "application/json; charset=utf-8";

    Hope this helps someone running into the same issue.

Add Comment