added example for developpers module

This commit is contained in:
Bachir Soussi Chiadmi
2018-01-06 11:28:03 +01:00
parent 5017966908
commit 5ec2f4311c
396 changed files with 26299 additions and 0 deletions
@@ -0,0 +1,14 @@
name: 'AJAX Example'
type: module
description: 'An example module showing how to use Drupal AJAX forms.'
package: 'Example modules'
# core: 8.x
dependencies:
- drupal:node
- examples:examples
# Information added by Drupal.org packaging script on 2017-12-17
version: '8.x-1.x-dev'
core: '8.x'
project: 'examples'
datestamp: 1513537386
@@ -0,0 +1,7 @@
ajax_example.library:
version: 1.x
css:
base:
css/ajax-example-base.css: {}
js:
js/ajax-example.js: {}
@@ -0,0 +1,72 @@
ajax_example.description:
title: 'AJAX Example'
route_name: 'ajax_example.description'
expanded: TRUE
ajax_example.simplest:
title: 'Simplest AJAX example'
route_name: 'ajax_example.simplest'
parent: ajax_example.description
weight: 0
ajax_example.submit-driven:
title: 'Submit-driven AJAX'
route_name: 'ajax_example.submit_driven_ajax'
parent: ajax_example.description
weight: 1
ajax_example.render-link:
title: 'AJAX link in a render array'
route_name: 'ajax_example.ajax_link_render'
parent: ajax_example.description
weight: 2
ajax_example.wizard-example:
title: 'Wizard example'
route_name: 'ajax_example.wizard'
parent: ajax_example.description
weight: 2
ajax_example.wizard-examplenojs:
title: 'Wizard example w/JS turned off'
route_name: 'ajax_example.wizardnojs'
parent: ajax_example.description
weight: 3
ajax_example.autocomplete-user:
title: 'Autocomplete user with entity_autocomplete'
route_name: 'ajax_example.autocomplete_user'
parent: ajax_example.description
weight: 4
ajax_example.autotextfields:
title: 'Generate textfields'
route_name: 'ajax_example.autotextfields'
parent: ajax_example.description
weight: 5
ajax_example.dependent-dropdown:
title: 'Dependent dropdown'
route_name: 'ajax_example.dependent_dropdown'
parent: ajax_example.description
weight: 6
ajax_example.dependent-dropdown-nojs:
title: 'Dependent dropdown w/ no JS'
route_name: 'ajax_example.dependent_dropdown'
route_parameters:
nojs: nojs
parent: ajax_example.description
weight: 6
ajax_example.dynamic-form-sections:
title: 'Dynamic form sections'
route_name: 'ajax_example.dynamic_form_sections'
parent: ajax_example.description
weight: 10
ajax_example.dynamic-form-sections-nojs:
title: 'Dynamic form sections w/ no JS'
route_name: 'ajax_example.dynamic_form_sections'
route_parameters:
nojs: nojs
parent: ajax_example.description
weight: 10
@@ -0,0 +1,23 @@
<?php
/**
* @file
* AJAX Examples module file with basic examples.
*/
/**
* @defgroup ajax_example Example: AJAX
* @ingroup examples
* @{
* These examples show basic AJAX concepts.
*
* General documentation is available at
* @link ajax AJAX Framework documentation @endlink and at the
* @link http://drupal.org/node/752056 AJAX Forms handbook page @endlink.
*
* The several examples here demonstrate basic AJAX usage.
*/
/**
* @} End of "defgroup ajax_example".
*/
@@ -0,0 +1,96 @@
ajax_example.description:
path: 'examples/ajax-example'
defaults:
_controller: '\Drupal\ajax_example\Controller\AjaxExampleController::description'
_title: 'AJAX Example'
requirements:
_permission: 'access content'
ajax_example.simplest:
path: 'examples/ajax-example/simplest'
defaults:
_form: '\Drupal\ajax_example\Form\Simplest'
_title: 'Simplest AJAX example'
requirements:
_permission: 'access content'
ajax_example.autotextfields:
path: 'examples/ajax-example/autotextfields'
defaults:
_form: '\Drupal\ajax_example\Form\Autotextfields'
_title: 'Generate textfields'
requirements:
_permission: 'access content'
ajax_example.submit_driven_ajax:
path: 'examples/ajax-example/submit-driven-ajax'
defaults:
_form: '\Drupal\ajax_example\Form\SubmitDriven'
_title: 'Submit-driven AJAX'
requirements:
_permission: 'access content'
ajax_example.dependent_dropdown:
path: 'examples/ajax-example/dependent-dropdown/{nojs}'
defaults:
_form: '\Drupal\ajax_example\Form\DependentDropdown'
_title: 'Dependent dropdown'
nojs: ajax
requirements:
_permission: 'access content'
ajax_example.dynamic_form_sections:
path: 'examples/ajax-example/dynamic-form-sections/{nojs}'
defaults:
_form: '\Drupal\ajax_example\Form\DynamicFormSections'
_title: 'Dynamic form sections'
nojs: 'ajax'
requirements:
_permission: 'access content'
ajax_example.wizard:
path: 'examples/ajax-example/wizard'
defaults:
_form: '\Drupal\ajax_example\Form\Wizard'
_title: 'Wizard with graceful degradation'
requirements:
_permission: 'access content'
ajax_example.wizardnojs:
path: 'examples/ajax-example/wizard-nojs/{no_js_use}'
defaults:
_form: '\Drupal\ajax_example\Form\Wizard'
_title: 'Wizard with graceful degradation, w/JS turned off'
no_js_use: TRUE
requirements:
_permission: 'access content'
ajax_example.ajax_link_render:
path: 'examples/ajax-example/ajax-link-renderable'
defaults:
_controller: '\Drupal\ajax_example\Controller\AjaxExampleController::renderLinkRenderableArray'
_title: 'AJAX link from a render array'
requirements:
_permission: 'access content'
# This route is for an AJAX callback. It is used by the AJAX system on
# ajax_example.ajax_link_render. It has a {nojs} parameter, which gives us
# a way to know whether the request is an AJAX request or is from some other
# source.
ajax_example.ajax_link_callback:
path: 'examples/ajax-example/ajax-link-callback/{nojs}'
defaults:
_controller: '\Drupal\ajax_example\Controller\AjaxExampleController::ajaxLinkCallback'
# We provide a default value for {nojs}, so that it can be an optional
# parameter.
nojs: 'nojs'
requirements:
_permission: 'access content'
ajax_example.autocomplete_user:
path: 'examples/ajax_example/user_autocomplete'
defaults:
_form: '\Drupal\ajax_example\Form\EntityAutocomplete'
_title: 'Autocomplete users with entity_autocomplete.'
requirements:
_permission: 'access content'
@@ -0,0 +1,9 @@
/*
* @file
* CSS for ajax_example.
*/
/* Put some buttons inline, for nicer formatting. */
.ajax-example-inline {
display: inline-block;
}
@@ -0,0 +1,19 @@
/**
* @file
* JavaScript for ajax_example.
*/
(function ($) {
// Re-enable form elements that are disabled for non-ajax situations.
Drupal.behaviors.enableFormItemsForAjaxForms = {
attach: function () {
// If ajax is enabled, we want to hide items that are marked as hidden in
// our example.
if (Drupal.ajax) {
$('.ajax-example-hide').hide();
}
}
};
})(jQuery);
@@ -0,0 +1,112 @@
<?php
namespace Drupal\ajax_example\Controller;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\AppendCommand;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Url;
use Drupal\examples\Utility\DescriptionTemplateTrait;
use Symfony\Component\HttpFoundation\Response;
/**
* Controller routines for AJAX example routes.
*/
class AjaxExampleController extends ControllerBase {
use DescriptionTemplateTrait;
/**
* {@inheritdoc}
*/
protected function getModuleName() {
return 'ajax_example';
}
/**
* Demonstrates a clickable AJAX-enabled link using the 'use-ajax' class.
*
* Because of the 'use-ajax' class applied here, the link submission is done
* without a page refresh.
*
* When using the AJAX framework outside the context of a form or a renderable
* array of type 'link', you have to include ajax.js explicitly.
*
* @return array
* Form API array.
*
* @ingroup ajax_example
*/
public function renderLinkRenderableArray() {
$build['my_div'] = [
'#markup' => $this->t('
The link below has been rendered as an element with the #ajax property, so if
javascript is enabled, ajax.js will try to submit it via an AJAX call instead
of a normal page load. The URL also contains the "/nojs/" magic string, which
is stripped if javascript is enabled, allowing the server code to tell by the
URL whether JS was enabled or not, letting it do different things based on that.'),
];
// We'll add a nice border element for our demo.
$build['ajax_link'] = [
'#type' => 'details',
'#title' => $this->t('This is the AJAX link'),
'#open' => TRUE,
];
// We build the AJAX link.
$build['ajax_link']['link'] = [
'#type' => 'link',
'#title' => $this->t('Click me'),
// We have to ensure that Drupal's Ajax system is loaded.
'#attached' => ['library' => ['core/drupal.ajax']],
// We add the 'use-ajax' class so that Drupal's AJAX system can spring
// into action.
'#attributes' => ['class' => ['use-ajax']],
// The URL for this link element is the callback. In our case, it's route
// ajax_example.ajax_link_callback, which maps to ajaxLinkCallback()
// below. The route has a /{nojs} section, which is how the callback can
// know whether the request was made by AJAX or some other means where
// JavaScript won't be able to handle the result. If the {nojs} part of
// the path is replaced with 'ajax', then the request was made by AJAX.
'#url' => Url::fromRoute('ajax_example.ajax_link_callback', ['nojs' => 'ajax']),
];
// We provide a DIV that AJAX can append some text into.
$build['ajax_link']['destination'] = [
'#type' => 'container',
'#attributes' => ['id' => ['ajax-example-destination-div']],
];
return $build;
}
/**
* Callback for link example.
*
* Takes different logic paths based on whether Javascript was enabled.
* If $type == 'ajax', it tells this function that ajax.js has rewritten
* the URL and thus we are doing an AJAX and can return an array of commands.
*
* @param string $nojs
* Either 'ajax' or 'nojs. Type is simply the normal URL argument to this
* URL.
*
* @return string|array
* If $type == 'ajax', returns an array of AJAX Commands.
* Otherwise, just returns the content, which will end up being a page.
*/
public function ajaxLinkCallback($nojs = 'ajax') {
// Determine whether the request is coming from AJAX or not.
if ($nojs == 'ajax') {
$output = $this->t("This is some content delivered via AJAX");
$response = new AjaxResponse();
$response->addCommand(new AppendCommand('#ajax-example-destination-div', $output));
// See ajax_example_advanced.inc for more details on the available
// commands and how to use them.
// $page = array('#type' => 'ajax', '#commands' => $commands);
// ajax_deliver($response);
return $response;
}
$response = new Response($this->t("This is some content delivered via a page load."));
return $response;
}
}
@@ -0,0 +1,124 @@
<?php
namespace Drupal\ajax_example\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Show textfields based on AJAX-enabled checkbox clicks.
*
* @ingroup ajax_example
*/
class Autotextfields extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'ajax_example_autotextfields';
}
/**
* {@inheritdoc}
*
* This form has two checkboxes which the user can check in order to then
* reveal the first and/or last name text fields.
*
* We could perform this behavior with #states. We might not want to if, for
* instance, we wanted to require a name, but let the user choose whether
* to enter first or last or both.
*
* For all the requests this class gets, the buildForm() method will always be
* called. If an AJAX request comes in, the form state will be set to the
* state the user changed that caused the AJAX request. So if the user enabled
* one of our checkboxes, it will be checked in $form_state.
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['description'] = [
'#type' => 'item',
'#markup' => $this->t('This form demonstrates changing the status of form elements through AJAX requests.'),
];
$form['ask_first_name'] = [
'#type' => 'checkbox',
'#title' => $this->t('Ask me my first name'),
'#ajax' => [
'callback' => '::textfieldsCallback',
'wrapper' => 'textfields-container',
'effect' => 'fade',
],
];
$form['ask_last_name'] = [
'#type' => 'checkbox',
'#title' => $this->t('Ask me my last name'),
'#ajax' => [
'callback' => '::textfieldsCallback',
'wrapper' => 'textfields-container',
'effect' => 'fade',
],
];
// Wrap textfields in a container. This container will be replaced through
// AJAX.
$form['textfields_container'] = [
'#type' => 'container',
'#attributes' => ['id' => 'textfields-container'],
];
$form['textfields_container']['textfields'] = [
'#type' => 'fieldset',
'#title' => $this->t("Generated text fields for first and last name"),
'#description' => t('This is where we put automatically generated textfields'),
];
// This form is rebuilt on all requests, so whether or not the request comes
// from AJAX, we should rebuild everything based on the form state.
// Checkbox values are expressed as 1 or 0, so we have to be sure to compare
// type as well as value.
if ($form_state->getValue('ask_first_name', NULL) === 1) {
$form['textfields_container']['textfields']['first_name'] = [
'#type' => 'textfield',
'#title' => $this->t('First Name'),
'#required' => TRUE,
];
}
if ($form_state->getValue('ask_last_name', NULL) === 1) {
$form['textfields_container']['textfields']['last_name'] = [
'#type' => 'textfield',
'#title' => $this->t('Last Name'),
'#required' => TRUE,
];
}
$form['textfields_container']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Click Me'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
drupal_set_message(
$this->t('Submit handler: First name: @first_name Last name: @last_name',
[
'@first_name' => $form_state->getValue('first_name', 'n/a'),
'@last_name' => $form_state->getValue('last_name', 'n/a'),
]
)
);
}
/**
* Callback for ajax_example_autotextfields.
*
* Selects the piece of the form we want to use as replacement markup and
* returns it as a form (renderable array).
*/
public function textfieldsCallback($form, FormStateInterface $form_state) {
return $form['textfields_container'];
}
}
@@ -0,0 +1,269 @@
<?php
namespace Drupal\ajax_example\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
/**
* Re-populate a dropdown based on form state.
*/
class DependentDropdown extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'ajax_example_dependentdropdown';
}
/**
* {@inheritdoc}
*
* The $nojs parameter is specified as a path parameter on the route.
*
* @see ajax_example.routing.yml
*/
public function buildForm(array $form, FormStateInterface $form_state, $nojs = NULL) {
// Add our CSS and tiny JS to hide things when they should be hidden.
$form['#attached']['library'][] = 'ajax_example/ajax_example.library';
// Explanatory text with helpful links.
$form['info'] = [
'#markup' =>
$this->t('<p>Like other examples in this module, this form has a path that
can be modified with /nojs to simulate its behavior without JavaScript.
</p><ul>
<li>@try_it_without_ajax</li>
<li>@try_it_with_ajax</li>
</ul>',
[
'@try_it_without_ajax' => Link::createFromRoute(
$this->t('Try it without AJAX'),
'ajax_example.dependent_dropdown', ['nojs' => 'nojs'])
->toString(),
'@try_it_with_ajax' => Link::createFromRoute(
$this->t('Try it with AJAX'),
'ajax_example.dependent_dropdown')
->toString(),
]
),
];
// Our first dropdown lets us select a family of instruments: String,
// Woodwind, Brass, or Percussion.
$instrument_family_options = static::getFirstDropdownOptions();
// When the AJAX request occurs, this form will be build in order to process
// form state before the AJAX callback is called. We can use this
// opportunity to populate the form as we wish based on the changes to the
// form that caused the AJAX request. If the user caused the AJAX request,
// then it would have been setting a value for instrument_family_options.
// So if there's a value in that dropdown before we build it here, we grab
// it's value to help us build the specific instrument dropdown. Otherwise
// we can just use the value of the first item as the default value.
if (empty($form_state->getValue('instrument_family_dropdown'))) {
// Use a default value.
$selected_family = key($instrument_family_options);
}
else {
// Get the value if it already exists.
$selected_family = $form_state->getValue('instrument_family_dropdown');
}
$form['instrument_family_fieldset'] = [
'#type' => 'fieldset',
'#title' => $this->t('Choose an instrument family'),
];
$form['instrument_family_fieldset']['instrument_family_dropdown'] = [
'#type' => 'select',
'#title' => $this->t('Instrument Type'),
'#options' => $instrument_family_options,
'#default_value' => $selected_family,
// Bind an ajax callback to the change event (which is the default for the
// select form type) of the first dropdown. It will replace the second
// dropdown when rebuilt.
'#ajax' => [
// 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'.
'callback' => '::instrumentDropdownCallback',
'wrapper' => 'instrument-fieldset-container',
],
];
// Since we don't know if the user has js or not, we always need to output
// this element, then hide it with with css if javascript is enabled.
$form['instrument_family_fieldset']['choose_family'] = [
'#type' => 'submit',
'#value' => $this->t('Choose'),
'#attributes' => ['class' => ['ajax-example-hide', 'ajax-example-inline']],
];
// We are using the path parameter $nojs to signal when to simulate the
// the user turning off JavaScript. We'll remove all the AJAX elements. This
// is not required, and is here so that we can demonstrate a graceful
// fallback without having to turn off JavaScript.
if ($nojs == 'nojs') {
// Removing the #ajax element tells the system not to use AJAX.
unset($form['instrument_family_fieldset']['instrument_family_dropdown']['#ajax']);
// Removing the ajax-example-hide class from the Choose button ensures
// that our JavaScript won't hide it.
unset($form['instrument_family_fieldset']['choose_family']['#attributes']);
}
// Since we're managing state for this whole fieldset (both the dropdown
// and enabling the Submit button), we want to replace the whole thing
// on AJAX requests. That's why we put it in this container.
$form['instrument_fieldset_container'] = [
'#type' => 'container',
'#attributes' => ['id' => 'instrument-fieldset-container'],
];
// Build the instrument field set.
$form['instrument_fieldset_container']['instrument_fieldset'] = [
'#type' => 'fieldset',
'#title' => $this->t('Choose an instrument'),
];
$form['instrument_fieldset_container']['instrument_fieldset']['instrument_dropdown'] = [
'#type' => 'select',
'#title' => $instrument_family_options[$selected_family] . ' ' . $this->t('Instruments'),
// When the form is rebuilt during ajax processing, the $selected_family
// variable will now have the new value and so the options will change.
'#options' => static::getSecondDropdownOptions($selected_family),
'#default_value' => !empty($form_state->getValue('instrument_dropdown')) ? $form_state->getValue('instrument_dropdown') : '',
];
$form['instrument_fieldset_container']['instrument_fieldset']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Submit'),
];
// We might normally use #state to disable the instrument fields based on
// the instrument family fields. But since the premise is that we don't have
// JavaScript running, #state won't work either. We have to set up the state
// of the instrument fieldset here, based on the selected instrument family.
if ($selected_family == 'none') {
$form['instrument_fieldset_container']['instrument_fieldset']['instrument_dropdown']['#title'] =
$this->t('You must choose an instrument family.');
$form['instrument_fieldset_container']['instrument_fieldset']['instrument_dropdown']['#disabled'] = TRUE;
$form['instrument_fieldset_container']['instrument_fieldset']['submit']['#disabled'] = TRUE;
}
else {
$form['instrument_fieldset_container']['instrument_fieldset']['instrument_dropdown']['#disabled'] = FALSE;
$form['instrument_fieldset_container']['instrument_fieldset']['submit']['#disabled'] = FALSE;
}
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$trigger = (string) $form_state->getTriggeringElement()['#value'];
switch ($trigger) {
case 'Submit':
// Submit: We're done.
drupal_set_message($this->t('Your values have been submitted. Instrument family: @family, Instrument: @instrument', [
'@family' => $form_state->getValue('instrument_family_dropdown'),
'@instrument' => $form_state->getValue('instrument_dropdown'),
]));
return;
}
// 'Choose' or anything else will cause rebuild of the form and present
// it again.
$form_state->setRebuild();
}
/**
* Provide a new dropdown based on the AJAX call.
*
* This callback will occur *after* the form has been rebuilt by buildForm().
* Since that's the case, the form should contain the right values for
* instrument_dropdown.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return array
* The portion of the render structure that will replace the
* instrument-dropdown-replace form element.
*/
public function instrumentDropdownCallback(array $form, FormStateInterface $form_state) {
return $form['instrument_fieldset_container'];
}
/**
* Helper function to populate the first dropdown.
*
* This would normally be pulling data from the database.
*
* @return array
* Dropdown options.
*/
public static function getFirstDropdownOptions() {
return [
'none' => 'none',
'String' => 'String',
'Woodwind' => 'Woodwind',
'Brass' => 'Brass',
'Percussion' => 'Percussion',
];
}
/**
* Helper function to populate the second dropdown.
*
* This would normally be pulling data from the database.
*
* @param string $key
* This will determine which set of options is returned.
*
* @return array
* Dropdown options
*/
public static function getSecondDropdownOptions($key = '') {
switch ($key) {
case 'String':
$options = [
'Violin' => 'Violin',
'Viola' => 'Viola',
'Cello' => 'Cello',
'Double Bass' => 'Double Bass',
];
break;
case 'Woodwind':
$options = [
'Flute' => 'Flute',
'Clarinet' => 'Clarinet',
'Oboe' => 'Oboe',
'Bassoon' => 'Bassoon',
];
break;
case 'Brass':
$options = [
'Trumpet' => 'Trumpet',
'Trombone' => 'Trombone',
'French Horn' => 'French Horn',
'Euphonium' => 'Euphonium',
];
break;
case 'Percussion':
$options = [
'Bass Drum' => 'Bass Drum',
'Timpani' => 'Timpani',
'Snare Drum' => 'Snare Drum',
'Tambourine' => 'Tambourine',
];
break;
default:
$options = ['none' => 'none'];
break;
}
return $options;
}
}
@@ -0,0 +1,222 @@
<?php
namespace Drupal\ajax_example\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
/**
* Dynamically-enabled form with graceful no-JS degradation.
*
* Example of a form with portions dynamically enabled or disabled, but with
* graceful degradation in the case of no JavaScript.
*
* The idea here is that certain parts of the form don't need to be displayed
* unless a given option is selected, but then they should be displayed and
* configured.
*
* The third $no_js_use argument is strictly for demonstrating operation
* without JavaScript, without making the user/developer turn off JavaScript.
*/
class DynamicFormSections extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'ajax_example_dynamicsectiondegrades';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $nojs = NULL) {
// Add our CSS and tiny JS to hide things when they should be hidden.
$form['#attached']['library'][] = 'ajax_example/ajax_example.library';
// Explanatory text with helpful links.
$form['info'] = [
'#markup' =>
$this->t('<p>Like other examples in this module, this form has a path that
can be modified with /nojs to simulate its behavior without JavaScript.
</p><ul>
<li>@try_it_without_ajax</li>
<li>@try_it_with_ajax</li>
</ul>',
[
'@try_it_without_ajax' => Link::createFromRoute(
$this->t('Try it without AJAX'),
'ajax_example.dynamic_form_sections', ['nojs' => 'nojs'])
->toString(),
'@try_it_with_ajax' => Link::createFromRoute(
$this->t('Try it with AJAX'),
'ajax_example.dynamic_form_sections')
->toString(),
]
),
];
$form['question_type_select'] = [
// This is our select dropdown.
'#type' => 'select',
'#title' => t('Question style'),
// We have a variety of form items you can use to get input from the user.
'#options' => [
'Choose question style' => 'Choose question style',
'Multiple Choice' => 'Multiple Choice',
'True/False' => 'True/False',
'Fill-in-the-blanks' => 'Fill-in-the-blanks',
],
// The #ajax section tells the AJAX system that whenever this dropdown
// emits an event, it should call the callback and put the resulting
// content into the wrapper we specify. The questions-fieldset-wrapper is
// defined below.
'#ajax' => [
'wrapper' => 'questions-fieldset-wrapper',
'callback' => '::promptCallback',
],
];
// The CSS for this module hides this next button if JS is enabled.
$form['question_type_submit'] = [
'#type' => 'submit',
'#value' => t('Choose'),
'#attributes' => ['class' => ['ajax-example-inline']],
// No need to validate when submitting this.
'#limit_validation_errors' => [],
'#validate' => [],
];
// This section allows us to demonstrate no-AJAX use without turning off
// javascript in the browser.
if ($nojs != 'nojs') {
// Allow JavaScript to hide the choose button if we're using AJAX.
$form['question_type_submit']['#attributes']['class'][] = 'ajax-example-hide';
}
else {
// Remove #ajax from the above, so it won't perform AJAX behaviors.
unset($form['question_type_select']['#ajax']);
}
// This fieldset just serves as a container for the part of the form
// that gets rebuilt. It has a nice line around it so you can see it.
$form['questions_fieldset'] = [
'#type' => 'details',
'#title' => $this->t('Stuff will appear here'),
'#open' => TRUE,
// We set the ID of this fieldset to questions-fieldset-wrapper so the
// AJAX command can replace it.
'#attributes' => ['id' => 'questions-fieldset-wrapper'],
];
// When the AJAX request comes in, or when the user hit 'Submit' if there is
// no JavaScript, the form state will tell us what the user has selected
// from the dropdown. We can look at the value of the dropdown to determine
// which secondary form to display.
$question_type = $form_state->getValue('question_type_select');
if (!empty($question_type) && $question_type !== 'Choose question style') {
$form['questions_fieldset']['question'] = [
'#markup' => t('Who was the first president of the U.S.?'),
];
// Build up a secondary form, based on the type of question the user
// chose.
switch ($question_type) {
case 'Multiple Choice':
$form['questions_fieldset']['question'] = [
'#type' => 'radios',
'#title' => t('Who was the first president of the United States'),
'#options' => [
'George Bush' => 'George Bush',
'Adam McGuire' => 'Adam McGuire',
'Abraham Lincoln' => 'Abraham Lincoln',
'George Washington' => 'George Washington',
],
];
break;
case 'True/False':
$form['questions_fieldset']['question'] = [
'#type' => 'radios',
'#title' => $this->t('Was George Washington the first president of the United States?'),
'#options' => [
'George Washington' => 'True',
0 => 'False',
],
'#description' => $this->t('Click "True" if you think George Washington was the first president of the United States.'),
];
break;
case 'Fill-in-the-blanks':
$form['questions_fieldset']['question'] = [
'#type' => 'textfield',
'#title' => $this->t('Who was the first president of the United States'),
'#description' => $this->t('Please type the correct answer to the question.'),
];
break;
}
$form['questions_fieldset']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Submit your answer'),
];
}
return $form;
}
/**
* Final submit handler.
*
* Reports what values were finally set.
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// This is only executed when a button is pressed, not when the AJAXfield
// select is changed.
// Now handle the case of the next, previous, and submit buttons.
// Only submit will result in actual submission, all others rebuild.
if ($form_state->getValue('question_type_submit') == 'Choose') {
$form_state->setValue('question_type_select', $form_state->getUserInput()['question_type_select']);
$form_state->setRebuild();
}
if ($form_state->getValue('submit') == 'Submit your answer') {
$form_state->setRebuild(FALSE);
$answer = $form_state->getValue('question');
// Special handling for the checkbox.
if ($answer == 1 && $form['questions_fieldset']['question']['#type'] == 'checkbox') {
$answer = $form['questions_fieldset']['question']['#title'];
}
if ($answer == $this->t('George Washington')) {
drupal_set_message($this->t('You got the right answer: @answer', ['@answer' => $answer]));
}
else {
drupal_set_message($this->t('Sorry, your answer (@answer) is wrong', ['@answer' => $answer]));
}
return;
}
// Sets the form to be rebuilt after processing.
$form_state->setRebuild();
}
/**
* Callback for the select element.
*
* Since the questions_fieldset part of the form has already been built during
* the AJAX request, we can return only that part of the form to the AJAX
* request, and it will insert that part into questions-fieldset-wrapper.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return array
* The form structure.
*/
public function promptCallback(array $form, FormStateInterface $form_state) {
return $form['questions_fieldset'];
}
}
@@ -0,0 +1,126 @@
<?php
namespace Drupal\ajax_example\Form;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* A simple autocomplete form which looks up usernames.
*
* @ingroup ajax_example
*/
class EntityAutocomplete implements FormInterface, ContainerInjectionInterface {
use StringTranslationTrait;
/**
* The entity type manager service.
*
* We need this for the submit handler.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Container injection factory.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* The service discovery container.
*
* @return self
* The form object.
*/
public static function create(ContainerInterface $container) {
$form = new static(
$container->get('entity_type.manager')
);
$form->setStringTranslation($container->get('string_translation'));
return $form;
}
/**
* Constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager service.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'ajax_example_autocomplete_user';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['info'] = [
'#markup' => '<div>' . t("This example uses the entity_autocomplete form "
. "element to select users. You'll need a few users on your system for "
. "it to make sense.") . '</div>',
];
// Here we use the delightful entity_autocomplete form element. It allows us
// to consistently select entities. See https://www.drupal.org/node/2418529.
$form['users'] = [
// A type of entity_autocomplete lets Drupal know it should autocomplete
// entities.
'#type' => 'entity_autocomplete',
// We can specify entity types to autocomplete.
'#target_type' => 'user',
// Specifying #tags as TRUE allows for multiple selections, separated by
// commas.
'#tags' => TRUE,
'#title' => t('Choose a user. Separate with commas.'),
];
$form['actions'] = [
'#type' => 'actions',
];
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Submit'),
];
return $form;
}
/**
* {@inheritdoc}
*
* Here we validate and signal an error if there are no users selected.
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$state_users = $form_state->getValue('users');
if (empty($state_users)) {
$form_state->setErrorByName('users', 'There were no users selected.');
}
}
/**
* {@inheritdoc}
*
* On submit, show the user the names of the users they selected.
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$state_users = $form_state->getValue('users');
$users = [];
foreach ($state_users as $state_user) {
$uid = $state_user['target_id'];
$users[] = $this->entityTypeManager->getStorage('user')->load($uid)->getUsername();
}
drupal_set_message('These are your users: ' . implode(' ', $users));
}
}
@@ -0,0 +1,80 @@
<?php
namespace Drupal\ajax_example\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* A relatively simple AJAX demonstration form.
*/
class Simplest extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'ajax_example_simplest';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['changethis'] = [
'#title' => $this->t("Choose something and explain why"),
'#type' => 'select',
'#options' => [
'one' => 'one',
'two' => 'two',
'three' => 'three',
],
'#ajax' => [
// #ajax has two required keys: callback and wrapper.
// 'callback' is a function that will be called when this element
// changes.
'callback' => '::promptCallback',
// 'wrapper' is the HTML id of the page element that will be replaced.
'wrapper' => 'replace-textfield-container',
],
];
// The 'replace-textfield-container' container will be replaced whenever
// 'changethis' is updated.
$form['replace_textfield_container'] = [
'#type' => 'container',
'#attributes' => ['id' => 'replace-textfield-container'],
];
$form['replace_textfield_container']['replace_textfield'] = [
'#type' => 'textfield',
'#title' => $this->t("Why"),
];
// An AJAX request calls the form builder function for every change.
// We can change how we build the form based on $form_state.
$value = $form_state->getValue('changethis');
// The getValue() method returns NULL by default if the form element does
// not exist. It won't exist yet if we're building it for the first time.
if ($value !== NULL) {
$form['replace_textfield_container']['replace_textfield']['#description'] =
$this->t("Say why you chose '@value'", ['@value' => $value]);
}
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// No-op. Our form doesn't need a submit handler, because the form is never
// submitted. We add the method here so we fulfill FormInterface.
}
/**
* Handles switching the available regions based on the selected theme.
*/
public function promptCallback($form, FormStateInterface $form_state) {
return $form['replace_textfield_container'];
}
}
@@ -0,0 +1,73 @@
<?php
namespace Drupal\ajax_example\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Submit a form without a page reload.
*/
class SubmitDriven extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'ajax_example_autotextfields';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// This container wil be replaced by AJAX.
$form['container'] = [
'#type' => 'container',
'#attributes' => ['id' => 'box-container'],
];
// The box contains some markup that we can change on a submit request.
$form['container']['box'] = [
'#type' => 'markup',
'#markup' => '<h1>Initial markup for box</h1>',
];
$form['submit'] = [
'#type' => 'submit',
// The AJAX handler will call our callback, and will replace whatever page
// element has id box-container.
'#ajax' => [
'callback' => '::promptCallback',
'wrapper' => 'box-container',
],
'#value' => $this->t('Submit'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
}
/**
* Callback for submit_driven example.
*
* Select the 'box' element, change the markup in it, and return it as a
* renderable array.
*
* @return array
* Renderable array (the box element)
*/
public function promptCallback(array &$form, FormStateInterface $form_state) {
// In most cases, it is recommended that you put this logic in form
// generation rather than the callback. Submit driven forms are an
// exception, because you may not want to return the form at all.
$element = $form['container'];
$element['box']['#markup'] = "Clicked submit ({$form_state->getValue('op')}): " . date('c');
return $element;
}
}
@@ -0,0 +1,229 @@
<?php
namespace Drupal\ajax_example\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\Core\Link;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
/**
* AJAX example wizard.
*/
class Wizard extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'ajax_example_wizard';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $no_js_use = FALSE) {
$url = Url::fromUri('internal:/examples/ajax-example/wizard-nojs');
$link = Link::fromTextAndUrl($this->t('examples/ajax-example/wizard-nojs'), $url)
->toString();
// Prepare link for multiple arguments.
$urltwo = Url::fromUri('internal:/examples/ajax-example/wizard');
$linktwo = Link::fromTextAndUrl($this->t('examples/ajax-example/wizard'), $urltwo)
->toString();
// We want to deal with hierarchical form values.
$form['#tree'] = TRUE;
$form['description'] = [
'#markup' => t('This example is a step-by-step wizard. The @link does it without page reloads; the @link1 is the same code but simulates a non-javascript environment, showing it with page reloads.', [
'@link' => $linktwo,
'@link1' => $link,
]),
];
$form['step'] = [
'#type' => 'hidden',
'#value' => !empty($form_state->getValue('step')) ? $form_state->getValue('step') : 1,
];
print_r($form_state->getValue('step'));
if ($form['step']['#value'] == 1) {
$form['step1'] = [
'#type' => 'fieldset',
'#title' => $this->t('Step 1: Personal details'),
];
$form['step1']['name'] = [
'#type' => 'textfield',
'#title' => $this->t('Your name'),
'#default_value' => empty($form_state->getValue([
'step1',
'name',
]) ? '' : $form_state->getValue(['step1', 'name'])),
'#required' => TRUE,
];
$form['next'] = [
'#type' => 'submit',
'#value' => $this->t('Next step'),
'#ajax' => [
'wrapper' => 'ajax-example-wizard',
'callback' => '::prompt',
],
];
}
// This simply allows us to demonstrate no-javascript use without
// actually turning off javascript in the browser. Removing the #ajax
// element turns off AJAX behaviors on that element and as a result
// ajax.js doesn't get loaded.
// For demonstration only! You don't need this.
if ($no_js_use) {
// Remove the #ajax from the above, so ajax.js won't be loaded.
// For demonstration only.
unset($form['next']['#ajax']);
unset($form['prev']['#ajax']);
}
return $form;
}
/**
* Wizard callback function.
*
* @param array $form
* Form API form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form API form.
*
* @return array
* Form array.
*/
public function prompt(array $form, FormStateInterface $form_state) {
return $form;
}
/**
* Save away the current information.
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
if ($form_state->getTriggeringElement()['#value'] == $this->t('Submit your information')) {
$value_message = $this->t('Your information has been submitted:') . ' ';
foreach ($form_state->getValue('value') as $step => $values) {
$value_message .= "$step: ";
foreach ($values as $key => $value) {
$value_message .= "$key=$value, ";
}
}
drupal_set_message($value_message);
$form_state->setRebuild(FALSE);
// Redirect to #action, else return.
return;
}
else {
$step = $form_state->getValue('step');
// Increment or decrement the step as needed. Recover values if they
// exist.
if ($form_state->getTriggeringElement()['#value']->__toString() == $this->t('Next step')) {
$step++;
}
elseif ($form_state->getTriggeringElement()['#value']->__toString() == $this->t('Previous step')) {
$step--;
}
switch ($step) {
case 1:
$form['step1'] = [
'#type' => 'fieldset',
'#title' => $this->t('Step 1: Personal details'),
];
$form['step1']['name'] = [
'#type' => 'textfield',
'#title' => $this->t('Your name'),
'#default_value' => empty($form_state->getValue([
'step1',
'name',
]) ? '' : $form_state->getValue(['step1', 'name'])),
'#required' => TRUE,
];
$form_state->setValue('step', 1);
break;
case 2:
unset($form['step1']);
unset($form['next']);
$form['step2'] = [
'#type' => 'fieldset',
'#title' => t('Step 2: Street address info'),
];
$form['step2']['address'] = [
'#type' => 'textfield',
'#title' => $this->t('Your street address'),
'#default_value' => empty($form_state->getValue([
'step2',
'address',
]) ? '' : $form_state->getValue(['step2', 'address'])),
'#required' => TRUE,
];
$form_state->setValue('step', $step);
break;
case 3:
$form['step3'] = [
'#type' => 'fieldset',
'#title' => $this->t('Step 3: City info'),
];
$form['step3']['city'] = [
'#type' => 'textfield',
'#title' => $this->t('Your city'),
'#default_value' => empty($form_state->getValue([
'step3',
'city',
]) ? '' : $form_state->getValue(['step3', 'city'])),
'#required' => TRUE,
];
$form_state->setValue('step', $step);
break;
}
if ($step == 3) {
$form['submit'] = [
'#type' => 'submit',
'#value' => $this->t("Submit your information"),
];
}
if ($step > 1 && !isset($form['prev'])) {
$form['prev'] = [
'#type' => 'submit',
'#value' => t("Previous step"),
// Since all info will be discarded, don't validate on 'prev'.
'#limit_validation_errors' => [],
// #submit is required to use #limit_validation_errors.
'#submit' => ['ajax_example_wizard_submit'],
'#ajax' => [
'wrapper' => 'ajax-example-wizard',
'callback' => '::prompt',
],
];
}
if ($step < 3 && !isset($form['next'])) {
$form['next'] = [
'#type' => 'submit',
'#value' => $this->t('Next step'),
'#limit_validation_errors' => [],
'#ajax' => [
'wrapper' => 'ajax-example-wizard',
'callback' => '::prompt',
],
];
}
$response = new AjaxResponse();
$response->addCommand(new HtmlCommand('#ajax-example-wizard', $form));
return $response;
}
}
}
@@ -0,0 +1,29 @@
{#
Description text for the Ajax Example.
#}
{% set simple_ajax_example = path('ajax_example.simplest') %}
{% set ajax_generate_textfields = path('ajax_example.autotextfields') %}
{% set ajax_submit = path('ajax_example.submit_driven_ajax') %}
{% set ajax_dependent_dropdown = path('ajax_example.dependent_dropdown') %}
{% set ajax_dependent_dropdown_nojs = path('ajax_example.dependent_dropdown', {'nojs': 'nojs'}) %}
{% set ajax_dynamic_form = path('ajax_example.dynamic_form_sections') %}
{% set ajax_dynamic_form_nojs = path('ajax_example.dynamic_form_sections', {'nojs': 'nojs'}) %}
{% set ajax_wizard_example = path('ajax_example.wizard') %}
{% set ajax_wizard_example_nojs = path('ajax_example.wizardnojs') %}
{% trans %}
<p>The AJAX example module provides many examples of AJAX including forms, links, and AJAX commands.</p>
<p><a href={{ simple_ajax_example }}>Simplest AJAX Example</a></p>
<p><a href={{ ajax_generate_textfields }}>Generate textfields</a></p>
<p><a href={{ ajax_submit }}>Submit-driven AJAX</a></p>
<p><a href={{ ajax_dependent_dropdown }}>Dependent dropdown</a></p>
<p><a href={{ ajax_dependent_dropdown_nojs }}>Dependent dropdown w/ no JS</a></p>
<p><a href={{ ajax_dynamic_form }}>Dynamic form sections</a></p>
<p><a href={{ ajax_dynamic_form_nojs }}>Dynamic form sections w/ no JS</a></p>
<p><a href={{ ajax_wizard_example }}>AJAX Wizard Example</a></p>
<p><a href={{ ajax_wizard_example_nojs }}>AJAX Wizard Example w/JS turned off</a></p>
{% endtrans %}
@@ -0,0 +1,79 @@
<?php
namespace Drupal\Tests\ajax_example\Functional;
use Drupal\Core\Url;
use Drupal\Tests\BrowserTestBase;
/**
* Verify tool block menu items and operability of all our routes.
*
* @group ajax_example
* @group examples
*
* @ingroup ajax_example
*/
class AjaxExampleMenuTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['ajax_example'];
/**
* The installation profile to use with this test.
*
* @var string
*/
protected $profile = 'minimal';
/**
* Tests links.
*/
public function testAjaxExampleLinks() {
// Login a user that can access content.
$this->drupalLogin(
$this->createUser(['access content', 'access user profiles'])
);
$assertion = $this->assertSession();
// Routes with menu links, and their form buttons.
$routes_with_menu_links = [
'ajax_example.description' => [],
'ajax_example.simplest' => [],
'ajax_example.autotextfields' => ['Click Me'],
'ajax_example.submit_driven_ajax' => ['Submit'],
'ajax_example.dependent_dropdown' => ['Submit'],
'ajax_example.dynamic_form_sections' => ['Choose'],
'ajax_example.wizard' => ['Next step'],
'ajax_example.wizardnojs' => ['Next step'],
'ajax_example.ajax_link_render' => [],
'ajax_example.autocomplete_user' => ['Submit'],
];
// Ensure the links appear in the tools menu sidebar.
$this->drupalGet('');
foreach (array_keys($routes_with_menu_links) as $route) {
$assertion->linkByHrefExists(Url::fromRoute($route)->getInternalPath());
}
// All our routes with their form buttons.
$routes = [
'ajax_example.ajax_link_callback' => [],
];
// Go to all the routes and click all the buttons.
$routes = array_merge($routes_with_menu_links, $routes);
foreach ($routes as $route => $buttons) {
$url = Url::fromRoute($route);
$this->drupalGet($url);
$assertion->statusCodeEquals(200);
foreach ($buttons as $button) {
$this->drupalPostForm($url, [], $button);
$assertion->statusCodeEquals(200);
}
}
}
}
@@ -0,0 +1,74 @@
<?php
namespace Drupal\Tests\ajax_example\Functional;
use Drupal\Core\Url;
use Drupal\Tests\BrowserTestBase;
/**
* Functional test of non-AJAX dependent dropdown example.
*
* @group ajax_example
* @group examples
*
* @ingroup ajax_example
*
* @see \Drupal\Tests\ajax_example\FunctionalJavascript\DependentDropdownTest
*/
class DependentDropdownTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['ajax_example'];
/**
* Test the dependent dropdown form without AJAX.
*/
public function testDependentDropdown() {
// Get the Mink stuff.
$session = $this->getSession();
$assert = $this->assertSession();
$page = $session->getPage();
// Get a URL object for the form, specifying no JS.
$dropdown_url = Url::fromRoute('ajax_example.dependent_dropdown', ['nojs' => 'nojs']);
// Get the form.
$this->drupalGet($dropdown_url);
// Check for the initial state.
$assert->fieldDisabled('instrument_dropdown');
$assert->fieldValueEquals('instrument_dropdown', 'none');
$submit_button = $page->findButton('edit-submit');
$this->assertTrue($submit_button->hasAttribute('disabled'));
// Run through the matrix of form submissions.
$families = [
'String' => ['Violin', 'Viola', 'Cello', 'Double Bass'],
'Woodwind' => ['Flute', 'Clarinet', 'Oboe', 'Bassoon'],
'Brass' => ['Trumpet', 'Trombone', 'French Horn', 'Euphonium'],
'Percussion' => ['Bass Drum', 'Timpani', 'Snare Drum', 'Tambourine'],
];
foreach ($families as $family => $instruments) {
// Post the form for the instrument family.
$this->drupalPostForm($dropdown_url, ['instrument_family_dropdown' => $family], 'Choose');
// Get the instrument dropdown elements.
$instrument_options = $page->findAll('css', '#edit-instrument-dropdown option');
$this->assertCount(count($instruments), $instrument_options);
// Make sure all the instruments are in the select dropdown.
foreach ($instrument_options as $instrument) {
$this->assertContains($instrument->getAttribute('value'), $instruments);
}
// Post each instrument. We have to 'choose' again in order to unlock the
// instrument dropdown.
foreach ($instruments as $instrument) {
$this->drupalPostForm($dropdown_url, ['instrument_family_dropdown' => $family], 'Choose');
$this->drupalPostForm(NULL, ['instrument_dropdown' => $instrument], 'Submit');
$assert->pageTextContains("Your values have been submitted. Instrument family: $family, Instrument: $instrument");
}
}
}
}
@@ -0,0 +1,70 @@
<?php
namespace Drupal\Tests\ajax_example\Functional;
use Drupal\Core\Url;
use Drupal\Tests\BrowserTestBase;
/**
* Functional test of non-AJAX dependent dropdown example.
*
* @group ajax_example
* @group examples
*
* @ingroup ajax_example
*
* @see Drupal\Tests\ajax_example\FunctionalJavascript\DynamicFormSectionsTest
*/
class DynamicFormSectionsTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['ajax_example'];
/**
* Test the dynamic sections form without AJAX.
*/
public function testDynamicFormSections() {
// Get the Mink stuff.
$assert = $this->assertSession();
$page = $this->getSession()->getPage();
// Get a URL object for the form, specifying no JS.
$dropdown_url = Url::fromRoute('ajax_example.dynamic_form_sections', ['nojs' => 'nojs']);
// Get the form.
$this->drupalGet($dropdown_url);
// Check for the initial state.
$detail_children = $page->findAll('css', 'div.details-wrapper *');
$this->assertEmpty($detail_children);
// Go through the dropdown options. First outlier is 'Choose question style'
// which should have an empty details section.
$this->drupalPostForm($dropdown_url, ['question_type_select' => 'Choose question style'], 'Choose');
$detail_children = $page->findAll('css', 'div.details-wrapper *');
$this->assertEmpty($detail_children);
// Cycle through the other dropdown values.
$question_styles = [
'Multiple Choice',
'True/False',
'Fill-in-the-blanks',
];
// These all add stuff to the details wrapper.
foreach ($question_styles as $question_style) {
$this->drupalPostForm($dropdown_url, ['question_type_select' => $question_style], 'Choose');
$detail_children = $page->findAll('css', 'div.details-wrapper *');
$this->assertNotEmpty($detail_children);
$this->drupalPostForm(NULL, ['question' => 'George Washington'], 'Submit your answer');
$assert->pageTextContains('You got the right answer: George Washington');
}
// One wrong answer to exercise that code path.
$this->drupalPostForm($dropdown_url, ['question_type_select' => 'Multiple Choice'], 'Choose');
$detail_children = $page->findAll('css', 'div.details-wrapper *');
$this->assertNotEmpty($detail_children);
$this->drupalPostForm(NULL, ['question' => 'Abraham Lincoln'], 'Submit your answer');
$assert->pageTextContains('Sorry, your answer (Abraham Lincoln) is wrong');
}
}
@@ -0,0 +1,80 @@
<?php
namespace Drupal\Tests\ajax_example\FunctionalJavascript;
use Drupal\Core\Url;
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
/**
* Test the user interactions for the Autotextfields example.
*
* @group ajax_example
*/
class AutofieldsTest extends JavascriptTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['ajax_example'];
/**
* Test the user interactions for the Autotextfields example.
*/
public function testAutotextfields() {
// Get our Mink stuff.
$session = $this->getSession();
$page = $session->getPage();
$assert = $this->assertSession();
// Get the page.
$form_url = Url::fromRoute('ajax_example.autotextfields');
$this->drupalGet($form_url);
// Check our initial state.
$assert->checkboxNotChecked('ask_first_name');
$assert->checkboxNotChecked('ask_last_name');
$assert->fieldNotExists('first_name');
$assert->fieldNotExists('last_name');
// Submit the form. This tests what happens when there are no user
// interactions because drupalPostForm() reloads the form.
$this->drupalPostForm($form_url, [], 'Click Me');
$assert->pageTextContains('Submit handler: First name: n/a Last name: n/a');
// Ask for the first name.
$page->checkField('ask_first_name');
$assert->assertWaitOnAjaxRequest();
$assert->fieldExists('first_name');
$assert->fieldNotExists('last_name');
// Submit the form. We have to find the field and set its value rather than
// use drupalPostForm(), because when we post the form, it will be rebuilt.
// We are testing the form state after AJAX has modified it, so we must
// preserve that.
$page->findField('first_name')->setValue('Dries');
$page->pressButton('Click Me');
$assert->pageTextContains('Submit handler: First name: Dries Last name: n/a');
// Ask for the first and last name.
$page->checkField('ask_first_name');
$assert->assertWaitOnAjaxRequest();
$assert->fieldExists('first_name');
$page->checkField('ask_last_name');
$assert->assertWaitOnAjaxRequest();
$assert->fieldExists('last_name');
// Submit the form.
$page->findField('first_name')->setValue('Dries');
$page->findField('last_name')->setValue('Buytaert');
$page->pressButton('Click Me');
$assert->pageTextContains('Submit handler: First name: Dries Last name: Buytaert');
// Ask for only the last name.
$page->checkField('ask_last_name');
$assert->assertWaitOnAjaxRequest();
$assert->fieldNotExists('first_name');
$assert->fieldExists('last_name');
// Submit the form.
$page->findField('last_name')->setValue('Buytaert');
$page->pressButton('Click Me');
$assert->pageTextContains('Submit handler: First name: n/a Last name: Buytaert');
}
}
@@ -0,0 +1,75 @@
<?php
namespace Drupal\Tests\ajax_example\FunctionalJavascript;
use Drupal\Core\Url;
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
/**
* Functional test of dependent dropdown example.
*
* @group ajax_example
*
* @see \Drupal\Tests\ajax_example\FunctionalJavascript\DependentDropdownTest
*/
class DependentDropdownTest extends JavascriptTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['ajax_example'];
/**
* Test the dependent dropdown form with AJAX.
*/
public function testDependentDropdown() {
// Get the Mink stuff.
$assert = $this->assertSession();
$page = $this->getSession()->getPage();
// Get a URL object for the form, specifying AJAX.
$dropdown_url = Url::fromRoute('ajax_example.dependent_dropdown', ['nojs' => 'ajax']);
// Get the form.
$this->drupalGet($dropdown_url);
// Check for the initial state.
$assert->fieldDisabled('instrument_dropdown');
$assert->fieldValueEquals('instrument_dropdown', 'none');
$submit_button = $page->findButton('edit-submit');
$this->assertTrue($submit_button->hasAttribute('disabled'));
// Run through the matrix of families.
$families = [
'String' => ['Violin', 'Viola', 'Cello', 'Double Bass'],
'Woodwind' => ['Flute', 'Clarinet', 'Oboe', 'Bassoon'],
'Brass' => ['Trumpet', 'Trombone', 'French Horn', 'Euphonium'],
'Percussion' => ['Bass Drum', 'Timpani', 'Snare Drum', 'Tambourine'],
];
foreach ($families as $family => $instruments) {
// Select a family.
$family_dropdown = $assert->fieldExists('instrument_family_dropdown');
$family_dropdown->setValue($family);
$assert->assertWaitOnAjaxRequest();
// Get the instrument dropdown elements.
$instrument_options = $page->findAll('css', 'select[name="instrument_dropdown"] option');
$this->assertCount(count($instruments), $instrument_options);
// Make sure all the instruments are in the select dropdown.
foreach ($instrument_options as $instrument) {
$this->assertContains($instrument->getAttribute('value'), $instruments);
}
// Post each instrument.
foreach ($instruments as $instrument) {
$this->drupalGet($dropdown_url);
$family_dropdown->setValue($family);
$assert->assertWaitOnAjaxRequest();
$this->drupalPostForm(NULL, ['instrument_dropdown' => $instrument], 'Submit');
$assert->pageTextContains("Your values have been submitted. Instrument family: $family, Instrument: $instrument");
}
}
}
}
@@ -0,0 +1,67 @@
<?php
namespace Drupal\Tests\ajax_example\FunctionalJavascript;
use Drupal\Core\Url;
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
/**
* Functional test of dependent dropdown example.
*
* @group ajax_example
*
* @see Drupal\Tests\ajax_example\Functional\DynamicFormSectionsTest
*/
class DynamicFormSectionsTest extends JavascriptTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['ajax_example'];
/**
* Test the dependent dropdown form with AJAX.
*/
public function testDynamicFormSections() {
// Get the Mink stuff.
$assert = $this->assertSession();
$page = $this->getSession()->getPage();
// Get a URL object for the form, specifying no JS.
$dropdown_url = Url::fromRoute('ajax_example.dynamic_form_sections', ['nojs' => 'ajax']);
// Get the form.
$this->drupalGet($dropdown_url);
// Check for the initial state.
$this->assertEmpty($page->findAll('css', 'div.details-wrapper *'));
// Cycle through the other dropdown values.
$question_styles = [
'Multiple Choice',
'True/False',
'Fill-in-the-blanks',
];
// Check expectations against the details wrapper.
$question_type_dropdown = $page->findField('question_type_select');
foreach ($question_styles as $question_style) {
$question_type_dropdown->setValue($question_style);
$assert->assertWaitOnAjaxRequest();
$this->assertNotEmpty($page->findAll('css', 'div.details-wrapper *'));
}
// Prompt to choose question should remove the question.
$question_type_dropdown->setValue('Choose question style');
$assert->assertWaitOnAjaxRequest();
$this->assertEmpty($page->findAll('css', 'div.details-wrapper *'));
// Submit the correct answers.
foreach ($question_styles as $question_style) {
$this->drupalGet($dropdown_url);
$question_type_dropdown->setValue($question_style);
$assert->assertWaitOnAjaxRequest();
$this->drupalPostForm(NULL, ['question' => 'George Washington'], 'Submit your answer');
$assert->pageTextContains('You got the right answer: George Washington');
}
}
}
@@ -0,0 +1,69 @@
<?php
namespace Drupal\Tests\ajax_example\FunctionalJavascript;
use Drupal\Core\Url;
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
/**
* Tests the behavior of the entity_autocomplete example.
*
* @group ajax_example
*/
class EntityAutocompleteTest extends JavascriptTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['ajax_example'];
/**
* Test the behavior of the submit-driven AJAX example.
*
* Behaviors to test:
* - GET the route ajax_example.autocomplete_user.
* - Examine the DOM to make sure our change hasn't happened yet.
* - Send an event to the DOM to trigger the autocomplete.
* - Wait for the autocomplete request to complete.
* - Examine the DOM to see if our expected change happened.
* - Submit some names to see if our form processed the user properly.
*/
public function testSubmitDriven() {
// Set up some accounts with known names.
$names = ['bb', 'bc'];
foreach ($names as $name) {
$this->createUser([], $name);
}
// Get our various Mink elements.
$assert = $this->assertSession();
$session = $this->getSession();
$page = $session->getPage();
// We'll be using the users field quite a bit, so let's make it a variable.
$users_field_name = 'edit-users';
// Get the form.
$this->drupalGet(Url::fromRoute('ajax_example.autocomplete_user'));
// Examine the DOM to make sure our change hasn't happened yet.
$assert->fieldValueEquals($users_field_name, '');
// Send an event to the DOM. This will start the autocomplete process.
$autocomplete_field = $page->findField($users_field_name);
$session->getDriver()->keyDown($autocomplete_field->getXpath(), 'b');
// Wait for the autocomplete request to complete.
$assert->waitOnAutocomplete();
// Examine the DOM to see if our expected change happened.
$results = $page->findAll('css', '.ui-autocomplete li');
$this->assertCount(2, $results);
foreach ($results as $result) {
$this->assertContains($result->getText(), $names);
}
// Submit to see if our form processed the user properly.
$this->submitForm([$users_field_name => 'bb, bc'], 'Submit');
$assert->pageTextContains('These are your users: bb bc');
}
}
@@ -0,0 +1,60 @@
<?php
namespace Drupal\Tests\ajax_example\FunctionalJavascript;
use Drupal\Core\Url;
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
/**
* Test the user interactions for the Simplest example.
*
* @group ajax_example
*/
class SimplestTest extends JavascriptTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['ajax_example'];
/**
* Test the user interactions for the Autotextfields example.
*/
public function testAutotextfields() {
// Get our Mink stuff.
$session = $this->getSession();
$page = $session->getPage();
$assert = $this->assertSession();
// Get the page.
$form_url = Url::fromRoute('ajax_example.simplest');
$this->drupalGet($form_url);
// Don't repeat ourselves. This makes it easier if we change the markup
// later.
$description_selector = '#replace-textfield-container div.description';
// Check our initial state.
$assert->elementExists('css', '#replace-textfield-container');
$assert->elementNotExists('css', $description_selector);
// Select values on the dropdown. Start with three so the change event is
// triggered.
foreach (['three', 'two', 'one'] as $value) {
// Select the dropdown value.
$page->selectFieldOption('changethis', $value);
// Wait for AJAX to happen.
$assert->assertWaitOnAjaxRequest();
// Assert that the description exists.
$assert->elementExists('css', $description_selector);
// Get the description element from the page.
$prompt_element = $page->find('css', $description_selector);
// Assert that the description element says what we expect it to say.
$this->assertEquals(
"Say why you chose '$value'",
$prompt_element->getText()
);
}
}
}
@@ -0,0 +1,45 @@
<?php
namespace Drupal\Tests\ajax_example\FunctionalJavascript;
use Drupal\Core\Url;
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
/**
* Tests the behavior of the submit-driven AJAX example.
*
* @group ajax_example
*/
class SubmitDrivenTest extends JavascriptTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['ajax_example'];
/**
* Test the behavior of the submit-driven AJAX example.
*
* Behaviors to test:
* - GET the route ajax_example.submit_driven_ajax.
* - Examine the DOM to make sure our change hasn't happened yet.
* - Submit the form.
* - Wait for the AJAX request to complete.
* - Examine the DOM to see if our expected change happened.
*/
public function testSubmitDriven() {
// Get the session assertion object.
$assert = $this->assertSession();
// Get the page.
$this->drupalGet(Url::fromRoute('ajax_example.submit_driven_ajax'));
// Examine the DOM to make sure our change hasn't happened yet.
$assert->pageTextNotContains('Clicked submit (Submit):');
// Submit the form.
$this->submitForm([], 'Submit');
// Wait on the AJAX request.
$assert->assertWaitOnAjaxRequest();
// Compare DOM to our expectations.
$assert->pageTextContains('Clicked submit (Submit):');
}
}