Select list in Drupal 7 custom form element - drupal

I have created several custom elements in a module, which work great for grouping several controls in a single form field.
However, I now need to add one that contains a drop-down list. The idea is to have a drop-down list of country codes, and a text field for the phone number.
It displays correctly and looks good until the form is submitted, which results in the error, "An illegal choice has been detected. Please contact the site administrator." This would seem to indicate that Drupal isn't recognising the options as being part of the select control.
Here's my code:
function my_module_element_info() {
$types = array(
'phone' => array(
'#input' => TRUE,
'#process' => array('my_module_phone_process'),
'#element_validate' => array('my_module_phone_validate'),
'#autocomplete_path' => FALSE,
'#theme_wrappers' => array('my_module_inline_form_element'),
),
);
return $types;
}
function my_module_phone_process($element, &$form_state, $complete_form) {
$element['#tree'] = TRUE;
$element['prefix'] = array(
'#type' => 'select',
'#value' => $element['#value']['prefix'] ,
'#options' => $element['#options'],
'#required' => $element['#required'],
);
$element['number'] = array(
'#type' => 'textfield',
'#size' => 20,
'#maxlength' => 40,
'#value' => $element['#value']['number'],
'#required' => $element['#required'],
);
if (isset($element['#attributes'])) {
$element['prefix']['#attributes'] = $element['#attributes'];
$element['number']['#attributes'] = $element['#attributes'];
}
$element['prefix']['#attributes']['class'][] = 'form-phone-prefix';
$element['number']['#attributes']['class'][] = 'form-phone-number';
if (isset($element['#ajax'])) {
$element['prefix']['#ajax'] = $element['#ajax'];
$element['number']['#ajax'] = $element['#ajax'];
}
return $element;
}
function my_module_phone_validate($element) {
if (!preg_match('/^[0-9 ]+$/', $element['#value']['number'])) {
form_error($element['number'], t('Phone number may contain only digits and spaces.'));
}
return $element;
}
Any help getting this working would be greatly appreciated.
Thanks for looking.
James

you can add the following attribute to the element causing this error.
'#validated' => TRUE,

This problem got parked on the back burner for a while, but I got back to it this week.
I think I now understand the reason for the error. If an element has a "#options" property, it is expected to behave like a select control, and one of the options should be submitted. As the field itself is a container for other controls, so has no input directly, there is no posted value matching an entry in "#options" so Drupal flags it as an invalid form submission.
After a lot of trial and error, I hit on something very simple. The "#options" are used to populate the child control, but once it's been populated, they are no longer needed on the parent. So I added a "#after_build" function to the element, and used it to remove the "#options", and it worked perfectly.
function common_pricing_phone_after_build($element, &$form_state) {
unset($element['#options']);
return $element;
}

Related

Drupal Ajax Forms

I have a form in Drupal that calls an external database in Netezza. Retrieve this data from Netezza lasts about 10 seconds. Then, based on that information I have to build a select control to let the user choose from a list of categories. When the user chooses a category I do another expensive call to Netezza to retrieve more information.
The problem is that for the second interaction (when the user chose a category) the form is reprocessed and therefore doing 2 expensive calls to Netezza, not one as anyone would expect or desire.
Do you know a workaround for this situation? Is there a way to do an ajax call using the Drupal Ajax Framework without rebuilding the entire form?
Thanks.
PD: Reading documentation about the Ajax Framework I guess a solution could be using another path specifiying #ajax['path'], but haven´t fully tested that behavior and will be thankful if you share your experience.
PD2: I would prefer a workaround based on the Drupal Ajax Framework, not in a caching mechanism.
I'd highly recommend you to have a look into Drupal Examples, specially the module called ajax_example.
this is a fast sample code, might not be running, but just to give you the idea
function expensive_form($form, &$form_state) {
$form['category'] = array(
'#title' => t('Cateogry'),
'#type' => 'select',
'#options' => first_expensive_operation(),
'#ajax' => array(
'callback' => 'choose_category_callback',
'wrapper' => 'ajax-div',
// 'method' defaults to replaceWith, but valid values also include
// append, prepend, before and after.
// 'method' => 'replaceWith',
// 'effect' defaults to none. Other valid values are 'fade' and 'slide'.
'effect' => 'slide',
// 'speed' defaults to 'slow'. You can also use 'fast'
// or a number of milliseconds for the animation to last.
// 'speed' => 'slow',
),
);
$form['ajax_fieldset'] = array(
'#title' => t("Ajax Fields"),
// The prefix/suffix provide the div that we're replacing, named by
// #ajax['wrapper'] above.
'#prefix' => '<div id="ajax-div">',
'#suffix' => '</div>',
'#type' => 'fieldset',
'#description' => t('This is where we get automatically updated something'),
);
// this will only be executed on the second run of the form
// when the category is set.
if (isset($form_state['values']['category'])) {
$form['ajax_fieldset']['something'] = array(
'#title' => t('Somethings'),
'#type' => 'select',
'#options' => second_expensive_operation($form_state['values']['category']),
);
}
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* Callback element needs only select the portion of the form to be updated.
* Since #ajax['callback'] return can be HTML or a renderable
* array, we can just return a piece of the form.
*/
function choose_category_callback($form, $form_state) {
return $form['ajax_fieldset'];
}

Symfony 1.4: Print out individual checkboxes

I have the following form element, it generates a checkbox:
$deliveryLocationChoices = array( '0' => 'Select if Applicable',
'front' => 'Front',
'rear' => 'Rear',
'left' => 'Left Side',
'right' => 'Right Side');
$this->setWidgets(array(
'delivery_location' => new sfWidgetFormChoice(array('choices' => $deliveryLocationChoices )),
));
It works, great, except I would like to print each individual checkbox item. Reason being is I need to customize positioning of each box.
Thanks
You can override the way checkboxes are going to be displayed by passing a renderer_options argument to your new sfWidgetFormChoice.
That would look something like this
$this->setWidgets(array(
'delivery_location' => new sfWidgetFormChoice(array('choices' => $deliveryLocationChoices, 'renderer_options' => array('formatter' => array($this, 'checkboxFormatter')))),
));
Then, within your form class, define the function 'checkboxFormatter' like this
public static function checkboxFormatter($widget, $inputs) {}
That way you'll be able to access your checkboxes through the $inputs var. You must return the displaying result at the end of the checkboxFormatter function.

How do I keep a Drupal form "markup" element from rendering inside the wrapper of the "submit" element?

I have programatically added a "markup" element to a Drupal form. When it renders on the page, the element appears in the wrapper for the submit button. Clarification: It should appear between the 'field_school_name_value' element and the 'distance' element. I have set the weights of every element in hopes that that would force the layout to be right, but it doesn't seems to help. What am I doing wrong?
<?php
function abq_misc_form_alter(&$form, &$form_state, $form_id) {
if ($form_id == 'views_exposed_form') {
$state_select = array(
'#attributes' => array(
'style' => 'width:10em;',
),
'#default_value' => 'All',
'#multiple' => FALSE,
'#options' => array_merge(array('All' => 'State'), location_get_provinces()),
'#title' => NULL,
'#type' => 'select',
'#weight' => 0,
);
$form['province'] = $state_select;
$school = &$form['field_school_name_value'];
$school['#attributes'] = array(
'size' => 15,
);
$school['#weight'] = 1;
// THIS GUY
$form['divider'] = array(
'#type' => 'item',
'#markup' => '<div>–or–</div>',
'#weight' => 2,
);
$form['distance']['#weight'] = 3;
$search_distance = &$form['distance']['search_distance'];
$search_distance['#attributes'] = array(
'placeholder' => 'miles',
'size' => '5',
);
$search_distance['#prefix'] = 'Within';
$search_distance['#suffix'] = 'of';
unset($search_distance['#title']);
$search_distance['#weight'] = 0;
$postal_code = &$form['distance']['postal_code'];
unset($postal_code['#title']);
$postal_code['#attributes'] = array(
'placeholder' => 'Zip Code',
'size' => '5',
);
$postal_code['#weight'] = 1;
hide($form['distance']['search_units']);
$form['submit']['#weight'] = 4;
}
}
I'm not sure why that's happening, there's no good reason for your element to be rendered inside the wrapper for the submit button.
An easy fix, though, would be to use the #prefix attribute on the submit button which would guarantee that your markup was rendered immediately before the wrapper for the submit button:
$form['submit']['#prefix'] = '<div>–or–</div>';
UPDATE
Just to address your edit, I think the same solution can apply if you set the #prefix of the distance element instead:
$form['distance']['#prefix'] = '<div>–or–</div>';
It's possible that there's some extra formatting done by another module that implements hook_form_alter which happens to run after yours, messing up your good work. By applying the prefix to the distance element you'll be ensuring it comes immediately before the field_school_name_value element.
I should mention that I've had problems with exactly this when taking a reference to an array member of the provided $form (which you're doing with $school = &$form['field_school_name_value'];). As an extra sanity check I'd recommend changing that bit of code to this and see if it helps (try this before the other suggestion above as it might just fix it):
$form['field_school_name_value']['#attributes'] = array('size' => 15);
$form['field_school_name_value']['#weight'] = 1;
instead of
$school = &$form['field_school_name_value'];
$school['#attributes'] = array(
'size' => 15,
);
$school['#weight'] = 1;
Although this is an almost 7 years old issue, I just run this problem.
Here is the solution: https://www.drupal.org/project/views/issues/2070533
First, you should declare your markup in your form_alter hook but the '#printed' key must be declared in your defining array. Then you should implement the template_preprocess_views_exposed_form() function in your module or in theme. Here you can render the required form element by drupal_render and add it to $variables. And finally you can print out the variable in the theme (as custom variable or among widgets).
But the simpliest version: clone the views-exposed-form.tpl.php to the template directory of your theme, name it as is suggested (eg. views-exposed-form--news.tpl.php) and print your linebreak where you want.

How to override default values of form on submit event

I have a form which has textfields with default values specified. On submit event, I want these default values to be changed with the new set of values that I am passing. I am using form_set_value($element, $value, $form_state) for this. However it is not updating. Any ideas? My code is
function sample_myform($form_state){
$form['field']['name'] = array(
'#type' => 'textfield',
'#title'=> 'Name: ',
'#maxlength'=> 127,
'#default_value' => param1,
);
$form['field']['placeholder'] = array(
'#type'=> 'value',
'#value' => array(),
);
$form['field']['button1'] = array(
'#type' => 'submit',
'#value' => 'Submit',
);
return $form;
}
function sample_myform_validate($form,&$form_state){
$name2 = $form_state['values']['name'];
form_set_value($form['field']['placeholder'], $name2, $form_state); */
form_set_value($form['field']['name'],'God',$form_state);
$form_state['rebuild'] = true;
}
One thing for sure, $form['field']['placeholder'] will never, ever change since you set #value. Once #value is set, Form API moves on. Be careful though with setting just #default_value on a #type value as that can be tampered with. You can do something like $form_state['placeholder'] = $name2; in validate and use that in the form builder function.
What you try to do with name works in Drupal 7 but I suspect you are in Drupal 6. The validate function overwrites $form_state['values'] just fine but that's not persisted for the form rebuild. Once again, save into $form_state as you need.

hook_load/hook_view not called

I have a module with four node types declared. My problem is, hook_load, hook_view is never called. I used drupal_set_message to find out if certain hook is being called. And I found out hook_load, hook_view isn't. Just to give you clear picture, here's my structure of hook_load
HERE'S UPDATED ONE
function mymodule_node_info(){
return array(
'nodetype1' => array(
'name' => t('nodetype1'),
'module' => 'mymodule_nodetype1',
'description' => t('....'),
'has_title' => TRUE,
'title_label' => t('Title'),
'has_body' => TRUE,
'body_label' => t('Body'),
),
'nodetype2' => array(
......
'module' => 'mymodule_nodetype2',
......
),
'nodetype3' => array(
......
'module' => 'mymodule_nodetype3',
......
),
'nodetype4' => array(
......
'module' => 'mymodule_nodetype4',
.......
),
);
}
function mymodule_nodetype1_load($node){
$result = db_query('SELECT * from {nodetype1table} WHERE vid = %d'
$node->vid
);
drupal_set_message("hook_load is provoked.","status");
return db_fetch_object($result);
}
I don't know why it is not called. I wrote this code base on drupal module writing book and follow the instructions. I've tried sample code from that book and it works ok. Only my code isn't working. Probably because of multiple node types in one module. Any help would be highly appreciated.
Your code doesn't work because hook_load() and hook_view() aren't module hooks: they're node hooks. The invocation is based off of content type names, not module names.
So, first you need to have declared your content types using hook_node_info():
function mymodule_node_info() {
$items = array();
$items['nodetype1'] = array(
'name' => t('Node Type 2'),
'module' => 'mymodule_nodetype1',
'description' => t("Nodetype 1 description"),
);
$items['nodetype2'] = array(
'name' => t('Node Type 2'),
'module' => 'mymodule_nodetype2',
'description' => t("Nodetype 2 description"),
);
$items['nodetype3'] = array(
'name' => t('Node Type 2'),
'module' => 'mymodule_nodetype3',
'description' => t("Nodetype 3 description"),
);
return $items;
}
Then, you need to use the name of the module you specified for each content type declared in hook_node_info() for your node hooks. That is, mymodule_nodetype1_load(), mymodule_nodetype2_view(), etc.
Edit
If you're trying to have a non-node based module fire when a node is viewed or loaded, you need to use hook_nodeapi():
function mymodule_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
switch ($op) {
case 'view':
mymodule_view_function($node);
break;
case 'load':
mymodule_load_function($node);
break;
}
}
Replace mymodule_load_function() and mymodule_load_function() with your own custom functions that are designed to act on the $node object.
Edit 2
Besides the syntax error in your hook_load() implementations, there's a piece of your code outside of what you're providing that's preventing the correct invocation. The following code works (if you create a nodetype1 node, the message "mymodule_nodetype1_load invoked" appears on the node): perhaps you can compare your entire code to see what you're missing.
function mymodule_node_info() {
return array(
'mymodule_nodetype1' => array(
'name' => t('nodetype1'),
'module' => 'mymodule_nodetype1',
'description' => t('....'),
'has_title' => TRUE,
'title_label' => t('Title'),
'has_body' => TRUE,
'body_label' => t('Body'),
),
'mymodule_nodetype2' => array(
'name' => t('nodetype2'),
'module' => 'mymodule_nodetype2',
'description' => t('....'),
'has_title' => TRUE,
'title_label' => t('Title'),
'has_body' => TRUE,
'body_label' => t('Body'),
),
);
}
function mymodule_nodetype1_form(&$node, $form_state) {
// nodetype1 form elements go here
return $form;
}
function mymodule_nodetype2_form(&$node, $form_state) {
// nodetype2 form elements go here
return $form;
}
function mymodule_nodetype1_load($node) {
$additions = new stdClass();
drupal_set_message('mymodule_nodetype1_load invoked');
return $additions;
}
function mymodule_nodetype2_load($node) {
$additions = new stdClass();
drupal_set_message('mymodule_nodetype2_load invoked');
return $additions;
}
If you're not reseting your environment after changes to your module, you might be running into caching issues. You should test your code in a sandbox environment that can be reset to a clean Drupal installation to ensure you're not focusing on old cruft from previous, incorrect node implementations.
Additionally, you should only be using hook_nodeapi() if you are trying to act on content types that are not defined by your module. Your content types should be using the node hooks (hook_load(), hook_view(), etc.).
Finally, it may be the case that you're using the wrong hooks because you're expecting them to fire in places they are not designed to. If you've gone through everything above, please update your post with the functionality you're expecting to achieve and where you expect the hook to fire.
I found the culprit why your code doesn't work. It's because I was using the test data created by the old codes. In my old codes, because of node declaration inside hook_node_info uses the same module value, I could only create one hook_form implementation and use "switch" statement to return appropriate form. Just to give you clear picture of my old codes-
function mymodule_node_info(){
return array(
'nodetype1' => array(
.....
'module' => 'mymodule',
.....
),
'nodetype2' => array(
......
'module' => 'mymodule',
......
),
.......
);
}
function mymodule_form(&$node, $form_state){
switch($node->type){
case 'nodetype1':
return nodetype1_form();
break;
case 'nodetype2':
return nodetype2_form();
break;
.....
}
}
When I created new data after I made those changes you have provided, hook_load is called. It works! I've tested several times(testing with old data created by previous code and testing with new data created after those changes) to make sure if that's the root cause and, I got the same result.I think drupal store form_id or module entry value of node declaration along with data and determine the hook_load call. That's probably the reason why it doesn't think it's a data of this node and thus hook_load isn't invoked.
And Thank you so much for your help.

Resources