Ajax elements available anywhere

By Sami Radi aka "Kainchi"

When I have chosen to work with Cakephp it was because of all these great features that we could use to create an ajax app easily. However, I had some problems to manage Ajax view elements like header, footer,... I figured out a solution that I would like to share with the community and have feedback about. (p.s : forgive me for my english, i'm not a native speaker)

Here's an overview of the problem. Ajax key feature is to smooth the user navigation by preventing page reloading through ajax requests. Let's say that you want to put an Ajax login into your website header. You need to be able to use the feature anywhere on the website.

After hours of readings i found out that to be compliant with cakephp MVC structure the process should be the following :

  • 1. Ajax call to current controller.
  • 2. request requested action from this controller or another controller.
  • 3. proceed with your algorithm.
  • 4. setting the values into the view that has to be rendered.
  • 5. render the view or element.
  • 6. end of Ajax call.

The problem :

I need the same ajax call on each page of my website. Let's take as an example the login action.

The simple process would be :

  • 0. the user displays the login view associated with the member controller
  • 1. the user clicks on the connect button
  • 2. the form is submitted to login action in the member controller
  • 3. the login action performs checks over the database
  • 4. the login action sets a session variable that indicates whether the user is logged or not and sets a view variable that indicates success or not
  • 5. the login view is reloaded
  • 6. the user can now access specific pages only accessible through session check


Now I want to make an ajax call to login action. The ajax simple process would be :

  • 0. the user displays the login view associated with the member controller
  • 1. the user clicks on the connect button
  • 2. an ajax request is sent to the login action with post data
  • 3. the login action performs checks over the database
  • 4. the login action sets a session variable that indicates whether the user is logged or not and sets a view variable that indicates success or not
  • 5. the login view is updated
  • 6. the user can now access specific pages only accessible through session check


What has to be noticed is that in these two case we are compliant with the MVC model. We display the login view associated with the login action in the member controller and we request the login action to perform a check over the login form. Requests are performed over the same view<->controller association.


Now we need to use the login action on every views of the website. We want it to be ajax. The view content is updated not reloaded

The simple process would be :

Bad Practice :

  • 0. the user displays the article view associated with the article controller
  • 1. the user clicks on the connect button
  • 2. an ajax request is sent to the login action in the member controller with post data
  • 3. the login action performs checks over the database
  • 4. the login action sets a session variable that indicates whether the user is logged or not and sets a view variable that indicates success or not
  • 5. the login view is updated
  • 6. What??? Have you just said the login view not article view!

Okay there's something wrong here. That's not the way I want things to work. I want to update the article view of the corresponding to the view action in the article controller but I need to request the login action in the member controller... Wait I could use the requestAction. So how should it work.

Good Practice :

  • 0. the user displays the article view associated with the article controller
  • 1. the user clicks on the connect button
  • 2. an ajax request is sent to the article login action in the article controller with post data
  • 3. a requestAction is sent to the login action in the member controller with url sent data
  • 4. the login action in the member controller performs checks over the database
  • 5. the login action sets a session variable that indicates whether the user is logged or not and returns a variable that indicates success or not to the article login action
  • 6. The article login action updates the article view

The problem now is that you need to put a login action in every class, a specific ajax view for this login action in every view directory, etc...

I came up with a solution to ease that using :

  • 1. 1 component (Ajaxthis.php)
  • 2. 1 helper (Ajaxthis.php)
  • 3. 1 view (ajax.thtml)


The code :


Ajaxthis Component


In your cake app components directory create ajaxthis.php


Component Class:

Download code <?php 
/*Using sanitize library*/
uses('sanitize');
/********************/
class AjaxthisComponent extends Object
{
    var 
$controller true;

    function 
startup(&$controller){
        
//Instantiation du controller parent
        
$this->controller = &$controller
    }

    
/**
    *Public : Call to controller action for initial parameters 
    **/
    
function initThis($ajaxCall null,$ajaxAction null,$ajaxParams = array()){
        if(!empty(
$ajaxCall)){
            if(!empty(
$ajaxAction)){
                if(empty(
$ajaxParams)){
                     if((
strtolower($this->controller->params['controller'])==strtolower($ajaxCall))&&(strtolower($this->controller->params['action'])==strtolower($ajaxAction))){
                        
$params call_user_func(array(&$this->controller,$ajaxAction));
                     }
                     else{
                        
$params $this->controller->requestAction('/'.$ajaxCall.'/'.$ajaxAction.'/');
                     }
                }
                else{
                     if((
strtolower($this->controller->params['controller'])==strtolower($ajaxCall))&&(strtolower($this->controller->params['action'])==strtolower($ajaxAction))){
                        
$params call_user_func(array(&$this->controller,$ajaxAction),$ajaxParams);
                     }
                     else{
                        
$params $this->controller->requestAction('/'.$ajaxCall.'/'.$ajaxAction.'/'.base64_encode(http_build_query($ajaxParams'''&')));
                    }
                }
            }
            else{
                if(empty(
$ajaxParams)){
                     if((
strtolower($this->controller->params['controller'])==strtolower($ajaxCall))&&(strtolower($this->controller->params['action'])==strtolower($ajaxAction))){
                        
$params call_user_func(array(&$this->controller,$ajaxCall),$ajaxParams);
                     }
                     else{
                        
$params $this->controller->requestAction('/'.$ajaxCall.'/');
                     }
                }
                else{
                     if((
strtolower($this->controller->params['controller'])==strtolower($ajaxCall))&&(strtolower($this->controller->params['action'])==strtolower($ajaxAction))){
                        
$params call_user_func(array(&$this->controller,$ajaxCall),$ajaxParams);
                     }
                     else{
                        
$params $this->controller->requestAction('/'.$ajaxCall.'/'.base64_encode(http_build_query($ajaxParams'''&')));
                    }
                }
            }
        }
        if(!isset(
$params)){
            return 
null;
        }
        else{
            return 
$params;
        }
    }

    
/**
    *Public : Processing ajax request and rendering
    **/
    
function ajaxThis($ajaxCall=null,$ajaxAction=null,$ajaxViews=null,$ajaxParams=null){
        
//Decoding values
        
if(!empty($ajaxCall)){
            
$decodedAjaxCall base64_decode($ajaxCall);
        }
        else{
            
$decodedAjaxCall null;
        }
        if(!empty(
$ajaxAction)){
            
$decodedAjaxAction base64_decode($ajaxAction);
        }
        else{
            
$decodedAjaxAction null;
        }
        if(!empty(
$this->controller->data)){
            if(!empty(
$ajaxParams)){
                
$decodedAjaxParams $this->_decodeAjaxParams($ajaxParams);
                
$decodedAjaxParams $decodedAjaxParams $this->controller->data;
                
$ajaxParams $this->encodeAjaxParams($decodedAjaxParams);
            }
        }
        else{
            
$decodedAjaxParams $this->_decodeAjaxParams($ajaxParams); 
        }
        
$decodedAjaxViews $this->_decodeAjaxViews($ajaxViews);
        
//Processing values
        
if(!empty($decodedAjaxCall)){
            if(empty(
$decodedAjaxParams)){
                if(empty(
$decodedAjaxAction)){
                    if((!empty(
$decodedAjaxViews))&&(!is_array($decodedAjaxViews))){
                        if(
strtolower($this->controller->name)==strtolower($decodedAjaxCall)){
                            
$ajaxParams call_user_func(array(&$this->controller$decodedAjaxViews));
                        }
                        else{
                            
//Requesting selected action (= view name) from selected controller
                            
$ajaxParams $this->controller->requestAction('/'.$decodedAjaxCall.'/'.$decodedAjaxViews.'/');
                        }
                    }
                    else{
                        if(
strtolower($this->controller->name)==strtolower($decodedAjaxCall)){
                            
$ajaxParams call_user_func(array(&$this->controller,$decodedAjaxCall));
                        }
                        else{
                            
//Requesting selected controller
                            
$ajaxParams $this->controller->requestAction('/'.$decodedAjaxCall.'/');
                        }
                    }
                }
                else{
                    if(
strtolower($this->controller->name)==strtolower($decodedAjaxCall)){
                        
$ajaxParams call_user_func(array(&$this->controller$decodedAjaxAction));
                    }
                    else{
                        
//Requesting selected action from selected controller
                        
$ajaxParams $this->controller->requestAction('/'.$decodedAjaxCall.'/'.$decodedAjaxAction.'/');
                    }
                }
            }
            else{
                if(empty(
$decodedAjaxAction)){
                    if((!empty(
$decodedAjaxViews))&&(!is_array($decodedAjaxViews))){
                        if(
strtolower($this->controller->name)==strtolower($decodedAjaxCall)){
                            
$ajaxParams call_user_func(array(&$this->controller,$decodedAjaxViews),$decodedAjaxParams);
                        }
                        else{
                            
//Requesting selected action (= view name) from selected controller  with params
                            
$ajaxParams $this->controller->requestAction('/'.$decodedAjaxCall.'/'.$decodedAjaxViews.'/'.$ajaxParams);
                        }
                    }
                    else{
                        if(
strtolower($this->controller->name)==strtolower($decodedAjaxCall)){
                            
$ajaxParams call_user_func(array(&$this->controller,$decodedAjaxCall),$decodedAjaxParams);
                        }
                        else{    
                            
//Requesting selected controller  with params
                            
$ajaxParams $this->controller->requestAction('/'.$decodedAjaxCall.'/'.$ajaxParams);
                        }
                    }
                }
                else{
                    if(
strtolower($this->controller->name)==strtolower($decodedAjaxCall)){
                        
$ajaxParams call_user_func(array(&$this->controller,$decodedAjaxAction),$decodedAjaxParams);
                    }
                    else{
                        
//Requesting selected action from selected controller with params
                        
$ajaxParams $this->controller->requestAction('/'.$decodedAjaxCall.'/'.$decodedAjaxAction.'/'.$ajaxParams);
                    }
                }
            }
        }
        else{
            if(empty(
$decodedAjaxParams)){
                if(empty(
$decodedAjaxAction)){
                    if((!empty(
$decodedAjaxViews))&&(!is_array($decodedAjaxViews))){
                        if(
strtolower($this->controller->name)==strtolower($decodedAjaxViews)){
                            
$ajaxParams call_user_func(array(&$this->controller,$decodedAjaxViews));
                        }
                        else{
                            
//Requesting selected action (= view name) from selected controller
                            
$ajaxParams $this->controller->requestAction('/'.$decodedAjaxViews.'/');
                        }
                    }
                }
                else{
                    if(
strtolower($this->controller->name)==strtolower($decodedAjaxAction)){
                        
$ajaxParams call_user_func(array(&$this->controller,$decodedAjaxAction));
                    }
                    else{
                        
//Requesting selected action from selected controller
                        
$ajaxParams $this->controller->requestAction('/'.$decodedAjaxAction.'/');
                    }
                }
            }
            else{
                if(empty(
$decodedAjaxAction)){
                    if((!empty(
$decodedAjaxViews))&&(!is_array($decodedAjaxViews))){
                        if(
strtolower($this->controller->name)==strtolower($decodedAjaxViews)){
                            
$ajaxParams call_user_func(array(&$this->controller,$decodedAjaxViews),$decodedAjaxParams);
                        }
                        else{
                            
//Requesting selected action (= view name) from selected controller  with params
                            
$ajaxParams $this->controller->requestAction('/'.$decodedAjaxViews.'/'.$ajaxParams);
                        }
                    }
                }
                else{
                    if(
strtolower($this->controller->name)==strtolower($decodedAjaxAction)){
                        
$ajaxParams call_user_func(array(&$this->controller,$decodedAjaxAction),$decodedAjaxParams);
                    }
                    else{
                        
//Requesting selected action from selected controller with params
                        
$ajaxParams $this->controller->requestAction('/'.$decodedAjaxAction.'/'.$ajaxParams);
                    }
                }
            }
        }
        
//setting the view vars (name of the element to render and ajax parameters)
        
$this->controller->set('ajaxViews',$decodedAjaxViews);
        
$this->controller->set($decodedAjaxCall.$decodedAjaxAction,$ajaxParams);
        
//rendering the view
        
$this->controller->render('ajax','ajax');
    }

    function 
getAjaxParams($params null){
        if(empty(
$params)){
            return 
null;
        }
        else{
            if(
is_array($params)){
                return 
$params;
            }
            else{
                return (
$this->_decodeAjaxParams($params));
            }
        }
    }

    function 
encodeAjaxParams($ajaxParams){
        return 
base64_encode(http_build_query($ajaxParams'''&'));
    }

    function 
_decodeAjaxViews($ajaxViews){
        
$clean = new Sanitize();
        if(empty(
$ajaxViews)){
            return 
null;
        }
        else{
            
$clean = new Sanitize();
            
$ajaxViews explode('&'base64_decode($ajaxViews));
            if(
is_array($ajaxViews)){
                foreach(
$ajaxViews as &$views){
                    
$views $clean->cleanValue($views);//sanitize
                    
if(!(strpos($views,'\\')===false)){
                        
$views stripslashes($views);
                    }
                }
                return 
$ajaxViews;
            }
            else{
                
$ajaxViews $clean->cleanValue($ajaxViews);//sanitize
                
return array($ajaxViews => $ajaxViews);
            }
        }
    }

    function 
_decodeAjaxParams($ajaxParams){
        if(empty(
$ajaxParams)){
            return 
null;
        }
        else{
            
//if ajaxParams are given
            
parse_str(base64_decode($ajaxParams),$ajaxParams);
            
$clean = new Sanitize();
            
$clean->cleanArrayR($ajaxParams);
            return 
$ajaxParams;
        }
    }

    
//normalize the case and sort an array values and keys 
    
function _normalizeArrays($arr = array()){
        if(
is_array($arr)){
            foreach(
$arr as &$subArr)
            if(
is_array($subArr)){
                foreach(
$subArr as &$value){
                    
$value ucwords(strtolower(trim($value)));
                }    
                
$subArr array_change_key_case($subArrCASE_UPPER);
                
ksort($subArr,SORT_STRING);
            }
        }
        return 
$arr;
    }

    
//Remove empty values from any multidimensiannal array
    
function _cleanArray($p_value){
        if (
is_array ($p_value)){
            if ( 
count ($p_value) == 0) {
                
$p_value null;
            } else {
                foreach (
$p_value as $m_key => $m_value) {
                    
$p_value[$m_key] = $this->_cleanArray($m_value);
                    if (empty (
$p_value[$m_key])) unset ($p_value[$m_key]);
                }
            }
        } else {
            if (empty (
$p_value)) {
                
$p_value null;
            }
        }
        return 
$p_value;
    }
}
?>

Ajaxthis Helper


In your cake app helper directory create ajaxthis.php


Helper Class:

Download code <?php 
class AjaxthisHelper extends Helper
{
    var 
$helpers = array('Ajax');
    
    
/**
    *Public : Call to controller action for view intial parameters (illegal, not compliant with cakePhp functionnal rules)
    **/
    
function initThis($ajaxCall null,$ajaxAction null,$ajaxParams = array()){
        
$params $this->view->controller->Ajaxthis->initThis($ajaxCall,$ajaxAction,$ajaxParams);
        if(!isset(
$params)){
            return 
null;
        }
        else{
            return 
$params;
        }
    }
    
    
/**
    *Public : Returning path to dummy ajax method with given params + the name of element to render and requested params
    **/
    
function ajaxThis($ajaxCall null,$ajaxAction null,$ajaxViews null,$ajaxParams = array()){
        if(!empty(
$ajaxAction)){
            return array(
                
'url' => '/'.$this->view->controller->params['controller'].'/ajaxThis/'.base64_encode($ajaxCall).'/'.base64_encode($ajaxAction).'/'.$this->_encodeAjaxViews($ajaxViews).'/'.base64_encode(http_build_query($ajaxParams'''&')),
                
'update' => $this->_filterAjaxViews($ajaxViews)
                );
        }
        else{
            return array(
                
'url' => '/'.$this->view->controller->params['controller'].'/ajaxThis/'.base64_encode($ajaxCall).'/'.$this->_encodeAjaxViews($ajaxViews).'/'.base64_encode(http_build_query($ajaxParams'''&')),
                
'update' => $this->_filterAjaxViews($ajaxViews)
                );
        }    
    }

    
/**
    *Private : Encoding ajaxViews array
    **/
    
function _encodeAjaxViews($ajaxViews){
        
$ajaxViews $this->_setAjaxViews($ajaxViews);
        if(!empty(
$ajaxViews)){
            return 
base64_encode($ajaxViews);
        }
        else{
            return 
$ajaxViews;
        }
    }
    
    
/**
    *Private : Filter ajaxViews array
    **/
    
function _filterAjaxViews($ajaxViews){
        
$ajaxViews $this->_setAjaxViews($ajaxViews);
        if(
strpos($ajaxViews '&')===false){
            if(!(
strpos($ajaxViews,'\\')===false)){
                return 
stripslashes($ajaxViews);
            }
            else{
                return 
$ajaxViews;
            }
        }
        else{
            
$ajaxViews explode('&' $ajaxViews);
            foreach(
$ajaxViews as &$view){
                
$view stripslashes($view);
                if(!(
strpos($view,'\\')===false)){
                    
$view stripslashes($view);
                    
//$view=substr($view,strpos($view,'\\')+1,strlen($view));
                
}
            }
            return 
$ajaxViews;
        }
    }
    
    
/**
    *Private : set corresponding ajaxViews
    **/
    
function _setAjaxViews($ajaxViews,$separator '&'){
        if (!empty(
$ajaxViews)){
            if(!
is_array($ajaxViews)){
                return 
$ajaxViews;
            }
            else{
                
$commonViews '';
                
$specificViews '';
                foreach(
$ajaxViews as $key => $view){
                    if(
$key == 'common'){
                        if(!
is_array($view)){
                            
$commonViews .= $view.$separator;
                        }
                        else{
                            foreach(
$view as $commonView){
                                
$commonViews .= $commonView.$separator;
                            }
                        }
                    }
                    else{
                        if(
strtolower($key) == strtolower($this->view->controller->params['controller'])){
                            if(!
is_array($view)){
                                
$specificViews .= $view.$separator;
                            }
                            else{
                                foreach(
$view as $action => $specificView){
                                    if(
strtolower($action) == strtolower($this->view->controller->params['action'])){
                                        if(!
is_array($specificView)){
                                            
$specificViews .= $specificView.$separator;
                                        }
                                        else{
                                            foreach(
$specificView as $subview){
                                                
$specificViews .= $subview.$separator;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                if(
$commonViews!=''){
                    if(
$specificViews!=''){
                        return 
substr($commonViews.$specificViews,0,strlen($commonViews.$specificViews)-1);
                    }
                    else{
                        return 
substr($commonViews,0,strlen($commonViews)-1);
                    }
                }
                else{
                    if(
$specificViews!=''){
                        return 
substr($specificViews,0,strlen($specificViews)-1);
                    }
                    else{
                        return 
null;
                    }
                }
            }
        }
        else{
            return 
null;
        }
    }
}
?>

AppController

(super class)

Put a copy of app_controller.php in your app directory with following code


Controller Class:

Download code <?php 
/*Using sanitize library*/
uses('sanitize');
/********************/
class AppController extends Controller {

    function 
ajaxThis($ajaxCall=null,$ajaxAction=null,$ajaxViews=null,$ajaxParams=null){
        
$this->Ajaxthis->ajaxThis($ajaxCall,$ajaxAction,$ajaxViews,$ajaxParams);
    }
}
?>

ajax.thtml


ajax.thtml view file in any view directory


View Template:

Download code
<?php
    
(!isset($ajaxParams)) ? $ajaxParams = array() : $ajaxParams;
    if(
is_array($ajaxViews)){
        foreach(
$ajaxViews as $ajaxView){
            echo 
$this->renderElement($ajaxView,$ajaxParams); 
        }
    }
    else{
        echo 
$this->renderElement($ajaxViews,$ajaxParams); 
    }
?>

How to use it :


create an ajax element in your elements directory


myajaxelement.thtml


View Template:

Download code
<?php
echo $ajax->div('myajaxelement');
    
//the name of the controller containing requested method
    
$mycontroller 'Mycontroller';
    
//requested method
    
$mymethod 'mymethod';
    
//elements to update with ajax
    
$myajaxelements = array('common' => 'myajaxelement');
    
//view params you want to send to the method
    //_____________________________________________________________________________________________default values_________________________________ajax values___________
    
(!isset($mycontrollermymethod )) ? $mycontrollermymethod = array('myfirstparam' => 'foo','mysecondparam'=> 'bar') : $mycontrollermymethod;
    
//call ajaxThis to build the path to requested method according to the current view 
    
$ajaxRequest $ajaxthis->ajaxThis($mycontroller,$mymethod