]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - vendor/oojs/oojs-ui/php/layouts/FieldLayout.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / vendor / oojs / oojs-ui / php / layouts / FieldLayout.php
1 <?php
2
3 namespace OOUI;
4
5 /**
6  * Layout made of a field and optional label.
7  *
8  * Available label alignment modes include:
9  *  - left: Label is before the field and aligned away from it, best for when the user will be
10  *    scanning for a specific label in a form with many fields
11  *  - right: Label is before the field and aligned toward it, best for forms the user is very
12  *    familiar with and will tab through field checking quickly to verify which field they are in
13  *  - top: Label is before the field and above it, best for when the user will need to fill out all
14  *    fields from top to bottom in a form with few fields
15  *  - inline: Label is after the field and aligned toward it, best for small boolean fields like
16  *    checkboxes or radio buttons
17  */
18 class FieldLayout extends Layout {
19         use LabelElement;
20         use TitledElement;
21
22         /**
23          * Alignment.
24          *
25          * @var string
26          */
27         protected $align;
28
29         /**
30          * Field widget to be laid out.
31          *
32          * @var Widget
33          */
34         protected $fieldWidget;
35
36         /**
37          * Error messages.
38          *
39          * @var array
40          */
41         protected $errors;
42
43         /**
44          * Notice messages.
45          *
46          * @var array
47          */
48         protected $notices;
49
50         /**
51          * @var ButtonWidget|string
52          */
53         protected $help;
54
55         protected $field, $header, $body, $messages;
56
57         /**
58          * @param Widget $fieldWidget Field widget
59          * @param array $config Configuration options
60          * @param string $config['align'] Alignment mode, either 'left', 'right', 'top' or 'inline'
61          *   (default: 'left')
62          * @param array $config['errors'] Error messages about the widget, as strings or HtmlSnippet
63          *   instances.
64          * @param array $config['notices'] Notices about the widget, as strings or HtmlSnippet instances.
65          * @param string|HtmlSnippet $config['help'] Explanatory text shown as a '?' icon.
66          * @throws Exception An exception is thrown if no widget is specified
67          */
68         public function __construct( $fieldWidget, array $config = [] ) {
69                 // Allow passing positional parameters inside the config array
70                 if ( is_array( $fieldWidget ) && isset( $fieldWidget['fieldWidget'] ) ) {
71                         $config = $fieldWidget;
72                         $fieldWidget = $config['fieldWidget'];
73                 }
74
75                 // Make sure we have required constructor arguments
76                 if ( $fieldWidget === null ) {
77                         throw new Exception( 'Widget not found' );
78                 }
79
80                 // Config initialization
81                 $config = array_merge( [ 'align' => 'left' ], $config );
82
83                 // Parent constructor
84                 parent::__construct( $config );
85
86                 // Properties
87                 $this->fieldWidget = $fieldWidget;
88                 $this->errors = isset( $config['errors'] ) ? $config['errors'] : [];
89                 $this->notices = isset( $config['notices'] ) ? $config['notices'] : [];
90                 $this->field = $this->isFieldInline() ? new Tag( 'span' ) : new Tag( 'div' );
91                 $this->messages = new Tag( 'ul' );
92                 $this->header = new Tag( 'span' );
93                 $this->body = new Tag( 'div' );
94                 if ( isset( $config['help'] ) ) {
95                         $this->help = new ButtonWidget( [
96                                 'classes' => [ 'oo-ui-fieldLayout-help' ],
97                                 'framed' => false,
98                                 'icon' => 'info',
99                                 'title' => $config['help'],
100                         ] );
101                 } else {
102                         $this->help = '';
103                 }
104
105                 // Traits
106                 $this->initializeLabelElement( array_merge( $config, [
107                         'labelElement' => new Tag( 'label' )
108                 ] ) );
109                 $this->initializeTitledElement(
110                         array_merge( $config, [ 'titled' => $this->label ] ) );
111
112                 // Initialization
113                 if ( $this->fieldWidget->getInputId() ) {
114                         $this->label->setAttributes( [ 'for' => $this->fieldWidget->getInputId() ] );
115                 }
116                 $this
117                         ->addClasses( [ 'oo-ui-fieldLayout' ] )
118                         ->toggleClasses( [ 'oo-ui-fieldLayout-disabled' ], $this->fieldWidget->isDisabled() )
119                         ->appendContent( $this->body );
120                 if ( count( $this->errors ) || count( $this->notices ) ) {
121                         $this->appendContent( $this->messages );
122                 }
123                 $this->body->addClasses( [ 'oo-ui-fieldLayout-body' ] );
124                 $this->header->addClasses( [ 'oo-ui-fieldLayout-header' ] );
125                 $this->messages->addClasses( [ 'oo-ui-fieldLayout-messages' ] );
126                 $this->field
127                         ->addClasses( [ 'oo-ui-fieldLayout-field' ] )
128                         ->appendContent( $this->fieldWidget );
129
130                 foreach ( $this->notices as $text ) {
131                         $this->messages->appendContent( $this->makeMessage( 'notice', $text ) );
132                 }
133                 foreach ( $this->errors as $text ) {
134                         $this->messages->appendContent( $this->makeMessage( 'error', $text ) );
135                 }
136
137                 $this->setAlignment( $config['align'] );
138                 // Call this again to take into account the widget's accessKey
139                 $this->updateTitle();
140         }
141
142         /**
143          * @param string $kind 'error' or 'notice'
144          * @param string|HtmlSnippet $text
145          * @return Tag
146          */
147         private function makeMessage( $kind, $text ) {
148                 $listItem = new Tag( 'li' );
149                 if ( $kind === 'error' ) {
150                         $icon = new IconWidget( [ 'icon' => 'alert', 'flags' => [ 'warning' ] ] );
151                         $listItem->setAttributes( [ 'role' => 'alert' ] );
152                 } elseif ( $kind === 'notice' ) {
153                         $icon = new IconWidget( [ 'icon' => 'info' ] );
154                 } else {
155                         $icon = null;
156                 }
157                 $message = new LabelWidget( [ 'label' => $text ] );
158                 $listItem
159                         ->appendContent( $icon, $message )
160                         ->addClasses( [ "oo-ui-fieldLayout-messages-$kind" ] );
161                 return $listItem;
162         }
163
164         /**
165          * Get the field.
166          *
167          * @return Widget Field widget
168          */
169         public function getField() {
170                 return $this->fieldWidget;
171         }
172
173         /**
174          * Return `true` if the given field widget can be used with `'inline'` alignment (see
175          * setAlignment()). Return `false` if it can't or if this can't be determined.
176          *
177          * @return bool
178          */
179         public function isFieldInline() {
180                 // This is very simplistic, but should be good enough. It's important to avoid false positives,
181                 // as that will cause the generated HTML to be invalid and go all out of whack when parsed.
182                 return strtolower( $this->getField()->getTag() ) === 'span';
183         }
184
185         /**
186          * Set the field alignment mode.
187          *
188          * @param string $value Alignment mode, either 'left', 'right', 'top' or 'inline'
189          * @return $this
190          */
191         protected function setAlignment( $value ) {
192                 if ( $value !== $this->align ) {
193                         // Default to 'left'
194                         if ( !in_array( $value, [ 'left', 'right', 'top', 'inline' ] ) ) {
195                                 $value = 'left';
196                         }
197                         // Validate
198                         if ( $value === 'inline' && !$this->isFieldInline() ) {
199                                 $value = 'top';
200                         }
201                         // Reorder elements
202                         $this->body->clearContent();
203                         if ( $value === 'top' ) {
204                                 $this->header->appendContent( $this->label, $this->help );
205                                 $this->body->appendContent( $this->header, $this->field );
206                         } elseif ( $value === 'inline' ) {
207                                 $this->header->appendContent( $this->label, $this->help );
208                                 $this->body->appendContent( $this->field, $this->header );
209                         } else {
210                                 $this->header->appendContent( $this->label );
211                                 $this->body->appendContent( $this->header, $this->help, $this->field );
212                         }
213                         // Set classes. The following classes can be used here:
214                         // * oo-ui-fieldLayout-align-left
215                         // * oo-ui-fieldLayout-align-right
216                         // * oo-ui-fieldLayout-align-top
217                         // * oo-ui-fieldLayout-align-inline
218                         if ( $this->align ) {
219                                 $this->removeClasses( [ 'oo-ui-fieldLayout-align-' . $this->align ] );
220                         }
221                         $this->addClasses( [ 'oo-ui-fieldLayout-align-' . $value ] );
222                         $this->align = $value;
223                 }
224
225                 return $this;
226         }
227
228         /**
229          * Include information about the widget's accessKey in our title. TitledElement calls this method.
230          * (This is a bit of a hack.)
231          *
232          * @param string $title Tooltip label for 'title' attribute
233          * @return string
234          */
235         protected function formatTitleWithAccessKey( $title ) {
236                 if ( $this->fieldWidget && method_exists( $this->fieldWidget, 'formatTitleWithAccessKey' ) ) {
237                         return $this->fieldWidget->formatTitleWithAccessKey( $title );
238                 }
239                 return $title;
240         }
241
242         public function getConfig( &$config ) {
243                 $config['fieldWidget'] = $this->fieldWidget;
244                 $config['align'] = $this->align;
245                 $config['errors'] = $this->errors;
246                 $config['notices'] = $this->notices;
247                 if ( $this->help !== '' ) {
248                         $config['help'] = $this->help->getTitle();
249                 }
250                 return parent::getConfig( $config );
251         }
252 }