how to create a chain select form in cakephp - cakephp

My business directory application calls for 3 chained select boxes, and I'm using cakephp to build this application.
The hierarchy and order of choices for the sections is this:
1 - business group
2 - business type
3 - city (included in table customer)
The relationships are:
customer HABTM business types
business groups have many business types
business types have one business group, HABTM customers
I have searched for jquery plugins that help with this, and found one by Remy Sharp, but it doesn't have the more complex relationships I have.
http://remysharp.com/2007/09/18/auto-populate-multiple-select-boxes/
What I imagine happening is the first selection box (business groups) is pre-populated and once a selection is made, an event listener send a message that filters the second selection box, and the same for the third.
What I don't know is how to structure the search action based on the event listener.
Any advice or am I way off base?
As always, I come to the well for help.
Much appreciated.
Paul
Thanks very much Nick, I've read many of your posts I really appreciate your response.
I've followed your instructions but have run into problems. I've tried my best to resolve them but haven't figured it out.
This is what I've done so far:
1) created 'chained' actions in both the business_type and business_directory (renamed customer to business directory, which is more appropriate.)
business type chained action:
function chained($business_group_id) {
$business_types = $this->BusinessType->find('list', array(
'conditions' => array( 'BusinessType.business_group_id' => $business_group_id)
));
$this->set('business_types', $business_types);
}
business directory chained action:
function chained($business_type_id) {
$business_directories = $this->BusinessDirectory->bindModel(array( 'hasOne' => array('business_directories_business_types' )));
$business_directories = $this->BusinessDirectory->find('all', array(
'fields' => array( ' BusinessDirectory.city'),
'conditions' => array( 'business_directories_business_types.business_type_id' => $business_type_id)
));
$this->set('business_directories', $business_directories);
}
I did find that with a HABTM relationship, using find 'list' didn't create the join query, whereas find 'all' did.
2) I then created a search action in the business directory and corresponding view.
For the business groups I created a getList action to populate the option list in the search form:
function getList() {
return $this->BusinessGroup->find('list');
}
In the search view, I've added the javascript for the chain select:
<script type="text/javascript">
<!--
$(function () {
var group = $('#businessGoup');
var type = $('#businessType');
var city = $('#businessDirectoryCity');
type.selectChain({
target: city,
url: '../business_directories/chained/'+$(this).val(),
data: { ajax: true, anotherval: "anotherAction" }
});
group.selectChain({
target: type,
url: '../business_types/chained/'+$(this).val()
}).trigger('change');
});
//-->
</script>
And the form:
create('business_directories', array('action'=>'/search_results')); ?>
input('business_group_id',
array( 'type' => 'select',
'id' => 'businessGoup',
'empty' => '-- Select Business Group --',
'multiple' => true,
'options' => $this->requestAction('/business_groups/getList' ),
'label' => 'Business Group'));
?>
input('business_type.id',
array( 'type' => 'select',
'id' => 'businessType',
'empty' => '-- Select Business Type --',
'multiple' => true,
'options' => 'none selected',
'label' => 'Business Type'));
?>
input('business_directories.id',
array( 'type' => 'select',
'id' => 'businessDirectoryCity',
'empty' => '-- Select City --',
'multiple' => true,
'options' => 'options',
'label' => 'City'));
?>
end('Search'); ?>
When I test the business type chain function, /business_types/chained/1, everything works.
But when I test the search view, I get a javascript alert error. Then when I check firebug, I get the following two errors:
Warning (2): Missing argument 1 for BusinessTypesController::chained() [APP\controllers\business_types_controller.php, line 71]
Notice (8): Undefined variable: business_group_id [APP\controllers\business_types_controller.php, line 73]
Any additional help with this is very much appreciated.
Thanks, Paul

What you need is to have 2 actions in the controllers (business_type and customer).
each action should look like this. In that case for the business type
function chained($parent_id){
$business_types = $this->BusinessType->find('list', array('conditions'=>'BusinessType.business_group_id'=>$parent_id));
$this->set('business_types', $business_types);
}
of course you need also view for that action which will format the values in the proper format for the chained select.
For Business group you need to show all values directly so no ajax is needed.
The Customer controller's action is similar, but you need to select cities of all related customers.
Then with the chained select you need to set the proper elements and set the proper actions which need to be called.
i.e.:
$('#id-of-the-business-group').selectChain({
target: $('#id-of-the-business-type-field'),
url: '/business_types/chained/'+$(this).val()
});

Related

The requested URL /drupalhr/drupal/employee/add/ was not found on this server

I have created a custom module in drupal with entities. I have installed the entity api module. I have created my database schema with just two columns (employee_id, first_name) through the help of employee_management.install file (where as employee_management is my custom module name) and employee is my entity name.
I have also written the requisite functions employee_management.module but still it shows me the error , Whenever i tried to add a new entity in the admin/structure/employee it shows me the following error: "Not Found".
The requested URL drupal/employee/add/ was not found on this server.
function employee_management_entity_info() {
$employee_info['employee'] = array(
// A human readable label to identify our entity.
'label' => t('Employee Entity'),
// The controller for our Entity - extends the Drupal core controller.
'controller class' => 'EmployeeController',
// The table defined in hook_schema()
'base table' => 'employee',
// Returns the uri elements of an entity
'uri callback' => 'employee',
// Fieldable that we can attach fields to it - the core functionality will
// do the heavy lifting here.
'fieldable' => TRUE,
// The unique key of our base table.
'entity keys' => array(
'id' => 'employee_id',
),
// FALSE disables caching - caching functionality is handled by Drupal core
'static cache' => TRUE,
// Attach bundles - i.e. alternative configurations of fields associated with a main entity.
'bundles' => array(
'employee' => array(
'label' => 'Employee',
// Information below is used by the Field UI - they "attach" themselves here and lets us
// do the standard field management that all the core entities enjoy.
'admin' => array(
'path' => 'admin/structure/employee/add',
'access arguments' => array('administer employee entities'),
),
),
),
// View modes allow entities to be displayed differently based on context. We simply have one option
// here but an alternative would be to have a Full and Teaser mode akin to node.
'view modes' => array(
'full' => array(
'label' => t('Full'),
'custom settings' => FALSE,
),
)
);
return $employee_info;
}
EDIT
function employee_uri($employee) {
return array(
'path' => 'employee/' . $employee->employee_id,
);
}
And here is the complete list of function in the file employee_management.module
You don't automagically get the route and form to create your entity, you'll have to implement that yourself. See hook_menu and this guide.

cakePHP virtualField within condition of another model

I have 2 models, User and Entity. I need to on my entities page, have pagination, and a simple search function. The search function will filter the entities, but the data it filters is a virtualField within the User model.
The Entity model:
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
};
The virtual field in the User model:
public $virtualFields = array("searchFor" => "CONCAT(User.first_name, ' ',User.last_name, ' ',User.email)");
And the condition within the entity controller:
$conditions["User.searchFor LIKE"] = "%".str_replace(" ","%",$this->passedArgs["searchFor"])."%";
$this->paginate = array(
'conditions' => $conditions,
'order' => array('Re.created' => 'DESC'),
'limit' => 20
);
From what I can read this is not possible because I cannot use virtualFields within a query of an associative model, and it says I must use it in "run time", but I really do not understand how to do that, or how to do that in this instance. Any ideas or a way I can get this to work?
You could try attaching the virtualField to the Entity model like so
$this->Entity->virtualFields['searchFor'] = $this->Entity->User->virtualFields['searchFor'];
But you have to make sure that a join is done and not 2 queries.
I believe its discussed in the book.
Edit: Book page
For this purpose I needed to do a search on a concatinated string from an associated model. Normally this can be done using Virtualfields in cake, but cake does not support using a virtualField from an associated model in the search.
Seeing that the 2 models were already linked with the belongsTo, I merely changed the condition to:
"conditions" => "CONCAT(User.first_name, ' ',User.last_name, ' ',User.email) LIKE" => "%".str_replace(" ","%",$this->passedArgs["searchFor"])."%"
Probably not the most elegant solution, but it works.

CakePHP using saveAll: How do I save extra data with the HABTM link record?

I have been able to use CakePHP's saveAll method to simultaneously create 'Members' and enroll them in an 'Event' (creating the HABTM link record), which is awesome. For example, this code creates two new 'Members' and adds a record for each of them to the 'EventsMember' table, enrolling them 'Event' 10:
$data = array(
'0' => array(
'Member' => array('email' => 'nobody#nowhere.com'),
'Event' => array('id' => 10)
),
'1' => array(
'Member' => array('email' => 'somebody#nowhere.com'),
'Event' => array('id' => 10)
)
);
$this->Member->saveAll($data);
However, the record in the 'EventsMember' table also has a field called 'role' that holds something like "Presenter" or "Host" or "Attendee" and I would like to save that data when I create the relationship. I tried this and it does not work (the 'EventsMember' 'role' field is always blank):
$data = array(
'0' => array(
'Member' => array('email' => 'nobody#nowhere.com'),
'Event' => array('id' => 10),
'EventsMember' => array('role' => 'Host')
),
'1' => array(
'Member' => array('email' => 'somebody#nowhere.com'),
'Event' => array('id' => 10),
'EventsMember' => array('role' => 'Attendee')
)
);
$this->Member->saveAll($data);
I'm wondering if this is even possible, and if maybe I have to use some kind of callback like beforeSave or afterSave to get this done? I've read that there are some problems with these callbacks when using saveAll, so I'm looking for any tips on what would be the best practice here.
Thanks!
EDIT: I took Adam's advice and made the following changes to my models:
// Event.php
var $hasMany = array('EventsMember');
// Member.php
var $hasMany = array('EventsMember');
// EventsMember.php
var $belongsTo = array('Event', 'Member');
Then in my controller, my code looked almost identical to my second example, except I called the saveAll() method from the EventsMember model, as described in the documentation:
$data = array(
'0' => array(
'Member' => array('email' => 'nobody#nowhere.com'),
'Event' => array('id' => 10),
'EventsMember' => array('role' => 'Host')
),
'1' => array(
'Member' => array('email' => 'somebody#nowhere.com'),
'Event' => array('id' => 10),
'EventsMember' => array('role' => 'Attendee')
)
);
$this->EventsMember->saveAll($data);
The result was no Member or EventsMember records were saved at all. I tried triggering the save from the Member model ($this->Member->saveAll($data);) and this saved the Member records, but not the joining EventsMember records.
I tried removing the pre-existing HABTM associations, and it made no difference. The beforeSave method of the EventsMember model is getting triggered when I call $this->EventsMember->saveAll($data); but it looks like it won't actually save anything.
I'm stymied.
UPDATE: It turns out that no records were created because the joining records were all being created with Event ids and Member ids of 0, which goes against a unique key I have on those two fields combined (that is, no Member can enroll in an Event twice).
Does this suggest that the join model saveAll functionality is not working as documented, since the Member record isn't getting created (meaning there is no Member id to use in the joining record), and the existing Event id is not being passed to the joining EventsMember record either?
VERDICT: I changed the controller to loop on each record and attempt to $this->EventsMember->saveAll($data) for each index of the array, instead of passing the entire array at once. It worked, but was significantly slower than my first example (at the very top). Mind you, I am using transactions, so perhaps using the atomic => false; option would speed things up, while still allowing me to recover from any invalid records.
Bottom line, if you need to save extra data in join table records, you have to process them one at a time. If not, use the method at the top for the best performance.
You can use The Join Model in this case.

CakePHP: How can I customize the labels for multiple checkboxes

I've done a lot of searching but havent been able to solve this yet. I have a user registration form where the user can select a number of store branches as favourites upon registration. I have this relationship set up as a HABTM.
I am able to display the store branches as multiple checkboxes, but I need to display the store name (the store branches belongs to store names) along with the branch name in the label for each checkbox. Something like:
Levi's - Canal Walk
where Canal Walk is the branch name, and Levi's is the store name (coming from a different table)
Can anyone please share some insight on how to do this?
I haven't tested this, but it might work (or help you along the right path):
$branches = $this->Branch->find('list', array(
'fields' => array('Branch.id', 'CONCAT(Store.name, " - ", Branch.name)'),
'joins' => array(
array(
'table' => 'stores',
'alias' => 'Store',
'type' => 'inner',
'foreignKey' => false,
'conditions' => array('Branch.store_id' => 'Store.id')
)
)
));
Set this in your controller, or keep things DRY by placing it in your model in a function such as findFullList()
Hope that helps!
I would do all of this in the view. If your HABTM relationship is setup correctly, a query of the Store model should work something like this:
$stores = $this->Store->findAll();
//Returns
Array([0] => Array(
'Store' => array(),
'Branch' => array(
[0] => array(),
[1] => array()...));
Then pass the $stores variable into the view, and iterate through it with a double nested loop.
foreach($stores as $store){
foreach($store['Branch'] as $branch){
//Code to output checkbox by using $branch['name']
}
}

Refill relationships in form in cakephp in edit mode

I got a controller named Posts, a model called Content which is properly linked with other models such as Category and Location.
In my view for add 'Content' i successfully populate the multi select lists with categories and locations to pick to relate to the post. Saving it all works perfectly.
Now in edit/update mode, I can once again fill the multi selects with categories and locations, but it will not select the ones related to the current post. When looking in the database, there are categories and locations successfully realted to the current post.
This is what I got in my controller:
$this->data = $this->Content->read();
$this->set('locations',$this->Content->Location->find('list',array('fields' => array('id','location'))));
$this->set('categories',$this->Content->Category->find('list',array('fields' => array('id','category'))));
And this is what I got in my view:
echo $this->Form->input('Location', array('type' => 'select','multiple' => 'true','options' => $locations));
echo $this->Form->input('Category', array('type' => 'select','multiple' => 'true','options' => $categories));
What am i missing here? How do i get the already related locations and categories, select in the multi select lists?
(filling of non relationship data, will repopulate textfields etc just perfectly)
Grateful for any help!
Jason
instead of
$this->data = $this->Content->read()
try
$params['conditions'] = array(
'Content.id' => $id
);
$params['contain'] = array(
'Category',
'Location'
);
$this->data = $this->Content->find('first', $params);
You will need the Containable Behaviour for that
Use this:
echo $this->Form->input('Location', array(
'label' => 'Location',
'type' => 'select',
'options' => $LocationArray,
'selected'=> 12(Selected Value)
);

Resources