I have a data table which is populated with data unrelated to drupal content (from a third party system). The data relates to photos which must be approved / flagged as inappropriate.
So I'm writing a Drupal admin module which should moderate this content. So far, I have built a table using theme('table',...) which shows 1 photo per row, along with some other meta data. I now want to include some form buttons in the table and build some Ajax actions which are triggered by those buttons.
This article looks relevant http://drupal.org/node/112358 but I'm concerned this isn't the Drupal 6 way of doing things.
Can anyone offer some advice on how best to approach this problem - preferably using core modules / form override functions. Drupal version is 6.14.
Custom theme functions allow you to render content in a completely custom way. You can also create a custom template for your content, this could include the buttons.
hook_theme() will allow you to create your own content type to add to the theme function so you could have theme('moderatedimages').
A workaround would be to put the HTML for the moderate buttons into the table data so that it is output by theme table. This would save you having to write your own theme function.
For the AJAX calls you will need to build your own menu callback using hook_menu() and a custom function. (Code snippets are from this tutorial.)
<?php
function mymodule_products_menu() {
$items = array();
$items['products/get'] = array(
'title' => 'mymodule callback',
'page callback' => 'mymodule_myfunction',
'access arguments' => array('access content'),
'type' => MENU_CALLBACK
);
return $items;
}
This will call the function mymodule_myfunction(). One thing to remember about this function is that you want to print the result and exit. You may want to use drupal_json() to encode the response.
Why are you using the standard theme_table() function? Just make your own theme function that includes the form elements you need. More on theme functions here and here.
I had the same problem. I found the solution in drupal-directory/modules/menu/menu.admin.inc. The Form-API and Themes will be used only!
Implementation:
-write your query with scheme-api (I think you did it already for your theme('table',...) )
-for each row create a form like this:
$form[$row->id]['column_name_1'] = array(...here description of the column -> checkbox, textfield...);
$form[$row->id]['column_name_2'] = array(...);
-write your own theme-funktion, witch use hook_theme_table (you habe already did it, you need only to change some cells into checkboxes or other form-elements.)
I write a submit-function now, but it doesn't work :-) For more information see the menu-modul.
Hier my code:
/**
* Form for editing the event types.
*
* #ingroup forms
*/
function mymodule_event_type_overview_form() {
$vid = variable_get('mymodule_category_vocabulary', '');
$sql = "SELECT term_data.tid AS tid,
term_data.name AS tname,
term_data.vid AS vid,
term_site_config.color AS color,
term_site_config.site_enabled AS site_enabled,
term_site_config.site_shown AS site_shown
FROM {term_data} term_data
INNER JOIN {term_site_config} term_site_config
ON term_data.tid = term_site_config.tid
WHERE term_data.vid = %d";
$result = db_query(db_rewrite_sql($sql), $vid);
$form = array(); while ($term = db_fetch_object($result)) {
$form[$term->tid]['tname'] = array(
'#type' => 'value',
'#value' => $term->tname,
);
$form[$term->tid]['color'] = array(
'#type' => 'value',
'#value' => $term->color,
);
$form[$term->tid]['enabled'] = array(
'#type' => 'checkbox',
'#default_value' => $term->site_enabled,
);
$form[$term->tid]['shown'] = array(
'#type' => 'checkbox',
'#default_value' => $term->site_shown,
);
// Build a list of operations.
$operations = array();
$operations['delete'] = l(t('delete'), 'admin/settings/mymodule/eventtype/'. $term->tid .'/delete');
$operations['edit'] = l(t('edit'), 'admin/settings/mymodule/eventtype/'. $term->tid .'/edit');
$form[$term->tid]['operations'] = array();
foreach ($operations as $op => $value) {
$form[$term->tid]['operations'][$op] = array('#value' => $value);
} }
if (element_children($form)) {
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save configuration'),
);
$form['reset'] = array(
'#type' => 'submit',
'#value' => t('Reset to defaults'),
);
if (!empty($_POST) && form_get_errors()) {
drupal_set_message(t('The settings have not been saved because of the errors.'), 'error');
} }else {
$form['empty'] = array('#value' => t('There are no event types yet.')); }
return $form; }
/**
* Theme the event type overview form into a table.
*
* #ingroup themeable
*/
function theme_mymodule_event_type_overview_form($form) {
$header = array(
t('Event type'),
t('Color'),
array('data' => t('Enabled'), 'class' => 'checkbox'),
array('data' => t('Shown'), 'class' => 'checkbox'),
array('data' => t('Operations'), 'colspan' => '2'), );
$rows = array(); foreach (element_children($form) as $id) {
$element = &$form[$id];
if (isset($element['tname'])) {
$row = array(
t($element['tname']['#value']),
t($element['color']['#value']),
array('data' => drupal_render($element['enabled']), 'class' => 'checkbox'),
array('data' => drupal_render($element['shown']), 'class' => 'checkbox'),
array('data' => drupal_render($element['operations']['delete'])),
array('data' => drupal_render($element['operations']['edit'])),
);
$rows[] = $row; } } $output = ''; if ($rows) {
$output .= theme('table', $header, $rows); }
$output .= drupal_render($form);
return $output;
}
You will get the html-code of the form if you call drupal_get_form('mymodule_event_type_overview_form');
and don't forget co write following function in mymodule.module:
/**
* Implementation of hook_theme().
*/ function mymodule_theme() {
return array(
'mymodule_event_type_overview_form' => array(
'arguments' => array(),
),
);
}
Have fun :-)
Katja
Related
I have an issue with multiple AJAX requests modifying a form within drupal 8.
Let me explain - I have been trying to build a quiz module within drupal, and decided to use a widget to create a variable amount of quiz questions, within this question widget is a list of possible answers.
I have created an AJAX button within my question widget which allows answers to be removed and it works the first time it is submitted, but for some reason the second time I run the ajax call the form is reset (like no changes have been made, and no answers have been removed). I have debugged the form array after I have unset an answer, aswell as when the ajax callback is run the second time.
The ajax-enabled button uses the default ajax replace method and I have tried different methods of returning the form (minus an answer) within my AJAX callback including simply unsetting the selected form element and returning the form, aswell as using the AjaxResponse and HtmlCommand classes. I have also tried to rebuild both the form, via formbuilder, and form_state with no joy.
Also each button and answer have unique names/id's.
Here is my widget code (including the button definition):
<?php
/**
* #file
* Contains \Drupal\pp_quiz\Plugin\Field\FieldWidget\QuestionWidget.
*/
namespace Drupal\pp_quiz\Plugin\Field\FieldWidget;
use Drupal\pp_quiz\Controller\QuizController;
use Drupal\pp_quiz\Ajax\AjaxHandler;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Plugin implementation of the 'question_widget' widget
*
* #FieldWidget(
* id = "question_widget",
* label = #Translation("Quiz question widget"),
* field_types = {
* "quizquestion_type",
* },
* )
*/
class QuestionWidget extends WidgetBase
{
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$ajaxhandler = new AjaxHandler();
$input = $form_state->getUserInput();
//grab answer count from element array (using delta index)
$question = $items->get($delta)->getValue();
$answercount = count(unserialize($question['answer']));
$element['question'] = array(
'#type'=>'text_format',
'#format' => 'normal',
'#title' => gettext('Question'),
'#description' => gettext("The Question Text"),
'#required' => TRUE,
'#element_validate' => array(
array(
'\Drupal\pp_quiz\Controller\QuizController',
'validateQuestion'
),
),
);
$element['answers_description'] = array('#markup' => 'Create answers below and select which are correct by checking boxes');
$tableheader = array(
'answer' => array('data' => t('Answer'), 'field' => 'answer'),
);
for ($i=0; $i<$answercount; $i++) {
$name = "{$delta}answer{$i}";
$options[$name] = array(
'answer' => array(
'data' => array(
'#type'=>'textfield',
//fix for losing answers on addmore button
'#value'=>isset($input[$name]) ? $input[$name] : '',
'#name' => $name,
'#required' => TRUE,
),
),
);
}
$element['answers'] = array(
'#type'=>'tableselect',
'#header' => $tableheader,
'#options' => $options,
);
$element['removeanswer'] = array(
'#type' => 'submit',
'#value' => t('Remove Answer'),
'#name' => "{$delta}removeanswer",
'#questionno' => $delta,
'#attributes' => array(
'class' => array('removeanswer')
),
'#ajax' => array(
'callback' => array(
$ajaxhandler,
'removeAnswerAjax',
),
'wrapper' => 'edit-field-quiz-questions-wrapper',
),
);
$element = array(
'#type' => 'question',
) + $element;
return $element;
}
}
As you can see above, my 'removeanswer' submit button element has an ajax callback to a function called 'removeAnswerAjax' on the class 'AjaxHandler'. Below is the code for this callback:
<?php
/**
* #file
* Contains \Drupal\pp_quiz\Ajax\AjaxHandler.
*/
namespace Drupal\pp_quiz\Ajax;
use \Drupal\pp_quiz\Controller\QuizController;
use \Drupal\pp_quiz\Entities\QuizResults;
use \Drupal\Core\Form\FormStateInterface;
use \Drupal\Core\Ajax\AjaxResponse;
use \Drupal\Core\Ajax\HtmlCommand;
class AjaxHandler {
public function removeAnswerAjax(&$form, FormStateInterface $form_state) {
$questionno = $form_state->getTriggeringElement()['#questionno'];
$response = new AjaxResponse();
//find selected answer for question number (questionno)
foreach($form['field_quiz_questions']['widget'][$questionno]['answers']['#value'] as $answer_key=>$answer) {
unset($form['field_quiz_questions']['widget'][$questionno]['answers']['#options'][$answer]);
unset($form['field_quiz_questions']['widget'][$questionno]['answers']['#default_value'][$answer]);
unset($form['field_quiz_questions']['widget'][$questionno]['answers'][$answer]);
}
$response->addCommand(new HtmlCommand('#edit-field-quiz-questions-wrapper', $form['field_quiz_questions']['widget']));
$form_state->setRebuild();
return $response;
}
}
If anyone could shed any light on why the form is being reset before it is passed as an argument to my ajax callback, I would love you forever :-)
Thanks for reading.
One possible reason that your Ajax doesn't takes effect the second time is rebuilding of the form, in Ajax callback. As you are rebuilding the form using -
$form_state->setRebuild()
When a form is rebuilt, all the field and form ids are suffixed by a random build number. thus the selector is not found and the response is not replaced in DOM.
Try removing the form rebuild.
I need to create dropdown list for all languages in my Drupal form, Is there any function in PHP to achieve this. I want to create an array of all languages and pass it to drupal form.
function video_upload_subtitles_form($form, &$form_state) {
$form = array('#attributes' => array('enctype' => 'multipart/form-data'));
$lang_list = array();//**how to create this array**
$form['video_name'] = array(
'#title' => t('Name Of the video'),
'#type' => 'textfield',
);
$form['sub_file'] = array(
'#type' => 'file',
'#title' => t('Upload video'),
'#description' => t('Pick a video file to upload.'),
);
$form['user_list']=array(
'#type'=>'select',
'#title' => t('Language'),
'#options' => $lang_list,//array of language
'#multiple' => false,
'#attributes'=>array('size'=>4),
'#weight'=>8,
);
$form['submit_button'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
I know listing of language can be done in php by listing and making a array manually but is it possible to do with any php function, just looking for some easy way to save time.
Got this cool website for Country List . Is there anything for the Language like this
Try this,
$select = db_select('Your table name', 's');
$select = $select->fields('s',array('languages_name','id'));
// 'languages_name' , 'id' this is a column
$queried_nodes = $select->execute()
->fetchAllAssoc('id');
$lang_list = array();
foreach ($queried_nodes as $result)
{
$lang_list[$result->languages_name] = t($result->languages_name);
}
after set a $lang_list variable
$form['user_list']=array(
'#type'=>'select',
'#title' => t('Language'),
'#options' => $lang_list,//array of language
'#multiple' => false,
'#attributes'=>array('size'=>4),
'#weight'=>8,
);
you are create a new Language table or content types and you use a drupal inbuilt language list so try this code.
include_once DRUPAL_ROOT . '/includes/iso.inc';
$lang = _locale_get_predefined_list();
$lang_list = array();
foreach ($lang as $key => $value)
{
$lang_list[$value[0]] = t($value[0]);
}
after use a $lang_list varible in form api
The easiest way is probably to put all the languages you want in a table in your DB.
Then you could just use the following code to create your select box:
$languages = db_query("SELECT id, name FROM languages")->fetchAllKeyed();
$form['language'] = array(
'#type' => 'select',
'#title' => t('Choose language'),
'#options' => $languages,
);
To populate your table of languages you can download a csv here.
My custom module code:
<?php
function my_module_menu() {
$items = array();
$items['form-example'] = array(
'title' => 'My Module Form',
'description' => 'A form to mess around with.',
'page callback' => 'drupal_get_form',
'page arguments' => array('my_module_form'),
'access callback' => TRUE
);
return $items;
}
function my_module_form($form, &$form_state, $no_js_use = FALSE) {
$form['file'] = array(
'#type' => 'file',
'#title' => t('Image'),
'#description' => t('Upload an image'),
);
$form['menu'] = array(
'#markup' => '<b>Add More:</b>'
);
$form['#tree'] = TRUE;
$form['names_fieldset'] = array(
'#type' => 'fieldset',
'#title' => t('Add more images'),
'#prefix' => '<div id="names-fieldset-wrapper">',
'#suffix' => '</div>',
);
if (empty($form_state['num_names'])) {
$form_state['num_names'] = 1;
}
for ($i = 0; $i < $form_state['num_names']; $i++) {
$form['names_fieldset']['name'][$i][0]= array(
'#title' => t('Image'),
'#type' => 'file',
'#weight' => '5',
'#description' => t('Upload an image'),
);
}
$form['names_fieldset']['add_name'] = array(
'#type' => 'submit',
'#value' => t('Add one more'),
'#submit' => array('my_module_add_more_add_one'),
'#ajax' => array(
'callback' => 'my_module_add_more_callback',
'wrapper' => 'names-fieldset-wrapper',
),
);
if ($form_state['num_names'] > 1) {
$form['names_fieldset']['remove_name'] = array(
'#type' => 'submit',
'#value' => t('Remove one'),
'#submit' => array('my_module_add_more_remove_one'),
'#ajax' => array(
'callback' => 'my_module_add_more_callback',
'wrapper' => 'names-fieldset-wrapper',
),
);
}
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
$form['#submit'][] = 'my_module_add_more_submit';
if ($no_js_use) {
if (!empty($form['names_fieldset']['remove_name']['#ajax'])) {
unset($form['names_fieldset']['remove_name']['#ajax']);
}
unset($form['names_fieldset']['add_name']['#ajax']);
}
return $form;
}
function my_module_add_more_callback($form, $form_state) {
return $form['names_fieldset'];
}
function my_module_add_more_add_one($form, &$form_state) {
$form_state['num_names']++;
$form_state['rebuild'] = TRUE;
//$form_state['no_redirect'] = TRUE;
}
function my_module_add_more_remove_one($form, &$form_state) {
if ($form_state['num_names'] > 1) {
$form_state['num_names']--;
}
$form_state['rebuild'] = TRUE;
}
function my_module_add_more_submit($form, &$form_state) {
$file = $form_state['values']['file']."<br \>";
$validators = array();
$file = file_save_upload('file', $validators, 'public://uploads');
print_r($file);
exit();
}
When i submit the form, i am trying to get the details of images, that are added through Add More option. But i am not able to get them. However i am only able to get the details of the first image (and able to upload it).
I want to know two things here:
How can i retrieve the details of the images that are added with Add More option (fieldset), and how can i upload them ?
When i browse and select an image in the fieldset, it is not retained in the form, after adding another image field. How to retain the selected images in fieldset ?
Look at this article -- http://evolvingweb.ca/story/poutine-maker-introduction-field-api-drupal-7-part-1 -- as it has some information on something missing in your code. $delta. $delta is the id assigned to values of fields, even if your field only has 1 item.
What do you see when you var_dump the file field you created? If you get all the information along with those added using 'Add one more' button, you can learn the correct structure of the values using: echo "<pre>"; print_R($form_state['values']); echo "</pre>":
There are a couple of issues with your code.
The first issue is that your submit function only deals with the first upload field which is indeed called "file". But does absolutely nothing to handle the other fields.
A second issue is that it will upload and save the first field every time you click on "Add one more" which will duplicate your upload. You would not experience this issue without AJAX, but if you want to add that, you will.
I would make the following changes:
Remove the $form['#tree'] = TRUE and add it to the fieldset instead. $form['names_fieldsets']['#tree'] = TRUE; after you declare the fieldset of course.
Change the way you declare the file fields in the fieldset (inside the for loop) to this:
for ($i = 0; $i < $form_state['num_names']; $i++) {
$form['names_fieldset'][$i]['name']= array(
'#title' => t('Image'),
'#type' => 'file',
'#weight' => '5',
'#description' => t('Upload an image'),
// We need this to know which file element this is.
// By default drupal would name all as files[names_fieldset]
'#name' => 'files[names_fieldset_' . $i . '_name]',
);
}
I would change the submit function like this (note that I assume you also do my above suggested changes):
function my_module_add_more_submit($form, &$form_state) {
if ($form_state['values']['op'] == 'Submit') {
$validators = array();
$files = array();
if (!empty($_FILES['files']['name']['file'])) {
$files[] = file_save_upload('file', $validators, file_default_scheme() . '://uploads');
}
foreach ($form_state['values']['names_fieldset'] as $name => $field) {
if ($name != 'add_name') {
$file_name = implode('_', $form['names_fieldset'][$name]['name']['#parents']);
if (!empty($_FILES['files']['name'][$file_name])) {
$files[] = file_save_upload($file_name, $validators, file_default_scheme() . '://uploads');
}
}
}
}
}
With this changes we set a form field name aware that's inside a tree. We only trigger uploads when the "Submit" button is clicked and only for form fields that actually had a file added to them. Also we upload using the default scheme and don't use always the public one.
Of course the code need some messages for the user to know how many files did upload, names, or any other deemed worthy info.
What I want to do is: I want to render a form on a page with a view. This view has a list of 'notes' (=CT Note). When you fill in the form, the note is stored, the form is cleared, and the new note is added to the list. All without page refreshes.
I create a module new_note, and addes this function:
function new_note_form($nodeid = NULL) {
ctools_include('ajax');
// drupal_add_js(drupal_get_path('module', 'custom_forms') . '/js/note_form.js');
//dpm($nodeid);
module_load_include('inc', 'node', 'node.pages');
$form = node_add('note');
$form['field_note_reference']['und']['#value'] = '2';
$form['field_note_reference']['und']['#validated'] = 'TRUE';
$form['field_note_reference']['#attributes']['class'][] = "hidden";
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit',
'#executes_submit_callback' => FALSE,
'#ajax' => array(
'callback' => 'ajax_note',
'wrapper' => 'status',
),
);
$output= drupal_render($form);
dpm($form);
print $output;
}
function ajax_note(&$form, &$form_state) {
return 'test';
}
I use this function in a display suite block field, which is rendered above the note list. So far so good.
The only problem is, that when I submit the form, the ajax is not called, and the normal submit is done.
Can anyone help me out
# Edit.
After what clive suggested I changed the code, and got the ajax working.
function new_notes_form($nodeid = NULL) {
global $user;
$node = (object) array(
'uid' => $user->uid,
'name' => (isset($user->name) ? $user->name : ''),
'type' => 'note',
'language' => LANGUAGE_NONE,
);
$form_state = array();
$form_state['build_info']['args'] = array($node);
form_load_include($form_state, 'inc', 'node', 'node.pages');
$form = drupal_build_form('note_node_form', $form_state);
$form['field_note_reference']['und']['#value'] = '2';
$form['field_note_reference']['#attributes']['class'][] = "hidden";
$form['submit'] = array(
'#type' => 'button',
'#value' => 'Submit',
'#limit_validation_errors' => array(),
'#ajax' => array(
'callback' => 'ajax_note_replace',
'wrapper' => 'status',
),
);
return $form;
}
function ajax_note_replace(&$form, &$form_state) {
dpm("test");
dpm($form);
$output = '<h1>' . t('Hello World') . '</h1>';
// $node = node_load('6');
// $output .= drupal_render(node_view($node, $style = 'teaser', $options = array()));
ctools_include('ajax');
$commands = array();
$commands[] = ajax_command_prepend(".view-content", $output);
print ajax_render($commands); // this function exits.
exit;
}
#Clive, can you help me out with the rest ? I want to save the node on callback (if valid?). In the ajax callback my node-id is null because it is not stored yet? how can I validate and save the node, otherwise set the not valid form items to red as normal.
It's likely to be because you're sidestepping Drupal's proper methods for creating forms so the AJAX preprocessing won't be performed. If you have a look at drupal_get_form() (the function used pretty much exclusively to prepare a form in Drupal) you'll see it in turn calls drupal_build_form() which is what you need to do:
function new_note_form($form, &$form_state, $nodeid = NULL) {
$form_state['build_info']['args'] = array('note');
$form = drupal_build_form('node_add', $form_state);
$form['submit'] = array(
'#type' => 'button',
'#value' => 'Submit',
'#limit_validation_errors' => array(),
'#ajax' => array(
'callback' => 'advanced_form_callback',
'wrapper' => 'status',
),
);
return $form;
}
echo render(drupal_get_form('new_note_form'));
I've changed the element from type submit to button because I find it works much better when you want to do some ajax processing, removed #executes_submit_callback, and added #limit_validation_errors which will stop the form from otherwise validating (I think that's what you were trying to do by setting #validated to TRUE but I might be wrong).
Hope that helps, it's untested but should give you a good place to start.
im using Drupal 7 and I want to add a new filter in views.
I have a custom table "clicks" with two fields; nid and clicks_left.
The filter should just contain a checkbox "Only display nodes with clicks left". So the filter should join node and clicks on nid..
I have read like thousands of pages of custom filters but can't get it to work =)
Please, could someone show me a working example so I understand?
I have come so far that the filter is displayed under filters but what do I need to add to do the join and get the checkbox? The relevant code below:
FILE clicks_views.inc:
function clicks_views_data() {
$data = array();
$data['clicks']['clicks_filter'] = array(
'group' => t('Clicks'),
'title' => t('Clicks left'),
'help' => t('Filter any Views based on clicks left'),
'filter' => array(
'field' => 'clicks_left',
'handler' => 'clicks_handler_filter',
),
);
return $data;
}
FILE clicks_handler_filter.inc:
<?php
class clicks_handler_filter extends views_handler_filter {
???
};
I know both functions are wrong ;)
Ok, I've found a solution. For anyone who needs it:
In clicks.module
function clicks_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'clicks') . '/includes'
);
}
In clicks.views.inc
function clicks_views_handlers() {
return array(
'info' => array(
'path' => drupal_get_path('module', 'clicks') . '/includes', // path to view files
),
'handlers' => array(
// register our custom filter, with the class/file name and parent class
'clicks_handler_filter' => array(
'parent' => 'views_handler_filter',
)
),
);
}
function clicks_views_data() {
$data = array();
if(module_exists('clicks')) {
$data['node']['clicks'] = array(
'group' => t('Clicks'),
'title' => t('Clicks left'),
'help' => t('Filter any Views based on clicks left'),
'filter' => array(
'handler' => 'clicks_handler_filter',
),
);
}
return $data;
}
In clicks_handler_filter.inc
class clicks_handler_filter extends views_handler_filter {
function admin_summary() { }
function operator_form() { }
function query() {
$table = $this->ensure_my_table();
$join = new views_join();
$join->construct('clicks', $this->table_alias, 'nid', 'nid');
$this->query->ensure_table('clicks', $this->relationship, $join);
$this->query->add_where($this->options['group'], "clicks.clicks_left", 0, ">");
}
}
This gives me a possibility to add a filter "clicks" that if enabled hides all results that doesn't have clicks left (clicks_left > 0)
Actually, if your values in your tables clicks are numeric you don't need to create your own handler, you can use the default from Views views_handler_filter_numeric.
You can see all handlers that already exists in the Views handlers.