Schneimi’s Dev Weblog


User specific caching in CakePHP

Posted in CakePHP by schneimi on February 2, 2013
Tags: , ,

CakePHP caching depends all on controllers and given arguments. But, what if you want different cache files for conditions that don’t come along with the arguments, like session variables?

In my case, I simply wanted separate cache files for each user of my app, because some actions deliver different views for different users, and I didn’t want to pass the User.id to each action in every controller that was concerned.

So I had a look into the CacheHelper and noticed that the file name for cache files is just build from the controller name and given arguments. First I tried to make a modified version of CacheHelper for my app. I don’t remember what problems I ran into, but it just didn’t work out very well.

Then I came up with the idea to internally manipulate the URL, just before the cache is read and written. Somehow this lead me to the lib/Cake/Routing/Filter/CacheDispatcher.php, where I needed just a few lines of additional code to put the User.id into place, which worked like a charm and had no side effects.

You have to copy the original lib/Cake/Routing/Filter/CacheDispatcher.php into your app app/Routing/Filter/UserCacheDispatcher.php and add the following lines of code. To put the dispatcher into action you finally have to register it in the bootstrap.php and replace the core CacheDispatcher.


app/Routing/Filter/UserCacheDispatcher.php

(...)
public function beforeDispatch($event) {
    // load SessionComponent
    App::uses('SessionComponent', 'Controller/Component');
    $session = new SessionComponent(new ComponentCollection());
    $userID = $session->read('User.userID');

    if (!empty($userID)) {
      // add user id to request url with 'u' prefix
      $event->data['request']->here = $event->data['request']->here.'/u'.$userID;

      // remove request parameters to solve issue with ajax request random number parameter
      $event->data['request']->query = '';
    } else {
      return;
    }
(...)


app/Config/bootstrap.php

Configure::write('Dispatcher.filters', array(
    'AssetDispatcher',
    'UserCacheDispatcher'
));

I wonder why this use case is not supported by CakePHP out of the box, or didn’t I just see it? Well, I am aware that this might not be the best solution, but it is at least a pretty simple and effective workaround. Any ideas on how to solve the problem in better ways are welcome.

CakePHP Base64 encoded caching file size

Posted in CakePHP by schneimi on February 2, 2013
Tags: , , ,

I stumbled upon the problem that some cached files in CakePHP took alot more space than expected, and I read about a similar problem on StackOverflow

Well, I took a deeper look into this and it looks like the serialized data in viewVars is used for the nocache parts of the view. So the solution should be to place the part where the base64 data is output between nocache tags.


<!--nocache--><?php echo $base64Data; ?><!--/nocache-->

But doing this, I had the effect, that on some views it worked, on others it didn’t. A closer look into CakePHP revealed, that preg_match_all() is used to look for the nocache parts, which cannot handle a very large amount of data (may depend on server settings) and only finds nocache parts up to a certain amount of data. It doesn’t even throw an error, wich I checked with preg_last_error(). I read about rising pcre memory limits, but it didn’t work for me, so I had to find another solution for that problem.

In my case image data was retrieved from database and placed base64 encoded within the view in an image src tag (src=”data:image/png;base64,…”). My solution was to replace the data in the image src tag with a link to an extra action that delivers just the image data and caches it seperately. This also has the advantage, that the image can now be cached by browsers apart from the view.

The first thing I did, was to care about the image data not coming with the find, in order not to get serialized in the viewVars. But I still needed the information about if there actually was an image in the data, which brought me to setup a virtual field in my model. The virtual field does the following, if there is image data present in the field it holds 1 (TRUE), otherwise 0 (FALSE).


app/Model/YourModel.php

public $virtualFields = array(
    'image' => 'IF(image IS NOT NULL, TRUE, FALSE)'
);

http://book.cakephp.org/2.0/en/models/virtual-fields.html

For the image to retrieve and use, I setup an extra action without view in my controller and cached the data manually. Unfortunately you also have to care about deleting it when your model gets deleted or updated, but you can easily do that in the model save and delete callbacks.

http://book.cakephp.org/2.0/en/models/callback-methods.html


app/Controller/YourController.php

public function image($id) {
  $this->autoRender = false;
  $this->cacheAction = false;

  // read data from cache
  $data = Cache::read('image_'.$id);

  if (empty(data)) {
    $data = $this->YourModel->find('first', array(
        'conditions' => array(
            'YourModel.id' => $id
        ),
        'fields' => array(
            'image_type'
            'image_data'
        )
    ));

    // write data into cache
    Cache::write('image_'.$id, $data);
  }

  // send cache header for browser caching
  header('Content-Type: '.$data['YourModel']['image_type']);
  header('Cache-Control: public, max-age=28800');

  // output the raw image data
  echo $data['YourModel']['image_data];
}

In the view the image src looks like this:

<img src="/your_controller/image/<? echo $id; ?>" />

Another idea would be to generally unset the viewVars if nocache parts aren’t used in a view. But I don’t know where to hook in for that.

Behaviors on model associations

Posted in CakePHP by schneimi on September 6, 2009
Tags: , , , ,

I made a simple behavior that serializes (beforeSave) and deserializes (afterFind) data to and from the database.

The behavior works fine as long as I do a find directly over the model where my behavior is attached, but if I do a find through associated models using the containable behavior, my behavior is not triggered at all and I have to deserialize the data manually after the find, which is very uncomfortable.

Model behavior afterFind is triggered

$this->Model->find(...);

Model behavior afterFind isn’t triggered

$this->OtherModel->contain('Model');
$this->OtherModel->find(...);

I found a ticket and a thread concerning this topic, and it turns out that this is an existing problem with CakePHP for a longer time already.

Because I use the latest CakePHP 1.2.5 and couldn’t find any working patch for it, I adapted the patch found in the thread to this version. I am not very familiar with the core, so it’s still quick&dirty and I cannot tell if it works for every setup. Anyway I hope this still helps other people that run into the same problem.

Here are the changes that have to be done in the cake\libs\model\datasources\dbo_source.php, just place the code after the lines:

Line 877: $resultSet[$i][$association] = $linkModel->afterFind($resultSet[$i][$association]);

foreach ($linkModel->Behaviors->attached() as $behavior) {
  if ($behavior != 'Containable') {
    $data = array(array($association => $resultSet[$i][$association]));
    $filtered_data = $linkModel->Behaviors->{$behavior}->afterFind($linkModel, $data, false);
    
    if ($filtered_data) {
      $resultSet[$i][$association] = $filtered_data[0][$association];
    }
  }
}

Line 741: function queryAssociation(&$model, &$linkModel, $type, $association, $assocData, &$queryData, $external = false, &$resultSet, $recursive, $stack) {

foreach ($linkModel->Behaviors->attached() as $behavior) {
  if ($behavior != 'Containable') {
    $return = $linkModel->Behaviors->{$behavior}->beforeFind($linkModel, $assocData);
    $assocData = (is_array($return)) ? $return : $assocData;
  }
}

Line 716: $data = $model->{$className}->afterFind(array(array($className => $results[$i][$className])), false);

foreach ($model->{$className}->Behaviors->attached() as $behavior) {
  if ($behavior != 'Containable') {
    $filtered_data = $model->{$className}->Behaviors->{$behavior}->afterFind($model->{$className}, $data, false);
    
    if ($filtered_data) {
      $data = $filtered_data;
    }
  }
}

edit:
I added support for the beforeFind callback, because I needed this on a softdelete behavior to filter associated deleted data as well.

I also found out that you don’t have to really hack the core, instead just copy the cake\libs\model\datasources\dbo_source.php into your app app\models\datasources\dbo_source.php and change this file instead.

Fast random find with CakePHP

Posted in CakePHP by schneimi on August 28, 2009
Tags: , , ,

I want to share my experience with random finds in CakePHP because they can be very slow done the wrong way.

In this example we have the model Audioplaylist which hasAndBelongsToMany Audio and we want to get a random playlist of Audios. We assume the table for Audio is pretty large and we also need the data of other related models as result, let’s say the owner’s username and the title of the related album. All models are set to $recursive = -1 and the ContainableBehavior is used to contain the data.

  • The first approach would be to make one find and get all the data at once.
  • $audios = $this->Audioplaylist->Audio->find('all', array('contain' => array('User.name',
                                                                                'Album.title'),
                                                             'order' => 'RAND()',
                                                             'limit' => $count));
    

    Happy waiting!

    You have to know that DB queries ordered by RAND() get slower the more fields are selected, and in this case we even select all fields of the Audio model.

  • Knowing that, we can improve the find in using the fields parameter to also contain the fields of the Audio model.
  • $audios = $this->Audioplaylist->Audio->find('all', array('contain' => array('User.name',
                                                                                'Album.title'),
                                                             'fields' => 'id',
                                                             'order' => 'RAND()',
                                                             'limit' => $count));
    

    This is already a lot faster than before, but still not very applicable because it still slows down with the amount of related data.

  • So let’s try another approach that looks more circumstantial but avoids the problem seen before. The idea is to make two finds, the first one just finds some random Audio Ids and the second will then use the Ids to catch all the related data.
  • $randomAudioIds = $this->Audioplaylist->Audio->find('list', array('fields' => 'id',
                                                                      'order' => 'RAND()',
                                                                      'limit' => $count));
    
    $audios = $this->Audioplaylist->Audio->find('all', array('contain' => array('User.name',
                                                                                'Album.title'),
                                                             'conditions' => array('Audio.id' => $randomAudioIds),
                                                             'order' => 'RAND()'));
    

    Sometimes more is less, especially if you need alot of data from related models, you will appreciate the performance of these two queries.

    Star rating plugin for CakePHP

    Posted in Ajax,CakePHP by schneimi on May 25, 2009
    Tags: , , , , , ,

    In the google groups I recently noticed the lack of a star rating helper/plugin for CakePHP.

    As I just implemented an AJAX rating system for my current project and always wanted to learn the CakePHP plugin system, I thought this was a good opportunity to make my code into a plugin and contribute to the community.

    My rating system supports multiple users and multiple models, so you can let people rate any model you want.

    I tried to keep it simple and flexible, but I am sure there is still much room for improvements. So let me know what you think about it, any suggestions and comments are welcome.

    Here is the plugin v2.5 for CakePHP 1.3 to download. Older versions for CakePHP 1.2 can be found here.

    This article can also be found in the Bakery.

    Features

    • Multi user, multi model rating
    • Guest rating
    • Just one element to place in your views
    • Seamless integration with AJAX
    • Prototype and jQuery support
    • Cross browser compatibility
    • Fallback for disabled javascript
    • Various configurations

    Requirements

    • CakePHP 1.2, 1.3
    • Prototype or jQuery javascript framework
    • User id stored in session for secure rating

    Demonstration
    A demo can be tested at
    http://ratingdemo.schneimi.hostingsociety.com/

      Installation and Use

      • Make sure you meet the requirements above. Make sure you meet the requirements above. For the download and integration of a javascript framework, please visit the Prototype or jQuery website.
        • Extract the plugin, including the subfolder ‘rating’, to your app plugins folder ‘app/plugins‘.
          • Copy the ‘rating/config/plugin_rating.php’ to your app configs folder ‘app/config’ and change the settings to your desire. It is recommended to let ‘Rating.showHelp’ set to true until everything works.
            • Apply the ‘install.sql’ to your database to create the ratings table.
              CREATE TABLE `ratings` (
                `id` int(11) unsigned NOT NULL auto_increment,
                `user_id` char(36) NOT NULL default '',
                `model_id` char(36) NOT NULL default '',  
                `model` varchar(100) NOT NULL default '',
                `rating` tinyint(2) unsigned NOT NULL default '0',
                `name` varchar(100) default '',
                `created` datetime default NULL,
                `modified` datetime default NULL,
                PRIMARY KEY (`id`),
                KEY `rating` (`model_id`,`model`,`rating`,`name`)
              );
              
            • Load the plugin javascript and css files in your layout file. Replace [your_framework] with prototype_min or jquery_min depending on the framework you use.
                <?php echo $javascript->link('/rating/js/[your_framework]'); ?>
                <?php echo $html->css('/rating/css/rating'); ?> 
                
            • For full model integration in your app, apply the following relation to your models. (replace [name_of_your_model])
                var $hasMany = array('Rating' =>
                                     array('className'   => 'Rating',
                                           'foreignKey'  => 'model_id',
                                           'conditions' => array('model' => '[name_of_your_model]'),
                                           'dependent'   => true,
                                           'exclusive'   => true
                                     )
                               );  
            • If you set ‘Rating.saveToModel’ to true, then add the defined ‘Rating.modelAverageField’ and ‘Rating.modelVotesField’ to all models you want to rate. To do that you can use the following SQL statements (replace [your_table] and [Rating.modelAverageField]).
              ALTER TABLE [your_table] ADD (`[Rating.modelAverageField]` decimal(3,1) unsigned default '0.0');
              ALTER TABLE [your_table] ADD (`[Rating.modelVotesField]` int(11) unsigned default '0');

              If the plugin shows the fields are still missing, try to clear the model cache of your app at ‘app/tmp/cache/models’.

            • You can change the styles of the rating element in the css file ‘rating/vendors/css/rating.css’.
              • Finally you can place the rating element in your views as follows. (replace [name_of_your_model] and [id_of_your_model])

                Default rating element for one model id

                  echo $this->element('rating', array('plugin' => 'rating',
                                                      'model' => '[name_of_your_model]',
                                                      'id' => [id_of_your_model]));
                 

                More ratings for one model id
                If you want to have different ratings for one model id like sound and picture of a movie, you can use the additional name parameter.

                  echo $this->element('rating', array('plugin' => 'rating',
                                                      'model' => '[name_of_your_model]',
                                                      'id' => [id_of_your_model],
                                                      'name' => 'sound'));
                
                  echo $this->element('rating', array('plugin' => 'rating',
                                                      'model' => '[name_of_your_model]',
                                                      'id' => [id_of_your_model],
                                                      'name' => 'picture'));
                

                Individual configuration of a rating element
                Sometimes you want to use more than one style of rating elements in your app. That can be reached with the ‘config’ parameter and different config files in ‘app/config’. Just clone the original ‘plugin_rating.php’ and give it a different name, which you then pass to the element. There is also the possibility to overload the config file settings on the element.

                  // Uses 'plugin_rating.php' in 'app/config'
                  echo $this->element('rating', array('plugin' => 'rating', 
                                                      'model' => '[name_of_your_model]',
                                                      'id' => [id_of_your_model]));
                  
                  // Uses 'plugin_rating_style1.php' in 'app/config'
                  echo $this->element('rating', array('plugin' => 'rating',
                                                      'model' => '[name_of_your_model]',
                                                      'id' => [id_of_your_model],
                                                      'config' => 'plugin_rating_style1'));
                
                  // overload default settings
                  echo $this->element('rating', array('plugin' => 'rating',
                                                      'model' => [name_of_model],
                                                      'id' => [id_of_model],
                                                      'config' => array('Rating.[setting_1]' => true,
                                                                       'Rating.[setting_2]' => false)));
                                                    
                  // overload individual settings
                  echo $this->element('rating', array('plugin' => 'rating',
                                                      'model' => [name_of_model],
                                                      'id' => [id_of_model],
                                                      'config' => array('plugin_rating_style1', array(
                                                          'Rating.[setting_1]' => true,
                                                          'Rating.[setting_2]' => false));
                

              Pausing remoteTimer

              Posted in Ajax,CakePHP by schneimi on November 25, 2008

              This is a simple example on how to create a remoteTimer that can be paused by the user, in this case this is done over a checkbox.

              Because the ajax-Helper offers little help in this case, we need the help of javascript. We also have to simulate pausing with start and stop, because the prototype remoteTimer doesn’t offer it.

              Javascript
              First of all we need a variable to store the timer, in order to be able to stop it later.

              var mytimer = null;
              

              Next we need a function to start the timer with the important parameters.

              function startTimer(url, update, frequency) {
                mytimer = new PeriodicalExecuter(function() {
                    new  Ajax.Updater(update, url , {asynchronous:true, evalScripts:true,
                        requestHeaders:['X-Update', update]})}, frequency);
              }
              

              And for last we need a function to stop the timer.

              function stopTimer() {
                mytimer.stop();
              }
              

              View
              Now we can use the javascript-functions in the view.

              echo $javascript->codeBlock("startTimer('/yourapp/posts/view', 'mydiv', 5)");
              echo $form->checkbox('pause', array('checked' => true,
                'onclick' => "if (this.checked){
                  startTimer('/yourapp/posts/view', 'mydiv', 5);
                } else {
                  stopTimer();
                }"));
              

              VLC like volume control with ajax slider

              Posted in CakePHP,JS by schneimi on March 13, 2008
              Tags: , , , ,

              I needed a volume control for my site, so I immediately thought of the Ajax slider and easily set up the control. But then I noticed, it looked too much like a timeline and came up with the idea of a VLC like control, where a bar fills up to the selected volume.

              After some experiments, I finally managed to get it working, so here is how it works. The idea is to underlay the track and handle of the slider with a volume bar, that is increased and decreased via javascript on slide (on change). For the volume bar beeing visible through the track, the background of the track is set transparent. The handle will not be visible, so you can click everywhere on the track to set the volume, but you are still able to drag it once you clicked in the track.

              This is how it looks like in two different states. vlc_volume.png

              Here is the HTML/PHP code for the view and the needed CSS and Javascript. I only tested it with Firefox 2.00.12, Opera 9.25 and IE7.

              HTML:

              <div id="volume_control">
                <div id="vol_track">
                  <div id="vol_handle"></div>
                </div>
                <div id="vol_bar"></div>
              </div>
              
              <?php echo $ajax->slider('vol_handle',
                                       'vol_track',
                                       array('range' => '$R(0, 100)',
                                             'sliderValue' => 50,
                                             'increment' => 1,
                                             'onChange' => 'function(vol){setVolume(vol)}',
                                             'onSlide' => 'function(vol){setVolume(vol)}'
                                            )); ?>

              CSS:

              div#volume_control {
                width: 50px;
                height: 16px;
                position: relative;
                background-color: #cccccc;
              }
              
              div#vol_handle {
                width: 0px;
                height: 16px;
                cursor: pointer;
              }
              
              div#vol_track {
                width: 50px;
                height: 16px;
                position: absolute;
                border: 1px inset #fff;
                background-color: transparent;
                cursor: pointer;
                z-index: 2;
              }
              
              div#vol_bar {
                width: 25px;
                height: 16px;
                position: absolute;
                background-color: #1743A7;
                z-index: 1;
              }

              Javascript:

              function setVolume(vol) {
                $('vol_bar').style.width = Math.round(vol/2) + 'px';
              }

              Multiple ajax requests problems and AjaxQueue as solution

              Posted in Ajax,CakePHP by schneimi on March 10, 2008
              Tags: , , , ,

              For my app I had to load many import processes at once by Ajax requests, so I ran into some serious problems.

              1. Session data was not available each second request
              I used the database option for Sessions, and that seemed to be the problem in this case. Because I don’t worry much about how sessions are saved, I changed it to cake in core.php and it solved this problem, not a really good solution, but I’m fine with it.

              2. Timed out Socket connections
              During the import process I had to make some Socket Connections and however there suddenly was no connection possible anymore after 10-15 requests, so the following ran into timeout.

              3. The solution: AjaxQueue
              After some search and search and search, I finally found a script called AjaxQueue posted on a mailing list. There you can set the maximum amout of simultaneous Ajax requests, exactly what I was looking for. After some testing it turned out to do a really wonderful job and all my problems were solved without loosing much of performance.

              The following code is for the Prototype framework, but it should be no problem to adapt it to other frameworks in replacing the “Ajax.”-statements to similar ones of another framework.

              var AjaxQueue = {
              batchSize: 1, //No.of simultaneous AJAX requests allowed, Default : 1
              urlQueue: [], //Request URLs will be pushed into this array
              elementsQueue: [], //Element IDs of elements to be updated on completion of a request ( as in Ajax.Updater )
              optionsQueue: [], //Request options will be pushed into this array
              setBatchSize: function(bSize){ //Method to set a different batch size. Recommended: Set batchSize before making requests
              this.batchSize = bSize;
              },
              push: function(url, options, elementID){ //Push the request in the queue. elementID is optional and required only for Ajax.Updater calls
              this.urlQueue.push(url);
              this.optionsQueue.push(options);
              if(elementID!=null){
              this.elementsQueue.push(elementID);
              } else {
              this.elementsQueue.push(“NOTSPECIFIED”);
              }

              this._processNext();
              },
              _processNext: function() { // Method for processing the requests in the queue. Private method. Don’t call it explicitly
              if(Ajax.activeRequestCount < AjaxQueue.batchSize) // Check if the currently processing request count is less than batch size { if(AjaxQueue.elementsQueue.first()=="NOTSPECIFIED") { //Check if an elementID was specified // Call Ajax.Request if no ElementID specified //Call Ajax.Request on the first item in the queue and remove it from the queue new Ajax.Request(AjaxQueue.urlQueue.shift(), AjaxQueue.optionsQueue.shift()); var junk = AjaxQueue.elementsQueue.shift(); } else { // Call Ajax.Updater if an ElementID was specified. //Call Ajax.Updater on the first item in the queue and remove it from the queue new Ajax.Updater(AjaxQueue.elementsQueue.shift(), AjaxQueue.urlQueue.shift(), AjaxQueue.optionsQueue.shift()); } } } }; Ajax.Responders.register({ //Call AjaxQueue._processNext on completion ( success / failure) of any AJAX call. onComplete: AjaxQueue._processNext }); /************* SYNTAX *************** AjaxQueue.setBatchSize(size); AjaxQueue.push(URL , OPTIONS, [ElementID]); ************** USAGE *************** AjaxQueue.setBatchSize(4); AjaxQueue.push("http://www.testingqueue.com/process/",{onSucess: funcSuccess, onfailure: funcFailure}); AjaxQueue.push("http://www.testingqueue.com/process1/",{onSucess: funcSuccess1, onfailure: funcFailure1}, "myDiv"); AjaxQueue.push("http://www.testingqueue.com/process2/",{onSucess: funcSuccess2, onfailure: funcFailure2}); AjaxQueue.push("http://www.testingqueue.com/process3/",{onSucess: funcSuccess3, onfailure: funcFailure3}); AjaxQueue.push("http://www.testingqueue.com/process4/",{onSucess: funcSuccess4, onfailure: funcFailure4}); AjaxQueue.push("http://www.testingqueue.com/process5/",{onSucess: funcSuccess5, onfailure: funcFailure5}); **********************************/[/sourcecode]

              Update multiple fields with one ajax request response

              Posted in Ajax,CakePHP by schneimi on October 27, 2007
              Tags: , , ,

              I searched for a possibility to update multiple fields with each ajax request made by a remoteTimer, but didn’t find any satisfying explanation on the web. To get that right, each field should get updated with the same response of a request.

              So I had a look closer into the ajax helper and the prototype framework and came up with a little hack that suffice my needs and might be also helpful for others.

              In the ajax helper (cake/libs/helpers/ajax.php) we first have to look into what happens when the options[update] parameter is set to an array with the different fields we want to update. That leads us to the ‘remoteFunction’ where following happens in that case:

              $func = "new Ajax.Updater(document.createElement('div'),";

              I have no clue what the creation of the div is really good for, but anyway we have to replace it with a JavaScript array holding the id’s of our fields:

              $update = '[';
              
              foreach($options['update'] as $option) {
                $update .= "'" . $option . "',";
              }            
              
              $update .= ']';                
              
              $func = "new Ajax.Updater({$update},";

              Now we must have a look into the prototype framework (app/webroot/js/prototype.js) and make sure the function Ajax.Updater can handle that array. The important function there is called updateContent:

              Ajax.Updater = Class.create();
              
              Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
              
                [...]
              
                updateContent: function() {
              
                  [...]
              
                  if (receiver = $(receiver)) {
                    if (this.options.insertion)
                      new this.options.insertion(receiver, response);
                    else
                      receiver.update(response);
                  }
              
                  [...]
                }
              });

              Because the receiver is now our array, we must step through it and send a response to each of it:

              Ajax.Updater = Class.create();
              
              Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
              
                [...]
              
                updateContent: function() {
              
                  [...]
              
                  if (receiver.constructor.toString().indexOf("Array") != -1) {
                    for(var i = 0; i < receiver.length; i++) {
                      if(r = $(receiver&#91;i&#93;)) {
                    if (this.options.insertion)
                      new this.options.insertion(r, response);
                    else
                      r.update(response);
                  }
                    }
                  } else {
                    if (receiver = $(receiver)) {
                      if (this.options.insertion)
                        new this.options.insertion(receiver, response);
                      else
                        receiver.update(response);
                    }
                  }
              
                  &#91;...&#93;
                }
              });&#91;/sourcecode&#93;
              After checking if the receiver is actually an array, we step through it and send the response to every our fields.
              
              Finished!
              
              You can now use it like:
              &#91;sourcecode language="php"&#93;
              <?php echo $ajax->remoteTimer(array('url' => 'controller/action', 'update' => array('field1', 'field2', 'field3'), 'frequency' => '5')); ?>

              Sortable table rows with ajax helper

              Posted in Ajax,CakePHP by schneimi on October 25, 2007
              Tags: , ,

              Most examples for drag&drop sorting with the ajax helper use lists (<ul>, <ol>) to demonstrate, but if you have several columns each row, you really would like to use a table and drag&drop it’s rows.

              There is a solution mentioned at script.aculo.us, where you just have to use the HTML 4.0 specified table looking like:

              <table>
                <thead><tr><td></td></tr></thead>
                <tfoot><tr><td></td></tr></tfoot>
                <tbody><tr><td></td></tr></tbody>
              </table>

              Now you can use the tbody as parent in the ajax sortable function and set the ‘tag’-option to ‘tr’.Here is a small example in cakePHP:

              <table>
                <thead>
               <tr><th>Sortable Table</th></tr>
                </thead>
                <tbody id="sortable_table">
               <tr><td>row 1</td></tr>
               <tr><td>row 2</td></tr>
               <tr><td>row 3</td></tr>
                </tbody>
              </table>
              
              <?php echo $ajax->sortable('sortable_table', array('tag' => 'tr')); ?>

              Because the table rows are not set to float, you don’t see any moving effect while dragging a row. I found no way to make it float without the table beeing messed up.

              Next Page »