cake populate a dropdown from model - cakephp

I am trying to populate a dropdpwn with some options. I am trying to have a dropdown with name title and country list for my input form.
For example:
Titles I need 'Mr', 'Mrs', 'Miss'
I have tried to ways:
Model
// Built a list of search options (unless you have this list somewhere else)
public function __construct($id = false, $table = null, $ds = null) {
$this->titles = array(
0 => __('Mr', true),
1 => __('Mrs', true),
2 => __('Miss', true));
parent::__construct($id, $table, $ds);
}
Controller
public function add() {
if ($this->request->is('post')) {
$this->Member->create();
if ($this->Member->save($this->request->data)) {
$this->Session->setFlash(__('The member has been saved'));
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('The member could not be saved. Please, try again.'));
}
}
}
View
<?php echo $this->Form->input('title',array('class' => 'span10', 'options' => $titles )); ?>
I get the error Undefined variable: titles
I have also tried in the model
public $validate = array(
'member_no' => array(
'notempty' => array(
'rule' => array('notempty'),
//'message' => 'Your custom message here',
//'allowEmpty' => false,
//'required' => false,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
'title' => array(
'titlesValid' => array(
'rule' => array('multiple', array(
'in' => array('Mr', 'Mrs', 'Miss'),
'min' => 1
)),
What I am missing and what would be the best solution for a longer list such as countries, it is only on form so i didnt think I would nees a title and countries table and link ids?

I find really weird that you have to create a variable in a construct of a model for a dropdown. Specially if you're only going to use it once.
But first things first. You're getting that error because you haven't set the variable to the view. In your controller, there needs to be something like this:
public function add() {
$this->set('titles', $your_titles_array);
if ($this->request->is('post')) {
//etc
Now, please please get the dropdown array away from there. There are two possible nicer places to put that. I'm not going to get picky and say you need to have this values as a table in you database. If you say it's just for one place and you want to have it hardcode, so be it.
One option is to put it in one model, like
class YourModel extends AppModel {
//all your other definitions
public function getTitles() {
return array(
0 => __('Mr', true),
1 => __('Mrs', true),
2 => __('Miss', true));
}
}
And in the controller do
public function add() {
$this->set('titles', $this->YourModel->getTitles());
if ($this->request->is('post')) {
//etc
But, I don't think you have an appropriate model to add that function. Where did you plan to add it? User model maybe? It can't be like Post model, for example, that wouldn't make any sense... So, give it a thought, if there's a logical place in a model you can put that function, then go ahead.
Otherwise, and if it's just for one form in one place just that one time, why not hardcode it to the view or the controller?
For larger lists, my recommendation is to do it in a model like I showed (but I insist, put it in the model where is logical to have it). Well, I rather have a table and reference the values, but it's not mandatory (though give it a thought, is really not that much work, is it? for larger lists, I mean).

Related

How should I declare my tree behavior in my model to preserve scope in CakePHP 2.5.x?

I'll be straightforward: I want to manage multiple (Link) trees according to their respective menu_id. As long as there is only one tree: no problem. Things get messed up when I start another tree in my link model with a different menu id.
I whish to be able to add, edit, remove, moveUp or moveDown while preserving the scope (menu_id).
This part of the documentation is unclear to me :
http://api.cakephp.org/2.5/source-class-TreeBehavior.html#41-49
Here my Link model.
<?php
App::uses('AppModel', 'Model');
class Link extends AppModel {
public $name = 'Link';
public $displayField = 'title';
public $actsAs = array('Tree' => array(
'parent' => 'parent_id',
'left' => 'lft',
'right' => 'rght',
'scope' => "WHAT-SHOULD-I-PLACE-HERE??",
));
public $belongsTo = array(
'Menu' => array(
'className' => 'Menu',
'foreignKey' => 'menu_id',
)
);
}
And my Menu model.
<?php
App::uses('AppModel', 'Model');
class Menu extends AppModel {
public $displayField = 'title';
public $hasMany = array(
'Link' => array(
'className' => 'Link',
'foreignKey' => 'menu_id',
'dependent' => false,
)
);
}
Thanks to BadHorsie I finally came up to understand that is it pointless to declare the scope in the model's behavior settings.
Instead you (currently I) need to attach the behavior on the fly, with the required scope before any action (add, edit, move up, move down etc) for this to work.
Now, what I need to make sure is that a link always has a menu_id.
And when someone edits a link, the parent_id dropdown must be repopulated according to the menu_id dropdown (javascript needed here and more validation rules in the model to ensure integrity).
The solution was to create a new function in the Link model. it's goal is to preserve the scope when any modification is made to the tree (move up, move down, remove from tree, delete, etc).
public function preserveScope($menuId) {
$this->Behaviors->attach('Tree', array(
'scope' => array(
'Link.menu_id' => $menuId
),
));
return true;
}
The scope is basically a SQL condition (in Cake format).
So you probably need to set the scope to the menu ID that you want.
'scope' => array(
'Link.menu_id' => 5
);
However, you probably don't know which ID yet when trying to set up the array on the class definition, so you might have to do it on the fly.
$this->Link->Behaviors->attach('Tree', array(
'scope' => array(
'Link.menu_id' => $id // You need to decide how to get this ID
),
));
I don't know when you would need to do this though. It's up to you to decide when to attach the behavior.
Edit: If the moveUp/moveDown methods are not working correctly, perhaps the scope field you are using is not correct?

Cakephp SaveAssociated and Save - Using same Model Validation code

Question: How can I use the same code in the model validation (in particular for child models) for both SaveAssociated and Save function calls in CakePHP,... given that SaveAssociated implementations expect the form data array to contain a numeric index [0] for data fields belonging to a child model?
Scenario:
Assuming I have a parent model with a hasMany relationship to several child models.
Typically if you use SaveAssociated to save data to all models at once, you would need to specify an index number (typically 0) on the view form input. Example:
echo $this->Form->input('MerchantControl.0.startdate', array('type' => 'text', 'class' => 'datepicker_start'));
As a result, any custom child model validation code will need to be written with [0] as well. See function urlParamNotUsedByOtherMerchants in the code sample below.
public $validate = array(
'urlparam' => array(
'In Use by other Merchants' => array(
'rule' => 'urlParamNotUsedByOtherMerchants',
'message' => 'URLPARAM belongs to another Merchant'
)
)
);
public function urlParamNotUsedByOtherMerchants($data) {
$searchfilter = array(
//Because of SaveAssociated, need to refer to index [0]
'MerchantControl.id !=' => $this->data['MerchantControl'][0]['merchant_id'],
'MerchantControl.urlparam ' => $data,
);
$merchantcontrol = $this->find('all', array('conditions' => $searchfilter));
if (sizeof($merchantcontrol) > 0) {
return false;
} else {
return true;
}
}
The problem is there are many other instances where I will also be using a "Save" and not a "SaveAssociated" in maintainence views where i directly update or create the child model only. In this case, this model validation code is going to fail with an error saying index "[0]" not defined or something similar.
How can I use the same code in the model validation (in particular for child models) for both SaveAssociated and Save function calls in CakePHP?
If I understand you correctly you want to check whether the urlparam is already used by another merchant or in other words whether it is unique.
Why don't you use the built-in validation rule isUnique?
Example:
public $validate = array(
'urlparam' => array(
'In Use by other Merchants' => array(
'rule' => 'isUnique',
'message' => 'URLPARAM belongs to another Merchant'
)
)
);

CakePHP Save newly created ID in Parent ID field of self assoc model

I have a parent/child self association table where when the id = parent_id, that id is it's own parent. However I'm having trouble saving data into my table from the add action/view
From add.ctp view - when adding a new record, I select a parent_id from the drop down box and enter a name.
<?php
echo $this->Form->input('parent_id', array('empty' => 'No Parent'));
echo $this->Form->input('name');
?>
If user selects "No Parent" this means I would like the parent_id = id where id is the unique ID automatically created in DB at time it is saved.
This is what is passed into $this->request->data when 'No Parent' is selected.
array(
'Item' => array(
'parent_id' => '',
'name' => 'testname'
)
)
I have tried to set the parent_id = id in the beforeSave but since id does not yet exist, there is nothing to assign parent_id to. I have also tried calling the "parent" model save first and saveAll in the controller but those don't work either.
Controller
public function add() {
if ($this->request->is('post')) {
$this->Item->create();
//have tried calling parent model in self association first but
//if ($this->Item->ParentItem->save($this->request->data)) {
if ($this->Item->saveAll($this->request->data)) {
$this->Session->setFlash(__('The item has been saved'));
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('The item could not be saved. Please, try again.'));
}
}
Item.php / Model relationship
public $belongsTo = array(
'ParentItem' => array(
'className' => 'Item',
'foreignKey' => 'parent_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
public $hasMany = array(
'ChildItem' => array(
'className' => 'Item',
'foreignKey' => 'parent_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
)
);
How can I take an ID that is just created/saved and save that to another field, in this case parent_id?
UPDATE:
I have been working on this some more and I have used getInsertId() to get the last inserted Id and I am trying to save that into the parent_id, but there is something that prevents this. I have removed all model validation to make sure it wasn't that. But is there something in Cake (or my association setup) that does not allow parent_id = id (i.e. a row is it's own parent?
This is the latest code in my add action... This saves a row to the DB, but w/o a parent_id. I then try to edit using the add action and set the parent_id = id, but even the edit wont allow a save.
public function add() {
if ($this->request->is('post')) {
$this->Item->create();
if ($this->Item->save($this->request->data)) {
$last_id = $this->Item->getInsertId();
$this->Item->saveField('parent_id', $last_id);
$this->Session->setFlash(__('The item has been saved'));
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('The item could not be saved. Please, try again.'));
}
}
I have also tried calling $this->Item->ParentItem->save(), saveAll, 'deep' => true, but still nothing is allowing me to update the parent_id column. A row ge
Thanks in advance
In Your model of association You can set foregin_key to parent_id, and will be automatic filled
So the problem I was having... trying to set a parent_id = id (making id it's own parent basically) in a self join model is due to this piece of code in my Model file
public $actsAs = array('Tree');
After reading through the Tree Behavior again, I realized that $this->Model->save (or saveField) does not really work well with Tree structures and updating parent IDs. Should use the behaviors functions instead form my understanding. Also the TreeBehavior expects some parent_ids to be null (at least top level parent_ids), so if I were to leave this as a tree, the ids with a null parent_id would be considered the parent.
http://book.cakephp.org/2.0/en/core-libraries/behaviors/tree.html
"The parent field must be able to have a NULL value! It might seem to work if you just give the top elements a parent value of zero, but reordering the tree (and possible other operations) will fail."
So I need to decide if I want to use tree behavior or have simple parent/child relationship without using Tree... think I will do the later as I don't have a need for multilevel tree, just a parent and one level of children.
Thanks for replies.

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

Edit of self referencing HABTM in cakephp works, sometimes

I'm using a self referencing HABTM model with Participants. You sign up for an event and when you log in to your reservation/profile you see a list of other participants and you can choose to add yourself and others into various groups; share hotel room, share transportation from airport etc.
What I've managed so far:
1) In my profile I see the list of all other participants with checkboxes. Great so far.
2) Adding another participant works fine. Next time I edit, the participant I added is shown as checked.
3) Removing another participant works fine too as long as you still have checked participants before you submit!
Again, with words:
There are 3 participants. I'm logged in as one of them, and I see the 2 other people on the participants list. I choose to check both of them. This works fine (always). Later I choose to remove one of them (by unchecking the checkbox and hitting submit). This also works fine (always). If I want to remove the last checkbox... nothing is updated (always!). What's curious is that I can add and remove any odd combination of participants and it will always work UNLESS I choose to remove every participants in one go (removing a one and only participant is a special case of "remove all checked participants").
As far as I know, HABTMs work by first deleting all relations, then re-saving them. I can see that in my tables when I remove, add, remove, add the same participant over and over again - the id on the HABTM table is always increasing. When I deselect all participants at once, however, the relations are not updated. The ids stay the same, so it's like the save never happened.
This behaviour is so specific and peculiar, I have a feeling I'm missing something obvious here. Anyway, here's the relevant code:
Model
class Participant extends AppModel {
var $hasAndBelongsToMany = array(
'buddy' => array(
'className' => 'Participant',
'joinTable' => 'participants_participants',
'foreignKey' => 'participant_id',
'associationForeignKey' => 'buddy_id',
'unique' => true,
)
);
Controller
function edit($id = null) {
if (!$id && empty($this->data)) {
$this->Session->setFlash(__('Invalid Participant', true));
$this->redirect(array('action'=>'index'));
}
if (!empty($this->data)) {
if ($this->Participant->saveAll($this->data)) {
$this->Session->setFlash(__('The Participant has been saved', true));
$this->redirect(array('action'=>'index'));
} else {
$this->Session->setFlash(__('The Participant could not be saved. Please, try again.', true));
}
}
if (empty($this->data)) {
$this->data = $this->Participant->read(null, $id);
}
// Fetching all participants except yourself
$allParticipants = $this->Participant->find('list', array('conditions' => array('participant.id ' => $id)));
// Fetching every participant that has added you to their list
$allBuddies = $this->Participant->ParticipantsParticipant->find('list', array(
'conditions' => array('buddy_id' => $id),
'fields' => 'ParticipantsParticipant.participant_id',
'order' => 'ParticipantsParticipant.participant_id ASC'
));
$this->set(compact('allParticipants','allBuddies'));
}
View
echo $form->create('Participant');
echo $associations->habtmCheckBoxes($allParticipants, $this->data['buddy'], 'buddy', 'div', '\'border: 1px solid #000;\'', '\'border: 1px solid #000;\'');
echo $form->end('Submit');
I'm using a slightly modified helper, habtmCheckBoxes, found here: http://cakeforge.org/snippet/detail.php?type=snippet&id=190
It works like this: function habtmCheckBoxes($rows=array(), $selectedArr=array(), $modelName, $wrapTag='p', $checkedDiv, $uncheckedDiv) {}
This happens because of the way HABTM works within CakePHP - it will only save HABTM data if the HABTM key exists in the array. Hence, when no checkboxes are checked, there is no data passed through and cake doesn't touch your existing habtm records.
A quick fix would be to add a few lines of code to your controller.
if (!empty($this->data)) {
if (empty($this->data['buddy'])) {
$this->data['buddy'] = array('buddy' => array(''));
}
if ($this->Participant->saveAll($this->data)) {
// ...
} else {
// ...
}
}
However, if might also be possible for you to use cake's form helper (instead of the other helper you are using) to do this in your view:
echo $form->inputs(array(
'legend' => 'Nominate your artwork for awards',
'buddy' => array('label' => false, 'multiple' => 'checkbox', 'options' => $allBuddies)
));

Resources