@vendasta/vform
v3.13.2
Published
Vendasta's Angular vForm
Downloads
81
Readme
VForm Angular
import { VFormModule } from '@vendasta/vform';
What is this?
This is an Angular component and service which allows us to convert existing VForm-backed Knockout frontends to Angular.
The core functionality of the form generation is handled mostly in the VFormStoreService
, while the translation to and from the VForm backend form definitions and data is handled by the VFormTranslationService
.
Fortunately, most developers won't have to worry about these.
How do I use this?
Converting an existing VForm-backed form:
Converting an existing VForm is as simple as including the VFormModule
from @vendasta/vform
in your module, then using the VFormComponent
and passing in a FormOptions, like so:
<v-form [formOptions]="formOptions"></v-form>
Generally this will be placed inside a <mat-card>
, but you can put it anywhere. VFormComponent is a fluid in that it fills the width of its container.
FormOptions
FormOptions is structured like this:
| Property | Type | Required | Notes
|--|:--:|:--:|--|
| formUrl | string
| true | The VForm backend URL, sans any query params. Params will be added automatically according to the action and the context.
| leftCustomButtons | ButtonOptions[]
| false | Defaults to a cancel and a submit button with default actions. According to Material guidelines, this should be a maximum of 2 buttons.
| rightCustomButtons | ButtonOptions[]
| false | Defaults to [] (no buttons). These will be displayed in an overflow menu on the right side of the form. According to Material guidelines, any actions beyond the 2 left buttons should go in here.
| cancelUrl | string
| false | ButtonAction.CANCEL redirects to this URL.
| successUrl | string
| false | ButtonAction.SUBMIT redirects to this URL on a successful submit if this property is set.
| context | string
| false | Should be encoded via encodeURIComponent
, defaults to the form page URL.
Context
The context
property is the context that gets passed to the VForm backend. For some forms, this may include necessary context such as AGID and PID. If you are passing in a context, ensure that it has been encoded using the Javascript built-in function encodeURIComponent
.
As you can see, buttons are a list of ButtonOptions
interfaces.
ButtonOptions
ButtonOptions is structured like this:
| Property | Type | Required | Notes |
|--|:--:|:--:|--|
| label | string
| true | The button's display text.
| action | ButtonAction or Function
| true | Can be passed ButtonAction.SUBMIT, ButtonAction.CLEAR, ButtonAction.CANCEL, the default supported actions. Alternatively, can be passed a custom function to run instead of a default action.
| color | string
| false | One of 'primary'
, 'accent'
, or 'warn'
. Will make this button a coloured material raised button.
Custom input width
The VForm component takes an inputWidth
input to change the width of the inputs. This should be passed a string with either pixel/em size or a percentage. For example, <v-form [formOptions]="formOptions" [inputWidth]="85%"></v-form>
.
Example FormOptions
Simple:
this.formOptions = {formUrl: '/form/v1/the-vform-url/'}
Robust:
this.formOptions = {
formUrl: '/form/v1/the-vform-url/',
leftCustomButtons: [
{label: 'Create Admin', action: ButtonAction.SUBMIT, color: 'primary'},
{label: 'Cancel', action: ButtonAction.CANCEL},
],
rightCustomButtons: [
{label: 'Clear', action: ButtonAction.CLEAR},
{label: 'Custom Button Action', action: () => {
console.log('💩');
}},
],
successUrl: '/success-url/',
cancelUrl: '/cancel-url/'
}
VForm Outputs
The v-form tag provides a few outputs you can/must use depending on the situation. If you don't provide a successUrl in your form options, it is assumed that you will be using one of the form response related outputs to redirect your user or do something else after the form is submitted. Here's a table of the outputs:
| Output | Type | Notes |
|--|:--:|--|
| formSubmitSuccess | boolean
| When the form is submitted and it gets a successful response from the vform api, this output will emit true
|
| formSubmitResult | FormResult
| Same situation as formSubmitSuccess, except this output will emit the response from the vform api. FormResult
is documented below this table. |
| hasError | boolean
| Will emit true
when there is an error in the form. |
| errorMessage | string
| Will contain the relevant error message if hasError
emitted true. |
| formLoadSuccess | FormState
| Emits a FormState
every time the state changes. If you want to know if the form is successfully loaded, check the formReady
attribute on FormState
(documented below). |
// Vapi/Vform responses come in this format.
export interface FormResult {
// The specified version of the api, e.g. "1.0"
version: string;
// Data returned from the endpoint. Can be basically anything, but strings and form errors are common.
data: any | string | FormErrorResponse;
// Request id that was generated to serve this request. Can be used to find log messages.
requestId: string;
// How long it took the api to return.
responseTime: number;
// Status code of the response, e.g. 200, 400, 500, etc.
statusCode: number;
}
export interface FormState {
formGroup: FormGroup;
formReady: boolean;
sections: Section[];
isLoading: boolean;
isSubmitting: boolean;
formSubmitUrl: string;
onSubmitSuccess: boolean;
}
Creating a new Angular form without a VForm backend
This is not currently supported, but may be in a future release.
I need help!
Contact Wisakejak.
Contributing
Not all form components have been created. To add custom components to VForm Angular:
First, add the control type into the ControlType enum in
vform.models.ts
. This should be identical to the control name returned from the vForm backend, but in upper case.In
vform.component.html
, add the following to the ngSwitch:<ng-container *ngSwitchCase="controlType.YOURCONTROLTYPE"> <your-control [placeholder]="field.label" [required]="field.required" [control]="f.controls[field.name]"></your-control> </ng-container>
You can put anything inside the
ng-container
, but keep the following in mind:- Stick to Material guidelines as much as possible
- The control should take Angular's FormControl as an input so that the
VFormStoreService
can control its state. - If possible, try to use Angular Material components for the new control.
Set the field value in the
setFieldData
method infields/field-base.ts
according to the data type of the field.If your new control needs custom behaviour when the form is altered, add this behaviour to the
formAlteringElementChanged
method invform.store.service.ts
.Update
CHANGELOG.md
andREADME.md
.Merge dat boi to master! after PR of course
Currently supported controls
The following fields are currently supported:
- Text/textbox
- Password
- Textarea
- Multitext
- Toggle/switch
- Radio button group
- Checkbox
- Dropdown
- CountryState
- PhotoUpload
- Multiselect (select2 support coming)
- Hidden
- Text-Separator
- Checkboxes
- Vsource
- Tag
Developing
Developing vform locally can be easy. Add these paths to your tsconfig.app.json:
{
"compilerOptions": {
"paths": {
"@vendasta/vform": ["../../frontend/angular/projects/vform/src/lib/"],
"@vendasta/vform/*": ["../../frontend/angular/projects/vform/src/lib/*"],
"@vendasta/*": ["../node_modules/@vendasta/*"],
"@agm/core": ["../node_modules/@agm/core"],
"@agm/core/*": ["../node_modules/@agm/core/*"],
"@ngx-translate": ["../node_modules/@ngx-translate"],
"@ngx-translate/*": ["../node_modules/@ngx-translate/*"],
"ngx-markdown": ["../node_modules/ngx-markdown"],
"ngx-markdown/*": ["../node_modules/ngx-markdown/*"],
"@angular/*": ["../node_modules/@angular/*"],
"core-js": ["../node_modules/core-js"],
"core-js/*": ["../node_modules/core-js/*"],
"rxjs": ["../node_modules/rxjs/"],
"rxjs/*": ["../node_modules/rxjs/*"],
"zone.js": ["../node_modules/zone.js/"],
"zone.js/*": ["../node_modules/zone.js/*"]
}
}
}
This pathing assumes you have the frontend repo and the project you're interested in cloned in the same folder. If you don't, just have the @vendasta/vform pathing go to wherever your frontend repo is located. The pathing on everything else assumes that your tsconfig.app.json is a folder in from your node_modules. Fix this as required!
Once these paths are in place, you should be able to make changes to vform in frontend and have your dev server reload when there are changes.
Vfilter
A close cousin of Vform is Vfilter, which is used to create views of filterable data. Consider a list of salespeople statistics that you'd like to filter and search on. Vfilter makes that easier. Here's an example of a Vfilter in action:
<!-- handleFeedData($event) simply does a this.feedData = [...$event]; to force that to update -->
<v-filter formUrl="/your-form-url" (feedData)="handleFeedData($event)">
<div *ngFor="let data of feedData" class="human">
<div class="human__first-name">{{ data.firstName }}</div>
<div class="human__last-name">{{ data.lastName }}</div>
</div>
</v-filter>
As you can see, VFilter relies on content projection to allow the VFilter implementer to display their data their way. Rather than building multiple different use-cases for displaying data into VFilter we can pass the decision of how to render the data back to the implementer. This is how the original Vfilter could work as well, we're just making that the only way this one works!
v-filter inputs
| Input | Type | Required | Notes |
|--|:--:|--|--|
| formUrl | string
| true | The form (filter) url that you are interested in. Should point to a derivative of a vform.VFilterHandler. |
| context | string \| object
| false | If you need to pass a context other than the current page url, pass one here. |
| ignoredQueryParams | string[]
| false | If you need to ignore certain query parameters because they interfere with your form you can use this input to do so. An example is marketId
in the Manage Salespeople handler of Partner Center. We don't want to use that query parameter to update our form so we mark it as ignored for that form. |
| infiniteScroll | boolean
| false | Whether or not you want the form to update via infinite scroll or not. |
v-filter outputs
| Output | Type | Required | Notes | |--|:--:|--|--| | feedData | any | true | This output must be used by the implementor to show the feed data. VFilter doesn't show any feed data on its own. This output will always emit the full set of data that should be shown, so there's no need to aggregate this data on the implementing side. E.g. in the case of an infinite scrolling handler, there could be the temptation to always add whatever data comes from this output onto what you already have. Don't do that! Just use exactly what this output provides and render that in the outlet. | | formSelectionChanges | any | false | This will be emitted whenever the form fields change. |