Formr is in the very early stages of development. Please be kind, and lend a hand if you can!
Formr is a multilingual, vanilla JS form builder and renderer. It is written in TypeScript. Formr is influenced by the amazing work of the folks at Draggable.
Current Features:
- Row/column based WYSIWYG form editor
- Build forms that translate to the client's language
- Themeable
- Multilingual editor interface
- Written in TypeScript
- Requires no dependencies to be loaded
- Supported by all major browsers, and IE11 (with a few polyfills)
Future Features:
- Form logic (smart forms)
- Form validation
Start by including the necessary JS and CSS files:
<link rel="stylesheet" href="/..../formr-editor.css" />
<link rel="stylesheet" href="/..../formr-renderer.css" />
<script src="../dist/js/formr.bundle.js"></script>Initialize the editor:
const editor = new Formr.Editor(container, config, data);or the renderer:
const renderer = new Formr.Renderer(container, config, data);containeris either anHTMLElementor a valid selector.configis an object containing the instance configuration datadatais optional, and contains form data to be pre-loaded and displayed once the instance is ready.
Both the Editor and the Renderer return a Promise that will Resolve() when the instance is ready to be used:
await editor.promise;
await renderer.promise;The editor is configured by passing an object via the config argument of the constructor.
config = {
theme: {
name: "bootstrap",
prefix: "/prefix/to/theme/files/",
data: "see themes below"
},
i18n: {
name: "FR",
prefix: "/prefix/to/i18n/files/",
data: "see i18n below"
},
plugins: {
[FormrPlugins.SuperPlugin,; {...}],
FormrPlugins.UltraPlugin;
}
}The renderer is configured by passing an object via the config argument of the constructor.
config = {
theme: {
name: "bootstrap",
prefix: "/prefix/to/theme/files/",
data: "see themes below"
},
plugins: {
[FormrPlugins.SuperPlugin,; {...}],
FormrPlugins.UltraPlugin;
}
}Notice that both the editor and renderer share much of their configurations.
Formr supports themes. Themes are implemented in JSON files and can either be fetched on-load, or passed in the config
of the instance, or both. If both an anonymous object and a remote file are provided, the two will be merged.
Themes are composed of an object of key-value pairs where the key is the class of the element being created and the
value is an array of classes to be added to that element.
namedefines the name of the file to load. ex: if name is "bootstrap", then the file "bootstrap.json" will be loadedprefixdefines the path to the theme file. In the example config above, the file "/prefix/to/theme/files/bootstrap.json" will be loadeddatais optional, and allows you to provide the theme data as an object directly in the configuration. This avoids an extra HTTP request, but for larger themes can be unmaintainable.
this.theme.getClasses("element-type");- If a theme file or object was loaded, then the
value(array of strings) associated with thekey"element-type" will be returned - If no language file or object was loaded, or the
keydoesn't exist, an empty array is returned.
{
"formr-control": ["form-group"],
"formr-input": ["form-control"],
"formr-help": ["form-text", "text-muted"]
}When either the editor or the renderer are displayed, any elements of class formr-control will also have form-group.
I18N is implemented the same way as themes. The key is the text that will be displayed in the default language (EN).
value is what will be displayed instead of key.
namedefines the name of the file to load. ex: if name is "EN", then the file "EN.json" will be loadedprefixdefines the path to the theme file. In the example config above, the file "/prefix/to/theme/files/EN.json" will be loadeddatais optional, and allows you to provide the I18N data as an object directly in the configuration. This avoids an extra HTTP request, but for larger files can be unmaintainable.
this.i18n.localize("String to Localize");- If a language file or object was loaded, then the
keyassociated with "String to Localize" will be displayed - If no language file or object was loaded, or the
keydoesn't exist, thekeyis displayed as-is.
{
"Add Row": "Ajouter Rangée",
"Change Language": "Selectionner Langue",
"Add Column": "Ajouter Colonne"
}Formr allows you to translate your forms. This is achieved with the Translation plugin. Creating a multilingual form is pretty simple:
- Add the plugin to the editor's config:
{
[FormrPlugins.TranslationPlugin, {defaultLanguage: 'en', supportedLanguages: ['en', 'fr']}]
}- Design your form in the default language. The default language would be whatever language you want displayed in the event a user requests a language that is not implemented.
- Hover the globe icon added to the menu by the Translation plugin and select the next language you want to work on.
- Translate your form!
Translations are stored in a traslations object added to the form data when you get editor.Data. In an effort to keep
the extra data as small as possible, only the differences are kept. That means that if you forget to translate an input's
label, it will show up in the default language while the rest of the form will be translated.
Both FormrEditor and FormrRenderer are event emitters. Plugins can subscribe to these events to influence the outcome
of certain actions.
initis fired before the editor's DOM is created and the config and data are loaded.initDoneis fired once the editor has been applied to the DOM and is ready to use. You should not use this event to determine when the editor is ready but ratherawait editor.promise.dataGeneratingis fired before form data is generated. Use this event to manipulate form elements beforegetData()is called.dataGenerated(data)is fired after form data has been generated. Use this to manipulate the generated data.
initis fired before the renderer's DOM is created and the config and data are loaded.initDoneis fired once the renderer has been applied to the DOM and is ready to use. You should not use this event to determine when the renderer is ready but ratherawait renderer.promise.rendering(data)is fired before form data is rendered. Use this to manipulate the form data before displaying it.rendereris fired after rendering. Use this to manipulate the rendered form.
Plugins can be designed 2 different ways; one plugin for both the renderer and editor, or one each. The following rules must be respected:
- Editor plugins must
implement EditorPlugininterface, and renderer plugins mustimplement RendererPlugin - A plugin's constructor must accept 1 or 2 arguments.
constructor(services: Container, config: any = null):servicesis anInverisy.Container(a DI container) and can be used to fetch any of the services made available to the editor. For example, a plugin could make use of theThemeobject in the container to apply classes from the loaded themeconfigis an optional anonymous object that can be used to hold arbitrary config data for your plugin. Place anything your plugin needs to get going in here.
See the TranslationPlugin in the source as en example.
Plugins are added to the editor/renderer by placing them in the plugins object of the config:
config = {
...
plugins;: {
[PluginConstructor;: EdiotorPlugin|RendererPlugin, PluginConfig;: any;], //This plugin will be passed `PluginConfig`
PluginConstructor //This plugin will only get the DI container
}
}If building the project with npm run build dev|prod, any files ending in ".Plugin.ts" in "src/plugins" will be compiled
into the FormrPlugins library.
If you'd rather build your plugin separately, you need only implement the required interfaces.
Form designers will be able to define custom logic based on user input such as hiding sections, changing styling, messages, etc
Formr will support powerful form validation features like error messages, regex validation, remote validation, etc
I've benefited from the open source community since my early days learning HTML. I am committed to providing Formr completely free of charge and with the least restrictive licence I am comfortable with. I will never charge for formr, I ask only that if you feel formr could be better, get in touch and let me know, or better yet, submit a pull request!
Special thanks to the wonderful people at Jetbrains for supporting open source projects with their software!