A native web component form module. The components are made to be as flexible, un-opinionated, and as close to the native implementation of form fields as possible, while adding a bunch of convenient functionality to make form implementation easier.
For all of these components, the following implementation is required:
.form-question
on the root element ( ! unless a checkbox or radio is part of a group )..helper-text
helper texts ( re invalid values ) will be displayed here. it's up to you to decide which element to use and where etc.[data-key]
this will be the key in theFormData
object.
<c-checkbox class="form-question"
data-key="c-checkbox-key">
<input type="checkbox"
id="c-checkbox-id"
tabindex="0"
name="checkbox-demo"
value="checkbox-solo"
required/>
<label for="c-checkbox-id">
Label
</label>
<small class="helper-text"></small>
</c-checkbox>
@required
[data-group]
on the child elements <c-checkbox>
or <c-radio>
@optional
[data-required]
on the root element <c-choice-group>
<c-choice-group class="form-question"
data-key="c-choice-group-key"
data-is-required>
<label>
Label
</label>
<c-checkbox data-group="c-choice-group-demo">
<input type="checkbox"
id="checkbox-a"
tabindex="0"
checked
name="demo"
value="checkbox-a"/>
<label for="checkbox-a">
Checkbox A
</label>
</c-checkbox>
<c-checkbox data-group="c-choice-group-demo">
<input type="checkbox"
id="checkbox-b"
name="demo"
tabindex="0"
value="checkbox-b"/>
<label for="checkbox-b">
Checkbox B
</label>
</c-checkbox>
<c-checkbox data-group="c-choice-group-demo">
<input type="checkbox"
id="checkbox-c"
name="demo"
tabindex="0"
value="checkbox-c"/>
<label for="checkbox-c">
Checkbox C
</label>
</c-checkbox>
<small class="helper-text"></small>
</c-choice-group>
c-radio
example:
<c-radio data-group="c-choice-group-demo">
<input type="radio"
id="radio-a"
tabindex="0"
name="demo"
value="radio-a"/>
<label for="radio-a">
Radio A
</label>
</c-radio>
For input[type="text,email,tel,url"]
- Use c-checkbox for type
checkbox
- Use c-radio for type
radio
in ac-choice-group
- Use c-file-input for type
file
<c-input class="form-question"
data-key="c-input-demo-key">
<label for="c-input-demo">
Label
</label>
<input id="c-input-demo"
type="text"
required/>
<small class="helper-text"></small>
</c-input>
<c-select class="form-question"
data-key="c-select-regular-demo-key">
<label for="c-select-demo">
Label
</label>
<select id="c-select-demo"
tabindex="0"
required">
<option value="foo">foo</option>
<option value="faa">faa</option>
<option value="fuu">fuu</option>
<option value="fee">fee</option>
</select>
<small class="helper-text"></small>
</c-select>
Another option is to have an external data source render the options of c-select
automatically using a JSON
or an array defined in some external file. This is useful for when you want to make a use an API to set all the options in
a c-select
element ( like country calling codes for example ). You have to format the raw data to the following simple structure:
[
{ label: 'a', value: 'a' },
{ label: 'b', value: 'b' },
{ label: 'c', value: 'c' }
]
or as JSON:
[
{ "label": "a", "value": "a" },
{ "label": "b", "value": "b" },
{ "label": "c", "value": "c" }
]
@required
[data-external-options]
to the root element <c-select>
and set the value to the target exported in DataSource.js
<c-select class="form-question"
data-key="c-select-external-data-demo-key"
data-external-options="sampleData">
<label for="c-select-demo">
Label
</label>
<select id="c-select-demo"
tabindex="0"
required">
</select>
<small class="helper-text"></small>
</c-select>
@optional
.char-counter
.counter
.max-length
<c-textarea class="form-question"
data-key="c-textarea-demo-key">
<label for="c-textarea-demo">
Label
</label>
<textarea id="c-textarea-demo"
tabindex="0"
required
maxlength="200"
rows="4">
</textarea>
<div class="char-counter">
<span class="counter"></span> / <span class="maxlength"></span>
</div>
<small class="helper-text"></small>
</c-textarea>
@note
[accept]
is not required, but if you do add this attribute, be sure to add the long accepted file name like:application/pdf
. This is because of the way the DataTransferItem handles types, which is slightly different from theFile
type when using a click event.
@required
.c-file-input-step
the base class that defines different views in the upload process.file-selection-step
the view before a file is selected.file-selected-step
the view when a file has been selected..hidden
the class to hide the step that is currently not relevant
@optional
[data-max-size]
in bytes, if a maximum size is desired
<c-file-input class="form-question"
data-key="c-file-input-demo-key"
data-max-size="5242880">
<label tabindex="0" for="c-file-input-demo"></label>
<input type="file"
required
accept=".doc,.docx,application/pdf,application/msword"
id="c-file-input-demo"/>
<!--step 1: file selection-->
<div class="c-file-input-step file-selection-step">
<h4 class="file-input-title">Label</h4>
<span class="file-input-subtitle">
Subtitle
</span>
</div>
<!--step 2: file selected-->
<div class="c-file-input-step file-selected-step hidden">
<h4 class="file-input-title">Label</h4>
<span class="file-input-subtitle">
Subtitle
</span>
</div>
<small class="helper-text"></small>
</c-file-input>
Components that do not extend CFormQuestion
but are 'wrapper' components to custom components that do extend CFormQuestion
.
Note that these wrapper components to not have [data-key]
, .form-question
nor .helper-text
: the nested children
that are CFormQuestions
types will have that.
You can use this wrapper components to duplicate a form question a set number of times. This is useful when you want to
allow the user to add 1 or more urls to their work in an application form for instance. Unique IDs are automatically generated,
and the form question in FormData
will look like the following:
{
...,
portfolio: ['https://url-one.com','https://url-two.com','https://url-three.com'],
...
}
@required
.duplicable-element
to the form question that should be duplicable
@optional
[data-max]
the maximum number of dupes, the default is 4[data-required]
<c-duplicable-form-question data-max="4">
<a class="button add-dupe-button"
tabindex="0"
role="button">+</a>
<c-input class="form-question duplicable-element"
data-key="portfolio">
<label for="portfolio">
Your portfolio urls
</label>
<input id="portfolio"
placeholder="url"
tabindex="0"
type="url"
name="portfolio"
class="theme-secondary bordered"/>
<small class="helper-text"></small>
</c-input>
</c-duplicable-form-question>
A c-fieldset
is a grouping of, for instance, 2 form questions that belong together. Below is an example of a phone number form question where
we might want to use a calling-code API to populate a c-select
element, and an additional c-input[type="tel"]
for the remaining part of the
phone number.
Note that the individual labels for the .form-question
elements are rendered in the DOM but will not be visible in the UI: it is the legend
that
will display one single label for the c-fieldset
. The FormData
for this will look like the following:
{
...,
calling-code: '020',
phone-number: '3456789',
...
}
<c-fieldset data-is-required>
<legend>
Phone number
</legend>
<div class="row">
<c-select class="form-question"
data-key="calling-code"
data-external-options="getSampleData">
<label for="calling-code">country code</label> <!-- in the DOM but not visible in the UI -->
<select id="calling-code"
tabindex="0"
required>
<option disabled selected>country code</option>
</select>
<small class="helper-text"></small>
</c-select>
<c-input class="form-question"
data-key="tel-number">
<label for="c-input-tel-demo">phone number</label> <!-- in the DOM but not visible in the UI -->
<input type="tel"
id="c-input-tel-demo"
tabindex="0"
form="demo-form"
required/>
<small class="helper-text"></small>
</c-input>
</div>
</c-fieldset>
<c-form>
<form class="c-form" id="demo-form">
...
</form>
</c-form>