Skip to end of metadata
Go to start of metadata

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[

Zend Framework: Zend_Controller_Action_Helper_AjaxContext Component Proposal

Proposed Component Name Zend_Controller_Action_Helper_AjaxContext
Developer Notes http://framework.zend.com/wiki/display/ZFDEV/Zend_Controller_Action_Helper_AjaxContext
Proposers Matthew Weier O'Phinney
Revision 0.8.0 - 03 January 2008: Initial proposal
0.9.0 - 04 January 2008: Updated proposal based on user feedback
0.9.1 - 04 January 2008: Added ContextSwitch helper to proposal; refactored to allow setting arbitrary context types
0.9.2 - 04 January 2008: Updated use cases to follow current laboratory code
0.9.3 - 06 January 2008: Updated use cases and skeletons to show getCurrentContext() method
0.9.4 - 07 January 2008: Updated UC-01 per Darby's comments
0.9.5 - 08 January 2008: Updated with feedback from Zend team; documents context enabling, adds support for building action controller context arrays, adds support for multiple headers, updates requirements to specify that the context will be prepended to existing view suffix for context suffix (wiki revision: 18)

Table of Contents

1. Overview

Zend_Controller_Action_Helper_AjaxContext enables view context switching and sets appropriate response headers when XmlHttpRequests are detected.

2. References

  • None

3. Component Requirements, Constraints, and Acceptance Criteria

  • This component will be optionally loaded, on-demand, as an action helper.
  • This component will disable layouts when XHR requests are detected.
  • This component will use properties from the current action controller and request object to determine if context switching should occur.
  • This component will utilize view script suffixes and HTTP response headers to accomplish context switching.
  • This component will default to HTML responses unless otherwise requested.
  • This component will use a configurable request parameter to determine the response context to utilize.
  • This component will allow configuration of the headers to send for the contexts supported.
    • This component will allow multiple configurable headers per context
  • This component will allow configuring the view suffix to use when context switching.
    • This component will simply prepend the suffix previously configured in the ViewRenderer with the new format.
    • This component will provide helper methods for creating and configuring the action controller context array

4. Dependencies on Other Framework Components

  • Zend_Controller_Action_Helper_Abstract
  • Zend_Controller_Action_Helper_ContextSwitch (part of this proposal)
  • Zend_Controller_Action_Helper_ViewRenderer
  • Zend_Controller_Request_Http
  • Zend_Controller_Response_Http
  • Zend_Layout

5. Theory of Operation

Zend_Controller_Action_Helper_AjaxContext is designed to make detecting XmlHttpRequests and switching view context trivial. When called, it checks to see if the request is an XHR request; if so, it then disables layouts. A check is made on the 'ajaxable' property of the associated action controller, and if the current action is in that list, it then determines the response format requested; the viewRenderer is then updated with a new view script suffix representing the format, and appropriate response headers are set.

View scripts for handling AJAX contexts still end in the default view suffix, but have an additional suffix prepended. For instance, if the ViewRenderer's default suffix is '.phtml', and an XML context is selected, the new suffix will be 'xml.phtml'; the JSON context becomes 'json.phtml', and HTML becomes 'ajax.phtml' (to differentiate from the standard HTML returned by the action).

AjaxContext would itself extend another helper, ContextSwitch. This does the heavy lifting; AjaxContext itself only performs the isXmlHttpRequest() check. It also allows specifying arbitrary contexts via accessors.

To select a context, you simply add an extra parameter to the request. By default, this parameter is 'format'. As an example, if you were visiting the path /foo/bar and wanted the XML context, you could specify /foo/bar?format=xml, or, using path parameters as supported by the default router, /foo/bar/format/xml.

6. Milestones / Tasks

  • Milestone 1: [DONE] Initial proposal published for review
  • Milestone 2: [DONE] Tests and initial code checked into the laboratory
  • Milestone 3: Final code with >80% coverage checked into incubator
  • Milestone 4: Documentation completed
  • Milestone 5: Promotion to core

7. Class Index

  • Zend_Controller_Action_Helper_ContextSwitch
  • Zend_Controller_Action_Helper_AjaxContext

8. Use Cases

9. Class Skeletons

]]></ac:plain-text-body></ac:macro>

]]></ac:plain-text-body></ac:macro>

Labels:
None
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Jan 03, 2008

    <p>It would be nicer if the setup for $ajaxContext in UC02 was done within init(), so that the action helper itself could call it's initContext() member function in its preDispatch() function (if the controller had $ajaxable defined, maybe).</p>

    <p>i.e. for UC01, it would be useful if you only had to define $ajaxable and didn't have to create a preDispatch() function just to call initContext().</p>

    <p>Regards,</p>

    <p>Rob...</p>

    1. Jan 04, 2008

      <p>I'll take that under consideration. My main reason for <strong>not</strong> doing so is that this makes it explicit that the given controller is supposed to handle AJAX calls... though you could argue that the presence of the "ajaxable" property would indicate that as well.</p>

      <p>Additionally, for the method you propose to work, you'd need to load the helper in your bootstrap, which means that it would be loaded and instantiated for every single request, regardless of whether or not the controller supports AJAX – a situation that many may feel is undesired.</p>

  2. Jan 03, 2008

    <p>A couple of questions:</p>

    <p>1. What if my ajax responses use layouts too?<br />
    2. Have you considered using (or providing the option to use) the "Accept" HTTP header to determine the client's preffered response format?</p>

    1. Jan 04, 2008

      <p>ad 1.: I don't think this is a common use case, should fall under the 20/80 rule. But for extensibility's sake, I think disabling layouts should be put in a separate (protected) method so you can just remove it from your subclasses.</p>

      1. Jan 04, 2008

        <p>Good idea – I'll make it a property with accessors, and then the developer can either set it using these, or extend the class to set a new default value.</p>

    2. Jan 04, 2008

      <p>1) you can always re-enable layouts after the call:</p>
      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      $this->_helper->ajaxContext()->initContext();
      $this->_helper->layout()->enableLayout();
      ]]></ac:plain-text-body></ac:macro>

      <p>2) I hadn't, but it's certainly a possibility. How would you envision this working? (Give me a sample request header, and how you'd expect the helper to deal with it.)</p>

  3. Jan 04, 2008

    <p>Nice idea... +1 vote <ac:emoticon ac:name="wink" /></p>

  4. Jan 04, 2008

    <p>ContextSwitch helper is now in the laboratory: <a href="http://framework.zend.com/svn/laboratory/Zend_Form/library/Zend/Controller/Action/Helper/ContextSwitch.php">Zend_Controller_Action_Helper_ContextSwitch</a>.</p>

    <p>It is backed by a complete unit test suite (I developed it using TDD, based on the skeletons in this proposal and the requirements as posted). I should have AjaxContext in shortly as well; please test and let me know if the API works as expected.</p>

    1. Jan 04, 2008

      <p>AjaxContext helper is also now in the laboratory, with unit tests: <a href="http://framework.zend.com/svn/laboratory/Zend_Form/library/Zend/Controller/Action/Helper/AjaxContext.php">AjaxContext helper</a>.</p>

      <p>TestDox output for the helpers:</p>

      <ul>
      <li>Zend_Controller_Action_Helper_ContextSwitch
      <ul>
      <li>Direct returns object instance</li>
      <li>Set suffix modifies context suffix</li>
      <li>Suffix accessors throw exception on invalid context type</li>
      <li>Set header modifies context header</li>
      <li>Set header without header argument disables header</li>
      <li>Header accessors throw exception on invalid context type</li>
      <li>Default context param</li>
      <li>Can set context param</li>
      <li>Default context</li>
      <li>Can set default context</li>
      <li>Set default context throws exception if context does not exist</li>
      <li>Context switch disables layouts by default</li>
      <li>Can choose whether layouts are disabled</li>
      <li>Can add contexts</li>
      <li>Add context throws exception if context already exists</li>
      <li>Set context overwrites existing context</li>
      <li>Can remove contexts</li>
      <li>Init context does nothing if no contexts set</li>
      <li>Init context does nothing if controller contexts is invalid</li>
      <li>Init context does nothing if action has no contexts</li>
      <li>Init context does nothing if action does not have context</li>
      <li>Init context does nothing if action does not have context and passed format invalid</li>
      <li>Init context sets view renderer view suffix</li>
      <li>Init context sets appropriate response header</li>
      <li>Init context uses passed format when context param present</li>
      <li>Init context disables layout by default</li>
      <li>Init context does not disable layout if disable layout disabled</li>
      </ul>
      </li>
      <li>Zend_Controller_Action_Helper_AjaxContext
      <ul>
      <li>Default contexts includes html</li>
      <li>Init context fails on non xhr requests</li>
      <li>Init context switches context with xhr requests</li>
      </ul>
      </li>
      </ul>

  5. Jan 05, 2008

    <p>It would be nice to have an addAjaxable() method that can be used inside the preDispatch() method to setup the ajaxable array.</p>

    <p>Example for UC-01:</p>

    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    public function preDispatch()
    {
    // Both string and array can be provided for the second argument.
    $this->addAjaxable('bar', 'html');
    $this->addAjaxable('bat', array('xml', 'json'));

    $this->_helper->ajaxContext()->initContext();
    }
    ]]></ac:plain-text-body></ac:macro>

    1. Jan 05, 2008

      <p>I don't feel this is a candidate for Zend_Controller_Action. However, I could potentially see this being a part of the helper itself:</p>

      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      public function preDispatch()
      {
      $ajaxContext = $this->_helper->getHelper('ajaxContext');
      $ajaxContext->addAjaxableAction('bar', 'html')
      ->addAjaxableAction('bat', array('xml', 'json'));
      $ajaxContext->initContext();
      }
      ]]></ac:plain-text-body></ac:macro>

      <p>This would of course necessitate a similar method in ContextSwitch:</p>

      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      public function preDispatch()
      {
      $contextSwitch = $this->_helper->getHelper('contextSwitch');
      $contextSwitch->addContextAction('bar', 'xml')
      ->addContextAction('bat', array('xml', 'json'));
      $contextSwitch->initContext();
      }
      ]]></ac:plain-text-body></ac:macro>

      <p>I'll consider it. However, I'm not sure how necessary functionality like this is as the creation of those arrays would already be present in the current controller; if anything, this solution just adds more verbosity to the setup, and would definitely eat more cycles.</p>

      1. Jan 06, 2008

        <p>Hi Matthew,</p>

        <p>I agree with placing those method in the helper itself. It's where they should be.</p>

        <p>The reason I suggested these methods is because I strongly dislike the overriding of variables to configure things which could be configured using methods. Unfortunately it's an issue that's present throughout the framework. It almost feels somewhat hackish.</p>

        <p>While this particular array is relatively simple, there are others classes in the framework (db table relations for example) that require more complex arrays. In these cases a nice verbose method will be quicker when developing, because of the simple reason an IDE can autocomplete it and they're easier to remember.<br />
        In the end, the cost savings of the time gained because developers don't have to memorize or lookup complex arrays are greater than the costs of those few extra cycles.</p>

        <p>I'm aware this doesn't apply to this proposal specifically and I absolutely don't want to offend anyone with this, but I felt it had to be said <ac:emoticon ac:name="smile" /></p>

  6. Jan 07, 2008

    <p>This looks really useful, and it should be a good addition to ZF. I have some minor feedback below:</p>

    <p>It seems strange to have view script file extensions of, for example, "<code>.json.phtml</code>" and "<code>.xml.phtml</code>" for JSON and XML responses, respectively. I would have found suffixes of "<code>.json</code>" and "<code>.xml</code>" to be simpler for the default case. Perhaps it can be explained why this isn't the case?</p>

    <p>Use case UC-01 does not show view scripts for <code>allAction()</code>.</p>

    <p>Since <code>$ajaxable</code> is a public property of the controller, is there some kind of validation that occurs for it when it is referenced to ease the development process?</p>

    <p>For use case UC-03, why include xml and json responses for the bar and bat actions if they are forced to HTML responses?</p>

    1. Jan 07, 2008

      <p>Thanks for the review, Darby.</p>

      <p>The main purpose of using 'json.phtml' and '.xml.phtml' is so that IDEs and code editors will syntax highlight the view scripts as PHP. </p>

      <p>I'll update UC-01 to show the allAction() view script.</p>

      <p>I'm not sure what you mean by validation with the $ajaxable property, but I'm assuming you mean "does it check that the action exists" or "does it check that the format exists". Regarding the action, it's getting that from the current request; it's entirely possible that the action may not appear in the controller itself; this may be okay, if the developer is overloading using __call(). Regarding the format, yes – it checks these against the contexts listed in the helper itself.</p>

      <p>Regarding UC-03, I simply wanted to show that it's possible to re-purpose a controller to use a single response context. </p>

      1. Jan 07, 2008

        <p>Regarding the <code>$ajaxable</code> property, it's a minor issue, but it seems that it would be easy for a developer to munge the expected structure of the array, overwrite it with something inadvertently, or something similar that would make the array not contain the required elements, structure, and/or types needed for this functionality to work as expected. I'm basically wondering how problems due to improper structure would be handled. Maybe some unit tests with various improper forms of the <code>$ajaxable</code> property would serve to illustrate what happens. For example:</p>

        <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
        public $ajaxable = 'invalid';
        public $ajaxable = array('foo' => 4);
        ]]></ac:plain-text-body></ac:macro>

        1. Jan 08, 2008

          <p>Yep – I see exactly where you're coming from. I'll add such tests to the suite, and make sure the code works with them. I suspect that this is a good argument for Jurrien's suggestion that methods be added for creating the $ajaxable/$contexts arrays, and I'll put those in the spec as well.</p>

  7. Jan 09, 2008

    <ac:macro ac:name="note"><ac:parameter ac:name="title">Zend Comments</ac:parameter><ac:rich-text-body>
    <p>This proposal is approved for incubator development, and the following issues are to be addressed:</p>
    <ul>
    <li>Document how to invoke context switching, and what the default context parameter is</li>
    <li>Add support for multiple HTTP headers per context using key/value pairs</li>
    <li>Context switch should prepend current context to current ViewRenderer view script suffix</li>
    <li>Create methods for building the action controller <code>$contexts</code> and <code>$ajaxable</code> arrays</li>
    </ul>
    </ac:rich-text-body></ac:macro>