Cakephp HABTM: View generating drop down instead of multi value selectbox - cakephp

I am trying to work with HABTM association between Profiles and Qualifications tables.
Model: Profile.php
App::uses('AppModel', 'Model');
class Profile extends AppModel {
public $hasAndBelongsToMany = array(
'Qualification' => array(
'className' => 'Qualification',
'joinTable' => 'profile_qualifications',
'foreignKey' => 'profile_id',
'associationForeignKey' => 'qualification_id',
'unique' => 'keepExisting'
)
);
}
Model: Qualification.php
App::uses('AppModel', 'Model');
class Qualification extends AppModel {
public $hasAndBelongsToMany = array(
'Profile' => array(
'className' => 'Profile',
'joinTable' => 'profile_qualifications',
'foreignKey' => 'qualification_id',
'associationForeignKey' => 'profile_id',
'unique' => 'keepExisting',
)
);
}
Controller: ProfilesController.php
App::uses('AppController', 'Controller');
class ProfilesController extends AppController {
public function edit() {
$this->Profile->id = $this->Auth->user('profile_id');
if ($this->request->is('post') || $this->request->is('put')) {
if ($this->Profile->save($this->request->data)) {
$this->Session->setFlash(__('The profile has been saved'));
$this->redirect(array('action' => 'view'));
} else {
$this->Session->setFlash(__('The profile could not be saved. Please, try again.'));
}
} else {
$this->request->data = $this->Profile->read(null, $this->Auth->user('profile_id'));
}
$salutations = $this->Profile->Salutation->find('list', array('fields' => array('Salutation.id', 'Salutation.abbr_name')));
$qualifications = $this->Profile->Qualification->find('list', array('fields' => array('Qualification.id', 'Qualification.abbr_name')));
$this->set(compact('salutations', 'qualifications'));
}
}
Vew: edit.ctp
<div class="profiles form">
<?php echo $this->Form->create('Profile'); ?>
<fieldset>
<legend><?php echo __('My Profile'); ?></legend>
<?php
echo $this->Form->input('salutation_id');
echo $this->Form->input('first_name');
echo $this->Form->input('middle_name');
echo $this->Form->input('last_name');
echo $this->Form->input('qualification'); /* gives drop down not multi select */
echo $this->Form->input('bio');
echo $this->Form->input('email');
echo $this->Form->input('mobile');
echo $this->Form->input('phone');
?>
</fieldset>
<?php echo $this->Form->end(__('Submit')); ?>
</div>
The edit view thus generated contains drop down to select a single value at a time for Qualifications attribute.
I want to know how can I generate a view with multi value selection box for qualifications ?
Moreover, what is the mistake in my code right now ?

First time poster, long time user.
I stumbled across this question today, and ended up using the subsequent solution which indeed does work quite nicely. However, I left myself wondering "why wouldn't CakePHP pickup on the HABTM relationship properly?" Especially considering (at least in my case) that the majority of the files had been baked in the cake console.
If we dive a little deeper into the issue, we discover that the problem is actually quite simple. A closer look at this snippet in the PostsController.php reveals how Cake builds in the HABTM relationship to the function, and uses $this->set() in order to pass it to the view (IMPORTANT: using lower-case plural versions "salutations"):
$salutations = $this->Profile->Salutation->find('list', array('fields' => array('Salutation.id', 'Salutation.abbr_name')));
$qualifications = $this->Profile->Qualification->find('list', array('fields' => array('Qualification.id', 'Qualification.abbr_name')));
$this->set(compact('salutations', 'qualifications'));
According to the Cake Cook Book, in order to take advantage of this HABTM in the front end when using the form helper is to specify the variable in singular form & title case (ie: "Salutation")
Snippet from the cook book:
Assuming that User hasAndBelongsToMany Group. In your controller, set a camelCase plural variable (group -> groups in this case, or ExtraFunkyModel -> extraFunkyModels) with the select options. In the controller action you would put the following:
$this->set('groups', $this->User->Group->find('list'));
And in the view a multiple select can be created with this simple code:
echo $this->Form->input('Group');
Should solve your issue without any necessary field tweaking.
Cheers!
Bake on.

Perhaps you need further configuration of your input:
echo $this->Form->input('Qualification',array(
'label' => 'Qualifications',
'type' => 'select',
'multiple' => true, // or 'checkbox' if you want a set of checkboxes
'options' => $qualifications,
'selected' => $html->value('Qualification.Qualification'),
));
I've used this blog post whenever I've come up against HABTM associations. It seems to me that a set of checkboxes maybe desired by default over a select input - maybe someone with greater CakePHP insight can chime in here?

Change
echo $this->Form->input('qualification');
to
echo $this->Form->input('qualification', array(
'multiple' => true
));
The CakePHP manual has more information on the form helper input options.

Related

cakephp passedArgs empty

I have a simple form in a a view and I am trying to access the $this=>passedArgs but it is coming back empty.
I am actualy trying to use the cakeDC search plugin which uses the $this=>passedArgs. It must be something simple I have not done to get the results from the form submit.
find view
<?php
echo $this->Form->create('Member', array(
'url' => array_merge(array('action' => 'find'), $this->params['pass'])
));
echo $this->Form->input('name', array('div' => false));
echo $this->Form->submit(__('Search'), array('div' => false));
echo $this->Form->end();
?>
Controller
public function find() {
debug($this->passedArgs);
exit;
}
I have tried $this->request->params
array(
'plugin' => null,
'controller' => 'members',
'action' => 'find',
'named' => array(),
'pass' => array(),
'isAjax' => false
)
I have add method get to the form.
This question has been asked before but their solution of having lower cases in the public $uses = array('order', 'product'); when it should be public $uses = array('Order', 'Product'); did not work.
Cakephp version 2.3.5
Thanks for any help
Update:
I have set my form to method get and this is the url:
http://localhost/loyalty/members/find?name=searchtext
I have removed the plugin and I still do not get anything $this->passedArgs, but I now get data for $this->request->data['name']. Once I put public $components = array('Search.Prg'); I get noting again for $this->request->data['name'].
I have tried again $this->Prg->parsedParams() with the Search plugin and I just get array()
The documentation is pretty clear on that.
You cannot just debug something that has not been set yet.
So including the plugin itself (and its component) is not enough.
From the readme/documenation:
public function find() {
$this->Prg->commonProcess();
$this->paginate['conditions'] = $this->ModelName->parseCriteria($this->passedArgs);
$this->set('...', $this->paginate());
}
Note the commonProcess() call which then only makes passedArgs contain what you need.

Multiple form with same model name on single page cakephp

I have two form on a single page: login form and register form. When I submit the register form, it validates both: form fields that are in login and registeration. How can I handle it if both form have the same model (user model)
Register form
<?php echo $this->Form->create('User', array('url' => array('controller' => 'users', 'action' => 'add'))); ?>
<?php echo $this->Form->input('username', array('label' => false, 'div' => false, 'class' => 'reg_input'));?>
<?php echo $this->Form->input('email', array('label' => false, 'div' => false, 'class' => 'reg_input'));?>
<?php echo $this->Form->input('password', array('label' => false, 'div' => false, 'class' => 'reg_input'));?>
<?php echo $this->Form->input('confirm_password', array('type' => 'password', 'label' => false, 'div' => false, 'class' => 'reg_input'));?>
<?php echo $this->Form->submit(__('Submit', true), array ('class' => 'reg_button', 'div' => false));
echo $this->Form->end();?>
and Login form is below
<?php echo $this->Form->create('User', array('controller' => 'users', 'action' => 'login'))?>
<?php echo $this->Form->input('User.username',array('label'=>false,'div'=>false, 'class' => 'reg_input'));?>
<?php echo $this->Form->input('User.password',array('label'=>false,'div'=>false, 'class' => 'reg_input'));?>
<?php echo $this->Form->submit(__('Log in', true), array ('class' => 'reg_button', 'div' => false)); ?>
<?php echo $this->Form->end();?>
When I submit registration form it validates both forms, I want to validate only the registration form.
How can I handle that?
I've come up with a "solution" (I find the approach dirty, but it works) for a different question (very similar to this). That other question worked with elements and views, though. I'll post the entire solution here to see if it helps someone (though I rather someone else comes with a different approach).
So, first: change the creation names for the two forms.
//for the registration
<?php echo $this->Form->create('Registration',
array('url' => array('controller' => 'users', 'action' => 'add'))); ?>
//for the login
<?php echo $this->Form->create('Login',
array('controller' => 'users', 'action' => 'login'))?>
The forms should work, look and post to the same actions, so no harm done.
Second step: I don't have your action code, so I'm going to explain what needs to be done in general
public function login() {
if ($this->request->is('post')) {
//we need to change the request->data indexes to make everything work
if (isset($this->request->data['Login'] /*that's the name we gave to the form*/)) {
$this->request->data['User'] = $this->request->data['Login'];
unset($this->request->data['Login']); //clean everything up so all work as it is working now
$this->set('formName', 'Login'); //we need to pass a reference to the view for validation display
} //if there's no 'Login' index, we can assume the request came the normal way
//your code that should work normally
}
}
Same thing for the registration (only need to change 'Login' to 'Registration').
Now, the actions should behave normally, since it has no idea we changed the form names on the view (we made sure of that changing the indexes in the action). But, if there are validation errors, the view will check for them in
$this->validationErrors['Model_with_errors']
And that 'Model_with_errors' (in this case 'User') won't be displayed in the respective forms because we've changed the names. So we need to also tweak the view. Oh! I'm assuming these both forms are in a view called index.ctp, for example, but if they are on separate files (if you're using an element or similar) I recommend add the lines of code for all the files
//preferably in the first line of the view/element (index.ctp in this example)
if (!empty($this->validationErrors['User']) && isset($formName)) {
$this->validationErrors[$formName] = $this->validationErrors['User'];
}
With that, we copy the model validation of the User to the fake-named form, and only that one. Note that if you have a third form in that view for the same model, and you use the typical $this->form->create('User'), then the validation errors will show for that one too unless you change the form name for that third one.
Doing that should work and only validate the form with the correct name.
I find this a messy approach because it involves controller-view changes. I think everything should be done by the controller, and the view shouldn't even blink about validation issues... The problem with that is that the render function of Controller.php needs to be replaced... It can be done in the AppController, but for every updgrade of Cakephp, you'll have to be careful of copying the new render function of Controller.php to the one replacing it in AppController. The advantage of that approach, though, is that the "feature" would be available for every form without having to worry about changing the views.
Well, it's just not that maintainable anyway, so better to leave it alone if it's just for this one case... If anyone is interested on how to handle this just in the controller side, though, comment and I'll post it.
You can duplicate your model and change his name and define $useTable as the same table name.
Example :
class Registration extends AppModel {
public $useTable = 'users';
You define the action in form->create like Nunser for your login form
<?php
echo $this->Form->create('User',array(
'url' => array(
'controller' => 'Users',
'action' => 'login',
'user' => true
),
'inputDefaults' => array(
'div' => false,
'label' => false
),
'novalidate'=>true,
));
?>
and your registration form
<?php
echo $this->Form->create('Registration',array(
'url' => array(
'controller' => 'Users',
'action' => 'validation_registration',
'user' => false
),
'inputDefaults' => array(
'div' => false,
'label' => false
),
'novalidate'=>true,
));
?>
In your controller define a method for registration validation and the most important define the render
public function validation_registration(){
$this->loadModel('Registration');
if($this->request->is('post')){
if($this->Registration->save($this->request->data)){
--- code ---
}else{
--- code ---
}
}
$this->render('user_login');
}
Sorry for my english ! Have a nice day ! :D
The create method on your login form is missing the 'url' key for creating the action attribute. I tried to re-create this once I fixed this and could not. Maybe that will fix it?

CakePHP (2.1) Media Plugin - Multi File Upload

I'm using the cakephp media plugin in my project using the monolithic style attachments table, i.e. all the attachments go in the one table with foreign_key, model, group etc. saved with the file details. So my model looks like:
class ProjectProfile extends AppModel {
var $name = 'ProjectProfile';
var $useDbConfig = 'default';
var $useTable = 'project_profiles';
var $actsAs = array('Media.Transfer', 'Media.Generator');
public $belongsTo = array(
'Project' => array(
'className' => 'Project',
'foreignKey' => 'pjID'
)
);
var $hasMany = array(
'Photo' => array(
'className' => 'Media.Attachment',
'order' => 'Photo.basename, Photo.id',
'foreignKey' => 'foreign_key',
'conditions' => array('Photo.model' => 'ProjectProfile', 'Photo.group' => 'Photo'),
'dependent' => true)
);
Then a saveAll in the controller when saving my record saves the attached file.
This all works fine, however I'd really like to be able to upload multiple files at once, which the plugin does support by doing this in the form:
echo $this->Form->hidden('Photo.0.model', array('value' => 'Photo'));
echo $this->Form->input('Photo.0.file', array('type' => 'file');
echo $this->Form->hidden('Photo.1.model', array('value' => 'Photo'));
echo $this->Form->input('Photo.1.file', array('type' => 'file');
echo $this->Form->hidden('Photo.2.model', array('value' => 'Photo'));
echo $this->Form->input('Photo.2.file', array('type' => 'file');
But I think you'd agree that's a bit cumbersome to have to click browse for each individual file. The simplist method I could see to to allow multiple file uploads was to use the HTML5 multiple file section option - http://bakery.cakephp.org/articles/veganista/2012/01/31/html_5_multiple_file_upload_with_cake :
echo $this->Form->input('files.', array('type' => 'file', 'multiple'));
This allows you to shift click in the file browser to select multiple files then puts the files into an array to save... however, this field format isn't handled by the media plugin. Also, there'd be no way to add the model, group etc. fields on the save as far as I could see.
So, does anybody know how I can handle multi file uploads with the media plugin using the monolithic model? I'm open to all suggestions.
Thanks in advance.
Will this CakePHP 2.x plugin - AjaxMultiUpload - work for you? I think that does exactly what you need it to.

CakePHP SaveAll not working

I can't seem to get my edit class to work. My validation works fine and when I use debug($this->data) after hitting the edit button all the displayed data is perfect, but not updating the tables.
Here is my edit class.
public function edit($id = null) {
if($this->request->is('get')) {
$this->request->data = $this->Bookmark->read(null, $id);
} else {
if($this->Bookmark->saveAll($this->request->data)) {
$this->Session->setFlash('The bookmark has been saved!');
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash('The bookmark could not be saved. Please, try again.');
}
}
}
Here is the view.
<?php
echo $this->Form->create('Bookmark', array(
'action' => 'edit',
'inputDefaults' => array(
'class' => 'input-text'
)
));
echo $this->Form->inputs(array(
'legend' => false,
'fieldset' => true,
'Bookmark.title',
'Url.url',
'Bookmark.id' => array('type' => 'hidden'),
'Url.id' => array('type' => 'hidden')
));
echo $this->Form->button('Edit');
echo $this->Form->end();
?>
I have updated my edit class, but that still didn't fix my error. What fixed it was the two hidden fields I added to the view.
'Bookmark.id' => array('type' => 'hidden'),
'Url.id' => array('type' => 'hidden')
Not really sure why, but I looked at some other edit views online and tried this and it now works.
Try following this page: http://book.cakephp.org/2.0/en/models/saving-your-data.html
In Cake 2.0.x you should be using $this->request->data, though that's not likely the problem. You'll also see they're not setting the id manually, but allowing the form to do that fom them.
If you try it as the book suggests, and it's still not working, post your new attempt as an Edit to this question.
Each time this happend to me was because of validation error. Check for validation errors like so
echo debug( $this->ModelName->invalidFields() );

CakePHP HABTM Form Submission

I have two tables, questions and tags, that have a HABTM relationship. When adding a question, I want to be able to specify a tag for the question (this would just be the first tag, with ability to add more tags later). The tags are pulled from their table. How can I configure my application so that when a question is added and a tag is specified, the join is reflected in the join table (questions_tags)?
Here is my question add action code :
function add() {
$tags = $this->Question->Tag->find('all');
$this->set('tags',$tags);
if (!empty($this->data)) {
$this->Question->create();
if ($this->Question->save($this->data)) {
$this->Session->setFlash(__('The question has been saved', true));
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('The question could not be saved. Please, try again.', true));
}
}
$users = $this->Question->User->find('list');
$tags = $this->Question->Tag->find('list');
$this->set(compact('users', 'tags'));
}
and here is my question add view code:
<?php
echo $this->Form->create('Question');
echo $this->Form->input('user_id',array('type' => 'hidden', 'value' => $this->Session->read('Auth.User.id')));
echo $this->Form->input('title');
echo $this->Form->input('details',array('type' => 'textarea'));
echo $this->Form->input('tag_id');
echo $this->Form->end(__('Submit', true));
?>
First make sure that your models are set up the right way. The fact that a user initially just adds one tag to your question does not change the fact that you should have a HABTM relation between the Question model and the Tag model (because you want the possibility add more tags later).
If your $this->data array is build according to the following schema:
$this->data = array(
'Question' => array(
'name' => 'Trick question'
),
'Tag' => array(
'Tag' => array(1,2,3)
)
);
Then a $this->Question->save() will save the Question data as well as the related Tag data (in this case Question 'Trick Question' with Tags with the id 1, 2 and 3).
Maybe take one step back and bake your Models, Views and Controllers for these two models (again) and see what Cake makes out of it. If I'm correct you'll just need a $this->Form->input('Tag') somewhere in your form (and if that not fills in the right data automatically you want to fill the options parameter with the result of $this->Question->Tag->find('list')).
if you have a single tag for a question, it's not HABTM. it has to be one to one or one to many relation.
in your questions model you can define property belongsTo:
class Question extends AppModel {
var $name = 'Question';
var $belongsTo = array(
'Tag' => array(
'className' => 'Tag',
'foreignKey' => 'tag_id'
)
);
}
Something like this.
here is a link describing how to set HABTM
HABTM

Resources