Set up JSON action output in CakePHP 1.3

Working  on setting up the Rest Plugin for CakePHP helped me realize that i wanted to set up JSON output for some of my actions. This way, if you request for example “www.yourapp.com/app/post/view/1.json” in the url, you will be returned the JSON post data. This excellent tutorial here will help you achieve it. The steps are simple:

  • Enabling JSON parsing in your app/config/routes.php: Router::parseExtensions('json');
  • Enable the RequestHandler component in your app_controller.php:
    //Only enabling json response parsing for url requested with the .json extension
    if ($this->RequestHandler->ext === 'json')
    {
        $this->RequestHandler->setContent('json', 'application/json');
    //Prevent debug output that'll corrupt your json data
        Configure::write('debug', 0);
    }
  • In your app/layouts folder, create a json folder with the default.ctp (so the file path should be app/layout/json/default.ctp) file with the following content:
    < ?php
    header("Pragma: no-cache");
    header("Cache-Control: no-store, no-cache, max-age=0, must-revalidate");
    header('Content-Type: application/json');
    echo $content_for_layout;
    ?>
  • Now for all controller actions that you want the json output enabled for, set the variable  in the viewVars for examples in my PostsController view action, i return the post data I want to view as:
    $this->set('post', $this->Post->read(null, $id));
  • Now second, we need to create the json view for this data. I created a "json" folder in my "views/posts" folder and created a "view.ctp" file (views/posts/json/view.ctp) with this content:
    <?php
        echo $this->Js->object($post); 
    //Some old posts you will find have this as echo $javascript->object($aPosts); This was done with the old Javascript helper, doing some //might give you trouble as it did for me. use the new JS helper syntax as of 1.3
    ?>
  • And that’s it you are home free, json output a go go for your action. Access http://www.yourapp.com/view/1 and you will get your regular html output, access http://www.yourapp.com/view/1.json and you will be returned your Json output, to view the Json file in Firefox install the JsonView extension.

Now you will notice that for all json views, we will be re-using the same code, basically just changing the name of the variable to output. This call for a JsonView, and I will post aboutit once i get it implemented correctly.

ExtJS: Show a loading mask during Ajax calls

This is my first ExtJS post in a long while and sorry it’s not related to ExtJS4; maybe in the near future.Recently I was asked to implement a page wide mask during Ajax calls to prevent user from interfering with the current call by clicking on the screen or doing something they otherwise should not be doing while the call is running. I initially used the “wait:true” option of the Ext.MessageBox configuration, but the stakeholders did not like it since the progress bar it displayed was not synced to the actual length of the call. This is how I ended up implementing it:

function showLoadingMask(loadingMessage)
{
if (Ext.isEmpty(loadingMessage))
loadText = 'Loading... Please wait';
//Use the mask function on the Ext.getBody() element to mask the body element during Ajax calls
Ext.Ajax.on('beforerequest',function(){Ext.getBody().mask(loadText, 'loading') }, Ext.getBody());
Ext.Ajax.on('requestcomplete',Ext.getBody().unmask ,Ext.getBody());
Ext.Ajax.on('requestexception', Ext.getBody().unmask , Ext.getBody());
}

Hope it helps!

 

The meat of the functionality is in binding the mask() and unmask() function of the Ext.Element object to the ‘beforerequest’, ‘requestcomplete’ and ‘requestexception’ events of the Ext.Ajax object. I need a custom message to be displayed depending on the context that’s the reason why i encapsulated that logic in a function. I also tweaked my CSS a little to customize the mask style, overriding the ExtJS style, as well as the style of the message displayed to the user:


.ext-el-mask
{
color:gray;
cursor:default;
opacity:0.6;
background-color:grey;
}

.ext-el-mask-msg div {
background-color: #EEEEEE;
border-color: #A3BAD9;
color: #222222;
font: 1.2em tahoma,arial,helvetica,sans-serif;
}

.ext-el-mask-msg {

padding: 10px;

}

How to POST cross domain and access the returned data using ExtJS

Please refer to my earlier post for background on this project.

Here is how to post data to a server on a different domain and receive data back using ExtJS (with inspiration from the GWT implementation).

First the FormPanel. The fields are standard and in the JS file we define two variables that are critical to making this work as they’ll allow us to keep track of what’s happening:

//These two variables are critical to the success of this operation

var xdm_formSubmitted = false;
var xdm_sameDomainRestored = false;
var renderDivId='myWidget';

var consigneeForm = new Ext.FormPanel(
{
 id: 'customerWidget',
 renderTo:renderDivId,
//By default, ExtJS uses XmlHttpRequest, this has to be a standard submit POST
standardSubmit: true,
 url: 'http://www.iamonanotherdomain.com/addThisCustomerForMe',
 border: false,
 hideBorders: true,
 items:
[
{
 xtype: 'textfield',
 name: 'customerFirstName'
 },
{
 xtype: 'textfield',
 name: 'customerLastName'
 }

],

bbar:
[
//This is where the form post is triggerd
{
 xtype: 'tbspacer',
 width: 300
} ,

{
 text: 'Save',
 handler: function()

{
 var form = Ext.getCmp('customerWidget').getForm();
 form.getEl().dom.target=iframePostContainerName;
 //On Submit we set this variable to true, you'll understand why in the event handler function
 xdm_formSubmitted = true;
 form.submit();
}
}
]

Notice that before submitting, we are assigning the target of the post to an iframe that exists on the page (form.getEl().dom.target=iframeName). This is the second and crucial step of the process. The iframe we are using is defined in the same js file:


//This hidden iframe is used to receive the results of the form POST:

{
 hidden: true,
 renderTo:'myHiddenIFrame',
 html:'<iframe id="' +iframePostContainerName + '" name="' +iframePostContainerName+ '"   onload=\"setupIFrameOnLoad(\'' + callbackFunction +  '\')"></iframe>'

}
//The callbackFunction variable is passed to the script by the calling page.

Which brings us to the third critical piece of the process which is to attach an event handler function to the onload event of the iframe, and this is where all the magic will happen. As you can see i’ve name my event handler function setupIFrameOnLoad and here is the definition:


setupIFrameOnLoad = function()
{
 //If the form was submitted and we have loaded data from our own domain, we are good. Thank you for coming
 //and here is your data! It's gonna be 5 dollars, Thank you!
 if(xdm_formSubmitted && xdm_sameDomainRestored)
 {
      //You can access your data now using the window.name property, but
      //I've got other plans in my callbackFunction so until such time...
      var callbackFunctionCall = callbackFunction + "()";
      eval(callbackFunctionCall);
 }
 //If this is the initial response from the POST, we are still in the POST server's domain
 else if(xdm_formSubmitted && !xdm_sameDomainRestored)
 {
       //Now you know we're about to restore the local domain right?
       xdm_sameDomainRestored = true;
       //localResourceUrl is passed by the calling page and points to a local 1px image or empty page
       document.getElementById(iframeName).contentWindow.location = localResourceUrl;
 }
 else
 {
       return ;
 }
}

What is happening here is basically the solution to the cross-domain issue in being able to pass data back to the calling page. Once your form is submitted, the response will be posted back to your iframe within your form page. Since the response is coming back from the server, security on the browser will keep us from accessing any of the properties on the window. To be able to access the data in window.name(which could be in JSON format if you wanted), the solution is simply to load a local resource file from your environment. It could be a simple blank 1px image or an empty HTML file, anything really to switch the iframe’s source content to be on the same domain where you are calling from. Once the iframe has reloaded you can simply access your data in the window.name property. To close the cycle, the response from your server needs to be a script which sets the window.name property. Using Spring, here is how my action was set up:

</pre>
private void addThisCustomerForMe(HttpServletRequest req, HttpServletResponse response) throws Exception
{

Long customerId = customerController.addOrUpdateCustomer(req);
response.setContentType("text/javascript");
OutputStreamWriter os = new OutputStreamWriter(response.getOutputStream());
StringBuffer buf = new StringBuffer("");
buf.append("<html>");
buf.append("<script type=\"text/javascript\">");
buf.append("  window.name="+customerId+";");    // can set json data here if you want
buf.append("</script>");
buf.append("</html>");

os.write(buf.toString());
os.flush();
os.close();

}

Of ExtJS’s ScriptTagProxy, Spring Actions, TreePanels and JsonStores

I recently had to implement an Organization lookup (backed by a LDAP repository) widget that will be used by some of the web applications I developed and this in a cross-domain environment.  The data displayed within the widget is broken down in Organization->Group->Person hierarchy and on selection of a Person and click of a button, a callback would sent some information about the selected person to a function in the calling application.

The  organizational hierarchy was implemented logically with a tree using an ExtJS TreePanel component. The challenge then became to propertly load this TreePanel. The ExtJS documentation specifies that a  “TreePanel must have a root node before it is rendered“,  which can be done by specifying the ‘root’ property, or using the setRootNode method. For the purpose of my widget, i needed the tree structure to be completely rendered with parent nodes already expanded to reveal children. What needed was to create this hierarchy in my Spring controller and return it to be loaded in my TreePanel. The Ext.data.ScriptTagProxy component was the key in allowing me to make the cross domain call the widget needs to retrieve the Tree data (Check this article if you are using IE6 and getting a Javascript error when using ScriptTagProxy).

Let’s get in the code, first starting with the Store definition:

var companyStore = new Ext.data.Store({

 autoLoad: true,
 //JSONP Proxy Setup
 proxy: new Ext.data.ScriptTagProxy({
 url: getCompanyTreeURL
 }),
 reader: new Ext.data.JsonReader(
 {
 //I named my json object root's 'node', just for the sake of it
 root: 'node',
 totalProperty: 'totalCount',
 idProperty: 'id',
 fields:
 [
 //Notice that these are TreeNode regular properties
 'id',
 'text',
 'children',
 'expanded',
 'leaf'
 ]
 }),
 listeners:
 {
 load: function(store, recs)
 {
 //Once the data is loaded from the backend, i can draw my tree, with the root node
 //already created by the backend
 var companyTree = new Ext.tree.TreePanel(
 {
 useArrows: true,
 autoScroll: true,
 animate: true,
 renderTo:'tree-div',
 enableDD: true,
 containerScroll: true,
 border: false,
 rootVisible: false,
 root: new Ext.tree.AsyncTreeNode(store.reader.jsonData.node[0])

 });
 }
 }
 });

This is an example of the JSON data that will be created manually in the backend and sent to the company store, and
then finally loaded in the TreePanel root node. Notice the stcCallback1001 function that the data is wrapped in? This is required when you are using a ScriptTagProxy, and you will need to manually do that wrapping in your backend code.

stcCallback1001(
{
 'node':
 [
 {
 'id':1,
 'children':
 [
 {
 'text':'Big Bad Company',
 'id':'222',
 'expanded':true,
 'children':
 [
 {
 'text':'Little Bad Company 1',
 'id':'1005',
 'leaf':true
 },
 {
 'text':'Little Bad Company 2',
 'id':'1010',
 'leaf':true
 }
 ]
 }
 ,
 {
 'text':'Bigger Bad Company',
 'id':'123',
 'expanded':true,
 'children':
 [
 {
 'text':'Little Bigger Bad Company',
 'id':'23423',
 'leaf':true
 }

 ]
 }
 ]
 }
 ]
})

Now switching to setting things up on the server side. If you want to ScriptTagProxy paradigm to work, your response content type needs to be set to “text/javascript” as it will be interpreted as such on the client side.


 @RequestMapping("getCompanyTree.json")
 public void getCompanyTreeList(@RequestParam("callback") String callback, HttpServletResponse response)
 {
 Company root = directoryService.getCompanyTree();
 response.setContentType("text/javascript");
 try
 {
 OutputStreamWriter os = new OutputStreamWriter(response.getOutputStream());
 String companyData = getCompanyNodes(root);
 //The callback parameter value was sent by the ScriptTagProxy object in the UI, we use it to wrap the data in the function call
 os.write(callback + "(");
 os.write("{'node':[{'id':1, " + companyData + "}]}");
 os.write( ")");
 os.flush();
 os.close();
 }catch (IOException e)
 {
 e.printStackTrace();
 }

 }

Still in the controller, this function will actually build out the tree structure in a string format using recursion:


public String getCompanyNodes(Company rootCompany)
 {
 String returnValue = new String();
 boolean firstNode = true;
 returnValue += "'children':[\n";
 for(Company comp : rootCompany.getChildren())
 {
 if(!firstNode)
 {
 returnValue += ",\n";
 }
 else
 {
 firstNode = false;
 }
 returnValue += "{\n";
 returnValue +="'text':'" + comp.getName() + "',\n" +
 "'id':'" + org.getId() + "',\n";
 if(comp.getChildren().size() > 0)
 {
 returnValue += "'expanded':true,\n";
 //A little recursion saves us a few lines of code
 String childrenNodeReturnValue = getCompanyNodes(org);
 returnValue += childrenNodeReturnValue;

 }
 else
 {
 returnValue += "'leaf':true\n";
 }
 returnValue += "}\n";;
 }
 returnValue += "]";
 return returnValue;
 }

There you have it. Hope it helps!