Sorting in CakePHP with custom routes - sorting

I was trying to find a solution on-line (as always) but came up with nothing obviously.
I'm using CakePHP and I need to add some sorting into some controllers. I would also like to create custom routes for that. So here's my code that is not working (pagination works, sorting nope):
RatesController.php:
public $paginate = array(
'Rate' => array(
'limit' => 10,
'order' => array(
'Rate.created' => 'desc'
),
'conditions' => array( 'Rate.accepted' => 1 ),
//'fields' => array('Rate.*', 'User.username')
),
'Airline' => array(
'limit' => 50,
/*'order' => array(
'Airline.rate' => 'desc',
'Airline.name' => 'asc'
),*/
'conditions' => array(
'Airline.rate >' => 0
),
'fields' => array('Airline.id', 'Airline.name', 'Airline.image_id', 'Airline.rate', 'Image.*')
)
);
function index($category = 'airlines'){
pr($this->request);
$data = $this->paginate('Airline');
pr($data);
}
And in view:
$this->Paginator->options(array(
'url' => array(
'controller' => 'rates',
'action' => 'index',
'category' => $category
)
));
echo $this->Paginator->first('«', array('escape' => false), null, array('class' => 'disabled', 'escape' => false));
echo $this->Paginator->prev('<', array('escape' => false), null, array('class' => 'disabled', 'escape' => false));
echo $this->Paginator->numbers(array(
'modulus' => 6,
'separator' => false
));
echo $this->Paginator->next('>', array('escape' => false), null, array('class' => 'disabled', 'escape' => false));
echo $this->Paginator->last('»', array('escape' => false), null, array('class' => 'disabled', 'escape' => false));
echo' sort by ';
echo $this->Paginator->sort('Airline.rate');
echo' or by ';
echo $this->Paginator->sort('Airline.name');
Routes:
Router::connectNamed(array('page'), array('default' => false, 'greedy' => false));
Router::connectNamed(array('sort', 'direction'), array('default' => false, 'greedy' => false));
Router::connect('/rate-airlines', array('controller' => 'rates', 'action' => 'index', 'category' => 'airlines'));
/*
Router::connect(
'/rate-airlines/:page/',
array('controller' => 'rates', 'action' => 'index', 'category' => 'airlines'),
array(
'named' => array('page' => '[a-z]+')
)
);
Router::connect(
'/rate-airlines/:page/:sort/:direction',
array('controller' => 'rates', 'action' => 'index', 'category' => 'airlines'),
array(
'pass' => array('page', 'sort', 'direction'),
//'id' => '[0-9]+',
'named' => array('page' => '[\d]+', 'sort', 'direction')
)
);*/
Router::connect(
'/rate-airlines/:sort/:direction',
array('controller' => 'rates', 'action' => 'index', 'category' => 'airlines'),
array(
'named' => array('sort', 'direction')
)
);
I seriously have no idea why it's not working, I've already spent hours trying to make it work, seeking answers.
Ps. No matter what I did I couldn't manage to put :sort and :direction into named array in request:
CakeRequest Object
(
[params] => Array
(
[plugin] =>
[controller] => rates
[action] => index
[named] => Array
(
)
[pass] => Array
(
)
[sort] => Airline.rate
[direction] => asc
[category] => airlines
[isAjax] =>
)
...
)
Any ideas please?
Mike

You really shouldn’t use route parameters for sorting. It was such a bad idea that CakePHP have dropped them in the forth-coming 3.x release.
Instead, use query string parameters. That’s exactly what they’re for: manipulating a single view. If you have a list, pass parameters like ‘sort’ and ‘direction’ there.
Say you have a URL like http://example.com/rates/?sort=name&direction=asc, you can then parse it in your controller as follows:
<?php
class RatesController extends AppController {
public function index() {
$sort = isset($this->request->query['sort']) ? $this->request->query['sort'] : 'created';
$direction = isset($this->request->query['direction']) ? $this->request->query['direction'] : 'desc';
$this->Paginator->settings = array(
'order' => array($sort => $direction)
);
$rates = $this->paginate('Rate');
$this->set(compact('rates'));
}
}
I also like to go one step further and use query strings for pagination…
<?php
class AppController extends Controller {
public $paginate = array(
'paramType' => 'querystring'
);
}
…But that’s just personal preference.
The above was written during a long day. After thinking about it, CakePHP handles sorting in pagination out of the box. You don’t need to manually specify routes containing the name parameters.
So long as you use the Pagination component and Paginator helper (as you have done), and defined a single route for your controller action, you don’t need to do anything else. In your case, your controller would look like:
<?php
class RatesController extends AppController {
public function index() {
$rates = $this->paginate('Rate');
$this->set(compact('rates'));
}
}
And your view would look like:
<?php echo $this->Paginator->sort('Airline.name'); ?>
If you wanted to rewrite /rates to /rates-airlines, then your single route would look like this:
<?php
Router::connect('/rates-airlines', array(
'controller' => 'rates',
'action' => 'index',
));
With the above, the paginate call will take into account the column to sort by and the direction.
Sorry for the convoluted answer above!

thanks for your replies. The thing is that I really need custom routing for pagination, so I finally managed to achieve what I wanted using following code:
In AppController.php (note that all $this->request->query[...] variables were added just because I also wanted to use sorting interface via forms with GET method)
function beforeFilter() {
if (isset($this->request->params['page'])) {
$this->request->params['named']['page'] = $this->request->params['page'];
}elseif( isset($this->request->query['page']) ){
$this->request->params['named']['page'] = $this->request->query['page'];
}
if (isset($this->request->params['sort'])) {
$this->request->params['named']['sort'] = $this->request->params['sort'];
}elseif (isset($this->request->query['sort'])) {
$this->request->params['named']['sort'] = $this->request->query['sort'];
}
if (isset($this->request->params['direction'])) {
$this->request->params['named']['direction'] = $this->request->params['direction'];
}elseif (isset($this->request->query['direction'])) {
$this->request->params['named']['direction'] = $this->request->query['direction'];
}
in routes.php
Router::connect('/rates-airlines', array('controller' => 'rates', 'action' => 'index', 'airlines'));
Router::connect(
'/rates-airlines/:page',
array('controller' => 'rates', 'action' => 'index', 'airlines'),
array(
'named' => array('page' => '[\d]+')
)
);
Router::connect(
'/rates-airlines/:page/:sort/:direction',
array('controller' => 'rates', 'action' => 'index', 'airlines'),
array(
'named' => array('page' => '[\d]+', 'sort', 'direction')
)
);
Router::connect(
'/rates-airlines/:sort/:direction',
array('controller' => 'rates', 'action' => 'index', 'airlines'),
array(
'named' => array('sort', 'direction')
)
);
Hope someone will find this useful!
Mike

There is an easier way. Just add one more route with '/*':
Router::connect('/rates-airlines', array('controller' => 'rates', 'action' => 'index', 'airlines'));
Router::connect('/rates-airlines/*', array('controller' => 'rates', 'action' => 'index', 'airlines'));
And all will work correctly.

Related

ZF2 simple form validation

I tried to validate a simple form in zend framework 2 for days now.
I checked the documentation and a lot of posts considering this topic but I found no solution!
I have a very simple form:
class AlimentForm extends Form
{
public function __construct($name = null)
{
parent::__construct('aliment');
$this->add(array(
'required'=>true,
'name' => 'year',
'type' => 'Text',
'options' => array(
'label' => 'Jahr',
),
));
$this->add(array(
'required'=>true,
'name' => 'number',
'type' => 'Text',
'options' => array(
'label' => 'Number',
),
));
$this->add(array(
'name' => 'submit',
'type' => 'Submit',
'attributes' => array(
'value' => 'Go',
'id' => 'submitbutton',
),
));
}
}
I created a custom InputFilter:
namespace Application\Form;
use Zend\InputFilter\InputFilter;
class AlimentInputFilter extends InputFilter {
public function init()
{
$this->add([
'name' => AlimentForm::year,
'required' => true,
'validators' => array(
array(
'name' => 'Between',
'options' => array(
'min' => 1900,
'max' => 3000,
),
),
),
]);
}
}
and finally in my controller I try to validate the form
public function alimentAction(){
$form = new AlimentForm();
$form->setInputFilter(new AlimentInputFilter());
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
$year = $form->get('year')->getValue();
$number = $form->get('number')->getValue();
return array('result' => array(
"msg" => "In the Year ".$year." you get ".$number." Points"
));
}
}
return array('form' => $form);
}
It can't be that difficult, but from all those different ways to validate a form I found in the net, I'm a bit confused...
What am I missing?
Greetings and thanks in advance
U.H.
Ok, I solved the problem.
I created a Model for the aliment Form that has a year and a number attribute
and I defined an input filter within that model:
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
class Aliment implements InputFilterAwareInterface
{
public $year;
public $number;
public function exchangeArray($data){
$this->year = (!empty($data['year']))?$data['year']:null;
$this->number = (!empty($data['number']))?$data['number']:null;
}
public function setInputFilter(InputFilterInterface $inputFilter)
{
throw new \Exception("Not used");
}
public function getInputFilter()
{
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
$inputFilter->add(array(
'name' => 'year',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'Between',
'options' => array(
'min' => 1900,
'max' => 3000
)
)
),
));
$inputFilter->add(array(
'name' => 'number',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'Between',
'options' => array(
'min' => 1,
'max' => 10
)
)
),
));
$this->inputFilter = $inputFilter;
}
return $this->inputFilter;
}
}
In the action method of the controller I was able to set the InputFilter of the model to the form and voila! It worked!
public function alimentAction(){
$form = new AlimentForm();
$request = $this->getRequest();
if ($request->isPost()) {
$aliment = new \Application\Model\Aliment;
$form->setInputFilter($aliment->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
$aliment->exchangeArray($form->getData());
$year = $form->get('year')->getValue();
return array(
'result' => array(
//your result
)
);
}
}
return array('form' => $form);
}
The form is correctly validated now and returns the corresponding error messages.
I hope, it help somebody whos got similar problems with validating!

symfony performance with voters

In my symfony application, I use voters to check if users can access some features.
Now, I have a navbar where the menus are displayed or hidden according to those rights.
This navbar alone renders in about 2 seconds. That's a lot added to every page.
If I comment out the render_controller of the navbar, I gain 2 seconds. If I return true at the top of every voter is_granted method, I gain 1.5 seconds.
How can I solve this ? Using cache ? not using voters ? simplifying my voters ? Is it not a best practice to use voters? even if symfony has to check attributes against the list of all attributes?
My navbar generator:
<?php
namespace AppBundle\Menu;
use AppBundle\Application\Core\ExplorerManager;
use AppBundle\Entity\User\Associate;
use AppBundle\Entity\User\Role;
use Symfony\Component\Routing\Router;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
use Symfony\Component\Translation\Translator;
class MenuBuilder
{
/**
* #var Router
*/
private $router;
/**
* #var Translator
*/
private $translator;
/**
* #var AuthorizationChecker
*/
private $authorizationChecker;
/**
* #param Router $router
* #param Translator $translator
* #param AuthorizationChecker $authorizationChecker
*/
public function __construct(Router $router, Translator $translator, AuthorizationChecker $authorizationChecker)
{
$this->router = $router;
$this->translator = $translator;
$this->authorizationChecker = $authorizationChecker;
}
public function getFrontendHeaderMenuItems($isAuthenticated)
{
$menuItems = array(
array(
'title' => 'pricing',
'route' => 'cms',
'route_parameters' => array('dir' => 'general', 'page' => 'foodmeup_premium')
),
// array(
// 'title' => 'recipes',
// 'route' => 'explore',
// 'route_parameters' => array('object' => ExplorerManager::RECETTE)
// ),
array(
'title' => 'blog',
'route' => 'explore',
'route_parameters' => array('object' => ExplorerManager::BLOG)
),
array(
'title' => 'contact',
'route' => 'message_foodmeup'
)
);
if (false == $isAuthenticated) {
$menuItems[] = array(
'title' => 'login',
'route' => 'login'
);
}
$menuItems = $this->generateMenuRoutes(
$menuItems,
'frontend_bars',
array()
);
return $menuItems;
}
public function getBackendLeftSidebarMenuItems()
{
$menuItems = array(
array(
'title' => 'dashboard',
'icon' => 'th-large',
'route' => 'dashboard'
),
array(
'title' => 'administration',
'icon' => 'star',
'is_granted' => Role::ROLE_ADMIN,
'children' => array(
array(
'title' => 'dashboard',
'route' => 'admin_dashboard',
),
array(
'title' => 'moderation',
'route' => 'moderate_posts',
),
array(
'title' => 'users',
'route' => 'switch_user',
'is_granted' => Role::ROLE_SUPERADMIN,
),
array(
'title' => 'subscriptions',
'route' => 'grant_partner_subscription',
'is_granted' => Role::ROLE_SUPERADMIN,
)
)
), array(
'title' => 'ingredients',
'icon' => 'flask',
'children' => array(
array(
'title' => 'explore_ingredients',
'route' => 'explore',
'route_parameters' => array('object' => ExplorerManager::CONSOMMABLE)
),
array(
'title' => 'my_ingredients',
'route' => 'user_ingredients_display',
'is_granted' => Associate::READ_INGREDIENT
), array(
'title' => 'create_import',
'route' => 'edit_ingredient',
'is_granted' => Associate::EDIT_INGREDIENT
), array(
'title' => 'stock',
'route' => 'set_ingredient_stock',
'is_granted' => Associate::READ_STOCK,
), array(
'title' => "buying_cost",
'route' => 'parameter_cost',
'is_granted' => Associate::READ_COST,
)
)
), array(
'title' => 'recipes',
'icon' => 'birthday-cake',
'children' => array(
array(
'title' => 'explore_recipes',
'route' => 'explore',
'route_parameters' => array('object' => ExplorerManager::RECETTE)
),
array(
'title' => 'my_recipes',
'route' => 'user_recipes_display',
'is_granted' => Associate::READ_RECIPE
), array(
'title' => 'create',
'route' => 'edit_recipe',
'is_granted' => Associate::EDIT_RECIPE
), array(
'title' => 'print',
'route' => 'print_recipes',
'is_granted' => Associate::READ_RECIPE
)
)
), array(
'title' => 'plannings',
'icon' => 'tasks',
'is_granted' => array(Associate::READ_PLANNING, Associate::EDIT_PLANNING, Associate::DELETE_PLANNING),
'children' => array(
array(
'title' => 'my_plannings',
'route' => 'display_plannings',
'is_granted' => Associate::READ_PLANNING
), array(
'title' => 'my_models',
'route' => 'display_plannings',
'route_parameters' => array('isModel' => true),
'is_granted' => Associate::READ_PLANNING
), array(
'title' => 'create_planning',
'route' => 'edit_planning',
'is_granted' => array(Associate::EDIT_PLANNING, Associate::CREATE_MODEL)
)
)
), array(
'title' => 'orders',
'icon' => 'phone',
'is_granted' => array(Associate::READ_ORDER, Associate::EDIT_ORDER, Associate::EDIT_ORDER, Associate::DELETE_ORDER),
'children' => array(
array(
'title' => 'my_orders',
'route' => 'display_planning_orders',
'is_granted' => Associate::READ_ORDER
), array(
'title' => 'automatic_order',
'route' => 'automatic_order',
'is_granted' => Associate::EDIT_ORDER
), array(
'title' => 'edit_order',
'route' => 'edit_order',
'is_granted' => Associate::EDIT_ORDER
)
)
), array(
'title' => 'suppliers',
'icon' => 'truck',
'is_granted' => array(Associate::EDIT_SUPPLIER, Associate::READ_SUPPLIER, Associate::DELETE_SUPPLIER),
'children' => array(
array(
'title' => 'my_suppliers',
'route' => 'display_suppliers',
'is_granted' => Associate::READ_SUPPLIER
), array(
'title' => 'select',
'route' => 'user_supplier_select',
'is_granted' => Associate::EDIT_SUPPLIER
), array(
'title' => 'create',
'route' => 'edit_organization',
'route_parameters' => array('category_slug' => 'fournisseur-de-consommables'),
'is_granted' => Associate::EDIT_SUPPLIER
)
)
), array(
'title' => 'teams',
'icon' => 'users',
'is_granted' => array(Associate::EDIT_TEAM, Associate::READ_TEAM, Associate::DELETE_TEAM, Associate::MANAGE_RIGHTS),
'children' => array(
array(
'title' => 'my_teams',
'route' => 'teams_display',
), array(
'title' => 'worker_parameter',
'route' => 'worker_parameter',
'is_granted' => Associate::EDIT_TEAM,
)
)
)
);
$menuItems = $this->generateMenuRoutes($menuItems, 'backend_bars', array());
return $menuItems;
}
private function generateMenuRoutes(Array $menuItems, $domain, $isGranted)
{
foreach ($menuItems as $key => $menuItem) {
if (array_key_exists('is_granted', $menuItems[$key])) {
$rights = is_array($menuItems[$key]['is_granted']) ? $menuItems[$key]['is_granted'] : array($menuItems[$key]['is_granted']);
$rights = array_map(function ($right) {
return $this->authorizationChecker->isGranted($right);
}, $rights);
if (in_array(true, $rights) == false) {
unset($menuItems[$key]);
continue;
}
}
if (array_key_exists('route', $menuItems[$key])) {
$menuItems[$key]['uri'] = $this->router->generate($menuItems[$key]['route'], array_key_exists('route_parameters', $menuItems[$key]) ? $menuItems[$key]['route_parameters'] : array());
} else {
$menuItems[$key]['uri'] = '#/';
}
$menuItems[$key]['title'] = $this->translator->trans($menuItems[$key]['title'], array(), $domain);
if (array_key_exists('children', $menuItems[$key])) {
$menuItems[$key]['children'] = $this->generateMenuRoutes($menuItems[$key]['children'], $domain, $isGranted);
}
}
return $menuItems;
}
public function extractTitles(Array $menuItems)
{
$titles = array();
foreach ($menuItems as $key => $menuItem) {
$titles[] = $menuItems[$key]['title'];
if (array_key_exists('children', $menuItems[$key])) {
$titles = array_merge($titles, $this->extractTitles($menuItems[$key]['children']));
}
}
return $titles;
}
}
Voters have performance drawbacks. So if you need a higher performance then it's better to use ACL or optimize voters loading, because symfony loops over all voters when each security check is performed.
Lets's suppose you have 30 links in a menu and there are 20 voters. When you check access to all links in 1 loop then symfony will call voters 600 times (the most of them will return Voter::ACCESS_ABSTAIN immediately, but it still takes time and in you case it takes too much time).
Update:
Symfony added voter caching for skipping call unneeded voters if new functions supportsAttribute or supportsType returned false once.
https://symfony.com/blog/new-in-symfony-5-4-faster-security-voters

PHPUnit mocks - assert method called

i'm new to phpunit and have read the documentation on mock objects but it isn't very clear.
I am trying to write a simple test that asserts a method within a class is called. With the following code, i am testing that when the Client::exchangeArray is called, a call is made to Client::getInputFilter.
class Client implements InputFilterAwareInterface
{
public function getInputFilter() {
if(!$this->_inputFilter){
$inputFactory = new InputFactory();
$inputFilter = new InputFilter();
$inputFilter->add($inputFactory->createInput(array(
'name' => 'id',
'required' => true,
'filters' => array(
array(
'name' => 'Int'
)
)
)));
$inputFilter->add($inputFactory->createInput(array(
'name' => 'name',
'required' => true,
'filters' => array(
array(
'name' => 'StripTags'
),
array(
'name' => 'StringTrim'
),
array(
'name' => 'StripNewLines'
),
array(
'name' => 'Alpha'
)
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 2,
'max' => 100
)
)
)
)));
$inputFilter->add($inputFactory->createInput(array(
'name' => 'surname',
'required' => true,
'filters' => array(
array(
'name' => 'StripTags'
),
array(
'name' => 'StringTrim'
)
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 2,
'max' => 100
)
)
)
)));
$inputFilter->add($inputFactory->createInput(array(
'name' => 'email',
'required' => false,
'filters' => array(
array(
'name' => 'StripTags'
),
array(
'name' => 'StringTrim'
)
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 2,
'max' => 150
)
),
array(
'name' => 'EmailAddress'
)
)
)));
$this->_inputFilter = $inputFilter;
}
return $this->_inputFilter;
}
public function exchangeArray($data){
$inputFilter = $this->getInputFilter();
$inputFilter->setData($data);
if(!$inputFilter->isValid()){
throw new \Exception('Invalid client data');
}
$cleanValues = $inputFilter->getValues();
$this->_id = (isset($cleanValues['id']) ? $cleanValues['id'] : null);
$this->_name = (isset($cleanValues['name']) ? $cleanValues['name'] : null);
$this->_surname = (isset($cleanValues['surname']) ? $cleanValues['surname'] : null);
$this->_email = (isset($cleanValues['email']) ? $cleanValues['email'] : null);
}
}
Here is my test case:
public function testExchangeArrayCallsInputFilter(){
$data = array('id' => 54,
'name' => 'john',
'surname' => 'doe',
'email' => 'john.doe#domain.com'
);
$mock = $this->getMock('Client', array('exchangeArray'));
$mock->expects($this->once())
->method('getInputFilter');
$mock->exchangeArray($data);
}
...and i'm getting the following error:
Expectation failed for method name is equal to
when invoked 1 time(s). Method was expected to be called 1 times,
actually called 0 times.
Where am i going wrong?
It all depends on what you want test and what you want mock. Basing on the name of your test I assume that you want test exchangeArray method.
The getMock method takes as second argument names of methods that you want mock. It means that they will never be called.
So, if you want to test exchangeArray method and mock getInputFilter you should pass "getInputFilter" in second argument, like below:
$mock = $this->getMock('Client', array('getInputFilter'));
$mock->expects($this->once())
->method('getInputFilter');
$mock->exchangeArray($data);
But be careful. You didn't tell your mock to return anything, so it will return null value. That means that you'll get a fatal error on the second line of exchangeArray method (trying to call a method on a non-object). You should prepare some faked filter object to deal with that, eg:
// $preparedFilterObject = ...
$mock = $this->getMock('Client', array('getInputFilter'));
$mock->expects($this->once())
->method('getInputFilter')
->will($this->returnValue($preparedFilterObject);
$mock->exchangeArray($data);
And if you want to invoke the "real" getInputFilter method - then you just can't mock this method.

dynamic dropdown by #ajax DRUPAL 7

I am trying to make dynamic 3 dropdown(DD) by #ajax property of drupal 7..
1st DD contains country list(coming from db)
2nd DD contains states list(coming from db)
3re DD contains city list(coming from db)
problem is as i choose country ..my states DD show states accordingly..by how to trigger my states DD so that my city DD also got updated at the same time my state updated..I have to click on states table only then my cities DD changes..
---MY CODE IN FORM_ALTER IS THIS---
$options_first = iripple_classifieds_country_list();
$selected = isset($form_state['values']['country_by_alter']) ? $form_state['values']['country_by_alter']: key($options_first);
$options_first = iripple_classifieds_country_list();
$form['country_by_alter'] = array(
'#type' => 'select',
'#title' => t('Country'),
'#validated' => TRUE,
'#options' => $options_first,
'#weight' => 5,
//'#disabled' =>TRUE,
'#ajax' => array(
'callback' => 'iripple_classifieds_country_callback',
'wrapper' => 'statereplace',
'effect' => 'fade',
// 'event' => 'onload'
),
'#attributes' => array(
// 'onload' => "jQuery('#edit-country-by-alter').trigger('click')"
)
);
$options_second = iripple_classifieds_state_list();
$selected_state = isset($form_state['values']['state_by_alter']) ? $form_state['values']['state_by_alter']: key($options_second);
$form['state_by_alter'] = array (
'#type' => 'select',
'#title' => t('State'),
'#options' => iripple_classifieds_selected_states($selected),
'#default_value' => isset($form_state['values']['state_by_alter']) ? $form_state['values']['state_by_alter']:'',
'#weight' => 7,
'#validated' => TRUE,
'#prefix' => '<div id="statereplace">',
'#suffix' => '</div>',
'#ajax' => array(
'callback' => 'iripple_classifieds_state_callback',
'wrapper' => 'cityreplace',
'event' => 'change'
)
);
$form['city_by_alter'] = array(
'#type' => 'select',
'#title' => t('City'),
'#options' => iripple_classifieds_selected_cities($selected_state),
'#default_value' => isset($form_state['values']['city_by_alter']) ? $form_state['values']['city_by_alter']:'',
'#weight' => 8,
'#validated' => TRUE,
'#prefix' => '<div id="cityreplace">',
'#suffix' => '</div>',
);
From what I have tried in the past, unless you aren't using the field module, the Hierarchial Select module with either your custom module based off the included hs_smallhierarchy or the hs_taxonomy modules/select menus would work for your three level select element.
The hs_taxonomy module has the database calls you would need, otherwise you can just pass the information and use the former. I've search a lot of different sites and this is something I've tried to accomplish for some time. Looking at their code would show you the Javascript you would have to use and process for the second and third select items because as you stated, unless it isn't stated one page load you will run into problems.
I've gotten a 6 Level Ebay Category to change the options correctly, but it won't update $form_state after a certain level.
function ajax_pra_menu() { $items['ajax_pra'] = array(enter code here
'title' => 'ajax Practice',
'page callback' => 'drupal_get_form',
'page arguments' => array('ajax_pra_dependent_dropdown'),
'access callback' => TRUE,enter code here` );
return $items;
}
function ajax_pra_dependent_dropdown($form, &$form_state) { $options_first = _ajax_pra_get_first_dropdown_options();
$options_two = _ajax_pra_get_second_dropdown_options();
$selected = isset($form_state['values']['dropdown_first']) ? $form_state['values']['dropdown_first'] : key($options_first);
$select_two = isset($form_state['values']['dropdown_second']) ? $form_state['values']['dropdown_second'] : key($options_two);
$form['dropdown_first'] = array(
'#title' => 'Item',
'#type' => 'select',
'#options' => $options_first,
'#default_value' => $selected,
'#ajax' => array(
'callback' => 'ajax_pra_dependent_dropdown_callback',
'wrapper' => 'dropdown-second-replace'
),
);
$form['dropdown_second'] = array(
'#title' => $options_first[$selected] . ' ' . t('type'),
'#type' => 'select',
'#prefix' => '<div id="dropdown-second-replace">',
'#suffix' => '</div>',
'#options' => _ajax_pra_get_second_dropdown_options($selected),
'#default_value' => isset($form_state['values']['dropdown_second']) ? $form_state['values']['dropdown_second'] : '',
'#ajax' => array(
'callback' => 'ajax_pra_two_dependent_dropdown_callback',
'wrapper' => 'dropdown-third-replace',
),
);
$form['dropdown_third'] = array(
'#title' => $options_two[$select_two] . ' ' . t('third'),
'#type' => 'select',
'#prefix' => '<div id="dropdown-third-replace">',
'#suffix' => '</div>',
'#options' => _ajax_pra_get_third_dropdown_options($select_two),
'#default_value' => isset($form_state['values']['dropdown_third']) ? $form_state['values']['dropdown_third'] : '',
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('save'),
);
}
function ajax_pra_dependent_dropdown_callback($form,$form_state)
{
return $form['dropdown_second'];
}
function ajax_pra_two_dependent_dropdown_callback($form,$form_state)
{
return $form['dropdown_third'];
}
function _ajax_pra_get_first_dropdown_options()
{
return drupal_map_assoc(
array(
t('Car'),
t('Bike'),
t('Mobile'),
)
);
}
function _ajax_pra_get_second_dropdown_options($key = ' ')
{
$options = array(
t('Car') => drupal_map_assoc(
array(
t('bmw'),
t('audi'),
)
),
t('Bike') => drupal_map_assoc(
array(
t('honda'),
t('suzuki'),
)
),
t('Mobile') => drupal_map_assoc(
array(
t('nokia'),
t('micro'),
)
),
);
if (isset($options[$key]))
{
return $options[$key];
}
else{
return array();
}
}
function _ajax_pra_get_third_dropdown_options($key = '')
{
$options = array(
t('bmw') => drupal_map_assoc(
array(
t('bmw x3'),
t('bmw x6'),
)
),
t('honda') => drupal_map_assoc(
array(
t('city'),
t('accord'),
)
),
);
if(isset($options[$key]))
{
return $options[$key];
}
else
{
return array();
}
}

Drupal Form APi Checkbox states

So I have create my checkbox in my form
$form['existing_customer'] = array(
'#type' => 'checkbox',
'#title' => t('Are you an existing customer'),
'#ajax' => array(
'callback' => 'checkbox_selected',
'wrapper' => 'subject',
),);
This calls my function and changes the values in my checkbox
The problem is I cannot get it to switch back if it is unchecked
function checkbox_selected(&$form, &$form_state) {
if ($form_state['values']['existing_customer'] == 1) {
$my_options = array( 'select' => t('Select'), 'mr' => t('Mr'), 'mrs' => t('Mrs'), 'miss' => t('Miss'), 'ms' =>t('Ms'), 'sir' =>t('Sir'), 'dr' => t('Dr'), 'prof' => t('Prof') );
}
elseif ($form_state['values']['existing_customer'] == 0){
$my_options = array( 'seconfZ' => t('jimmy'), 'mr' => t('Mr'), );
}
$form['subject'] = array(
'#type' => 'select',
'#title' => t('Subject'),
'#options' => $my_options//$form['subject_options']['#value']
);
return $form['subject'];
}
I thought I could do a switch on the checkbox value or state but no joy?
Oftentimes, when you use the Form API #ajax system, the wrapper that you specify is actually replaced with another element AFTER drupal_html_id() has been called again on the element wrapper. So I would check the markup of the "subject" element in Firebug/Web Inspector after your AJAX stuff happens--I'm betting that the wrapper div is now something like "subject--1".
To fix this, you need to manually set a wrapper div on the item you are replacing--one that won't change when the form is rebuilt. For example, in your form builder:
$form['existing_customer'] = array(
'#type' => 'checkbox',
'#title' => t('Are you an existing customer'),
'#ajax' => array(
'callback' => 'checkbox_selected',
'wrapper' => 'subject-wrapper',
),
);
$form['subject'] = array(
'#prefix' => '<div id="subject-wrapper">',
...
`#suffix' => '</div>',
);
Hope that helps!

Resources