form_builder 1.0.0 form_builder: ^1.0.0 copied to clipboard
flutter row-column based form builder, build form quickly,create field fast,less code,more powerful
flutter_form_builder #
basic usage #
FormManagement formManagement = FormManagement();
Widget form = FormBuilder(
{bool readOnly,//set form's readonly state
bool visible, // set form's visible state
MainAxisAlignment? mainAxisAlignment, // customize column
MainAxisSize? mainAxisSize,
CrossAxisAlignment? crossAxisAlignment,
TextDirection? textDirection,
VerticalDirection? verticalDirection,
TextBaseline? textBaseline,
bool enableLayoutFormManagement = false,
FormValueChanged? onChanged,
this.formManagement,
).append(ClearableTextFormField(
name: 'username',
labelText: 'username',
clearable: true,
selectAllOnFocus: true,
validator: (value) =>
value.isEmpty ? 'username can not be empty !' : null,
))
.append(Builder(builder: (context) {
return ButtonFormField(
child: Text(username.readOnly ? 'editable' : 'readOnly'),
onPressed: () {
username.readOnly = !username.readOnly;
(context as Element).markNeedsBuild();
});
}))
.append(SwitchInlineFormField(name: 'rememberMe'));
form widget tree #
Column
Row
FormField
base field widget tree #
base field is default form field
Flexible(
fit: state.visible ? FlexFit.tight : FlexFit.loose,
child: Padding(
padding: state.padding ?? const EdgeInsets.all(5),
child: Visibility(
maintainState: true,
child: BaseFormField,
visible: state.visible,
),
),
flex: state.flex,
)
methods #
FormBuilder #
create a new row
if form layout's last row is not empty,will creat a new row
FormBuilder builder = formBuilder.newRow();
customize last row
FormBuilder builder = formBuilder.customize({
MainAxisAlignment? mainAxisAlignment,
MainAxisSize? mainAxisSize,
CrossAxisAlignment? crossAxisAlignment,
TextDirection? textDirection,
VerticalDirection? verticalDirection,
TextBaseline? textBaseline,
})
append a field
append a field to last row
FormBuilder builder = formBuilder.append(Widget field);
append a builder field
FormBuilder builder = formBuilder.appendBuilder(FieldBuilder builder);
append a field take up one row
FormBuilder builder = formBuilder.oneRowField(Widget field);
append a builder field take up one row
FormBuilder builder = formBuilder.oneRowBuilder(FieldBuilder builder);
FormManagement #
whether has a name field
bool hasField => formManagement.hasField(String name);
whether form is visible
bool visible = formManagement.visible;
hide|show form
formManagement.visible = true|false;
whether form is readOnly
bool readOnly = formManagement.readOnly;
set form readonly|editable
formManagement.readOnly = true|false;
get rows of form
int rows = formManagement.rows;
get column of row
int column = formManagement.getColumn(row);
create FormFieldManagement
FormFieldManagement formFieldManagement = formManagement.newFormFieldManagement(String name);
create FormPositionManagement
FormPositionManagement formPositionManagement = formManagement.newFormPositionManagement(int row,{int? column});
create FormLayoutManagement(experimental)
FormLayoutManagement formLayoutManagement = formManagement.newFormLayoutManagement()
get form data
only contains field which has a name
Map<String,dynamic> dataMap = formManagement.data;
set form data
Map<String,dynamic> formData = {};
formManagement.data = formData;//will trigger field's onChanged
formManagement.setData(formData,{trigger:trigger});
reset form
formManagement.reset();
validate form
formManagement.validate();
whether form is valid
unlike validate method, this method won't display error msg
bool isValid = formManagement.isValid
get error
get all errors after validate
Map<String, FormFieldManagement> errorMap = formManagement.error;
quietly validate
validate all field and get error , this method will not display error msg
List<FormFieldManagementError> errors = formManagement.quietlyValidate();
FormFieldManagement #
whether field is focusable
bool focusable => formFieldManagement.focusable
whether field is focused
bool focused => formFieldManagement.hasFocus
focus|unfocus field
formFieldManagement.focus = true|false
set focuslistener
call this method in WidgetsBinding.instance!.addPostFrameCallback
formFieldManagement.focusListener = (key,hasFocus){
// when key is null, root node focused
// otherwise sub node focused
}
get ValueFieldManagement
field must be a valuefield ,otherwise an error will be throw
ValueFieldManagement ValueFieldManagement = formFieldManagement.valueFieldManagement
whether field is ValueField
bool isValueField = formFieldManagement.isValueField;
whether field support textSelection
bool supportTextSelection = formFieldManagement.supportTextSelection
get TextSelectionManagement
if field don't support textSelection ,an error will be throw
TextSelectionManagement textSelectionManagement = formFieldManagement.textSelectionManagement
whether field is readOnly
bool readOnly = formFieldManagement.readOnly;
set readOnly
formFieldManagement.readOnly = true|false
whether field is visible
bool visible = formFieldManagement.visible;
set visible
formFieldManagement.visible = true|false
update field's state
see supported states for every field here
formFieldManagement.update({});
update one field state
see supported states for every field here
formFieldManagement.update1(String key,dynamic value);
make field visible in viewport
not work if field or form is invisible
Future<void> future = formFieldManagement.ensureVisible(
{Duration? duration,
Curve? curve,
ScrollPositionAlignmentPolicy? alignmentPolicy,
double? alignment})
ValueFieldManagement #
get value
dynamic value => valueFieldManagement.value
set value
valueFieldManagement.value = value;//will trigger onChanged
valueFieldManagement.setValue(dynamic value,{bool trigger})
whether field is valid
this method won't display error msg
bool isValid = valueFieldManagement.isValid;
validate field
this method will display error and return whether field is valid
bool isValid = valueFieldManagement.validate();
reset field
valueFieldManagement.reset();
get error
String? error = valueFieldManagement.error;
FormPositionManagement #
whether all fields is readOnly
bool get readOnly => formPositionManagement.readOnly;
set readOnly on all fields
formPositionManagement.readOnly = true|false;
whether at least one field is visible
bool visible = formPositionManagement.visible;
set visible on all fields
formPositionManagement.visible = true|false;
FormLayoutManagement (experimental) #
whether a layout is editing
bool isEditing = formLayoutManagement.isEditing;
get rows of currrent editing layout
int rows = formLayoutManagement.rows;
get columns of a row in current editing layout
int columns = formLayoutManagement.getColumns(int row);
customize column
void customizeColumn({
MainAxisAlignment? mainAxisAlignment,
MainAxisSize? mainAxisSize,
CrossAxisAlignment? crossAxisAlignment,
TextDirection? textDirection,
VerticalDirection? verticalDirection,
TextBaseline? textBaseline,
})
customize row
void customizeRow({
int? row,
MainAxisAlignment? mainAxisAlignment,
MainAxisSize? mainAxisSize,
CrossAxisAlignment? crossAxisAlignment,
TextDirection? textDirection,
VerticalDirection? verticalDirection,
TextBaseline? textBaseline,
})
remove in layout
formLayoutManagement.remove(int row,{int column});
insert field at position
void insert(
{int? column,
int? row, //if row is null,append after last row
required Widget field,
bool newRow = false,//whether create a new row
})
swap two rows
formLayoutManagement.swapRow(int oldRow, int newRow)
start edit current layout
you should enableLayoutFormManagement
formLayoutManagement.startEdit();
apply edited layout
formLayoutManagement.apply();
cancel editing layout
formLayoutManagement.cancel();
field states #
ClearableTextFormField #
name | Type | nullable |
---|---|---|
labelText | String | true |
hintText | String | true |
keyboardType | TextInputType | true |
autofocus | bool | false |
maxLines | int | true |
minLines | int | true |
maxLength | int | true |
clearable | bool | false |
prefixIcon | Widget | true |
inputFormatters | List< TextInputFormatter> | true |
style | TextStyle | true |
toolbarOptions | ToolbarOptions | true |
selectAllOnFocus | bool | false |
suffixIcons | List< Widget> | true |
textInputAction | TextInputAction | true |
inputDecorationTheme | InputDecorationTheme | true |
textCapitalization | TextCapitalization | true |
DateTimeFormField #
name | Type | nullable |
---|---|---|
labelText | String | true |
hintText | String | true |
maxLines | int | false |
type | DateTimeType | false |
formatter | DateTimeFormatter | true |
style | TextStyle | true |
inputDecorationTheme | InputDecorationTheme | true |
firstDate | DateTime | false |
lastDate | DateTime | false |
NumberFormField #
name | Type | nullable |
---|---|---|
labelText | String | true |
hintText | String | true |
autofocus | bool | false |
clearable | bool | false |
prefixIcon | Widget | true |
style | TextStyle | true |
suffixIcons | List< Widget> | true |
textInputAction | TextInputAction | true |
inputDecorationTheme | InputDecorationTheme | true |
decimal | int | false |
max | double | true |
allowNegative | bool | false |
SelectorFormField #
name | Type | nullable |
---|---|---|
labelText | String | true |
hintText | String | true |
multi | bool | false |
clearable | bool | false |
selectorThemeData | SelectorThemeData | false |
selectedItemLayoutType | SelectedItemLayoutType | false |
SliderFormField #
name | Type | nullable |
---|---|---|
labelText | String | true |
max | double | false |
min | double | false |
divisions | int | false |
activeColor | Color | true |
inactiveColor | Color | true |
RangeSliderFormField #
name | Type | nullable |
---|---|---|
labelText | String | true |
max | double | false |
min | double | false |
divisions | int | false |
activeColor | Color | true |
inactiveColor | Color | true |
ListTileFormField #
name | Type | nullable |
---|---|---|
labelText | String | true |
split | int | false |
items | List< ListTileItem<T>> | false |
hasSelectAll | bool | false |
checkboxRenderData | CheckboxRenderData | true |
radioRenderData | RadioRenderData | true |
switchRenderData | SwitchRenderData | true |
listTileThemeData | ListTileThemeData | true |
FilterChipFormField #
name | Type | nullable |
---|---|---|
labelText | String | true |
items | List< FilterChipItem<T>> | false |
pressElevation | double | true |
count | int | true |
layoutType | ChipLayoutType | false |
chipThemeData | ChipThemeData | true |
RateFormField #
name | Type | nullable |
---|---|---|
labelText | String | true |
rateThemeData | RateThemeData | true |
SingleSwitchFormField #
name | Type | nullable |
---|---|---|
label | Widget | true |
switchRenderData | SwitchRenderData | true |
SingleCheckboxFormField #
name | Type | nullable |
---|---|---|
label | Widget | true |
checkboxRenderData | CheckboxRenderData | true |
ButtonFormField #
name | Type | nullable |
---|---|---|
icon | Widget | true |
child | Widget | false |
currently support fields #
field | return value | nullable |
---|---|---|
ClearableTextFormField | string | false |
DateTimeFormField | DateTime | true |
SelectorFormField | List< T> | false |
ListTileFormField | List< T> | false |
InlineFormField | bool | false |
NumberFormField | num | true |
SliderFormField | double | false |
RangeSliderFormField | RangeValues | false |
FilterChipFormField | List< T> | false |
RateFormField | dobule | true |
SingleSwitchFormField | bool | false |
SingleCheckboxFormField | bool | false |
ButtonFormField | - | - |
build your own form field #
build a value field #
if you want to build a nullable value field ,just extends BaseValueField
this is an example:
class CustomNullableValueField<T> extends BaseValueField<T> {
CustomNullableValueField({
String? name,//important !,used to maintain state and used as a key when you get data via FormManagement.data
required T value,
String? label,
TextStyle? textStyle,
T? initialValue,
ValueChanged<T?>? onChanged,
FormFieldValidator<T>? validator,
}) : super(
{
'label': StateValue<String?>(label),
'textStyle': StateValue<TextStyle?>(textStyle),
}, //this is a initStateMap
name:name,
initialValue: initialValue,
validator: validator,
onChanged: onChanged,
builder: (state) {
bool readOnly = state.readOnly;
Map<String, dynamic> stateMap = state.currentMap;
ThemeData themeData = Theme.of(context);
// state is ValueFieldState<T>,if you override ValueFieldState,you can cast it as your custom ValueFieldState,in this example you can cast it to _CustomNullableValueFieldState,you can get name via state.name(nullable)
// readOnly : whether this field should readOnly
// stateMap : can be regarded as the lastest initStateMap , user can change stateMap var FormFieldManagement's update,
// in this example ,you should get label&textStyle form stateMap rather than directly use them
return Row(
children: [
Text(
stateMap['label'] ??
'radio', //don't use just label here,you should get lastest label from stateMap,the key is initStateMap's label key
style: stateMap['textStye'],
),
Radio<T>(
focusNode: state
.focusNode, //state has prepared a focusnode for you,just use it rather than create a new one
activeColor: themeData.primaryColor,
groupValue: state.value,
value: value,
onChanged:
readOnly ? null : (value) => state.didChange(value),
)
],
);
},
);
@override
_CustomNullableValueFieldState<T> createState() =>
_CustomNullableValueFieldState();
}
if you also want to build custom ValueFieldState,extends it !
class _CustomNullableValueFieldState<T> extends BaseValueFieldState<T>
with TextSelectionManagement // if you form field support textselection
{
late final TextEditingController
textEditingController; //just an example here !
@override
void initState() {
super.initState();
textEditingController = TextEditingController(
text:
widget.initialValue == null ? '' : widget.initialValue.toString());
}
@override
void reset() {
super.reset();
textEditingController.text =
widget.initialValue == null ? '' : widget.initialValue.toString();
}
@override
void dispose() {
textEditingController.dispose();
super.dispose();
}
@override
void selectAll() {
setSelection(0, textEditingController.text.length);
}
@override
void setSelection(int start, int end) {
TextSelectionManagement.setSelectionWithTextEditingController(
start, end, textEditingController);
}
}
the last step is insert your field
FormBuilder.append(CustomNullableValueField<String>(
onChanged: (value) => print(value), value: '123')),
build a nonnull value field is almost the same,just extends BaseNonnullValueField
this is an example:
class CustomNonnullableValueField extends BaseNonnullValueField<bool> {
CustomNonnullableValueField({
String? label,
TextStyle? textStyle,
required bool initialValue,
ValueChanged<bool>? onChanged,
NonnullFieldValidator<bool>? validator,
}) : super(
{
'label': StateValue<String?>(label),
'textStyle': StateValue<TextStyle?>(textStyle),
},
initialValue: initialValue,
validator: validator,
onChanged: onChanged,
builder: (state) {
bool readOnly = state.readOnly;
Map<String, dynamic> stateMap = state.currentMap;
ThemeData themeData = Theme.of(state.context);
return Row(
children: [
Text(
stateMap['label'] ?? 'checkbox',
style: stateMap['textStye'],
),
Checkbox(
activeColor: themeData.primaryColor,
value: state.value,
onChanged: readOnly ? null : (value) => state.didChange(value),
)
],
);
},
);
}
build a commonfield #
class Label extends BaseCommonField {
final String label;
Label(this.label, {int flex = 1})
: super(
{'label': StateValue<String>(label)},
flex: flex,
builder: (state) {
Map<String, dynamic> stateMap = state.currentMap;
ThemeData themeData = Theme.of(state.context);
return Text(
stateMap['label'],
style: TextStyle(fontSize: 18, color: themeData.primaryColor),
);
},
);
}
build a Stateless Field #
FormBuilder.append(Builder(builder: (context) {
BuilderInfo info = BuilderInfo.of(context);
Position position = info.position;
return Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'I\'m a stateless field',
style: TextStyle(fontSize: 20),
),
Container(
color: info.themeData.primaryColor.withOpacity(0.3),
child: ListView(
shrinkWrap: true,
children: [
createRow('row', '${position.row}'),
createRow('column', '${position.column}'),
createRow('inline', '${info.inline}'),
],
),
)
],
),
flex: 1,
);
}))
project status #
1.0.0 release