Multirecord helper/behavior
Edit and add multiple record forms. A behavior and helper working together to create multiple records form in the same way Form->inputs() work. With a very few lines of code, go from one to many records at once.
This project stated when I was to look at a component and redid it to learn more about components. I chose a multiple record component by Marcel Raaijmakers (Marcelius) and his article http://bakery.cakephp.org/articles/view/how-to-create-multirecord-forms as my project.
The main goal was to be able to edit, add and delete multiple record set. Add 4 posts at once, edit 3 users at a time, delete all the selected files.
The problem was to get the form helper to create a form, which matched the data set Model->saveAll wants.
As Marcel Raaijmakers points out: [parafrase]"The form need to be on a format saveAll() can handle, so the Helper has to create a data set on the right format when posted. It has to be on the form ModelAlias.{n}.fieldName while when it comes from Model->find('all',) it is on the form {n}.ModelAlias.fieldName."[/parafrase]
At first I tried to write a component to rewrite the parameter array sent to the functions on the controller. This way the data format would fit what Model->saveAll() wants. This proved harder than I was prepared for, and instead of a component and a helper, it became a behavior and a helper.
The behavior do the save, find and delete operations. And the helper duplicates the Form->inputs() functionality.
Other important aspects for the project:
* Usability
* Stick with convention
* Only minor changes to controller and views to make it work
* Helper code-page 2
* Behavior code-page 3
* Full working example-page 4
Download code
Instead of
Download code
The $ids are either a string on the form $ids = "3 5 8" or an array $ids = array(2,5,8)
When Cake parses the url and sent parameters to the function, an url on the form
http://host/app/controller/edit/3+5+8 is sent to the controller function as a string on the form "3 5 8".
This is the reason I want the funtion to take in the string. To find the multi record set, you only need to add the ids to the url.
The function findMulti() returns an array on the form ModelAlias.{n}.fieldName, a normal find('all') which returns data on the format {n}.ModelAlias.fieldName.
Download code
Instead of
Download code
By manipulating the post data, the data is now on a form where it can use saveAll($this->data) the only thing done, is validating all, before the save goes trough.
Download code
Instead of
Download code
Here again the $ids is a string on the form $ids = "3 5 8" or an array $ids = array(3,5,8) in the same way the edit() function did.
Download code
Instead of
Download code
This will create a form with the same number of records as is in $this->data. It expects the data to be on the format the MultipleRecords Behavior provides.
The inputs() function require Form->create() to be run first.
Download code
When this form button is pressed, it posts the data to the add form, sending it back to the view, while not saving it. This is done so the entire form set is generated again, with one more empty set. The reason for this is to make it work with the security component. As the security component hashes the fields, an ajax call to add an empty set would fail when the form is finely posted. It also keeps whatever you have already written, but not saved. The same would go for script calls to remove single records.
The main goal was to be able to edit, add and delete multiple record set. Add 4 posts at once, edit 3 users at a time, delete all the selected files.
The problem was to get the form helper to create a form, which matched the data set Model->saveAll wants.
As Marcel Raaijmakers points out: [parafrase]"The form need to be on a format saveAll() can handle, so the Helper has to create a data set on the right format when posted. It has to be on the form ModelAlias.{n}.fieldName while when it comes from Model->find('all',) it is on the form {n}.ModelAlias.fieldName."[/parafrase]
At first I tried to write a component to rewrite the parameter array sent to the functions on the controller. This way the data format would fit what Model->saveAll() wants. This proved harder than I was prepared for, and instead of a component and a helper, it became a behavior and a helper.
The behavior do the save, find and delete operations. And the helper duplicates the Form->inputs() functionality.
Other important aspects for the project:
* Usability
* Stick with convention
* Only minor changes to controller and views to make it work
Content:
* Description-this page.* Helper code-page 2
* Behavior code-page 3
* Full working example-page 4
Behvior
FindMulti
To find data I did thisDownload code
$this->data = $this->Model->findMulti($ids);
Instead of
Download code
$this->data = $this->Model->find('all');
The $ids are either a string on the form $ids = "3 5 8" or an array $ids = array(2,5,8)
When Cake parses the url and sent parameters to the function, an url on the form
http://host/app/controller/edit/3+5+8 is sent to the controller function as a string on the form "3 5 8".
This is the reason I want the funtion to take in the string. To find the multi record set, you only need to add the ids to the url.
The function findMulti() returns an array on the form ModelAlias.{n}.fieldName, a normal find('all') which returns data on the format {n}.ModelAlias.fieldName.
SaveMulti
For edit and add I also wanted to save multiple recordsDownload code
$this->Model->saveMulti($this->data);
Instead of
Download code
$this->Model->save($this->data);
By manipulating the post data, the data is now on a form where it can use saveAll($this->data) the only thing done, is validating all, before the save goes trough.
DeleteMulti
To delete all I did thisDownload code
$this->Model->deleteMulti($ids)
Instead of
Download code
$this->Model->del($id)
Here again the $ids is a string on the form $ids = "3 5 8" or an array $ids = array(3,5,8) in the same way the edit() function did.
MultipleRecords Helper
The helper is made to supply the Form helper they are used together. It only needs two functions.$multipleRecords->inputs()
To add the multiple records formDownload code
echo $form->create('Post', array('url'=>array('action' => 'edit')));
echo $multipleRecords->inputs();
echo $form->end('Submit');
Instead of
Download code
echo $form->create('Post', array('url'=>array('action' => 'edit')));
echo $$form->inputs();
echo $form->end('Submit');
This will create a form with the same number of records as is in $this->data. It expects the data to be on the format the MultipleRecords Behavior provides.
The inputs() function require Form->create() to be run first.
$multipleRecords->add()
The other function is only a helper to add one more empty record set than already displayed on screen.Download code
echo $multipleRecords->add('Add Post');
When this form button is pressed, it posts the data to the add form, sending it back to the view, while not saving it. This is done so the entire form set is generated again, with one more empty set. The reason for this is to make it work with the security component. As the security component hashes the fields, an ajax call to add an empty set would fail when the form is finely posted. It also keeps whatever you have already written, but not saved. The same would go for script calls to remove single records.
Comments
Comment
1 Nice implementation
I think you implementation is more cake-ish then mine because of the extended form helper capabilities you added. It's also good to know that more people ran into the same problems and I hope these articles will trigger people to further develop this kind of behavior.
Like you, I also started to do a rewrite from {n}.ModelAlias.fieldName to ModelAlias.{n}.fieldName and visa versa, but as you said its not easy, mostly because it gets complicated with deep (habtm) relations. But I'm still working on that so eventually this would also work: (I hope :-p)
$this->Post->save($this->data); //so no extra component is needed
Anyway, great extensive article!
Marcelius
Comment
2 Small update
I changed the example code so it now works correctly.
Thank you Marcelius.
Question
3 I don't know if it's bug, but it's not working on version 1.2.2.8120
Nice post, but it's not working on version 1.2.2.8120.
When I try add new post filling the form e pressing Add Post button nothing happens and I notice message appear on screem.
Notice (8): Undefined variable: numberOfRecords [APP\views\posts\add.ctp, line 8]
And still the label "Add Post 1" that should be increment, don't do!!!
I'm new on cake and disable cache on core configuration.
Thanks.
Alexandre
Comment
4 Thanks for the reply
I checked the code, and it works for me, at cake version 1.2.2.8166
Be careful to don't mix the add and the edit functions in the controller but add both.