Commit 2a5c62de authored by catch's avatar catch
Browse files

Issue #3000068 by andypost, claudiu.cristea, pingwin4eg, Mile23, voleger,...

Issue #3000068 by andypost, claudiu.cristea, pingwin4eg, Mile23, voleger, johndevman: Deprecate drupal_process_states()
parent 5be2df42
......@@ -9,13 +9,13 @@
*/
use Drupal\Component\Gettext\PoItem;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Bytes;
use Drupal\Component\Utility\Environment;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SortArray;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Form\FormHelper;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\Element\Link;
use Drupal\Core\Render\HtmlResponseAttachmentsProcessor;
......@@ -588,16 +588,15 @@ function drupal_js_defaults($data = NULL) {
* @param $elements
* A renderable array element having a #states property as described above.
*
* @see form_example_states_form()
* @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
* \Drupal\Core\Form\FormHelper::processStates() instead.
*
* @see https://www.drupal.org/node/3000069
* @see \Drupal\Core\Form\FormHelper::processStates()
*/
function drupal_process_states(&$elements) {
$elements['#attached']['library'][] = 'core/drupal.states';
// Elements of '#type' => 'item' are not actual form input elements, but we
// still want to be able to show/hide them. Since there's no actual HTML input
// element available, setting #attributes does not make sense, but a wrapper
// is available, so setting #wrapper_attributes makes it work.
$key = ($elements['#type'] == 'item') ? '#wrapper_attributes' : '#attributes';
$elements[$key]['data-drupal-states'] = Json::encode($elements['#states']);
@trigger_error('drupal_process_states() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Form\FormHelper::processStates() instead. See https://www.drupal.org/node/3000069', E_USER_DEPRECATED);
FormHelper::processStates($elements);
}
/**
......
......@@ -2,6 +2,7 @@
namespace Drupal\Core\Form;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Render\Element;
/**
......@@ -12,16 +13,20 @@
class FormHelper {
/**
* Rewrite #states selectors.
* Rewrites #states selectors in a render element.
*
* When a structure of elements is being altered, their HTML selectors may
* change. In such cases calling this method will check if there are any
* states in element and its children, and rewrite selectors in those states.
*
* @param array $elements
* A renderable array element having a #states property.
* A render array element having a #states property.
* @param string $search
* A partial or entire jQuery selector string to replace in #states.
* @param string $replace
* The string to replace all instances of $search with.
*
* @see drupal_process_states()
* @see self::processStates()
*/
public static function rewriteStatesSelector(array &$elements, $search, $replace) {
if (!empty($elements['#states'])) {
......@@ -35,7 +40,10 @@ public static function rewriteStatesSelector(array &$elements, $search, $replace
}
/**
* Helper function for self::rewriteStatesSelector().
* Helps recursively rewrite #states selectors.
*
* Not to be confused with self::processStates(), which just prepares states
* for rendering.
*
* @param array $conditions
* States conditions array.
......@@ -43,6 +51,8 @@ public static function rewriteStatesSelector(array &$elements, $search, $replace
* A partial or entire jQuery selector string to replace in #states.
* @param string $replace
* The string to replace all instances of $search with.
*
* @see self::rewriteStatesSelector()
*/
protected static function processStatesArray(array &$conditions, $search, $replace) {
// Retrieve the keys to make it easy to rename a key without changing the
......@@ -67,4 +77,136 @@ protected static function processStatesArray(array &$conditions, $search, $repla
}
}
/**
* Adds JavaScript to change the state of an element based on another element.
*
* A "state" means a certain property of a DOM element, such as "visible" or
* "checked", which depends on a state or value of another element on the
* page. In general, states are HTML attributes and DOM element properties,
* which are applied initially, when page is loaded, depending on elements'
* default values, and then may change due to user interaction.
*
* Since states are driven by JavaScript only, it is important to understand
* that all states are applied on presentation only, none of the states force
* any server-side logic, and that they will not be applied for site visitors
* without JavaScript support. All modules implementing states have to make
* sure that the intended logic also works without JavaScript being enabled.
*
* #states is an associative array in the form of:
* @code
* [
* STATE1 => CONDITIONS_ARRAY1,
* STATE2 => CONDITIONS_ARRAY2,
* ...
* ]
* @endcode
* Each key is the name of a state to apply to the element, such as 'visible'.
* Each value is a list of conditions that denote when the state should be
* applied.
*
* Multiple different states may be specified to act on complex conditions:
* @code
* [
* 'visible' => CONDITIONS,
* 'checked' => OTHER_CONDITIONS,
* ]
* @endcode
*
* Every condition is a key/value pair, whose key is a jQuery selector that
* denotes another element on the page, and whose value is an array of
* conditions, which must bet met on that element:
* @code
* [
* 'visible' => [
* JQUERY_SELECTOR => REMOTE_CONDITIONS,
* JQUERY_SELECTOR => REMOTE_CONDITIONS,
* ...
* ],
* ]
* @endcode
* All conditions must be met for the state to be applied.
*
* Each remote condition is a key/value pair specifying conditions on the
* other element that need to be met to apply the state to the element:
* @code
* [
* 'visible' => [
* ':input[name="remote_checkbox"]' => ['checked' => TRUE],
* ],
* ]
* @endcode
*
* For example, to show a textfield only when a checkbox is checked:
* @code
* $form['toggle_me'] = [
* '#type' => 'checkbox',
* '#title' => t('Tick this box to type'),
* ];
* $form['settings'] = [
* '#type' => 'textfield',
* '#states' => [
* // Only show this field when the 'toggle_me' checkbox is enabled.
* 'visible' => [
* ':input[name="toggle_me"]' => ['checked' => TRUE],
* ],
* ],
* ];
* @endcode
*
* The following states may be applied to an element:
* - enabled
* - disabled
* - required
* - optional
* - visible
* - invisible
* - checked
* - unchecked
* - expanded
* - collapsed
*
* The following states may be used in remote conditions:
* - empty
* - filled
* - checked
* - unchecked
* - expanded
* - collapsed
* - value
*
* The following states exist for both elements and remote conditions, but are
* not fully implemented and may not change anything on the element:
* - relevant
* - irrelevant
* - valid
* - invalid
* - touched
* - untouched
* - readwrite
* - readonly
*
* When referencing select lists and radio buttons in remote conditions, a
* 'value' condition must be used:
* @code
* '#states' => [
* // Show the settings if 'bar' has been selected for 'foo'.
* 'visible' => [
* ':input[name="foo"]' => ['value' => 'bar'],
* ],
* ],
* @endcode
*
* @param array $elements
* A render array element having a #states property as described above.
*/
public static function processStates(array &$elements) {
$elements['#attached']['library'][] = 'core/drupal.states';
// Elements of '#type' => 'item' are not actual form input elements, but we
// still want to be able to show/hide them. Since there's no actual HTML
// input element available, setting #attributes does not make sense, but a
// wrapper is available, so setting #wrapper_attributes makes it work.
$key = ($elements['#type'] == 'item') ? '#wrapper_attributes' : '#attributes';
$elements[$key]['data-drupal-states'] = Json::encode($elements['#states']);
}
}
......@@ -60,7 +60,7 @@
* - #required: (bool) Whether or not input is required on the element.
* - #states: (array) Information about JavaScript states, such as when to
* hide or show the element based on input on other elements.
* See drupal_process_states() for documentation.
* See \Drupal\Core\Form\FormHelper::processStates() for documentation.
* - #title: (string) Title of the form element. Should be translated.
* - #title_display: (string) Where and how to display the #title. Possible
* values:
......
......@@ -9,6 +9,7 @@
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Controller\ControllerResolverInterface;
use Drupal\Core\Form\FormHelper;
use Drupal\Core\Render\Element\RenderCallbackInterface;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\Core\Security\DoTrustedCallbackTrait;
......@@ -394,7 +395,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
// Add any JavaScript state information associated with the element.
if (!empty($elements['#states'])) {
drupal_process_states($elements);
FormHelper::processStates($elements);
}
// Get the children of the element, sorted by weight.
......
......@@ -219,7 +219,7 @@ public function renderPlaceholder($placeholder, array $elements);
* prepended to #children.
* - If this element has #states defined then JavaScript state information
* is added to this element's #attached attribute by
* drupal_process_states().
* \Drupal\Core\Form\FormHelper::processStates().
* - If this element has #attached defined then any required libraries,
* JavaScript, CSS, or other custom data are added to the current page by
* \Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments().
......@@ -328,7 +328,7 @@ public function renderPlaceholder($placeholder, array $elements);
*
* @see \Drupal\Core\Render\ElementInfoManagerInterface::getInfo()
* @see \Drupal\Core\Theme\ThemeManagerInterface::render()
* @see drupal_process_states()
* @see \Drupal\Core\Form\FormHelper::processStates()
* @see \Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments()
* @see \Drupal\Core\Render\RendererInterface::renderRoot()
*/
......
......@@ -2,6 +2,7 @@
namespace Drupal\KernelTests\Core\Render;
use Drupal\Core\Form\FormHelper;
use Drupal\Core\Render\HtmlResponseAttachmentsProcessor;
use Drupal\KernelTests\KernelTestBase;
......@@ -37,4 +38,47 @@ public function providerAttributes() {
];
}
/**
* Tests deprecation of the drupal_process_states() function.
*
* @dataProvider providerElements
*
* @expectedDeprecation drupal_process_states() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Form\FormHelper::processStates() instead. See https://www.drupal.org/node/3000069
*/
public function testDrupalProcessStates($elements) {
// Clone elements because processing changes array.
$expected = $elements;
drupal_process_states($expected);
FormHelper::processStates($elements);
$this->assertEquals($expected, $elements);
}
/**
* Provides a list of elements to test.
*/
public function providerElements() {
return [
[
[
'#type' => 'date',
'#states' => [
'visible' => [
':input[name="toggle_me"]' => ['checked' => TRUE],
],
],
],
],
[
[
'#type' => 'item',
'#states' => [
'visible' => [
':input[name="foo"]' => ['value' => 'bar'],
],
],
],
],
];
}
}
......@@ -2,6 +2,7 @@
namespace Drupal\Tests\Core\Form;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Form\FormHelper;
use Drupal\Tests\UnitTestCase;
......@@ -82,4 +83,45 @@ public function testRewriteStatesSelector() {
$this->assertSame($expected, $form, 'The #states selectors were properly rewritten.');
}
/**
* @covers ::processStates
* @dataProvider providerElements
*/
public function testProcessStates($elements, $key) {
$json = Json::encode($elements['#states']);
FormHelper::processStates($elements);
$this->assertEquals(['core/drupal.states'], $elements['#attached']['library']);
$this->assertEquals($json, $elements[$key]['data-drupal-states']);
}
/**
* Provides a list of elements to test.
*/
public function providerElements() {
return [
[
[
'#type' => 'date',
'#states' => [
'visible' => [
':input[name="toggle_me"]' => ['checked' => TRUE],
],
],
],
'#attributes',
],
[
[
'#type' => 'item',
'#states' => [
'visible' => [
':input[name="foo"]' => ['value' => 'bar'],
],
],
],
'#wrapper_attributes',
],
];
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment