# ContextSwitch and AjaxContext

The ContextSwitch action helper is intended for facilitating returning different response formats on request. The AjaxContext helper is a specialized version of ContextSwitch that facilitates returning responses to XmlHttpRequests.

To enable either one, you must provide hinting in your controller as to what actions can respond to which contexts. If an incoming request indicates a valid context for the given action, the helper will then:

- Disable layouts, if enabled.

- Set an alternate view suffix, effectively requiring a separate view script for the context.

- Send appropriate response headers for the context desired.

- Optionally, call specified callbacks to setup the context and/or perform post-processing.

As an example, let's consider the following controller:

    class NewsController extends Zend_Controller_Action
    {
        /**
         * Landing page; forwards to listAction()
         */
        public function indexAction()
        {
            $this->_forward('list');
        }

        /**
         * List news items
         */
        public function listAction()
        {
        }

        /**
         * View a news item
         */
        public function viewAction()
        {
        }
    }

Let's say that we want the `listAction()` to also be available in an XML format. Instead of creating a different action, we can hint that it can return an XML response:

    class NewsController extends Zend_Controller_Action
    {
        public function init()
        {
            $contextSwitch = $this->_helper->getHelper('contextSwitch');
            $contextSwitch->addActionContext('list', 'xml')
                          ->initContext();
        }

        // ...
    }

What this will do is:

- Set the 'Content-Type' response header to '`text/xml`'.

- Change the view suffix to '`xml.phtml`' (or, if you use an alternate view suffix, 'xml.[your suffix]').

Now, you'll need to create a new view script, '`news/list.xml.phtml`', which will create and render the XML.

To determine if a request should initiate a context switch, the helper checks for a token in the request object. By default, it looks for the 'format' parameter, though this may be configured. This means that, in most cases, to trigger a context switch, you can add a 'format' parameter to your request:

- Via URL parameter: `/news/list/format/xml` (recall, the default routing schema allows for arbitrary key to value pairs following the action)

- Via `GET` parameter: `/news/list?format=xml`

ContextSwitch allows you to specify arbitrary contexts, including what suffix change will occur (if any), any response headers that should be sent, and arbitrary callbacks for initialization and post processing.

## Default Contexts Available

By default, two contexts are available to the ContextSwitch helper: json and xml.

- JSON. The JSON context sets the 'Content-Type' response header to '`application/json`', and the view script suffix to '`json.phtml`'.

  By default, however, no view script is required. It will simply serialize all view variables, and emit the JSON response immediately.

  This behaviour can be disabled by turning off the automatic JSON serialization:

      $this->_helper->contextSwitch()->setAutoJsonSerialization(false);

- XML. The XML context sets the 'Content-Type' response header to '`text/xml`', and the view script suffix to '`xml.phtml`'. You will need to create a new view script for the context.

## Creating Custom Contexts

Sometimes, the default contexts are not enough. For instance, you may wish to return YAML, or serialized PHP, an RSS or ATOM feed, etc. ContextSwitch allows you to do so.

The easiest way to add a new context is via the `addContext()` method. This method takes two arguments, the name of the context, and an array specification. The specification should include one or more of the following:

- suffix: the suffix to prepend to the default view suffix as registered in the ViewRenderer.

- headers: an array of header to value pairs you wish sent as part of the response.

- callbacks: an array containing one or more of the keys 'init' or 'post', pointing to valid PHP callbacks that can be used for context initialization and post processing.

  Initialization callbacks occur when the context is detected by ContextSwitch. You can use it to perform arbitrary logic that should occur. As an example, the JSON context uses a callback to disable the ViewRenderer when the automatic JSON serialization is on.

  Post processing occurs during the action's `postDispatch()` routine, and can be used to perform arbitrary logic. As an example, the JSON context uses a callback to determine if the automatic JSON serialization is on; if so, it serializes the view variables to JSON and sends the response, but if not, it re-enables the ViewRenderer.

There are a variety of methods for interacting with contexts:

- `addContext($context, array $spec)`: add a new context. Throws an exception if the context already exists.

- `setContext($context, array $spec)`: add a new context or overwrite an existing context. Uses the same specification as `addContext()`.

- `addContexts(array $contexts)`: add many contexts at once. The `$contexts` array should be an array of context to specification pairs. If any of the contexts already exists, it will throw an exception.

- `setContexts(array $contexts)`: add new contexts and overwrite existing ones. Uses the same specification as `addContexts()`.

- `hasContext($context)`: returns `TRUE` if the context exists, `FALSE` otherwise.

- `getContext($context)`: retrieve a single context by name. Returns an array following the specification used in `addContext()`.

- `getContexts()`: retrieve all contexts. Returns an array of context to specification pairs.

- `removeContext($context)`: remove a single context by name. Returns `TRUE` if successful, `FALSE` if the context was not found.

- `clearContexts()`: remove all contexts.

## Setting Contexts Per Action

There are two mechanisms for setting available contexts. You can either manually create arrays in your controller, or use several methods in ContextSwitch to assemble them.

The principle method for adding action to context relations is `addActionContext()`. It expects two arguments, the action to which the context is being added, and either the name of a context or an array of contexts. As an example, consider the following controller class:

    class FooController extends Zend_Controller_Action
    {
        public function listAction()
        {
        }

        public function viewAction()
        {
        }

        public function commentsAction()
        {
        }

        public function updateAction()
        {
        }
    }

Let's say we wanted to add an XML context to the 'list' action, and XML and JSON contexts to the 'comments' action. We could do so in the `init()` method:

    class FooController extends Zend_Controller_Action
    {
        public function init()
        {
            $this->_helper->contextSwitch()
                 ->addActionContext('list', 'xml')
                 ->addActionContext('comments', array('xml', 'json'))
                 ->initContext();
        }
    }

Alternately, you could simply define the array property `$contexts`:

    class FooController extends Zend_Controller_Action
    {
        public $contexts = array(
            'list'     => array('xml'),
            'comments' => array('xml', 'json')
        );

        public function init()
        {
            $this->_helper->contextSwitch()->initContext();
        }
    }

The above is less overhead, but also prone to potential errors.

The following methods can be used to build the context mappings:

- `addActionContext($action, $context)`: marks one or more contexts as available to an action. If mappings already exists, simply appends to those mappings. `$context` may be a single context, or an array of contexts.

  A value of `TRUE` for the context will mark all available contexts as available for the action.

  An empty value for `$context` will disable all contexts for the given action.

- `setActionContext($action, $context)`: marks one or more contexts as available to an action. If mappings already exists, it replaces them with those specified. `$context` may be a single context, or an array of contexts.

- `addActionContexts(array $contexts)`: add several action to context pairings at once. `$contexts` should be an associative array of action to context pairs. It proxies to `addActionContext()`, meaning that if pairings already exist, it appends to them.

- `setActionContexts(array $contexts)`: acts like `addActionContexts()`, but overwrites existing action to context pairs.

- `hasActionContext($action, $context)`: determine if a particular action has a given context.

- `getActionContexts($action = null)`: returns either all contexts for a given action, or all action to context pairs.

- `removeActionContext($action, $context)`: remove one or more contexts from a given action. `$context` may be a single context or an array of contexts.

- `clearActionContexts($action = null)`: remove all contexts from a given action, or from all actions with contexts.

## Initializing Context Switching

To initialize context switching, you need to call `initContext()` in your action controller:

    class NewsController extends Zend_Controller_Action
    {
        public function init()
        {
            $this->_helper->contextSwitch()->initContext();
        }
    }

In some cases, you may want to force the context used; for instance, you may only want to allow the XML context if context switching is activated. You can do so by passing the context to `initContext()`:

    $contextSwitch->initContext('xml');

## Additional Functionality

A variety of methods can be used to alter the behaviour of the ContextSwitch helper. These include:

- `setAutoJsonSerialization($flag)`: By default, JSON contexts will serialize any view variables to JSON notation and return this as a response. If you wish to create your own response, you should turn this off; this needs to be done prior to the call to `initContext()`.

      $contextSwitch->setAutoJsonSerialization(false);
      $contextSwitch->initContext();

  You can retrieve the value of the flag with `getAutoJsonSerialization()`.

- `setSuffix($context, $suffix, $prependViewRendererSuffix)`: With this method, you can specify a different suffix to use for a given context. The third argument is used to indicate whether or not to prepend the current ViewRenderer suffix with the new suffix; this flag is enabled by default.

  Passing an empty value to the suffix will cause only the ViewRenderer suffix to be used.

- `addHeader($context, $header, $content)`: Add a response header for a given context. `$header` is the header name, and `$content` is the value to pass for that header.

  Each context can have multiple headers; `addHeader()` adds additional headers to the context's header stack.

  If the `$header` specified already exists for the context, an exception will be thrown.

- `setHeader($context, $header, $content)`: `setHeader()` acts just like `addHeader()`, except it allows you to overwrite existing context headers.

- `addHeaders($context, array $headers)`: Add multiple headers at once to a given context. Proxies to `addHeader()`, so if the header already exists, an exception will be thrown. `$headers` is an array of header to context pairs.

- `setHeaders($context, array $headers.)`: like `addHeaders()`, except it proxies to `setHeader()`, allowing you to overwrite existing headers.

- `getHeader($context, $header)`: retrieve the value of a header for a given context. Returns `NULL` if not found.

- `removeHeader($context, $header)`: remove a single header for a given context.

- `clearHeaders($context, $header)`: remove all headers for a given context.

- `setCallback($context, $trigger, $callback)`: set a callback at a given trigger for a given context. Triggers may be either 'init' or 'post' (indicating callback will be called at either context initialization or postDispatch). `$callback` should be a valid PHP callback.

- `setCallbacks($context, array $callbacks)`: set multiple callbacks for a given context. `$callbacks` should be trigger to callback pairs. In actuality, the most callbacks that can be registered are two, one for initialization and one for post processing.

- `getCallback($context, $trigger)`: retrieve a callback for a given trigger in a given context.

- `getCallbacks($context)`: retrieve all callbacks for a given context. Returns an array of trigger to callback pairs.

- `removeCallback($context, $trigger)`: remove a callback for a given trigger and context.

- `clearCallbacks($context)`: remove all callbacks for a given context.

- `setContextParam($name)`: set the request parameter to check when determining if a context switch has been requested. The value defaults to 'format', but this accessor can be used to set an alternate value.

  `getContextParam()` can be used to retrieve the current value.

- `setAutoDisableLayout($flag)`: By default, layouts are disabled when a context switch occurs; this is because typically layouts will only be used for returning normal responses, and have no meaning in alternate contexts. However, if you wish to use layouts (perhaps you may have a layout for the new context), you can change this behaviour by passing a `FALSE` value to `setAutoDisableLayout()`. You should do this before calling `initContext()`.

  To get the value of this flag, use the accessor `getAutoDisableLayout()`.

- `getCurrentContext()` can be used to determine what context was detected, if any. This returns `NULL` if no context switch occurred, or if called before `initContext()` has been invoked.

## AjaxContext Functionality

The AjaxContext helper extends ContextSwitch, so all of the functionality listed for ContextSwitch is available to it. There are a few key differences, however.

First, it uses a different action controller property for determining contexts, `$ajaxable`. This is so you can have different contexts used for AJAX versus normal HTTP requests. The various *`ActionContext()`* methods of AjaxContext will write to this property.

Second, it will only trigger if an XmlHttpRequest has occurred, as determined by the request object's `isXmlHttpRequest()` method. Thus, if the context parameter ('format') is passed in the request, but the request was not made as an XmlHttpRequest, no context switch will trigger.

Third, AjaxContext adds an additional context, HTML. In this context, it sets the suffix to '`ajax.phtml`' in order to differentiate the context from a normal request. No additional headers are returned.

### Allowing Actions to Respond To Ajax Requests

In this following example, we're allowing requests to the actions 'view', 'form', and 'process' to respond to AJAX requests. In the first two cases, 'view' and 'form', we'll return HTML snippets with which to update the page; in the latter, we'll return JSON.

    class CommentController extends Zend_Controller_Action
    {
        public function init()
        {
            $ajaxContext = $this->_helper->getHelper('AjaxContext');
            $ajaxContext->addActionContext('view', 'html')
                        ->addActionContext('form', 'html')
                        ->addActionContext('process', 'json')
                        ->initContext();
        }

        public function viewAction()
        {
            // Pull a single comment to view.
            // When AjaxContext detected, uses the comment/view.ajax.phtml
            // view script.
        }

        public function formAction()
        {
            // Render the "add new comment" form.
            // When AjaxContext detected, uses the comment/form.ajax.phtml
            // view script.
        }

        public function processAction()
        {
            // Process a new comment
            // Return the results as JSON; simply assign the results as
            // view variables, and JSON will be returned.
        }
    }

On the client end, your AJAX library will simply request the endpoints '`/comment/view`', '`/comment/form`', and '`/comment/process`', and pass the 'format' parameter: '`/comment/view/format/html`', '`/comment/form/format/html`', '`/comment/process/format/json`'. (Or you can pass the parameter via query string: e.g., "?format=json".)

Assuming your library passes the 'X-Requested-With: XmlHttpRequest' header, these actions will then return the appropriate response format.