Is there way to bind an ajax callback when the language is changed, i want to update a nodereference-dropdown when a language is changed (to show the values only in that language).
following code is not working (form_alter) although other callbacks are working.
Can someone help me how can i achieve that?
$form['language']['#ajax'] = array(
'callback' => 'mymodule_something_language_callback',
'wrapper' => 'my-module-replace',
'#weight' => 2
);
Thanks.
FROM COMMENTS
heres the var_dump of $form['language'];
array
'#type' => string 'select' (length=6)
'#title' => string 'Language' (length=8)
'#default_value' => string 'und' (length=3)
'#options' =>
array
'und' => string 'Language neutral' (length=16)
'en' => string 'English' (length=7)
'ar' => string 'Arabic' (length=6)
The problem is that the Locale module alters this form element after hook_form_alter() is called (see this post).
Here's how I solved this problem:
First, change the order in which Drupal implements its hooks, putting 'form_alter' as the last one:
<?php
/**
* Implementation of hook_module_implements_alter()
*/
function chronos_module_implements_alter(&$implementations, $hook) {
if ($hook == 'form_alter') {
// Move mymodule_form_alter() to the end of the list. module_implements()
// iterates through $implementations with a foreach loop which PHP iterates
// in the order that the items were added, so to move an item to the end of
// the array, we remove it and then add it.
$group = $implementations['chronos'];
unset($implementations['chronos']);
$implementations['chronos'] = $group;
}
}
Next, add a form element and the '#ajax' element that you want on $form['language']:
<?php
/**
* Implements hook_form_alter().
*/
function mymodule_form_alter(&$form, &$form_state, $form_id) {
if ($form_id == 'page_node_form') {
// alter the form
$form['container'] = array(
'#prefix' => '<div id="ajax-language">',
'#suffix' => '</div>',
);
$form['language']['#ajax'] = array(
'callback' => 'mymodule_save_language_callback',
'wrapper' => 'ajax-language'
);
return $form;
}
}
Lastly, add your callback:
<?php
/**
* Returns changed part of the form.
*
* #return renderable array
*
* #see ajax_example_form_node_form_alter()
*/
function chronos_save_language_callback($form, $form_state) {
# set session variables or perform other actions here, if applicable
return $form['container'];
}
Related
I have a site on Drupal 8.6 and Bootstrap 3.3.7
I created a custom module for the customer to accept the terms and conditions of the store when placing an order.
This displays a link to a checkbox before payment to display the terms and conditions in a modal window.
When I place an order here are the warnings in the logs (sorry my code is too long to be published here):
https://pastebin.com/1p5m1Ved
https://pastebin.com/XYbqDJje
https://pastebin.com/P93bStKh
Here is the file that created the problem :
<?php
namespace Drupal\commerce_marketplace_terms_and_conditions\Plugin\Commerce\CheckoutPane;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Form\FormStateInterface;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneBase;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneInterface;
use Drupal\Core\Link;
use Drupal\Core\Url;
/**
* Provides the completion message pane.
*
* #CommerceCheckoutPane(
* id = "marketplace_terms_and_conditions",
* label = #Translation("Marketplace Terms and Conditions"),
* default_step = "review",
* )
*/
class MarketplaceTermsAndConditions extends CheckoutPaneBase implements CheckoutPaneInterface {
/**
* {#inheritdoc}
*/
public function buildPaneForm(array $pane_form, FormStateInterface $form_state, array &$complete_form) {
$store_name = $this->order->getStore()->getName();
$store_id = $this->order->getStoreId();
$pane_form['#attached']['library'][] = 'core/drupal.dialog.ajax';
$attributes = [
'attributes' => [
'class' => 'use-ajax',
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode([
'width' => auto
]),
],
];
$link = Link::fromTextAndUrl(
$this->t('terms and conditions of the store "#store_name"', ['#store_name' => $store_name]),
Url::fromUri("internal:/store/$store_id/cgv", $attributes)
)->toString();
$pane_form['marketplace_terms_and_conditions'] = [
'#type' => 'checkbox',
'#default_value' => FALSE,
'#title' => $this->t('I have read and accept #terms.', ['#terms' => $link]),
'#required' => TRUE,
'#weight' => $this->getWeight(),
];
return $pane_form;
}
}
What's wrong with my custom module and how to fix the problem ? Thank you
Change this to
'width' => auto
this
'width' => 'auto'
It is assuming it to be a constant, as you can see from the errors, it has to be either a variable or a string.
I created a module, but the link is not correct.
My site now shows :
/store/2?0=/cgv
The correct link should be :
/store/2/cgv
Why doesn't it work ? where is the error ?
What should I change in the code below, to get the link ?
<?php
namespace Drupal\commerce_agree_cgv\Plugin\Commerce\CheckoutPane;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Form\FormStateInterface;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneBase;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneInterface;
use Drupal\Core\Link;
use Drupal\Core\Url;
/**
* Provides the completion message pane.
*
* #CommerceCheckoutPane(
* id = "agree_cgv",
* label = #Translation("Agree CGV"),
* default_step = "review",
* )
*/
class AgreeCGV extends CheckoutPaneBase implements CheckoutPaneInterface {
/**
* {#inheritdoc}
*/
public function buildPaneForm(array $pane_form, FormStateInterface $form_state, array &$complete_form) {
$store_id = $this->order->getStoreId();
$pane_form['#attached']['library'][] = 'core/drupal.dialog.ajax';
$attributes = [
'attributes' => [
'class' => 'use-ajax',
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode([
'width' => 800,
]),
],
];
$link = Link::createFromRoute(
$this->t('the general terms and conditions of business'),
'entity.commerce_store.canonical',
['commerce_store' => $store_id, '/cgv'],
$attributes
)->toString();
$pane_form['cgv'] = [
'#type' => 'checkbox',
'#default_value' => FALSE,
'#title' => $this->t('I have read and accept #cgv.', ['#cgv' => $link]),
'#required' => TRUE,
'#weight' => $this->getWeight(),
];
return $pane_form;
}
}
Because $link is not built correctly :
$link = Link::createFromRoute(
$this->t('the general terms and conditions of business'),
'entity.commerce_store.canonical',
['commerce_store' => $store_id, '/cgv'], # -> this is wrong
$attributes
)->toString();
$route_parameters: (optional) An associative array of parameter names
and values.
You did not specify any name for the 2nd route parameters, so the corresponding array key fallback to the first available numeric indice, that is 0, meaning [ '/cgv' ] becomes [ 0 => '/cgv' ] and you don't get the link you expected.
I think (if I understood your issue correctly) what you need is to define in the first place that specific route handling cgv's for a given commerce_store, that is with the /cgv appended :
$route_collection = new RouteCollection();
$route = (new Route('/commerce_store/{commerce_store}/cgv'))
->addDefaults([
'_controller' => $_controller,
'_title_callback' => $_title_callback,
])
->setRequirement('commerce_store', '\d+')
->setRequirement('_entity_access', 'commerce_store.view');
$route_collection->add('entity.commerce_store.canonical.cgv', $route);
... so that you can build links based on that specific route :
$link = Link::createFromRoute(
$this->t('the general terms and conditions of business'),
'entity.commerce_store.canonical.cgv',
['commerce_store' => $store_id],
$attributes
)->toString();
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 want to create a ctools access check for my panel selection rule.
What I wanna do, is to check a field value in a content type. The field is named field_layout with the options 3,2,1.
I created the access check and settings and the rule is showing up in the selection rule options. I can add it without any problems and set it up as I want to.
The only problem I have is, that the rule wont take effect ... :-/
Here is the code I use:
<?php
/**
* Plugins are described by creating a $plugin array which will
* be used by the system that includes the file.
*/
$plugin = array(
'title' => t('Node: field layout'),
'description' => t('Controls access by field_layout'),
'callback' => 'he_layout_field_layout_ctools_access_check',
'settings form' => 'he_layout_field_layout_ctools_settings',
);
/**
* Custom callback defined by 'callback' in the $plugin array.
*
* Check for access.
*/
function he_layout_field_layout_ctools_access_check($conf, $context) {
// If for some unknown reason that $context isn't set, we just want to be sure.
if (empty($context) || empty($context->data) || empty($context->data->field_layout)) {
return FALSE;
}
// If the layout set in the panels visibility rule settings is different from the field_layout
// access to the pane is denied.
$layout = $context->data->field_layout;
if ($layout !== $conf['field_layout'][$context->data->field_layout[field_language('node', $context->data, 'field_layout')][0]['value']]) {
return FALSE;
}
return TRUE;
}
/**
* Settings form for the 'field_layout' access plugin.
*/
function he_layout_field_layout_ctools_settings($form, &$form_state, $conf) {
$form['settings']['field_layout'] = array(
'#type' => 'radios',
'#title' => t('Layout'),
'#options' => array(
0 => '3',
1 => '2',
2 => '1',
),
'#default_value' => $conf['field_layout'],
);
return $form;
}
The code is based on this tutorial:
http://ramlev.dk/blog/2012/03/30/create-a-ctools-access-plugin/
Someone got an idea why this wont work?
#Basti's comment is correct, just one more step up:
$plugin = array(
'title' => t('Node: field layout'),
'description' => t('Controls access by field_layout'),
'callback' => 'he_layout_field_layout_ctools_access_check',
'settings form' => 'he_layout_field_layout_ctools_settings',
// 'required context' => new ctools_context_required(t('Node'), 'node'),
);
It is ok if don't need the context for your plugin. But the $context argument in the access check receives exactly the context you mentioned, which means you always get null when you specify no required context.
This way, you alway have false at the first check from this: if (empty($context)
I am trying to include a Drupal form element in a table I am generating with theme_table/theme('table',..). Specifically, I am trying to include a submit button with an AHAH attached.
Currently, I am just including as one cell in each row a call to drupal_render to render my dynamically generated AHAH element. The button renders fine, but without the AHAH attached.
So, my question is: is there a way to attach an AHAH to something that is just drupal_rendered?
If not, how else can I attach an AJAX/AHAH call to an element in a theme_table-generated table? I need to allow users to perform certain actions on rows of data in the table but need the page to not refresh.
TIA,
Benjy
I believe AHAH only works with drupal_get_form(). You'd have to write the AJAX yourself ( handbook page).
Maybe you could save the return value from drupal_get_form() in a variable and pass it to the theme function?
I think this is sort of a bug but i created a very easy generic workaround :) Solution by Januz did not work for me since the table was used inside a form already and also it's only applicable to very specific use cases and not a generic solution.
You can have a look at the following code and the function i wrote as an example
Notice : You may probably need to modify the code to suit your needs
/**
* #param $form
* #param $form_state
* #param $form_id
*/
function mymodule_form_alter(&$form, &$form_state, $form_id) {
$test_button = array(
'test_button' => array(
'#type' => 'button',
'#value' => t('Add'),
'#name' => 'test_button',
'#id' => 'test_button',
'#weight' => 2,
'#ajax' => array(
'callback' => '_mymodule_ajax_callback',
)
)
);
$form['mytable'] = array(
'#weight' => 1,
'#theme' => 'table',
'#header' => array(
array("data" => "Item"),
),
'#rows' => array(
'data' => array(
array(
'data' => $test_button
),
)
),
);
// This is a work around, ajax elements do not work when used in drupal tables
mymodule_table_ajax_workaround($test_button, $form, $form_state);
}
/**
* Ajax enabled form elements do not work then used inside a drupal table and rendered via theme_table
* This is a workaround to address the issue
* #param $elements
* #param $form
* #param $form_state
*/
function mymodule_table_ajax_workaround($elements, &$form, &$form_state) {
foreach ($elements as $element_name => $element_info) {
drupal_add_js(array('ajax' => array(
$element_name => array(
'callback' => $element_info['#ajax']['callback'],
'event' => 'mousedown',
'keypress' => true,
'prevent' => 'click',
'url' => '/system/ajax',
'submit' => array(
'_triggering_element_name' => $element_name . '_fake',
'_triggering_element_value' => $element_info['#value'],
)
),
)), 'setting');
$form['form_ajax_workaround'][$element_name] = array(
'#name' => $element_name . '_fake',
'#input' => true,
'#value' => $element_info['#value'],
'#ajax' => array(
'callback' => $element_info['#ajax']['callback']
)
);
}
}
You can do this via theme function and table generation. AHAH will work. The only problem is when you have multiple "tables", things start getting hairy.
Short explanation (correct me if I am wrong!): AJAX enabled form elements appear invisible to Drupal's Form API when they remain inside a "#theme" => "table" render array. This is probably due to render() using element_children() which does not look into a table's #header or #rows elements. A way to workaround this is to add dummy elements to the form which are visible to the Form API and bind AJAX events to the original elements to trigger the dummy elements.
I forked Sina Salek's great workaround for Drupal 7, main differences are:
Different form field keys, names and ID's are allowed,
The callback from the JS AJAX settings object is removed since it does not seem to exist in JS,
Added the base path variable to the AJAX URL for sites that are not installed in the web root,
Use "#type" => "value" for the dummy buttons instead of the internal options "#input" => TRUE,
The dummy element is available through the exact same name as the original element, without a "_fake" postfix, since the original elements are invisible to the Form API and the dummy elements do not appear in the DOM,
The full #ajax array is copied to the dummy element instead of just the callback element so other settings (ie. for the throbber) remain.
Allows the user to set custom JS AJAX options per button.
Example of implementing the workaround per AJAX enabled element when building a form:
/**
* My form.
*/
function MY_MODULE_my_form($form, &$form_state) {
// Define button. NB: An ID and name are required!
$my_button = array(
'#type' => 'button',
'#id' => 'test_button',
'#name' => 'test_button',
'#value' => t('Button label'),
'#ajax' => array('callback' => '_MY_MODULE_ajax_callback'),
);
// Call table AJAX workaround function on the button.
MY_MODULE_table_ajax_workaround($my_button, $form);
// Define table.
$form['my_table'] = array(
'#theme' => 'table',
'#header' => array(
array('data' => t('Column header')),
),
'#rows' => array(
'data' => array(
array('data' => $my_button),
)
),
);
return $form;
}
/**
* Workaround for AJAX enabled form elements that remain inside tables.
*
* Problem: AJAX enabled form elements appear invisible to Drupal's Form API
* when they remain inside a #theme => table render array. This is probably due
* to render() using element_children() which does not look into a table's
* #header or #rows elements.
*
* Workaround: Add dummy elements to the form which are visible to the Form API
* and bind AJAX events to the original elements to trigger the dummy elements.
*
* Based on:
* #link http://stackoverflow.com/questions/1981781
*
* Shared at:
* #link http://stackoverflow.com/a/31098784/328272
*
* Another workaround is the render elements using custom theme functions, but
* this seems slightly more complicated and rendered elements are no longer
* alterable.
* #link https://www.drupal.org/node/2101557#comment-7920151
*/
function MY_MODULE_table_ajax_workaround($element, &$form, $js_ajax_options = array()) {
// Add dummy element.
$form['table_ajax_workaround'][$element['#id']] = array(
'#type' => 'value',
'#name' => $element['#name'],
'#value' => $element['#value'],
'#ajax' => $element['#ajax'],
);
// Bind AJAX event to the original element, default properties are used. See
// Drupal.ajax in misc/ajax.js.
$js_setting['ajax'][$element['#id']] = drupal_array_merge_deep(array(
'event' => 'mousedown',
'keypress' => true, // Use lowercase booleans to support IE.
'prevent' => 'click',
'url' => base_path() . 'system/ajax',
'submit' => array(
'_triggering_element_name' => $element['#name'],
'_triggering_element_value' => $element['#value'],
),
), $js_ajax_options);
// Add JS setting.
drupal_add_js($js_setting, 'setting');
}