A widget is a representation of an HTML input element. The widget handles the rendering of the HTML, and the extraction of data from a data object that corresponds to how the widget's values(s) would be submitted by a form.
Tip
Widgets should not be confused with the form fields </fields>
. Form fields deal with the logic of input validation and are used directly in templates. Widgets deal with rendering of HTML form input elements on the web page and extraction of raw submitted data. However, widgets do need to be assigned <widget-to-field>
to form fields.
Whenever you specify a field on a form, newforms will use a default widget that is appropriate to the type of data that is to be displayed. To find which widget is used on which field, see the documentation about Build-in Field types <ref-built-in-field-types>
.
However, if you want to use a different widget for a field, you can just use the widget
argument on the field definition. For example:
var CommentForm = forms.Form.extend({
name: forms.CharField()
, url: forms.URLField()
, comment: forms.CharField({widget: forms.Textarea})
})
This would specify a form with a comment that uses a larger :jsTextarea
widget, rather than the default :jsTextInput
widget.
Many widgets have optional extra arguments; they can be set when defining the widget on the field. In the following example, we set additional HTML attributes to be added to the TextArea
to control its display:
var CommentForm = forms.Form.extend({
name: forms.CharField()
, url: forms.URLField()
, comment: forms.CharField({
widget: forms.Textarea({attrs: {rows: 6, cols: 60}})
})
})
See the built-in widgets
for more information about which widgets are available and which arguments they accept.
Widgets inheriting from the :jsSelect
widget deal with choices. They present the user with a list of options to choose from. The different widgets present this choice differently; the :jsSelect
widget itself uses a <select>
HTML list representation, while :jsRadioSelect
uses radio buttons.
:jsSelect
widgets are used by default on :jsChoiceField
fields. The choices displayed on the widget are inherited from the :jsChoiceField
and setting new choices with :jsChoiceField#setChoices
will update Select.choices
. For example:
var CHOICES = [['1', 'First'], ['2', 'Second']]
var field = forms.ChoiceField({choices: CHOICES, widget: forms.RadioSelect})
print(field.choices())
// => [['1', 'First'], ['2', 'Second']]
print(field.widget.choices
// => [['1', 'First'], ['2', 'Second']]
field.widget.choices = []
field.setChoices([['1', 'First and only']])
print(field.widget.choices)
// => [['1', 'First and only']]
Widgets which offer a choices
property can however be used with fields which are not based on choice -- such as a :jsCharField
-- but it is recommended to use a :jsChoiceField
-based field when the choices are inherent to the model and not just the representational widget.
Widgets are rendered with minimal markup - by default there are no CSS class names applied, or any other widget-specific attributes. This means, for example, that all :jsTextInput
widgets will appear the same on your pages.
If you want to make one widget instance look different from another, you will need to specify additional attributes at the time when the widget object is instantiated and assigned to a form field (and perhaps add some rules to your CSS files).
For example, take the following simple form:
var CommentForm = forms.Form.extend({
name: forms.CharField()
, url: forms.URLField()
, comment: forms.CharField()
})
This form will include three default :jsTextInput
widgets, with default rendering -- no CSS class, no extra attributes. This means that the input boxes provided for each widget will be rendered exactly the same:
var f = new CommentForm({autoId: false})
print(reactHTML(f.asTable()))
/* =>
<tr><th>Name:</th><td><input type="text" name="name"></td></tr>
<tr><th>Url:</th><td><input type="url" name="url"></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment"></td></tr>
*/
On a real Web page, you probably don't want every widget to look the same. You might want a larger input element for the comment, and you might want the 'name' widget to have some special CSS class. It is also possible to specify the 'type' attribute to take advantage of the new HTML5 input types. To do this, you use the Widget.attrs
argument when creating the widget:
var CommentForm = forms.Form.extend({
name: forms.CharField({
widget: forms.TextInput({attrs: {className: 'special'}})
})
, url: forms.URLField()
, comment: forms.CharField({widget: forms.TextInput({attrs: {size: '40'}})
})
Note
Widgets are rendered as ReactElement
objects -- in the example above, we used className
instead of class
as React has standardised on the JavaScript-safe versions of attribute names, which avoid conflicting with JavaScript reserved words.
The extra attributes will then be included in the rendered output:
var f = new CommentForm({autoId: false})
print(reactHTML(f.asTable()))
/* =>
<tr><th>Name:</th><td><input class="special" type="text" name="name"></td></tr>
<tr><th>Url:</th><td><input type="url" name="url"></td></tr>
<tr><th>Comment:</th><td><input size="40" type="text" name="comment"></td></tr>
*/
You can also set the HTML id
using Widget.attrs
.
Base widgets :jsWidget
and :jsMultiWidget
are extended by all the built-in widgets <built-in widgets>
and may serve as a foundation for custom widgets.
This abstract widget cannot be rendered, but provides the basic attribute
Widget.attrs
. You may also implement or override therender()
method on custom widgets.
- :js
widget.attrs
An object containing HTML attributes to be set on the rendered widget:
var name = forms.TextInput({attrs: {size:10, title: 'Your name'}}) print(reactHTML(name.render('name', 'A name'))) // => <input size="10" title="Your name" type="text" name="name" value="A name">"
Key Widget methods are:
- :js
Widget#render
Returns a
ReactElement
representation of the widget. This method must be implemented by extending widgets, or anError
will be thrown.The 'value' given is not guaranteed to be valid input, therefore extending widgets should program defensively.
- :js
Widget#valueFromData
Given an object containing input data and this widget's name, returns the value of this widget. Returns
null
if a value wasn't provided.
A widget that is composed of multiple widgets. :jsMultiWidget
works hand in hand with the :jsMultiValueField
.
MultiWidget
has one required argument:
- MultiWidget.widgets
A list containing the widgets needed.
And one required method:
- :js
MultiWidget#decompress
This method takes a single "compressed" value from the field and returns a list of "decompressed" values. The input value can be assumed valid, but not necessarily non-empty.
This method must be implemented by the widgets extending
MultiWidget
, and since the value may be empty, the implementation must be defensive.The rationale behind "decompression" is that it is necessary to "split" the combined value of the form field into the values for each widget.
An example of this is how :js
SplitDateTimeWidget
turns aDate
value into a list with date and time split into two separate values.Tip
Note that :js
MultiValueField
has a complementary method :jsMultiValueField#compress
with the opposite responsibility - to combine cleaned values of all member fields into one.
Other methods that may be useful to implement include:
- :js
MultiWidget#render
The
value
argument must be handled differently in this method then in :jsWidget#render
because it has to figure out how to split a single value for display in multiple widgets.The
value
argument used when rendering can be one of two things:
- A list.
- A single value (e.g., a string) that is the "compressed" representation of a list of values.
If
value
is a list, the output of :jsMultiWidget#render
will be a concatenation of rendered child widgets. Ifvalue
is not a list, it will first be processed by the method :jsMultiWidget#decompress
to create the list and then rendered.When
render()
runs, each value in the list is rendered with the corresponding widget -- the first value is rendered in the first widget, the second value is rendered in the second widget, etc.Unlike in the single value widgets,
render()
doesn't have to be implemented by extending widgets.- :js
MultiWidget#formatOutput
Given a list of rendered widgets (as
ReactElement
objects), returns the list or aReactElement
object containing the widgets. This hook allows you to lay out the widgets any way you'd like.
Here's an example widget which extends :jsMultiWidget
to display a date with the day, month, and year in different select boxes. This widget is intended to be used with a :jsDateField
rather than a :jsMultiValueField
, so we've implemented :jsWidget#valueFromData
:
var DateSelectorWidget = forms.MultiWidget.extend({
constructor: function(kwargs) {
kwargs = extend({attrs: {}}, kwargs)
widgets = [
forms.Select({choices: range(1, 32), attrs: kwargs.attrs})
, forms.Select({choices: range(1, 13), attrs: kwargs.attrs})
, forms.Select({choices: range(2012, 2017), attrs: kwargs.attrs})
]
forms.MultiWidget.call(this, widgets, kwargs)
}
, decompress: function(value) {
if (value instanceof Date) {
return [value.getDate(),
value.getMonth() + 1, // Make month 1-based for display
value.getFullYear()]
}
return [null, null, null]
}
, formatOutput: function(renderedWidgets) {
return React.createElement('div', null, renderedWidgets)
}
, valueFromData: function(data, files, name) {
var parts = this.widgets.map(function(widget, i) {
return widget.valueFromData(data, files, name + '_' + i)
})
parts.reverse() // [d, m, y] => [y, m, d]
return parts.join('-')
}
})
The constructor creates several :jsSelect
widgets in a list. The "super" constructor uses this list to setup the widget.
The :jsMultiWidget#formatOutput
method is fairly vanilla here (in fact, it's the same as what's been implemented as the default for MultiWidget
), but the idea is that you could add custom HTML between the widgets should you wish.
The required method :jsMultiWidget#decompress
breaks up a Date
value into the day, month, and year values corresponding to each widget. Note how the method handles the case where value
is null
.
The default implementation of :jsWidget#valueFromData
returns a list of values corresponding to each Widget
. This is appropriate when using a MultiWidget
with a :jsMultiValueField
, but since we want to use this widget with a :jsDateField
which takes a single value, we have overridden this method to combine the data of all the subwidgets into a 'yyyy-mm-dd'
formatted date string and returns it for validation by the :jsDateField
.
Newforms provides a representation of all the basic HTML widgets, plus some commonly used groups of widgets, including the input of text <text-widgets>
, various checkboxes and selectors <selector-widgets>
, uploading files <file-upload-widgets>
, and handling of multi-valued input <composite-widgets>
.
These widgets make use of the HTML elements <input>
and <textarea>
.
Text input:
<input type="text" ...>
Text input:
<input type="number" ...>
Text input:
<input type="email" ...>
Text input:
<input type="url" ...>
Password input:
<input type='password' ...>
Takes one optional argument:
PasswordInput.renderValue
Determines whether the widget will have a value filled in when the form is re-displayed after a validation error (default is
false
).
Text area:
<textarea>...</textarea>
:jsHiddenInput
Hidden input:
<input type='hidden' ...>
Note that there also is a :js
MultipleHiddenInput
widget that encapsulates a set of hidden input elements.
Date input as a simple text box:
<input type='text' ...>
Takes same arguments as :js
TextInput
, with one more optional argument:
DateInput.format
The format in which this field's initial value will be displayed.
If no
format
argument is provided, the default format is the first format found in the current locale'sDATE_INPUT_FORMATS <ref_locale_items_table>
.
Date/time input as a simple text box:
<input type='text' ...>
Takes same arguments as :js
TextInput
, with one more optional argument:
DateTimeInput.format
The format in which this field's initial value will be displayed.
If no
format
argument is provided, the default format is the first format found in the current locale'sDATETIME_INPUT_FORMATS <ref_locale_items_table>
.
Time input as a simple text box:
<input type='text' ...>
Takes same arguments as :js
TextInput
, with one more optional argument:
TimeInput.format
The format in which this field's initial value will be displayed.
If no
format
argument is provided, the default format is the first format found in the current locale'sTIME_INPUT_FORMATS <ref_locale_items_table>
.
Checkbox:
<input type='checkbox' ...>
Takes one optional argument:
CheckboxInput.checkTest
A function that takes the value of the CheckBoxInput and returns
true
if the checkbox should be checked for that value.
Select widget:
<select><option ...>...</select>
Select.choices
This attribute is optional when the form field does not have a
choices
attribute. If it does, it will override anything you set here when the attribute is updated on the :jsField
.
Select widget with options 'Unknown', 'Yes' and 'No'
Similar to
Select
, but allows multiple selection:<select multiple='multiple'>...</select>
Similar to
Select
, but rendered as a list of radio buttons within<li>
tags:<ul> <li><input type='radio' ...></li> ... </ul>For more granular control over the generated markup, you can loop over the radio buttons. Assuming a form
myform
with a fieldbeatles
that uses aRadioSelect
as its widget:myForm.boundField('beatles').subWidgets().map(function(radio) { return <div className="myRadio">{radio.render()}</div> })
This would generate the following HTML:
<div class="myRadio"> <label for="id_beatles_0"><input id="id_beatles_0" type="radio" name="beatles" value="john"><span> </span><span>John</span></label> </div> <div class="myRadio"> <label for="id_beatles_1"><input id="id_beatles_1" type="radio" name="beatles" value="paul"><span> </span><span>Paul</span></label> </div> <div class="myRadio"> <label for="id_beatles_2"><input id="id_beatles_2" type="radio" name="beatles" value="george"><span> </span><span>George</span></label> </div> <div class="myRadio"> <label for="id_beatles_3"><input id="id_beatles_3" type="radio" name="beatles" value="ringo"><span> </span><span>Ringo</span></label> </div>That included the
<label>
tags. To get more granular, you can use each radio button'stag()
,choiceLabel
andidForLabel()
. For example, this code...:myForm.boundField('beatles').subWidgets().map(function(radio) { return <label htmlFor={radio.idForLabel()}> {radio.choiceLabel} <span className="radio">{radio.tag()}</span> </label> })
...will result in the following HTML:
<label for="id_beatles_0"> <span>John</span> <span class="radio"><input id="id_beatles_0" type="radio" name="beatles" value="john"></span> </label> <label for="id_beatles_1"> <span>Paul</span> <span class="radio"><input id="id_beatles_1" type="radio" name="beatles" value="paul"></span> </label> <label for="id_beatles_2"> <span>George</span> <span class="radio"><input id="id_beatles_2" type="radio" name="beatles" value="george"></span> </label> <label for="id_beatles_3"> <span>Ringo</span> <span class="radio"><input id="id_beatles_3" type="radio" name="beatles" value="ringo"></span> </label>If you decide not to loop over the radio buttons -- e.g., if your layout simply renders the
beatles
BoundField
-- they'll be output in a<ul>
with<li>
tags, as above.
Similar to :js
SelectMultiple
, but rendered as a list of check buttons:<ul> <li><input type='checkbox' ...></li> ... </ul>Like :js
RadioSelect
, you can loop over the individual checkboxes making up the lists.
File upload input:
<input type='file' ...>
File upload input:
<input type='file' ...>
, with an additional checkbox input to clear the field's value, if the field is not required and has initial data.
:jsMultipleHiddenInput
Multiple
<input type='hidden' ...>
widgets.A widget that handles multiple hidden widgets for fields that have a list of values.
MultipleHiddenInput.choices
This attribute is optional when the form field does not have a
choices
attribute. If it does, it will override anything you set here when the attribute is updated on the :jsField
.
Wrapper (using :js
MultiWidget
) around two widgets: :jsDateInput
for the date, and :jsTimeInput
for the time.
SplitDateTimeWidget
has two optional attributes:
SplitDateTimeWidget.dateFormat
Similar to
DateInput.format
SplitDateTimeWidget.timeFormat
Similar to
TimeInput.format
:jsSplitHiddenDateTimeWidget
Similar to :js
SplitDateTimeWidget
, but uses :jsHiddenInput
for both date and time.