Drupal 7 node_save() causes AJAX to fail - ajax

I've created custom node form with only two fields attached. Each field have own "Save" AJAX button. On "Save" button click, everything goes as if it is default node form submission. Here is the full code:
/**
* Form;
*/
function mymodule_custom_form($form, &$form_state) {
$node = node_load(123);
$node->langcode = entity_language('node', $node);
// Store node object in form state
if (!isset($form_state['node'])) {
if (!isset($node->title)) {
$node->title = NULL;
}
node_object_prepare($node);
$form_state['node'] = $node;
}
else {
$node = $form_state['node'];
}
// Basic node information.
// These elements are just values so they are not even sent to the client.
$properties = array('nid', 'vid', 'uid', 'created', 'type', 'language');
foreach ($properties as $key) {
$form[$key] = array(
'#type' => 'value',
'#value' => isset($node->$key) ? $node->$key : NULL,
);
}
// Changed must be sent to the client, for later overwrite error checking.
$form['changed'] = array(
'#type' => 'hidden',
'#default_value' => isset($node->changed) ? $node->changed : NULL,
);
// TEST 1 field
field_attach_form('node', $node, $form, $form_state, $node->langcode, array(
'field_name' => 'field_test_1'
));
// Set the form prefix and suffix to support AJAX
$form['field_test_1']['#prefix'] = '<div id="wrapper-field-test-1">';
$form['field_test_1']['#suffix'] = '</div>';
// the submit button
$form['field_test_1']['save'] = array(
'#type' => 'submit',
'#value' => t('Save'),
'#name' => 'button-field-test-1',
'#ajax' => array(
'callback' => 'mymodule_custom_form_ajax_submit',
'wrapper' => 'wrapper-field-test-1',
'method' => 'replace',
'effect' => 'fade',
)
);
// TEST 2 field
field_attach_form('node', $node, $form, $form_state, $node->langcode, array(
'field_name' => 'field_test_2'
));
// Set the form prefix and suffix to support AJAX
$form['field_test_2']['#prefix'] = '<div id="wrapper-field-test-2">';
$form['field_test_2']['#suffix'] = '</div>';
// the submit button
$form['field_test_2']['save'] = array(
'#type' => 'submit',
'#value' => t('Save'),
'#name' => 'button-field-test-2',
'#ajax' => array(
'callback' => 'mymodule_custom_form_ajax_submit',
'wrapper' => 'wrapper-field-test-2',
'method' => 'replace',
'effect' => 'fade',
)
);
return $form;
}
/**
* Form validate;
*/
function mymodule_custom_form_validate($form, &$form_state) {
$field_name = reset($form_state['triggering_element']['#parents']);
// Validate only the stuff we need
$fields = array(
'field_test_1',
'field_test_2'
);
foreach ($fields as $field => $bundle) {
if ($field_name != $field) {
unset($form_state['values'][$field], $form_state['input'][$field]);
}
}
// $form_state['node'] contains the actual entity being edited, but we must
// not update it with form values that have not yet been validated, so we
// create a pseudo-entity to use during validation.
$node = (object) $form_state['values'];
node_validate($node, $form, $form_state);
entity_form_field_validate('node', $form, $form_state);
}
/**
* Form submit;
*/
function mymodule_custom_form_submit($form, &$form_state) {
// Execute all submit functions
$node = $form_state['node'];
entity_form_submit_build_entity('node', $node, $form, $form_state);
node_submit($node);
foreach (module_implements('node_submit') as $module) {
$function = $module . '_node_submit';
$function($node, $form, $form_state);
}
// Save the node
node_save($node);
$form_state['values']['nid'] = $node->nid;
$form_state['nid'] = $node->nid;
}
/**
* Form ajax submit;
*/
function mymodule_custom_form_ajax_submit($form, &$form_state) {
$field_name = reset($form_state['triggering_element']['#parents']);
// validate the form
drupal_validate_form('mymodule_custom_form', $form, $form_state);
// if there are errors, return the form to display the error messages
if (form_get_errors()) {
$form_state['rebuild'] = TRUE;
return $form[$field_name];
}
// process the form
mymodule_custom_form_submit($form, $form_state);
// Show the processing box
$form[$field_name] = array('#markup' => 'Thanks!');
$form[$field_name]['#prefix'] = '<div id="wrapper-' . str_replace('_', '-', $field_name) . '">';
$form[$field_name]['#suffix'] = '</div>';
// return the confirmation message
return $form[$field_name];
}
The code works perfectly, except that node_save($node) causes The content on this page has either been modified by another user, or you have already submitted modifications using this form. As a result, your changes cannot be saved. error.
No errors, if I remove it. But I need to save node and trigger all the hooks.

I think the issue caused by this line :
// process the form
mymodule_custom_form_submit($form, $form_state);
in your ajax function, try to use the node_save into your ajax function. mymodule_custom_form_submit is the conventional hook usually used. It seems to be several save process at the same time.

I don't know if you've solved it or not, but I've been stuck in a similar situation.
I avoid the error: The content on this page has either been modified..., modifying changed value in $form_state. In your submit function: mymodule_custom_form_submit, right after node_save($node), Add this line:
$form_state['input']['changed'] = $node->changed;

Drupal 7 full Save & Stay callback function, please add wrapper for form:
function <MODULE_NAME>_node_ajax_save_callback($form, &$form_state){
// If error, return form.
if (form_get_errors()) {
return $form;
}
node_form_submit($form, $form_state);
$form['changed']['#value'] = $form_state['node']->changed;
return $form;
}

Related

Drupal 8 Form alter the ajax callback is not working

I am getting my ajax callback in normal custom form, but on form alter its not working.
function sample_ajax_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
if ($form_id === 'node_sampleajax_form' || $form_id === 'node_sampleajax_edit_form') {
$form['field_nametrain']= array(
'#title' => t('training name'),
'#type' => 'select',
'#options' => _load_training(),
'#required' => FALSE,
'#ajax' => [
'callback' => [$this, 'changeOptionsAjax'],
// 'callback' => '::changeOptionsAjax',
'wrapper' => 'second_field_wrapper',
],
);
$form['field_namedomain'] = [
'#type' => 'select',
'#title' => t('Domain program'),
'#options' => $this->getOptions($form_state),
'#prefix' => '<div id="second_field_wrapper">',
'#suffix' => '</div>',
];
return $form;
}
}
function _load_training() {
$training = array('- Select domain training -');
$query = db_select("node__field_trainingname", "a");
$query->fields("a", array('field_trainingname_value', 'entity_id'));
$query->orderBy("a.field_trainingname_value");
$result = $query->execute();
while($row = $result->fetchObject()){
$training[$row->entity_id] = $row->field_trainingname_value;
}
return $training;
}
function changeOptionsAjax(array &$form, FormStateInterface $form_state) {
return $form['field_namedomain'];
}
function getOptions(array &$form, FormStateInterface $form_state) {
$cvvv = $form_state->getValue('field_nametrain');
<!-- return ["shgsh", $form_state->get(['field_nametrain'])]; -->
$options = array('- Select subdomain category -');
$query = db_select("node__field_trainingname", "a");
$query->fields("a", array('field_trainingname_value', 'entity_id'));
$query = db_select("node__field_cms", "b");
$query->fields("b", array('field_cms_value', 'entity_id'));
$query->join("node__field_trainingname", "b", "b.entity_id=a.entity_id");
$query->condition("a.entity_id", $cvvv);
$result = $query->execute();
while($row = $result->fetchObject()){
$options[$row->entity_id] = $row->field_cms_value;
}
return $options;
}
On using $this->getOptions($form_state) it represent the error log it is not an object and throws website encounter error in front end. But on custom form no error came only in formalter it throws error.
Kindly suggest me ideas to apply in form_alter of Drupal 8
The .module file, where your form alter hook is located, is not a class, therefore there is no $this. Your custom form however is a class (usually in your_module/src/Form/YourForm.php), that's why it works there but not in the .module file.
Further reading: http://www.php.net/manual/en/language.oop5.basic.php
and What does the variable $this mean in PHP?
In your case you should be able to just call
'#options' => getOptions($form, $form_state),
And more on a side note: I would strongly recommend to do some code refactoring.
In your custom submit handler, firt get the form object from the form state.
$formObj = $formState->getFormObject();
then call submitForm() on the form object and pass the form and form state variables.
$formObj->submitForm($form, $formState);
and finally, you just need to simply trigger the save() function on the object.
$formObj->save($form, $formState);
So the whole solution is something like
function YOR_CUSTOM_SUBMIT_HANLDLER(array $form, FormStateInterface $form_state) {
/** #var Drupal\user\RegisterForm $entity */
$formObj = $form_state->getFormObject();
$formObj->submitForm($form, $form_state);
$formObj->save($form, $form_state);
}

drupal 7 ajax dependent dropdown

I have created a custom html form that is having 2 dropdowns State and City. When selects a State i want to populate the city dropdown. But the feature is not working. I already followed many urls but unable to solve the issue.
See the code below i have tried. Can anybody help me.
function zenuineform($form, &$form_state) {
$value_dropdown_first = isset($form_state['values']['comp_state']) ? $form_state['values']['comp_state'] : 1;
$form = array();
$form['comp_state']=array(
'#type'=>'select',
'#title'=>t('State : '),
'#options'=> $state_arr,
'#default_value' => $state,
'#ajax' => array(
// When 'event' occurs, Drupal will perform an ajax request in the
// background. Usually the default value is sufficient (eg. change for
// select elements), but valid values include any jQuery event,
// most notably 'mousedown', 'blur', and 'submit'.
'event' => 'change',
'callback' => 'zenuineform_ajax_callback',
'wrapper' => 'filtered_city',
),
);
$form['comp_city']=array(
'#type'=>'select',
'#title'=>t('City : '),
'#default_value' => $city,
'#prefix' => '<div id="filtered_city">',
'#suffix' => '</div>',
'#options' => zenuineform_second_dropdown_options($value_dropdown_first),
);
return $form;
}
function zenuineform_ajax_callback($form, $form_state) {
return $form['comp_city'];
}
function zenuineform_second_dropdown_options($state_id = '') {
if($state_id !='' ) {
$result = db_query('SELECT * FROM zen_city WHERE state_id = '.$state_id);
if ($result) {
$options = '<option value="">--Select--</option>';
while ($row = $result->fetchAssoc()) {
$rows[$row['id']] = $row['city_name'];
}
return $rows;
}
}
else {
return array('--Select--');
}
}

How to make an ajax call on submit button of a webform?

These are my code, I have done the form alter in template.php.
Please advice me ! I created a webform by webform module.My aim is to submit the form without the page load.
My code follows here:
function business_form_alter(&$form, &$form_state, $form_id) {
if($form_id == 'webform_client_form_7') {
$form['actions']['submit']['#ajax'] = array(
'callback' => 'business_webform_js_submit',
'wrapper' => 'webform-client-form-7',
'method' => 'replace',
'effect' => 'fade',
);
}
function business_webform_ajax_submit($form, $form_state) {
$sid = $form_state['values']['details']['sid'];
if ($sid) {
$node = node_load($form_state['values']['details']['nid']);
$confirmation = array(
'#type' => 'markup',
'#markup' => check_markup($node->webform['confirmation'], $node- >webform['confirmation_format'], '', TRUE),
);
return $confirmation;
}
else {
return $form;
}
}
There is a small mistake in your code. The name of the #ajax['callback'] function is different than the actual function.
You might want to rename the function:
function business_webform_js_submit($form, $form_state)
And it should work.

Returning user input after AJAX call in Drupal 7 hook_form_FORM_ID_alter

I am using the hook_form_FORM_ID_alter in Drupal 7 to create a custom form so that the user can enter and edit data which is then attached to a custom node type.
For a new node the default number of input boxes in a group is 10, this can then be added to in groups of 5. When the node is reloaded for editing the saved data is used to create the form with whatever number of inputs have been saved previously and also the ability to add more fields in the same manner as required.
I have managed to get both the initial version of the form and the editing version to work using the following code however when the 'add five' button is pressed and the AJAX called (in both cases), any values which have been entered without saving are removed.
<?php
/**
* Implements hook_form_FORM_ID_alter().
*/
function entries_form_form_entries_node_form_alter(&$form, &$form_state, $form_id) {
$node = $form['#node'];
// fieldset
$form["section"] = array(
'#type' => 'fieldset',
'#title'=> 'Section 1',
);
$form["section"]["termwrapper"] = array(
"#type" => 'container',
"#attributes" => array(
"id" => 'groupWrapper',
),
);
$form["section"]["termwrapper"]["terms"]["#tree"] = TRUE;
if(!isset($form_state['fired'])){
$form_state['terms'] = $node->entries_form['term'];
}
foreach ($form_state['terms'] as $key => $values) {
$form["section"]["termwrapper"]["terms"][$key] = array(
'#type' => 'textfield',
'#size' => 20,
'#attributes' => array(
'class' => array('left'),
),
'#value' => $values,
);
}
$form['section']['addFive_button'] = array(
'#type' => 'submit',
'#value' => t('+5'),
'#submit' => array('entries_form_add_five_submit'),
'#ajax' => array(
'callback' => 'entries_form_commands_add_callback',
'wrapper' => 'groupWrapper',
),
'#prefix' => "<div class='clear'></div>",
);
dpm($form_state);
}
function entries_form_commands_add_callback($form, $form_state) {
return $form["section"]["termwrapper"];
}
function entries_form_add_five_submit($form, &$form_state){
$form_state['rebuild'] = TRUE;
$form_state['fired'] = 1;
$values = $form_state['values'];
$form_state['terms'] = $values['terms'];
$numterms = count($values['terms']);
$addfivearray = array_fill($numterms,5,'');
$form_state['terms'] = array_merge($values['terms'],$addfivearray);
}
/**
* Implements hook_node_submit().
*/
function entries_form_node_submit($node, $form, &$form_state) {
$values = $form_state['values'];
$node->entries_form['term'] = $values['term'];
}
/**
* Implements hook_node_prepare().
*/
function entries_form_node_prepare($node) {
if (empty($node->entries_form)){
$node->entries_form['term'] = array_fill(0, 10, '');
}
}
/**
* Implements hook_node_load().
*/
function entries_form_node_load($nodes, $types) {
if($types[0] == 'entries'){
$result = db_query('SELECT * FROM {mytable} WHERE nid IN(:nids)', array(':nids' => array_keys($nodes)))->fetchAllAssoc('nid');
foreach ($nodes as &$node) {
$node->entries_form['term'] = json_decode($result[$node->nid]->term);
}
}
}
/**
* Implements hook_node_insert().
*/
function entries_form_node_insert($node) {
if (isset($node->entries_form)) {
db_insert('mytable')
->fields(array(
'nid' => $node->nid,
'term' => json_encode($node->entries_form['term']),
))
->execute();
}
}
How can I keep the values which have been typed in and retain the ajax functionality?
Any help or pointers much appreciated. This is my first dip into Drupal so I'm sure there is something which hopefully is quite obvious that I'm missing.
Ok, I think I finally have the answer.
Within the foreach that builds the form input boxes I had set '#value' => $values, when it seems that '#default_value' => $values, should have been set instead.
The updated section of the code that is now working for me is as follows
foreach ($form_state['terms'] as $key => $values) {
$form["section"]["termwrapper"]["terms"][$key] = array(
'#type' => 'textfield',
'#size' => 20,
'#attributes' => array(
'class' => array('left'),
),
'#default_value' => $values,
);
}
Seems to be as simple as that. Hope this helps someone else.

Passing argument in ajax_callback: Return array is not getting populated

I am trying to populate the second select box with the all date fields of the first selected content type name in select box. I am using ajax_callback to fetch the selected value by $form_state. I am getting error, which I can't determine why. Can anybody help?
This is the my custom module code.
function mymodule_settings($form, &$form_state){
$content_type_options = array();
$result = db_query("SELECT * FROM {node_type}");
foreach($result as $record){
$content_type_options[$record->type] = $record->name;
}
$form = array();
$form['content_type'] = array(
'#title' => t('Content Types'),
'#description' => t('Select a content type.'),
'#type' => 'select',
'#options' => $content_type_options,
'#ajax' => array(
'event' => 'change',
'wrapper' => 'reg-start-date',
'callback' => 'mymodule_datefields_ajax_callback',
'method' => 'replace',
),
);
$form['checkboxes_fieldset'] = array(
'#title' => t("Start Date"),
'#prefix' => '<div id="reg-start-date">',
'#suffix' => '</div>',
'#type' => 'select',
'#description' => t('Select the date field'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
);
return $form;
}
function mymodule_datefields_ajax_callback($form, $form_state) {
$fieldname = $form_state['triggering_element']['#value'];
$field_query = db_query("SELECT fc.field_name FROM {field_config} fc, {field_config_instance} fci
WHERE fc.field_name = fci.field_name
AND fc.type = 'datetime'
AND fci.bundle = '".$fieldname."'");
$datefield_options = array();
foreach($field_query as $record){
$datefield_options = $record;
}
return $datefield_options;
//dpm($form_state, 'AJAX $form_state');
}
Here is the error, which I am getting in popup -
An AJAX HTTP error occurred. HTTP Result Code: 200 Debugging
information follows. Path: /module_dev/?q=system/ajax StatusText: OK
ResponseText: Fatal error: Cannot use object of type stdClass as array
in /var/www/module_dev/includes/common.inc on line 5786
I went through the /var/www/module_dev/includes/common.inc on line 5786, and this is the code I find there.
function drupal_render(&$elements) {
// Early-return nothing if user does not have access.
if (empty($elements) || (isset($elements['#access']) && !$elements['#access'])) {
return;
}
// Do not print elements twice.
if (!empty($elements['#printed'])) {
return;
}
// Try to fetch the element's markup from cache and return.
if (isset($elements['#cache'])) {
$cached_output = drupal_render_cache_get($elements);
if ($cached_output !== FALSE) {
return $cached_output;
}
}
// If #markup is set, ensure #type is set. This allows to specify just #markup
// on an element without setting #type.
if (isset($elements['#markup']) && !isset($elements['#type'])) {
$elements['#type'] = 'markup';
}
// If the default values for this element have not been loaded yet, populate
// them.
if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) {
$elements += element_info($elements['#type']);
}
// Make any final changes to the element before it is rendered. This means
// that the $element or the children can be altered or corrected before the
// element is rendered into the final text.
if (isset($elements['#pre_render'])) {
foreach ($elements['#pre_render'] as $function) {
if (function_exists($function)) {
$elements = $function($elements);
}
}
}
// Allow #pre_render to abort rendering.
if (!empty($elements['#printed'])) {
return;
}
// Get the children of the element, sorted by weight.
$children = element_children($elements, TRUE);
// Initialize this element's #children, unless a #pre_render callback already
// preset #children.
if (!isset($elements['#children'])) {
$elements['#children'] = '';
}
// Call the element's #theme function if it is set. Then any children of the
// element have to be rendered there.
if (isset($elements['#theme'])) {
$elements['#children'] = theme($elements['#theme'], $elements);
}
// If #theme was not set and the element has children, render them now.
// This is the same process as drupal_render_children() but is inlined
// for speed.
if ($elements['#children'] == '') {
foreach ($children as $key) {
$elements['#children'] .= drupal_render($elements[$key]);
}
}
// Let the theme functions in #theme_wrappers add markup around the rendered
// children.
if (isset($elements['#theme_wrappers'])) {
foreach ($elements['#theme_wrappers'] as $theme_wrapper) {
$elements['#children'] = theme($theme_wrapper, $elements);
}
}
// Filter the outputted content and make any last changes before the
// content is sent to the browser. The changes are made on $content
// which allows the output'ed text to be filtered.
if (isset($elements['#post_render'])) {
foreach ($elements['#post_render'] as $function) {
if (function_exists($function)) {
$elements['#children'] = $function($elements['#children'], $elements);
}
}
}
// Add any JavaScript state information associated with the element.
if (!empty($elements['#states'])) {
drupal_process_states($elements);
}
// Add additional libraries, CSS, JavaScript an other custom
// attached data associated with this element.
if (!empty($elements['#attached'])) {
drupal_process_attached($elements);
}
$prefix = isset($elements['#prefix']) ? $elements['#prefix'] : '';
$suffix = isset($elements['#suffix']) ? $elements['#suffix'] : '';
$output = $prefix . $elements['#children'] . $suffix;
// Cache the processed element if #cache is set.
if (isset($elements['#cache'])) {
drupal_render_cache_set($output, $elements);
}
$elements['#printed'] = TRUE;
return $output;
}
What your AJAX callback should return is the rendering array for a form element that replaces the content of the <div> tag whose CSS ID is set as #ajax[wrapper]. What your AJAX callback is returning is an array of objects, which is not what the render API is expecting. (The render API uses arrays.) That is why you get an error saying that a object cannot be used as array.
See ajax_example_simplest() as example of form builder using AJAX; in particular, see its AJAX callback, ajax_example_simplest_callback().
In short, the code of mymodule_datefields_ajax_callback() should be the following one.
function mymodule_datefields_ajax_callback($form, $form_state) {
return $form['checkboxes_fieldset'];
}
The form builder should use the following code.
function mymodule_settings($form, &$form_state){
$content_type_options = array();
$result = db_query("SELECT * FROM {node_type}");
foreach ($result as $record) {
$content_type_options[$record->type] = $record->name;
}
// $form is already passed as argument; you don't need to initialize it to an empty array.
// $form = array();
$form['content_type'] = array(
'#title' => t('Content Types'),
'#description' => t('Select a content type.'),
'#type' => 'select',
'#options' => $content_type_options,
'#ajax' => array(
'event' => 'change',
'wrapper' => 'reg-start-date',
'callback' => 'mymodule_datefields_ajax_callback',
'method' => 'replace',
),
);
// An AJAX request call to the form builder function has been done.
if (!empty($form_state['values']['content_type'])) {
// Use $form_state['values']['content_type'] to get the option list.
// Set the value of $date_options with that list.
$field_query = db_query("query to execute");
$date_options = array();
foreach ($field_query as $record) {
$date_options[$record->field_name] = $record->field_name;
}
}
else {
$date_options = array();
}
$form['checkboxes_fieldset'] = array(
'#title' => t("Start Date"),
'#prefix' => '<div id="reg-start-date">',
'#suffix' => '</div>',
'#type' => 'select',
'#options' => $date_options,
'#description' => t('Select the date'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
);
return $form;
}
$date_options is an array with the format value => string_to_show; it's is the same format you used for $content_type_options.

Resources