惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Vulnerabilities – Threatpost
月光博客
月光博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
美团技术团队
Last Week in AI
Last Week in AI
Jina AI
Jina AI
P
Privacy International News Feed
有赞技术团队
有赞技术团队
WordPress大学
WordPress大学
宝玉的分享
宝玉的分享
T
Tenable Blog
阮一峰的网络日志
阮一峰的网络日志
P
Proofpoint News Feed
T
Tailwind CSS Blog
Apple Machine Learning Research
Apple Machine Learning Research
P
Privacy & Cybersecurity Law Blog
人人都是产品经理
人人都是产品经理
S
Schneier on Security
Google DeepMind News
Google DeepMind News
爱范儿
爱范儿
C
Cisco Blogs
K
Kaspersky official blog
C
Cybersecurity and Infrastructure Security Agency CISA
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
O
OpenAI News
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
IT之家
IT之家
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
酷 壳 – CoolShell
酷 壳 – CoolShell
T
Tor Project blog
博客园 - 【当耐特】
腾讯CDC
V
V2EX
A
Arctic Wolf
Webroot Blog
Webroot Blog
S
Securelist
小众软件
小众软件
大猫的无限游戏
大猫的无限游戏
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园 - 三生石上(FineUI控件)
The GitHub Blog
The GitHub Blog
量子位
J
Java Code Geeks
博客园 - 叶小钗
S
SegmentFault 最新的问题
Project Zero
Project Zero
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Scott Helme
Scott Helme
Cyberwarzone
Cyberwarzone

Secret Weblog

Becoming More Xee: A Modern XPath and XSLT Engine in Rust Looking for new challenges! Repeat Yourself, A Bit The Curious Case of Quentell The Humble For Loop in Rust The Humble For Loop in JavaScript Don Question Best Practices I Was a 1980s Teenage Programmer Part 5: Achieving Assembly I Was a 1980s Teenage Programmer Part 4: The Call of Assembly The Tooling Shift I Was a 1980s Teenage Programmer Part 3: MSX-2 JavaScript: when you need two ways to do it! Empowering Programming Languages Bloat and Retrofuturism Refreshing my Blog Again Random Rust Impressions Apilar: An Alife System I Was a 1980s Teenage Programmer Part 2: Olivetti M24 I Was a 1980s Teenage Programmer: the Alphatronic SolidJS fits my brain Is premature optimization the root of all evil? Framework Patterns: JavaScript edition Roll Your Own Frameworks Framework Patterns Secret Weblog Highlights Refactoring to Multiple Exit Points Seven Years: A Very Personal History of the Web Morepath 0.16 released! Is Morepath Fast Yet? Introducing Bob Strongpinion Punctuated Equilibrium in Software Morepath 0.15 released! Impressions of React Europe 2016 Morepath 0.14 released! Morepath 0.13 now with Dectate Dectate: advanced configuration for Python code JavaScript Dependencies Revisited: An Example Project The Incredible Drifting Cyber A Brief History of Reselect The Emerging GraphQL Python stack Thoughts about React Europe Build a better batching UI with Morepath and Jinja2 GraphQL and REST Server Templating in Morepath 0.10 10 reasons to check out the Morepath web framework in 2015 A Review of the Web and how Morepath fits in Morepath 0.9 released! Better REST with Morepath 0.8 Morepath 0.7: new inter-app linking They say something I don Life at the Boundaries: Conversion and Validation BowerStatic 0.4 released! Morepath 0.6 released! Morepath 0.5(.1) and friends released! New HTTP 1.1 RFCs versus WSGI Against On Naming In Open Source My visit to EuroPython 2014 Morepath 0.4.1 released (with Python 3 fixes) Morepath 0.4 and breaking changes Announcing BowerStatic Morepath 0.3 released! Morepath 0.2 Morepath Python 3 support The Call of Python 2.8 Morepath 0.1 released! WebOb and Werkzeug compared Morepath: from Werkzeug to WebOb Racing the Morepath: SQLAlchemy Integration The Centre Cannot Hold Breaking Morepath Changes Morepath Update How to do REST with Morepath Morepath Security the Gravity of Python 2 #python2.8 discussion channel on freenode Alex Gaynor on Python 3 Morepath Documentation Starting to Take Shape Back to the Center Morepath App Reuse Implementing Grok Grok: the Idea Why Linux Works for Me On the Morepath Reg, Now With More Generic! The New Zope as a Web Framework Jim Fulton, Zope Architect Renewing Zope Object Publishing The Weirdness of Zope The Rise of Zope My Exit from Zope Reg: Component Architecture Reimagined JSConf EU 2013 impressions Obviel 1.0! JS Dependency Tools Redux Obviel 1.0rc1 released! Succinct data structures
mstform: a form library for mobx-state-tree
Martijn Faassen · 2019-08-15 · via Secret Weblog

Introduction

So I've written a new web form library: mstform.

The first form library I ever wrote was called ZFormulator and I released it in 1999. My first frontend form library was Obviel Forms and it I wrote it in 2012 or so. I've been at it for a while.

Much has changed in web development since 1999, and I've learned a thing or two along the way as well. So a little while ago, almost against my will (though I admit I also enjoy it a lot), I helped to create another form library, one for the frontend again. It integrates with mobx-state-tree. You'd probably use React to render the form, though you don't have to.

A whirlwind introduction to mobx-state-tree

So what, you may ask, is mobx-state-tree, and why should I want to use it in my React application? mobx-state-tree is a state management library - it manages the state you show in your frontend UI. It's built on top of another React state management library named MobX. MobX is magic. You define your model classes, and you mark a few properties and methods so that MobX can observe them. You also mark your React components as observers. It's easy. After that when you change your model instances your React UI will automatically and efficiently update itself.

As I said, mobx-state-tree (MST to make it less of a mouthful) is built on MobX. The way you mark your React components as observers is identical. What it then adds is the following: it forces you to define your models in a very particular way and then it gives you a ton of features in return.

Here's a tiny example:

const Animal = types
  .model("Animal", {
    name: types.string,
    hunger: types.integer,
  })
  .views((self) => ({
    get isSated() {
      return self.hunger === 0;
    },
  }))
  .actions((self) => ({
    feed(amount) {
      self.hunger -= amount;
      if (self.hunger < 0) {
        self.hunger = 0;
      }
    },
  }));

Here we have a model Animal. It has two properties, name and hunger. There's also a special view property we have defined with a getter, isSated. We've also defined an action which manipulates the properties.

MST then gives you some interesting features:

  • You get run-time checks if you put in the wrong type of value. With TypeScript you also get compile-time checks.
  • You can automatically serialize this to JSON and deserialize it from JSON.
  • You can install hooks to monitor any changes to properties.
  • There is a mechanism for defining references between models, and the JSON serializer works with it.

mstform builds on MST to help you create web forms.

Rendering a web form: you're on your own

In the past, for a library like Formulator or Grok or Obviel Forms, I made sure it could render your form. That's nice to have when you have simple forms -you just write a form description or perhaps a schema, and boom, the system automatically shows a form. Django's form system works like that too.

The problem with automatically rendering a form is that any complex form will have special UI. Perhaps you want to display a piece of text between two widgets. Perhaps you want to render repeating sub-forms in a certain way. Perhaps you want to layout the form so that it responds to the width of the browser window. I don't know. If you automatically render a form and that's the default way of using the form library, the form library has to supply concepts and hooks so that you can influence the form rendering process. These hooks are typically difficult to use for the developer and are incomplete, as it's difficult to anticipate everything.

With mstform, I let all of that go. It doesn't try to render forms. It isn't in control of rendering your form at all. It would be fairly straightforward to build something on top of mstform that did this for you, but it's not a priority. You have React. You have your own form components or use some UI library that already provides them. mstform instead makes it easy to integrate with these components.

What does mstform do then?

If mstform doesn't render your form, what does it do then? It manages the form contents - the form state. In fact, an earlier name for this library was FormState, until I discovered there already was another library out there with that name.

mstform in its essence lets you define a form that represents a MST model. Here is an example for the Animal model we defined above:

import { Form, Field, converters } from "mstform";

const form = new Form(Animal, {
  name: new Field(converters.string),
  hunger: new Field(converters.integer),
});

You can then use this form to manage the state of instances of that model:

// use MST to create an instance of an animal
const elephant = Animal.create({ name: "Elephant", hunger: 3 });

// now make a form state for elephant
const formState = form.state(elephant);

You can access fields from the form state:

const nameField = formState.field("name");

Once you have a field, you can render it with React. Here is how you could render an input text:

import { Component }, React from "react";
import { observer } from "mobx-react";

@observer
class Input extends Component {
    render() {
        const { type, field } = this.props;
        return <input type={type} {...field.inputProps} />;
    }
}

You can do a lot of other things with the field accessor too: get its error message with field.error, check whether the field is required with field.required, check whether the field is empty with field.isEmpty and so on.

What does that give you?

Why should I use MST with mstform instead of rolling my own, you may ask?

Here we come to features of mstform:

Convert form input

The form input is often just a bunch of texts, but you have a data model underneath. For instance, if I enter an integer in a text input it is a string. mstform converts this into an integer.

Show edit forms

Say you already already have form content, because you just loaded it from the backend and deserialized it for instance, you need to convert that content back again to its display state. For instance, render an integer as a string so it can be displayed in a text input.

Client-side validation

Even after the conversion was successful, you want to validate that the value fulfills some criteria, such as being within a certain range. You still need to do validation on the backend to be sure, but this way you can show errors right away.

Error handling

An easy way to show errors, both in conversion and validation. We can also show backend-generated errors, to which we will get in a bit.

Repeated forms and sub forms

You can express nested and repeated structures in your models easily in the form.

Backend integration

You can define how your form is saved to the backend. You can only submit a form if it's valid. The backend can return validation errors and warnings which can be displayed in the form.

Server-side validation

You can also set it up so that your backend generates validation errors during user interaction. This way the backend can remain in charge of validation as the single source of truth, while you still dynamically display errors right away.

Form access states

Make your fields required, disabled, hidden and read-only. Disable individual fields or a whole sub-form at once. You have to write rendering code that can handle these states as mstform doesn't do rendering, but once you have it your forms become very flexible indeed.

Modify the underlying object

You can modify the underlying MST model instance with your own code and the form automatically updates itself. Modify a field value, add a new repeated item, the works. It's just that simple.

Derived values between fields

A field can have a default value that is based on the values filled in with other fields.

Support for different kinds of React input components

Some components get the value with onChange, others get the value as event.target.value. Some components display their value with value, others with checked. mstform converters have defaults, but you can override them to suit your needs.

Support for internationalized decimal input

Decimal numbers tend to have quite a few differences between countries. For instance, in the US you use periods for the fraction, and commas for the thousands, but in the Netherlands it's the other way around. mstform has a special decimal parser built-in which takes care of that.

TypeScript support

mstform is written in TypeScript and exports TypeScript type definitions for your development convenience.

For more, consult the mstform documentation.

Conclusion

Setting up a form with mstform takes a bit of effort, though it's not difficult. Once you do, a lot of code you might write as custom and failure-prone React components goes away, while you retain a lot of flexibility. Your users edit a form, and a proper MST instance sits under it. You can immediately sends its JSON to the backend. You can load up new form contents from the backend just as easily.

Is this the last form library I will ever write? Experience teaches me not to make such promises. But mstform does pack a lot of my experience with web forms in it. I hope you'll give it a try!

Credits

mstform was created by myself and team of people at a customer of mine named ISProjects. I am very grateful to ISProjects for their great support in this and quite a few other interesting projects I got to work on for them. And they're looking for developers!