While the changelog is the most comprehensive list of changes, this guide provides high-level overview and organizes changes by category.
Fields and Field Layouts have been completely revamped in Formie 3. Previously, Formie extended the base Field class from Craft for all fields, including Element fields like Assets and Entries. While convenient to make use of Craft's implementation for field functionality, it's proved more of a hindrance with limiting what Formie fields can do, and causing issues when Craft update their own field classes.
Similarly, Craft has the concept of a Field Layout, which is attached to an element (in our case, a Submission) and defines what content an element has. But these are heavily opinionated and tied to control-panel behaviour, and Formie forms are designed to be used on the front-end. They're also more complex with Pages/Rows/Fields, as opposed to Tabs/Fields in Craft.
Finally, there's performance. Formie fields were stored alongside Craft's own fields, and made use of Craft's field layouts. Having potentially hundreds of fields stored alongside Craft fields meant that Craft would initialise these fields on every request, when they don't need to. Alongside content table changes from Craft itself, this meant big changes were in order.
So — Formie now stores all Fields, Rows, Pages and Field Layouts in its own database tables, with it's own logic. Submissions now also make use of our own Field Layout.
We've strived to make things backward compatible, and deprecated existing behaviour to ease the migration. This should only affect you if:
- You have created a custom field class
- You have custom JavaScript validation for fields
- You iterate through or access submission content
- You call
getCustomFields()
in your templates for Submissions - You call
getFieldLayout()
in your templates for Submissions - You manually render form Pages or Fields
The following describes Twig template changes for dealing with fields. You no longer need to get the Field Layout for the element, instead just accessing Pages, Rows and Fields directly from the element.
Fetching the fields for a Form can be done with form.getFields()
.
// Formie v2
{% for field in form.getCustomFields() %}
{# ... #}
// Formie v3
{% for field in form.getFields() %}
{# ... #}
Fetching submission content no longer requires you to get the Field Layout for the submission with submission.getFieldLayout().getCustomFields()
. You can now just use submission.getFields()
.
// Formie v2
{% set fieldLayout = submission.getFieldLayout() %}
{% if fieldLayout %}
{% for field in fieldLayout.getCustomFields() %}
{# ... #}
// Formie v3
{% for field in submission.getFields() %}
{# ... #}
Pages and Rows can use getFields()
instead of getCustomFields()
.
// Formie v2
{% for page in form.getPages() %}
{% for field in page.getCustomFields() %}
{# ... #}
// Formie v3
{% for page in form.getPages() %}
{% for field in page.getFields() %}
{# ... #}
In addition, for rows, you should use row.getFields()
as opposed to row.fields
(although both will work).
// Formie v2
{% for page in form.getPages() %}
{% for row in page.getRows() %}
{% for field in row.fields %}
{# ... #}
// Formie v3
{% for page in form.getPages() %}
{% for row in page.getRows() %}
{% for field in row.getFields() %}
{# ... #}
:::tip If you haven't created your own custom field class for Formie, you can skip this section. :::
Because of the re-architecture of Formie fields, this may affect your custom field, depending on what it is. In Formie 2, we mixed classes and traits so that we could extend from the Craft base Field class. All traits have now been removed in favour of proper classes that you can extend from.
To outline some of the changes to Formie field classes (in case your field extends one), refer to the below table.
Field | Summary |
---|---|
Address |
Now extends verbb\formie\base\SubField |
Categories |
Now extends verbb\formie\base\ElementField , and no longer extends craft\fields\Categories |
Checkboxes |
Now extends verbb\formie\base\OptionsField |
Date |
Now extends verbb\formie\base\SubField |
Dropdown |
Now extends verbb\formie\base\OptionsField |
Entries |
Now extends verbb\formie\base\ElementField , and no longer extends craft\fields\Entries |
FileUpload |
Now extends verbb\formie\base\ElementField , and no longer extends craft\fields\Assets |
Group |
Now extends verbb\formie\base\SingleNestedField |
Heading |
Now extends verbb\formie\base\CosmeticField |
Html |
Now extends verbb\formie\base\CosmeticField |
Name |
Now extends verbb\formie\base\SubField |
Phone |
Now extends verbb\formie\base\SubField |
Products |
Now extends verbb\formie\base\ElementField , and no longer extends craft\commerce\fields\Products |
Radio |
Now extends verbb\formie\base\OptionsField |
Repeater |
Now extends verbb\formie\base\MultiNestedField |
Section |
Now extends verbb\formie\base\CosmeticField |
Summary |
Now extends verbb\formie\base\CosmeticField |
Table |
No longer extends craft\fields\Table |
Tags |
Now extends verbb\formie\base\ElementField , and no longer extends craft\fields\Tags |
Users |
Now extends verbb\formie\base\ElementField , and no longer extends craft\fields\Users |
Variants |
Now extends verbb\formie\base\ElementField , and no longer extends craft\commerce\fields\Variants |
This shouldn't affect the vast majority of custom field classes, but it's a good reference in case you were extending one of the above.
We've also cleaned up class namespaces for fields. While we have aliases setup for old classes to ease the migration, these will be removed in Formie 4. If your custom class contains any reference to these, they should be updated.
Old Class | New Class |
---|---|
verbb\formie\base\FormField |
verbb\formie\base\Field |
verbb\formie\base\FormFieldInterface |
verbb\formie\base\FieldInterface |
verbb\formie\base\FormFieldTrait |
Removed. Use verbb\formie\base\Field |
verbb\formie\base\NestedFieldTrait |
Removed. Use verbb\formie\base\NestedField |
verbb\formie\base\RelationFieldTrait |
Removed. Use verbb\formie\base\ElementField |
verbb\formie\base\SubfieldInterface |
verbb\formie\base\SubFieldInterface |
verbb\formie\base\SubfieldTrait |
Removed. Use verbb\formie\base\SubField |
verbb\formie\fields\formfields\Address |
verbb\formie\fields\Address |
verbb\formie\fields\formfields\Agree |
verbb\formie\fields\Agree |
verbb\formie\fields\formfields\Calculations |
verbb\formie\fields\Calculations |
verbb\formie\fields\formfields\Categories |
verbb\formie\fields\Categories |
verbb\formie\fields\formfields\Checkboxes |
verbb\formie\fields\Checkboxes |
verbb\formie\fields\formfields\Date |
verbb\formie\fields\Date |
verbb\formie\fields\formfields\Dropdown |
verbb\formie\fields\Dropdown |
verbb\formie\fields\formfields\Email |
verbb\formie\fields\Email |
verbb\formie\fields\formfields\Entries |
verbb\formie\fields\Entries |
verbb\formie\fields\formfields\FileUpload |
verbb\formie\fields\FileUpload |
verbb\formie\fields\formfields\Group |
verbb\formie\fields\Group |
verbb\formie\fields\formfields\Heading |
verbb\formie\fields\Heading |
verbb\formie\fields\formfields\Hidden |
verbb\formie\fields\Hidden |
verbb\formie\fields\formfields\Html |
verbb\formie\fields\Html |
verbb\formie\fields\formfields\MissingField |
verbb\formie\fields\MissingField |
verbb\formie\fields\formfields\MultiLineText |
verbb\formie\fields\MultiLineText |
verbb\formie\fields\formfields\Name |
verbb\formie\fields\Name |
verbb\formie\fields\formfields\Number |
verbb\formie\fields\Number |
verbb\formie\fields\formfields\Password |
verbb\formie\fields\Password |
verbb\formie\fields\formfields\Payment |
verbb\formie\fields\Payment |
verbb\formie\fields\formfields\Phone |
verbb\formie\fields\Phone |
verbb\formie\fields\formfields\Products |
verbb\formie\fields\Products |
verbb\formie\fields\formfields\Radio |
verbb\formie\fields\Radio |
verbb\formie\fields\formfields\Recipients |
verbb\formie\fields\Recipients |
verbb\formie\fields\formfields\Repeater |
verbb\formie\fields\Repeater |
verbb\formie\fields\formfields\Section |
verbb\formie\fields\Section |
verbb\formie\fields\formfields\Signature |
verbb\formie\fields\Signature |
verbb\formie\fields\formfields\SingleLineText |
verbb\formie\fields\SingleLineText |
verbb\formie\fields\formfields\Summary |
verbb\formie\fields\Summary |
verbb\formie\fields\formfields\Table |
verbb\formie\fields\Table |
verbb\formie\fields\formfields\Tags |
verbb\formie\fields\Tags |
verbb\formie\fields\formfields\Users |
verbb\formie\fields\Users |
verbb\formie\fields\formfields\Variants |
verbb\formie\fields\Variants |
We have also changed several methods.
Old | What to do instead |
---|---|
Formie::log() |
Formie::info() |
Field::hasSubfields() |
Field::hasSubFields() |
Field::getBaseFieldConfig() |
Field::getFieldTypeConfig() |
Field::getExtraBaseFieldConfig() |
Field::getFieldTypeConfigData() |
Field::getSavedFieldConfig() |
Field::getFormBuilderConfig() |
Field::getFieldDefaults() |
Field::getFieldTypeConfigDefaults() |
Field::getAllFieldDefaults() |
Field::getFieldTypeConfigDefaults() |
Field::getFieldValue() |
$element->getFieldValue($field->handle) |
Field::getContentGqlMutationArgument() |
Field::getContentGqlMutationArgumentType() |
To adhere to more strict typing introduced in PHP 8, we've modified some method signatures. Your custom field classes will need to be updated to reflect these changes.
// Formie v2 vs Formie v3
public function getInputHtml($value, ElementInterface $element = null): string
protected function cpInputHtml(mixed $value, ?ElementInterface $element, bool $inline): string
// Formie v2 vs Formie v3
public function normalizeValue(mixed $value, ?ElementInterface $element = null): mixed
public function normalizeValue(mixed $value, ElementInterface $element = null): mixed
// Formie v2 vs Formie v3
public function getValueForIntegration(mixed $value, $integrationField, $integration, ?ElementInterface $element = null, $fieldKey = ''): mixed
public function getValueForIntegration(mixed $value, IntegrationField $integrationField, IntegrationInterface $integration, ?ElementInterface $element = null, string $fieldKey = ''): mixed
// Formie v2 vs Formie v3
public function getValueForEmail(mixed $value, $notification, ?ElementInterface $element = null): mixed
public function getValueForEmail(mixed $value, Notification $notification, ?ElementInterface $element = null): mixed
// Formie v2 vs Formie v3
public function populateValue($value): void
public function populateValue(mixed $value): void
// Formie v2 vs Formie v3
public function setForm($value): void
public function setForm(?Form $value): void
// Formie v2 vs Formie v3
public function getDefaultValue($attributePrefix = '')
public function getDefaultValue(): mixed
// Formie v2 vs Formie v3
public function setNamespace($value): void
public function setNamespace(string|bool|null $value): void
// Formie v2 vs Formie v3
public function getConditionsJson($element = null): ?string
public function getConditionsJson(): ?string
// Formie v2 vs Formie v3
public function isConditionallyHidden(Submission $element): bool
public function isConditionallyHidden(Submission $submission): bool
// Formie v2 vs Formie v3
protected function setPrePopulatedValue($value)
protected function setPrePopulatedValue(mixed $value): mixed
// Formie v2 vs Formie v3
protected function defineValueAsString($value, ElementInterface $element = null): string
protected function defineValueAsString(mixed $value, ElementInterface $element = null): string
// Formie v2 vs Formie v3
protected function defineValueAsJson($value, ElementInterface $element = null): mixed
protected function defineValueAsJson(mixed $value, ElementInterface $element = null): mixed
// Formie v2 vs Formie v3
protected function defineValueForExport($value, ElementInterface $element = null): mixed
protected function defineValueForExport(mixed $value, ElementInterface $element = null): mixed
// Formie v2 vs Formie v3
protected function defineValueForIntegration($value, $integrationField, $integration, ElementInterface $element = null, $fieldKey = ''): mixed
protected function defineValueForIntegration(mixed $value, IntegrationField $integrationField, IntegrationInterface $integration, ElementInterface $element = null, string $fieldKey = ''): mixed
// Formie v2 vs Formie v3
protected function defineValueForSummary($value, ElementInterface $element = null): string
protected function defineValueForSummary(mixed $value, ElementInterface $element = null): string
// Formie v2 vs Formie v3
protected function defineValueForEmail($value, $notification, ElementInterface $element = null): string
protected function defineValueForEmail(mixed $value, Notification $notification, ElementInterface $element = null): string
// Formie v2 vs Formie v3
protected function getContentGqlMutationArgument(): Type|array|null
protected function getContentGqlMutationArgumentType(): Type|array
:::tip If you haven't created your own custom JavaScript validation handlers, you can skip this section. :::
Client-side validation has been greatly improved, and now aligns more closely to server-side validation from Craft/Yii. Previously, we used bouncer.js
to handle client-side validation, which while excellent wasn't really translating well to some opinionated Formie concepts, or being used to compliment Craft/Yii's server-side validation.
In fact, we used bouncer.js
as inspiration for our own validation library.
This breaking change affects how you register your custom validators.
// Formie v2
import { t } from 'vendor/formie/frontend/utils/utils';
let $form = document.querySelector('#formie-form-1');
function customRule() {
return {
minLength(field) {
const limit = field.getAttribute('data-limit');
// Don't trigger this validation unless there's the `data-limit` attribute
if (!limit) {
return false;
}
return !(field.value.length > limit);
},
};
}
function customMessage() {
return {
minLength(field) {
return t('The value entered must be at least {limit} characters long.', {
limit: field.getAttribute('data-limit'),
});
},
};
}
$form.addEventListener('registerFormieValidation', (e) => {
e.preventDefault();
// Add our custom validations logic and methods
e.detail.validatorSettings.customValidations = {
...e.detail.validatorSettings.customValidations,
...this.customRule(),
};
// Add our custom messages
e.detail.validatorSettings.messages = {
...e.detail.validatorSettings.messages,
...this.customMessage(),
};
// ...
});
// Formie v3
let $form = document.querySelector('#formie-form-1');
$form.addEventListener('onFormieThemeReady', (event) => {
event.detail.addValidator('minLength', ({ input }) => {
const limit = input.getAttribute('data-limit');
// Don't trigger this validation unless there's the `data-limit` attribute
if (!limit) {
return true;
}
return input.value.length > limit;
}, ({ label, input, t }) => {
const limit = input.getAttribute('data-limit');
return t('The value entered in {label} must be at least {limit} characters long.', { label, limit });
});
});
Now, we use a single registration function addValidator()
to register our custom validator and message. This is in contrast to separate registration functions for the validation logic and message. We also have access to more variables in the respective callbacks for the field
(Formie's outer field wrapper) the input
(the <input>
or similar HTML element) and the label
of the field.
We can also make use of Craft's translations far easier with the t()
function available. You could also use string interpolation as well (i.e. ${label} is invalid.
) along with accessing other aspects of the DOM.
We also use the new onFormieThemeReady
event to register these validations only if you're using Formie's Theme JS (where client-side validation is enabled).
The returned value is now the opposite of what it was. Previously, a true
return was used if the validation were to be applied. This was somewhat confusing, as returning true
from a cognitive point of view usually means a positive change. Returning true
for something that meant a failure didn't seem to make sense.
As such, validators should now return true
for passing and false
for failing. Essentially, describing whether the field value passed validation or not.
// Formie v3
function customRule() {
return {
minLength(field) {
// Don't trigger this validation unless there's the `data-limit` attribute
if (!field.getAttribute('data-limit')) {
return false;
}
return !(field.value.length > 5);
},
};
}
// Formie v3
function({ input }) {
// Don't trigger this validation unless there's the `data-limit` attribute
if (!input.getAttribute('data-limit')) {
return true;
}
return input.value.length > 5;
}
Repeater and Group fields have had a major overhaul in Formie 3, most notably that their values are no longer elements. This is to simplify the field, provide a lower learning curve to dealing with them, and performance. They also don't use nested field layouts for the same goals. Instead, they are simple arrays.
This should only effect users who customise behaviour of these fields, or when working with the values of these fields. However, it should be much more simplified to do so.
// Formie v2
{# Fields would return a `verbb\formie\elements\db\NestedBlockQuery` object #}
{% set groupValue = myGroupField.one() %}
{{ groupValue.innerField1 }}
{{ groupValue.innerField2 }}
{# Fields would return a `verbb\formie\elements\db\NestedBlockQuery` object #}
{% set repeaterValue = myRepeaterField.all() %}
{% for row in repeaterValue %}
{{ row.innerField1 }}
{{ row.innerField2 }}
{% endfor %}
// Formie v3
{# Fields are now an array #}
{{ myGroupField.innerField1 }}
{{ myGroupField.innerField2 }}
{# Fields are now an array #}
{% for row in myRepeaterField %}
{{ row.innerField1 }}
{{ row.innerField2 }}
{% endfor %}
In addition, you can also now access field value directly from the submission, with dot-notation syntax.
{{ submission.getFieldValue('multiName.firstName') }}
{{ submission.getFieldValue('myGroupField.innerField1') }}
{{ submission.getFieldValue('myRepeaterField.0.innerField1') }}
{{ submission.getFieldValue('myRepeaterField.1.innerField1') }}
The latter example of a Repeater includes the zero-based index of the row you're fetching values for.
Sub-Fields have also received a revamp in their implementation. We wanted to provide the means to manage more advanced settings for sub-fields, along with being able to re-order them.
Sub-Fields feature a new user interface, similar to a Group field. Sub-fields can be enabled/disabled, re-ordered and can have all their settings managed like regular fields. Sub-fields cannot be removed from their parent field, only disabled - which is the main difference between achieving a similar thing with a Group field.
Additionally, sub-fields are now stored in the formie_fields
database table as fields within their own layout (again, the same as a Group or Repeater field).
We also use a new "type" for sub-fields under the verbb\formie\fields\subfields
namespace. This allows us to extend existing fields to provide additional functionality specific to that fields' use-case. If you're creating your own sub-fields, this is completely optional, and any Formie field will be able to be used as a sub-field. For example, an Address Country sub-field extends a Dropdown field, but provides an easier way to manage the number of options to automatically generate based on a list of countries, rather than managing them yourself.
Current Sub-Fields are Name (multi-field enabled), Address, and Date (dropdown and input layouts). Phone fields were previously a sub-field, but are now just a regular field.
Under the hood, a verbb\formie\base\SubField
extends the verbb\formie\base\SingleNestedField
class, which in turn extends verbb\formie\base\NestedField
. The SingleNestedField
is the same class that a Group field uses, as the two types of fields are almost identical.
All sub-fields are migrated over to this new implementation. If you have custom sub-field's, you'll need to review your implementation to follow the updated behaviour.
As field settings now exist in their respective sub-field classes and fields, we have removed many properties.
autocompleteIntegration
autocompleteEnabled
autocompleteCollapsed
autocompleteLabel
autocompletePlaceholder
autocompleteDefaultValue
autocompletePrePopulate
autocompleteRequired
autocompleteErrorMessage
autocompleteCurrentLocation
address1Enabled
address1Collapsed
address1Label
address1Placeholder
address1DefaultValue
address1PrePopulate
address1Required
address1ErrorMessage
address1Hidden
address2Enabled
address2Collapsed
address2Label
address2Placeholder
address2DefaultValue
address2PrePopulate
address2Required
address2ErrorMessage
address2Hidden
address3Enabled
address3Collapsed
address3Label
address3Placeholder
address3DefaultValue
address3PrePopulate
address3Required
address3ErrorMessage
address3Hidden
cityEnabled
cityCollapsed
cityLabel
cityPlaceholder
cityDefaultValue
cityPrePopulate
cityRequired
cityErrorMessage
cityHidden
stateEnabled
stateCollapsed
stateLabel
statePlaceholder
stateDefaultValue
statePrePopulate
stateRequired
stateErrorMessage
stateHidden
zipEnabled
zipCollapsed
zipLabel
zipPlaceholder
zipDefaultValue
zipPrePopulate
zipRequired
zipErrorMessage
zipHidden
countryEnabled
countryCollapsed
countryLabel
countryPlaceholder
countryDefaultValue
countryPrePopulate
countryRequired
countryErrorMessage
countryHidden
countryOptionLabel
countryOptionValue
dayLabel
dayPlaceholder
monthLabel
monthPlaceholder
yearLabel
yearPlaceholder
hourLabel
hourPlaceholder
minuteLabel
minutePlaceholder
secondLabel
secondPlaceholder
ampmLabel
ampmPlaceholder
minYearRange
maxYearRange
timeLabel
includeDate
includeTime
prefixEnabled
prefixCollapsed
prefixEnabled
prefixCollapsed
prefixLabel
prefixPlaceholder
prefixDefaultValue
prefixPrePopulate
prefixRequired
prefixErrorMessage
firstNameEnabled
firstNameCollapsed
firstNameLabel
firstNamePlaceholder
firstNameDefaultValue
firstNamePrePopulate
firstNameRequired
firstNameErrorMessage
middleNameEnabled
middleNameCollapsed
middleNameLabel
middleNamePlaceholder
middleNameDefaultValue
middleNamePrePopulate
middleNameRequired
middleNameErrorMessage
lastNameEnabled
lastNameCollapsed
lastNameLabel
lastNamePlaceholder
lastNameDefaultValue
lastNamePrePopulate
lastNameRequired
lastNameErrorMessage
countryCollapsed
countryShowDialCode
If you wish to access the sub-field settings, you'll need to access the sub-field first. For example, if you were in the context of a field class:
// Formie v2
$firstNameEnabled = $this->firstNameEnabled;
if ($firstNameEnabled) {
$label = $this->firstNameLabel;
}
// Formie v3
$firstName = $this->getFieldByHandle('firstName');
if ($firstName && $firstName->enabled) {
$label = $firstName->label;
}
We have removed the relatedTo
query parameter, which you might have used for checking against element field content. For example, you might have an Entries field in your form, and you want to find all Submissions that are relatedTo
a specific entry.
While you can still do this in Formie 3, you should query the specific field's content.
{% set relatedEntry = craft.entries.id(123).one() %}
// Formie v2
{% set submissions = craft.formie.submissions().form('contactForm').relatedTo(relatedEntry).all() %}
// Formie v3
{% set submissions = craft.formie.submissions().form('contactForm').entriesFieldHandle(relatedEntry).all() %}
Inline with Craft's own improvement of removing the content database table and the about Field/Field Layout changes, Formie now no longer stores submission content in a fmc_*
database table. Instead, it's a single content
JSON column in your formie_submissions
database table. This not only has performance benefits, but makes it easy to view your content directly from the database.
Importantly, this change also means that you can now have unlimited fields in a form, so if you've been hanging out to make the biggest web form in history - your time is now!
This won't mean anything for day-to-day use of Formie, but is useful to know if you're familiar with looking at the raw content of a submission.
The fieldInputContainer
key for Theme Config has been renamed to fieldInputWrapper
to follow clear consistency with the terms "wrapper" and "container".
In addition, the .fui-input-container
class now no longer exists, replaced with .fui-input-wrapper
, so any CSS overrides you have will need to be updated.
:::tip If you haven't created your own custom integration class for Formie, you can skip this section. :::
To simplify and streamline integration logic for OAuth-based providers, we're using the Auth Module to handle the authentication flow and token handling. For non-OAuth-based providers, there will be no change.
While things should be seamlessly migrated from Formie to Auth, if you have custom integrations that use OAuth, you'll need to integrate those with the Auth module. Formie no longer contains many of the helper methods that an older OAuth integration would've relied on.
In addition, if you were using any of the OAuth-specific functions in Formie, those are now removed.
The following classes have been removed
verbb\formie\events\OauthTokenEvent
verbb\formie\events\TokenEvent
verbb\formie\models\Token
verbb\formie\records\Token
verbb\formie\services\Tokens
The formie_tokens
database table has been removed and items migrated to the auth_oauth_tokens
database table.
For the Integration
class, the following applies:
Integration::tokenId
property removedIntegration::getAuthorizeUrl()
method removedIntegration::getAccessTokenUrl()
method removedIntegration::getResourceOwner()
method removedIntegration::getOauthScope()
method removedIntegration::getOauthAuthorizationOptions()
method removedIntegration::oauthVersion()
method removedIntegration::oauth2Legged()
method removedIntegration::oauthConnect()
method removedIntegration::getOauthProviderConfig()
method removedIntegration::getOauthProvider()
method removedIntegration::beforeFetchAccessToken()
method removedIntegration::afterFetchAccessToken()
method removed
Custom Integration classes should now implement a getOAuthProviderClass()
method which is a Auth Provider.
Refer also to our OAuth Integrations for some existing implementations. We also have an example integration to get started with.
When querying fields or pages, you should use the label
property instead of name
.
// Formie v2
{
formieForm (handle: "contactForm") {
title
pages {
name
rows {
rowFields {
name
}
}
}
}
}
// Formie v3
{
formieForm (handle: "contactForm") {
title
pages {
label
rows {
rowFields {
label
}
}
}
}
}
Previously, all Sub-Field label, enabled state and more were stored as properties on the outer Sub-Field. These are now nested as individual FieldInterface
items.
// Formie v2
{
formieForm (handle: "contactForm") {
title
formFields {
label
prefixLabel
firstNameLabel
middleNameLabel
lastNameLabel
}
}
}
// Formie v3
{
formieForm (handle: "contactForm") {
title
formFields {
label
... on Field_Name {
fields {
label
}
}
}
}
}
The EVENT_MODIFY_FIELD_CONFIG
event had been moved to be on the individual field class, not the verbb\formie\services\Fields
service.
// Formie v2
use verbb\formie\events\ModifyFieldConfigEvent;
use verbb\formie\services\Fields;
use yii\base\Event;
Event::on(Fields::class, Fields::EVENT_MODIFY_FIELD_CONFIG, function(ModifyFieldConfigEvent $event) {
$config = $event->config;
// ...
});
// Formie v3
use verbb\formie\events\ModifyFieldConfigEvent;
use verbb\formie\fields\formfields\SingleLineText;
use yii\base\Event;
Event::on(SingleLineText::class, SingleLineText::EVENT_MODIFY_FIELD_CONFIG, function(ModifyFieldConfigEvent $event) {
$config = $event->config;
// ...
});
The EVENT_MODIFY_FRONT_END_SUBFIELDS
event had been renamed to EVENT_MODIFY_NESTED_FIELD_LAYOUT
and differs slightly. Instead of modifying the config of a field as an array, before it's created as a field, you can now modify the created field in the context of a field layout.
// Formie v2
use craft\helpers\ArrayHelper;
use verbb\formie\events\ModifyFrontEndSubfieldsEvent;
use verbb\formie\fields\formfields\Address;
use yii\base\Event;
Event::on(Address::class, Address::EVENT_MODIFY_FRONT_END_SUBFIELDS, function(ModifyFrontEndSubfieldsEvent $event) {
$address1 = $event->rows[0][0];
// Modify the `address1` field - a `SingleLineText` field.
$address1->name = 'Address Line 1';
$event->rows[0][0] = $address1;
// Change the order of fields - remove them first
$address2 = ArrayHelper::remove($event->rows[1], 0);
$address3 = ArrayHelper::remove($event->rows[2], 0);
// Add them to the first row
$event->rows[0][] = $address2;
$event->rows[0][] = $address3;
});
// Formie v3
use verbb\formie\events\ModifyNestedFieldLayoutEvent;
use verbb\formie\fields\Address;
use yii\base\Event;
Event::on(Address::class, Address::EVENT_MODIFY_NESTED_FIELD_LAYOUT, function(ModifyNestedFieldLayoutEvent $event) {
// Lookup the last name sub-field. We can no longer rely on a static order
$address1 = $event->fieldLayout->getFieldByHandle('address1');
// Modify the `address1` field - a `SingleLineText` field.
$address1->label = 'Address Line 1';
});
The EVENT_MODIFY_FRONT_END_SUBFIELDS
event had been renamed to EVENT_MODIFY_NESTED_FIELD_LAYOUT
and differs slightly. Instead of modifying the config of a field as an array, before it's created as a field, you can now modify the created field in the context of a field layout.
// Formie v2
use verbb\formie\events\ModifyFrontEndSubfieldsEvent;
use verbb\formie\fields\formfields\Date;
use yii\base\Event;
use DateTime;
Event::on(Date::class, Date::EVENT_MODIFY_FRONT_END_SUBFIELDS, function(ModifyFrontEndSubfieldsEvent $event) {
$field = $event->field;
$defaultValue = $field->defaultValue ? $field->defaultValue : new DateTime();
$year = intval($defaultValue->format('Y'));
$minYear = $year - 10;
$maxYear = $year + 10;
$yearOptions = [];
for ($y = $minYear; $y < $maxYear; ++$y) {
$yearOptions[] = ['value' => $y, 'label' => $y];
}
$event->rows[0]['Y'] = [
'handle' => 'year',
'options' => $yearOptions,
'min' => $minYear,
'max' => $maxYear,
];
});
// Formie v3
use verbb\formie\events\ModifyNestedFieldLayoutEvent;
use verbb\formie\fields\Date;
use yii\base\Event;
Event::on(Date::class, Date::EVENT_MODIFY_NESTED_FIELD_LAYOUT, function(ModifyNestedFieldLayoutEvent $event) {
// Lookup the last name sub-field. We can no longer rely on a static order
$year = ArrayHelper::firstWhere($event->fields, 'handle', 'year');
$yearOptions = [];
$minYear = 2000;
$maxYear = 2050;
for ($y = $minYear; $y < $maxYear; ++$y) {
$yearOptions[] = ['value' => $y, 'label' => $y];
}
// Modify the `year` field - a `SingleLineText` field.
$year->options = $yearOptions;
$year->min = $minYear;
$year->max = $maxYear;
});
The EVENT_MODIFY_FRONT_END_SUBFIELDS
event had been renamed to EVENT_MODIFY_NESTED_FIELD_LAYOUT
and differs slightly. Instead of modifying the config of a field as an array, before it's created as a field, you can now modify the created field in the context of a field layout.
// Formie v2
use verbb\formie\events\ModifyFrontEndSubfieldsEvent;
use verbb\formie\fields\formfields\Name;
use yii\base\Event;
Event::on(Name::class, Name::EVENT_MODIFY_FRONT_END_SUBFIELDS, function(ModifyFrontEndSubfieldsEvent $event) {
$lastName = $event->rows[0][3];
// Modify the `lastName` field - a `SingleLineText` field.
$lastName->name = 'Surname';
$event->rows[0][3] = $lastName;
// Change the order of fields - remove them first
$firstName = ArrayHelper::remove($event->rows[0], 1);
$lastName = ArrayHelper::remove($event->rows[0], 3);
// Reverse the order
$event->rows[0][1] = $lastName;
$event->rows[0]3 = $firstName;
});
// Formie v3
use verbb\formie\events\ModifyNestedFieldLayoutEvent;
use verbb\formie\fields\Name;
use yii\base\Event;
Event::on(Name::class, Name::EVENT_MODIFY_NESTED_FIELD_LAYOUT, function(ModifyNestedFieldLayoutEvent $event) {
// Lookup the last name sub-field. We can no longer rely on a static order
$lastName = $event->fieldLayout->getFieldByHandle('lastName');
// Modify the `lastName` field - a `SingleLineText` field.
$lastName->label = 'Surname';
});
The EVENT_MODIFY_PREFIX_OPTIONS
has been moved to the inner Prefix sub-field class.
// Formie v2
use verbb\formie\events\ModifyNamePrefixOptionsEvent;
use verbb\formie\fields\Name;
use yii\base\Event;
Event::on(Name::class, Name::EVENT_MODIFY_PREFIX_OPTIONS, function(ModifyNamePrefixOptionsEvent $event) {
$event->options[] = ['label' => Craft::t('formie', 'Mx.'), 'value' => 'mx'];
});
// Formie v3
use verbb\formie\events\ModifyNamePrefixOptionsEvent;
use verbb\formie\fields\subfields\NamePrefix;
use yii\base\Event;
Event::on(NamePrefix::class, NamePrefix::EVENT_MODIFY_PREFIX_OPTIONS, function(ModifyNamePrefixOptionsEvent $event) {
$event->options[] = ['label' => Craft::t('formie', 'Mx.'), 'value' => 'mx'];
});