An open API service indexing awesome lists of open source software.

https://github.com/margarita-form/margarita-form

Modern form library made with TypeScript
https://github.com/margarita-form/margarita-form

angular-form angular-forms form form-builder form-validation forms margarita margarita-form react-form react-forms reactive-forms typescript

Last synced: 2 months ago
JSON representation

Modern form library made with TypeScript

Awesome Lists containing this project

README

        

# Margarita Form - Form library that makes creating forms easy 🔥

Start creating extendable reactive forms with any framework today.

## Forms for modern web development

Goal of margarita form is to help you to create, extend and validate forms easily. Margarita form provides methods to conditionally enable, transform and observe your form, it's value, state and everything related to it.

## Get started

- [React](#react) → `npm install @margarita-form/react` → [View React example](#example-dynamic-form-with-react)
- [Angular](#angular) → `npm install @margarita-form/angular` → [View Angular example](#example-dynamic-form-with-angular)
- [HTML and JS](#html-and-js) → `npm install @margarita-form/core` → [View HTML & JS example](#example-dynamic-form-with-html-and-js)
- [Other frameworks](#other-frameworks) → `npm install @margarita-form/core` → [View TypeScript example](#example-dynamic-form-with-other-frameworks)

## Features

- **Asynchronous validation** - Validate form fields with build in validators or create your own. Margarita form validation supports validators that can be functions, promises or even observables.
- **Schema based controls** - Form’s controls are created with a simple schema that consists of fields. Schema can be created with simple JSON data from your database or from javascript objects extended with custom functionality.
- **Conditional form fields** - With margarita form’s active and visible states you can control conditional cases for your form easily. Connect any function, promise or observable to render fields conditinally.
- **Written with TypeScript** - TypeScript support is crucial for modern libraries and so margarita form is build to be typescript native library with support to utilize your custom types.
- **CMS & CRM compatible** - It’s possible to create form schema with any CMS or CMR and use all features of margarita form with that. Existing forms can be extended with margarita form’s api and data can be submitted to where ever needed.
- **Storage and syncronization** - You can enable locally stored data to allow users continue filling the form later. On top of storage you can allow form data syncronization between tabs via [Broadcast Channel API](https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API)
- **Form field localization** - Margarita form supports localization for form fields. You can create forms that generate values or you can localize form's content.
- **Framework agnostic** - Margarita form is build to be framework agnostic. It can be used with any framework or without any framework at all.

## How Margarita form works in action?

1. **Define your schema.** Schema is the foundation for your form. Schema is an array of fields and with each field you can define controls name, type, state, behavior and validations. From schema / fields margarita form will create controls that then are used in your code to control form's value, state and content.

2. **Connect your ui components to form controls.** Margarita form does not define how your form should look like. You easily connect any UI components to the form controls and create dynamic forms without hassle.

3. **Handle form submission.** Saving form data is easy. Before user can submit the form, margarita form validates all active controls. If form is valid, a callback you defined get run with a form context and saving data to any target can be handled with native Promise api.

## React

### Install the [React package](https://www.npmjs.com/package/@margarita-form/react):

```bash
npm install @margarita-form/react
```

### Example dynamic form with React

**Component** / [readme-example.tsx](https://github.com/margarita-form/margarita-form/blob/main/apps/react-demos/src/app/readme-example/readme-example.tsx)

```tsx
import { useMargaritaForm, MargaritaFormField } from '@margarita-form/react';

// Create value and custom field types
interface MyFormValue {
name?: string;
message?: string;
}

interface MyFormField extends MargaritaFormField {
type: 'text' | 'textarea' | 'number' | 'email' | 'password' | 'tel' | 'url' | 'date';
title: string;
}

type RootField = MargaritaFormField<{ value: MyFormValue; fields: MyFormField }>;

// Create form component
export function CustomForm() {
// Initialize form
const form = useMargaritaForm({
name: 'my-form', // Name for each form should be unique
fields: [
{
name: 'name',
title: 'Full name',
type: 'text',
validation: { required: true },
attributes: { placeholder: 'John Doe' }, // Attributes are passed to the input
},
{
name: 'email',
title: 'Email',
type: 'email',
validation: { required: true },
attributes: { placeholder: '[email protected]' },
},
{
name: 'message',
title: 'Message',
type: 'textarea',
validation: { required: true },
attributes: { placeholder: 'Lorem ipsum' }, // Attributes are passed to the input
},
],
handleSubmit: {
// This required function is called when the form is valid
valid: async ({ value }) => {
console.log('Valid submit', { value });
alert(JSON.stringify(value, null, 2));
},
// This optional function is called when the form is invalid
invalid: async ({ control, value }) => {
const { allErrors } = control.state;
console.log('Form is not valid!', { value, allErrors });
},
},
config: {
handleSuccesfullSubmit: 'reset', // Change what happens when form is successfully submitted
},
});

return (


{/* Render the current value */}

Current value


{JSON.stringify(form.value)}

{/* Render the form */}

Form



{/* Map controls and connect to inputs */}
{form.controls.map((control) => {
const uid = control.name + '-' + control.key;

// Use switch to render different types of fields
switch (control.field.type) {
case 'textarea':
return (


{control.field.title}


);

default:
return (


{control.field.title}


);
}
})}

{/* Submit the form */}
Submit


);
}
```

**[View full React example app](https://github.com/margarita-form/margarita-form/tree/main/apps/react-demos/src/app)**

### More resources

- [React reference docs (WIP)](https://margarita-form.github.io/margarita-form/modules/_margarita_form_react.html)
- [Even more dynamic form example that is used for testing stuff out](https://github.com/margarita-form/margarita-form/blob/main/apps/baking-oven/src/app/app.tsx)

## Angular

### Install the [Angular package](https://www.npmjs.com/package/@margarita-form/angular):

```bash
npm install @margarita-form/angular
```

### Example dynamic form with Angular

**Component** / [readme-example.component.ts](https://github.com/margarita-form/margarita-form/blob/main/apps/angular-demos/src/app/readme-example/readme-example.component.ts)

```ts
import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MargaritaFormField, MargaritaFormModule, MargaritaFormService } from '@margarita-form/angular';

// Create value and custom field types
interface MyFormValue {
name?: string;
message?: string;
}

interface MyFormField extends MargaritaFormField<{ fields: MyFormField }> {
type: 'text' | 'textarea' | 'number' | 'email' | 'password' | 'tel' | 'url' | 'date';
title: string;
}

type RootField = MargaritaFormField<{ value: MyFormValue; fields: MyFormField }>;

// Create form component
@Component({
selector: 'app-readme-example',
standalone: true,
imports: [CommonModule, MargaritaFormModule],
styleUrl: './readme-example.component.scss',
templateUrl: './readme-example.component.html',
})
export class ReadmeExampleComponent {
private formService = inject(MargaritaFormService);

// Initialize form
public form = this.formService.createForm({
name: 'my-form', // Name for each form should be unique
fields: [
{
name: 'name',
title: 'Full name',
type: 'text',
validation: { required: true },
attributes: { placeholder: 'John Doe' }, // Attributes are passed to the input
},
{
name: 'email',
title: 'Email',
type: 'email',
validation: { required: true },
attributes: { placeholder: '[email protected]' },
},
{
name: 'message',
title: 'Message',
type: 'textarea',
validation: { required: true },
attributes: { placeholder: 'Lorem ipsum' }, // Attributes are passed to the input
},
],
handleSubmit: {
// This required function is called when the form is valid
valid: async ({ value }) => {
console.log('Valid submit', { value });
alert(JSON.stringify(value, null, 2));
},
// This optional function is called when the form is invalid
invalid: async ({ control, value }) => {
const { allErrors } = control.state;
console.log('Form is not valid!', { value, allErrors });
},
},
config: {
handleSuccesfullSubmit: 'reset', // Change what happens when form is successfully submitted
},
});
}
```

**Template** / [readme-example.component.html](https://github.com/margarita-form/margarita-form/blob/main/apps/angular-demos/src/app/readme-example/readme-example.component.html)

```html

Current value


{{ form.value | json }}

Form



{{ control.field.title }}







Submit

```

**[View full Angular example app](https://github.com/margarita-form/margarita-form/tree/main/apps/angular-demos/src/app)**

#### More resources

- [Angular reference docs (WIP)](https://margarita-form.github.io/margarita-form/modules/_margarita_form_angular.html)

### HTML and JS

#### Add the standalone browser script for latest release

```html

```

or for specific version

```html

```

#### Example dynamic form with HTML and JS

**Example file** / [vanilla-html-js.html](https://github.com/margarita-form/margarita-form/blob/main/apps/frameworkless-demos/src/vanilla-html-js.html)

```html




Margarita Form / Vanilla HTML anf JS example





Margarita Form / Vanilla HTML anf JS example






Current value



Form







Submit



const form = MargaritaForm.createMargaritaForm({
name: 'my-form', // Name for each form should be unique
fields: [
{
name: 'name',
title: 'Full name',
type: 'text',
validation: { required: true },
attributes: { placeholder: 'John Doe' }, // Attributes are passed to the input
},
{
name: 'email',
title: 'Email',
type: 'email',
validation: { required: true },
attributes: { placeholder: '[email protected]' },
},
{
name: 'message',
title: 'Message',
type: 'textarea',
validation: { required: true },
attributes: { placeholder: 'Lorem ipsum' }, // Attributes are passed to the input
},
],
handleSubmit: {
// This required function is called when the form is valid
valid: async ({ value }) => {
console.log('Valid submit', { value });
alert(`Form is valid!\n${JSON.stringify(value, null, 2)}`);
},
// This optional function is called when the form is invalid
invalid: async ({ control, value }) => {
const { allErrors } = control.state;
alert(`Form is not valid!\n${JSON.stringify(allErrors, null, 2)}`);
console.log('Form is not valid!', { value, allErrors });
},
},
config: {
handleSuccesfullSubmit: 'reset', // Change what happens when form is successfully submitted
},
onValueChanges: ({ value }) => {
const currentValueEl = document.querySelector('#current-value');
if (!currentValueEl) throw new Error('Element with id "current-value" was not found');
currentValueEl.innerText = JSON.stringify(value);
},
});

const formEl = document.querySelector('form');
if (!formEl) throw new Error('Element with tag "form" was not found');
form.setRef(formEl);

const getInputEl = (control) => {
switch (control.field.type) {
case 'textarea':
return document.createElement('textarea');
default:
return document.createElement('input');
}
};

const formFieldsContainerEl = formEl.querySelector('.form-fields-container');
if (!formFieldsContainerEl) throw new Error('Element with class "form-fields-container" was not found');

form.controls.forEach((control) => {
const fieldEl = document.createElement('div');
fieldEl.classList.add('field-wrapper');

const labelEl = document.createElement('label');
labelEl.setAttribute('for', control.uid);
labelEl.innerText = `${control.field.title}:`;

const inputEl = getInputEl(control);
inputEl.setAttribute('id', control.uid);
inputEl.setAttribute('name', control.name);
inputEl.setAttribute('type', control.field.type);

formFieldsContainerEl.appendChild(fieldEl);
fieldEl.appendChild(labelEl);
fieldEl.appendChild(inputEl);
control.setRef(inputEl);
});

```

**[View full example file](https://github.com/margarita-form/margarita-form/blob/main/apps/frameworkless-demos/src/vanilla-html-js.html)**

### More resources

- [Core reference docs (WIP)](https://margarita-form.github.io/margarita-form/modules/_margarita_form_core.html)

## Other frameworks

The plan is to make more framework specific libraries but even now you can use @margarita-form/core to implement the form logic into any project!

### Install the [Core package](https://www.npmjs.com/package/@margarita-form/core):

```bash
npm install @margarita-form/core
```

### Example dynamic form with other frameworks

**Example TypeScript** / [vanilla-typescript.ts](https://github.com/margarita-form/margarita-form/blob/main/apps/frameworkless-demos/src/app/vanilla-typescript/vanilla-typescript.ts)

```ts
import { MargaritaFormControl, MargaritaFormField, createMargaritaForm } from '@margarita-form/core';

interface MyFormValue {
name?: string;
message?: string;
}

interface MyFormField extends MargaritaFormField {
type: 'text' | 'textarea' | 'number' | 'email' | 'password' | 'tel' | 'url' | 'date';
title: string;
}

type RootField = MargaritaFormField<{ value: MyFormValue; fields: MyFormField }>;

type MyFormControl = MargaritaFormControl;

const form = createMargaritaForm({
name: 'my-form', // Name for each form should be unique
fields: [
{
name: 'name',
title: 'Full name',
type: 'text',
validation: { required: true },
attributes: { placeholder: 'John Doe' }, // Attributes are passed to the input
},
{
name: 'email',
title: 'Email',
type: 'email',
validation: { required: true },
attributes: { placeholder: '[email protected]' },
},
{
name: 'message',
title: 'Message',
type: 'textarea',
validation: { required: true },
attributes: { placeholder: 'Lorem ipsum' }, // Attributes are passed to the input
},
],
handleSubmit: {
// This required function is called when the form is valid
valid: async ({ value }) => {
console.log('Valid submit', { value });
alert(`Form is valid!\n${JSON.stringify(value, null, 2)}`);
},
// This optional function is called when the form is invalid
invalid: async ({ control, value }) => {
const { allErrors } = control.state;
alert(`Form is not valid!\n${JSON.stringify(allErrors, null, 2)}`);
console.log('Form is not valid!', { value, allErrors });
},
},
config: {
handleSuccesfullSubmit: 'reset', // Change what happens when form is successfully submitted
},
onValueChanges: ({ value }) => {
const currentValueEl = document.querySelector('#current-value');
if (!currentValueEl) throw new Error('Element with id "current-value" was not found');
currentValueEl.innerText = JSON.stringify(value);
},
});

const formEl = document.querySelector('form');
if (!formEl) throw new Error('Element with tag "form" was not found');
form.setRef(formEl);

const getInputEl = (control: MyFormControl) => {
switch (control.field.type) {
case 'textarea':
return document.createElement('textarea');
default:
return document.createElement('input');
}
};

const formFieldsContainerEl = formEl.querySelector('.form-fields-container');
if (!formFieldsContainerEl) throw new Error('Element with class "form-fields-container" was not found');

form.controls.forEach((control) => {
const fieldEl = document.createElement('div');
fieldEl.classList.add('field-wrapper');

const labelEl = document.createElement('label');
labelEl.setAttribute('for', control.uid);
labelEl.innerText = `${control.field.title}:`;

const inputEl = getInputEl(control);
inputEl.setAttribute('id', control.uid);
inputEl.setAttribute('name', control.name);
inputEl.setAttribute('type', control.field.type);

formFieldsContainerEl.appendChild(fieldEl);
fieldEl.appendChild(labelEl);
fieldEl.appendChild(inputEl);
control.setRef(inputEl);
});
```

**Example HTML** / [vanilla-typescript.html](https://github.com/margarita-form/margarita-form/blob/main/apps/frameworkless-demos/src/vanilla-typescript.html)

```html




Margarita Form / Vanilla TypeScript example




Margarita Form / Vanilla TypeScript example






Current value



Form







Submit



```

**[View full example app with Vite server](https://github.com/margarita-form/margarita-form/blob/main/apps/frameworkless-demos)**

### More resources

- [Core reference docs (WIP)](https://margarita-form.github.io/margarita-form/modules/_margarita_form_core.html)

## Read more

Proper documentation is coming soon!
For now you can check [reference documentation](https://margarita-form.github.io/margarita-form/index.html) and the following examples!