4 * Contains \Drupal\bootstrap\Plugin\ProcessManager.
7 namespace Drupal\bootstrap\Plugin;
9 use Drupal\bootstrap\Bootstrap;
10 use Drupal\bootstrap\Theme;
11 use Drupal\bootstrap\Utility\Element;
12 use Drupal\Component\Utility\NestedArray;
13 use Drupal\Core\Form\FormStateInterface;
16 * Manages discovery and instantiation of Bootstrap form process callbacks.
18 * @ingroup plugins_process
20 class ProcessManager extends PluginManager {
23 * Constructs a new \Drupal\bootstrap\Plugin\ProcessManager object.
25 * @param \Drupal\bootstrap\Theme $theme
26 * The theme to use for discovery.
28 public function __construct(Theme $theme) {
29 parent::__construct($theme, 'Plugin/Process', 'Drupal\bootstrap\Plugin\Process\ProcessInterface', 'Drupal\bootstrap\Annotation\BootstrapProcess');
30 $this->setCacheBackend(\Drupal::cache('discovery'), 'theme:' . $theme->getName() . ':process', $this->getCacheTags());
34 * Global #process callback for form elements.
36 * @param array $element
37 * The element render array.
38 * @param \Drupal\Core\Form\FormStateInterface $form_state
39 * The current state of the form.
40 * @param array $complete_form
41 * The complete form structure.
44 * The altered element array.
46 * @see \Drupal\bootstrap\Plugin\Alter\ElementInfo::alter
48 public static function process(array $element, FormStateInterface $form_state, array &$complete_form) {
49 if (!empty($element['#bootstrap_ignore_process'])) {
55 $theme = Bootstrap::getTheme();
58 $e = Element::create($element, $form_state);
61 if (($e->getProperty('ajax') && !$e->isButton()) || $e->getProperty('autocomplete_route_name')) {
62 static::processAjax($e, $form_state, $complete_form);
65 // Add "form-inline" class.
66 if ($e->hasClass('container-inline')) {
67 $e->replaceClass('container-inline', 'form-inline');
69 if ($e->isType(['color', 'date', 'number', 'range', 'tel', 'weight'])) {
70 $e->addClass('form-inline', 'wrapper_attributes');
73 // Process input groups.
74 if ($e->getProperty('input') && ($e->getProperty('input_group') || $e->getProperty('input_group_button'))) {
75 static::processInputGroups($e, $form_state, $complete_form);
82 * Processes elements with AJAX properties.
84 * @param \Drupal\bootstrap\Utility\Element $element
86 * @param \Drupal\Core\Form\FormStateInterface $form_state
87 * The current state of the form.
88 * @param array $complete_form
89 * The complete form structure.
91 public static function processAjax(Element $element, FormStateInterface $form_state, array &$complete_form) {
92 $ajax = $element->getProperty('ajax');
94 // Show throbber AJAX requests in an input button group.
95 if (!$element->isType('hidden') && (!isset($ajax['progress']['type']) || $ajax['progress']['type'] === 'throbber')) {
96 // Use an icon for autocomplete "throbber".
97 $icon = Bootstrap::glyphicon('refresh');
98 $element->appendProperty('field_suffix', Element::create($icon)->addClass(['ajax-progress', 'ajax-progress-throbber']));
99 $element->setProperty('input_group', TRUE);
104 * Processes elements that have input groups.
106 * @param \Drupal\bootstrap\Utility\Element $element
107 * The element object.
108 * @param \Drupal\Core\Form\FormStateInterface $form_state
109 * The current state of the form.
110 * @param array $complete_form
111 * The complete form structure.
113 protected static function processInputGroups(Element $element, FormStateInterface $form_state, array &$complete_form) {
114 // Automatically inject the nearest button found after this element if
115 // #input_group_button exists.
116 if ($element->getProperty('input_group_button')) {
117 // Obtain the parent array to limit search.
118 $array_parents = $element->getProperty('array_parents', []);
120 // Remove the current element from the array.
121 array_pop($array_parents);
123 // Retrieve the parent element.
124 $parent = Element::create(NestedArray::getValue($complete_form, $array_parents), $form_state);
126 // Find the closest button.
127 if ($button = self::findButton($parent)) {
128 // Since this button is technically being "moved", it needs to be
129 // rendered now, so it doesn't get printed twice (in the original spot).
130 $element->appendProperty('field_suffix', $button->setIcon()->render());
134 $input_group_attributes = ['class' => ['input-group-' . ($element->getProperty('input_group_button') ? 'btn' : 'addon')]];
135 if ($prefix = $element->getProperty('field_prefix')) {
136 $element->setProperty('field_prefix', [
137 '#type' => 'html_tag',
139 '#attributes' => $input_group_attributes,
140 '#value' => Element::create($prefix)->renderPlain(),
144 if ($suffix = $element->getProperty('field_suffix')) {
145 $element->setProperty('field_suffix', [
146 '#type' => 'html_tag',
148 '#attributes' => $input_group_attributes,
149 '#value' => Element::create($suffix)->renderPlain(),
156 * Traverses an element to find the closest button.
158 * @param \Drupal\bootstrap\Utility\Element $element
159 * The element to iterate over.
161 * @return \Drupal\bootstrap\Utility\Element|FALSE
162 * The first button element or FALSE if no button could be found.
164 protected static function &findButton(Element $element) {
166 foreach ($element->children() as $child) {
167 if ($child->isButton()) {
170 if ($result = &self::findButton($child)) {