{"id":13793357,"url":"https://github.com/zaceno/hyperapp-form","last_synced_at":"2025-03-21T13:31:43.188Z","repository":{"id":40289042,"uuid":"245024247","full_name":"zaceno/hyperapp-form","owner":"zaceno","description":"Utility library for plumbing forms with hyperapp","archived":false,"fork":false,"pushed_at":"2023-02-03T05:23:57.000Z","size":279,"stargazers_count":9,"open_issues_count":12,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-04-25T22:20:17.470Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zaceno.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-03-04T23:18:24.000Z","updated_at":"2022-05-27T06:19:17.000Z","dependencies_parsed_at":"2023-02-18T03:40:14.108Z","dependency_job_id":null,"html_url":"https://github.com/zaceno/hyperapp-form","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zaceno%2Fhyperapp-form","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zaceno%2Fhyperapp-form/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zaceno%2Fhyperapp-form/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zaceno%2Fhyperapp-form/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zaceno","download_url":"https://codeload.github.com/zaceno/hyperapp-form/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244806112,"owners_count":20513382,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T23:00:19.517Z","updated_at":"2025-03-21T13:31:42.811Z","avatar_url":"https://github.com/zaceno.png","language":"JavaScript","funding_links":[],"categories":["Utilities"],"sub_categories":[],"readme":"# Hyperapp-form\n\nNo matter what framework you're using, working with forms\ncan be frustrating. It often involves lots of plumbing that can feel pretty\ntangential to the main logic of your app. This library tries to\nalleviate some of this drudgery for developers using Hyperapp (v2)\n\n## Getting it into your project\n\n### ES Module import\n\nYou can simply import the form components as es-modules:\n\n```js\nimport * as form from 'https://unpkg.com/hyperapp-form'\n```\n\n### NPM Package\n\nAlternatively, install it as a dependency in your project\n\n```sh\nnpm i hyperapp-form\n```\n\nand import the form components from the installed package:\n\n```js\nimport * as form from 'hyperapp-form'\n```\n\n\u003e This library has a peer dependency on hyperapp, so remember to also\n\u003e install hyperapp!\n\n## Basics\n\n### Initializing\n\nA form using this library, needs to keep some state in the app's global state. Before rendering\nthe form, make sure to initialize the form state using the `init` function. Typically, in the\ninitial state of your app, or in the action that causes the form to be rendered\n\n```js\nconst ShowForm = state =\u003e ({\n    ...state,\n    page: 'form',\n    form: form.init(),\n})\n```\n\n### The form component\n\nWhen you render your form, rather than using a plain `\u003cform\u003e\u003c/form\u003e` tag pair, use the `form`\ncomponent from this library instead.\n\n```jsx\nstate =\u003e (\n    \u003cmain\u003e\n        \u003ch1\u003eMy form:\u003c/h1\u003e\n        \u003cform.form\n            state={state.form}\n            getFormState={s =\u003e s.form}\n            setFormState={(s, x) =\u003e ({ ...s, form: x })}\n            onsubmit={HandleSubmittedForm}\n        \u003e\n            ... form contents here ...\n        \u003c/form.form\u003e\n    \u003c/main\u003e\n)\n```\n\nYou'll need to pass a handfull of props to the form, described below:\n\n`state` is simply to tell the form its current state. However, we take care of updating the form state with actions under the hood. So we need to know how to get and set the form state in the actions as well. That is why you must pass `getFormState` and `setFormState`.\n\nFinally, `onsubmit` is the action where you handle the result of a successfully submitted form. The data entered into the form will be passed as a key-value object in the payload of the action.\n\n```js\nconst HandleSubmittedForm = (state, data) =\u003e [\n    state,\n    data.sendCopy \u0026\u0026\n        sendEmailEffect({\n            address: data.email,\n            message: data.message,\n        }),\n]\n```\n\n### inputs\n\nIn order to get some data to your submit-handler, it needs to be entered in the form somewhere. Instead of adding a plain `\u003cinput\u003e` tag, add an `input` component.\n\n```jsx\n\u003cform.input type=\"text\" name=\"foo\" placeholder=\"anything\" /\u003e\n```\n\nThis component returns a regular `\u003cinput\u003e` tag with all the same properties you set on it, only wired to the `form` component that contains it. This is one of the core ideas of the library: to be as close as possible to writing a regular form in html, and not introduce any surprising elements in the DOM.\n\n### Submitting\n\nButtons for submitting forms can also be added\n\n```jsx\n\u003cform.button type=\"submit\"\u003eSubmit\u003c/form.button\u003e\n```\n\nAll that we've covered so far, is combined in a live runnable example you can try out (while inspecting the code) here: [https://zaceno.github.io/hyperapp-form/#flow/submitting](https://zaceno.github.io/hyperapp-form/#flow/submitting)\n\nGo there, type \"bar\" in the input field then click submit. Notice two things:\n\n-   After the form was submitted, the input and submit-button became disabled. A form, once submitted, cannot be submitted again. You must initialize the state to render the form editable once more (Use the \"Reset\" button in the example to do this).\n-   The data submitted was: `{foo: \"bar\"}`. \"bar\" is the text you typed, and `foo` is because you typed it in an input with `name=\"foo\"`.\n\n### Validation On Submit\n\nMost often you want the data submitted from a form to conform to certain rules. If the entered data is not conformant, you don't want the submission to go through, but to leave the form editable, with hints so users can correct it.\n\nVisit [https://zaceno.github.io/hyperapp-form/#flow/validating](https://zaceno.github.io/hyperapp-form/#flow/validating) for an example.\n\nTry typing some letters in the input box, and hit enter. Notice:\n\n-   The form did not submit. (The submitted value is still null)\n-   The form is still editable.\n-   The error message \"Code must be six digits\" is displayed.\n-   The input gets a red border and color.\n\nThis is all due to the `validator` prop on the input:\n\n```jsx\n\u003cform.input type=\"text\" name=\"code\" validator={validcode} /\u003e\n```\n\nwhere the `validcode` validator is defined as:\n\n```js\nconst validcode = x =\u003e\n    !!x \u0026\u0026 x.match(/^\\d{6}$/) ? '' : 'Code must be six digits'\n```\n\nWhen a form is submitted, each validator attached to a `form.input` is called with the value for that input. If any the validators return a string message (or any truthy value in fact), we will _not_ call the `onsubmit` handler, and we leave the form open.\n\nMoreover, each input whose validator returned an error message, will have \"error\" added to its class list.\n\nThe error message is displayed thanks to this component:\n\n```jsx\n\u003cform.error /\u003e\n```\n\nIt returns a virtual node representing the html:\n\n```html\n\u003cp class=\"error\" hidden=\"???\"\u003eSome error message\u003c/p\u003e\n```\n\nIf any of the validators returned a message, _one_ of those messages will be shown. The `p` tag is rendered always, but it is hidden until there is a message to show.\n\n### Validation while editing\n\nValidators also run as the user types in fields where there is an error. When the error is corrected, the \"error\" class is removed, and the error message dissapears.\n\nAnd even if a form is not submitted, when the user blurs a field containing a bad value, that particular field is validated. We validate on blur so as not to unnecessarily annoy users.\n\nThe onblur validation only applies to text-style inputs (text, password, email et c). Others like radios, checkboxes, select-dropdowns are validated immediately when the value changes.\n\nGive it a try!\n\n### Forms with initial values\n\nSometimes the form is meant for changing a bunch of values already existing on the server. So we want all pre-existing values set on\nthe form from the beginning.\n\nYou can see how to do that in this example: [https://zaceno.github.io/hyperapp-form/#flow/initvals](https://zaceno.github.io/hyperapp-form/#flow/initvals)\n\nNotice how we initialize the form with an object as the first argument:\n\n```js\nconst init = {\n    submitted: null,\n    form: form.init({\n        foo: 'default value',\n        hidden: 'not editable',\n    }),\n}\n```\n\nYou will notice that the input has the value \"default value\" from the start. That is because the name of the input is `\"foo\"`.\n\nYou do not see the text \"not editable\" anywhere because there is no `form.input` with the name `\"hidden\"`. However, if you submit the form, you will see that both values were passed through to the onsubmit handler.\n\n### Forms with initial errors\n\nIn the end, all the validation we do on the client side is just\nto help the user produce sensible data to send to a server. The real validation happens there, and sometimes it finds problems the client side can't check for.\n\nIn those cases, we probably want to present the form again, with the values intact and an error message explaining what went wrong.\n\nSee the example [https://zaceno.github.io/hyperapp-form/#flow/initerrors](https://zaceno.github.io/hyperapp-form/#flow/initerrors)\n\nThe email input already has the \"error\" class from the start, and the error message is explaining that something went wrong server side. This, again, is because of how we initialized the form:\n\n```js\nconst init = {\n    submitted: null,\n    form: form.init(\n        {\n            email: 'boo@example.com',\n        },\n        {\n            email: 'We could not verify your email, please double check it!',\n        }\n    ),\n}\n```\n\nAs before, we are setting the \"email\" default value to an email address the user probably typed before. But we are also passing a second argument - these are the errors that the form should register from the start.\n\nThese initial errors will be cleared the first time a user validates the input or submits the form (when all inputs are validated).\n\n## More Components\n\nYou've already encountered regular input components in the preceeding examples. But `\u003cinput type=\"checkbox\"\u003e` and `\u003cinput type=\"radio\"\u003e` work a bit differently. And there's a couple other components to look at while we're at it.\n\n### Checkboxes\n\nYou render a checkbox by using the `form.input` component, with the `type=\"checkbox\"` property. You should also give it a `name` property.\n\nIf the `name` of the checkbox is \"foo\", then if the form is submitted with the checkbox checked, the values submitted will have `{foo: 'on'}`. You can also assign a value, `value=\"bar\"`, in which case the values would have `{foo: \"bar\"}`\n\nIf the form is submitted with multiple checkboxes having the same name, then the values will be listed as an array. For example, consider these:\n\n```jsx\n\u003cform.input type=\"checkbox\" name=\"foo\" value=\"a\" /\u003e\n\u003cform.input type=\"checkbox\" name=\"foo\" value=\"b\" /\u003e\n\u003cform.input type=\"checkbox\" name=\"foo\" value=\"c\" /\u003e\n\u003cform.input type=\"checkbox\" name=\"foo\" value=\"d\" /\u003e\n\u003cform.input type=\"checkbox\" name=\"foo\" value=\"e\" /\u003e\n```\n\nIf the form is submitted with the 'a', 'c' and 'd' boxes checked, the values will have: `{foo: ['a', 'c', 'd']}`\n\nHave a look at [https://zaceno.github.io/hyperapp-form/#components/checkboxes](https://zaceno.github.io/hyperapp-form/#components/checkboxes) for a more in-depth example.\n\nJust like the other inputs, you can set a validator on checkboxes. Validators apply by name, so if you have multiple checkboxes with the same name, you don't need to set the validator on each of them, but it doesn't hurt either. Unlike text-style inputs, validation occurs immediately on input. Not on blur.\n\n### Radio buttons\n\nYou render radiobuttins by setting `type=\"radio\"` on a `form.input`. For radio buttons to make sense, there should be more than one having the same `name` but different values. For example if you have:\n\n```jsx\n\u003cform.input type=\"radio\" name=\"foo\" value=\"a\" /\u003e\n\u003cform.input type=\"radio\" name=\"foo\" value=\"b\" /\u003e\n\u003cform.input type=\"radio\" name=\"foo\" value=\"c\" /\u003e\n```\n\nThen only one of those can be checked at a time. The one (if any) that is checked when the form is submitted will also be the value submitted for name \"foo\".\nValidation works the same as for checkboxes (see above).\n\nFor a more complete example see [https://zaceno.github.io/hyperapp-form/#components/radios](https://zaceno.github.io/hyperapp-form/#components/radios)\n\n### Dropdown menus\n\nTo make a dropdown, do as you would with regular html and just replace the `\u003cselect\u003e` tag with the `form.select` component, e g:\n\n```jsx\n\u003cform.select name=\"age\"\u003e\n    \u003coption value=\"\"\u003eSelect your age:\u003c/option\u003e\n    \u003coption value=\"age-0\"\u003eunder 18\u003c/option\u003e\n    \u003coption value=\"age-1\"\u003eunder 40\u003c/option\u003e\n    \u003coption value=\"age-2\"\u003eunder 80\u003c/option\u003e\n    \u003coption value=\"age-3\"\u003eover 80\u003c/option\u003e\n\u003c/form.select\u003e\n```\n\nThere is no special component for the `\u003coption\u003e` or `\u003coptgroup\u003e` tags - just use the regular html-tags.\n\nYou can attach a `validator` to the `form.select` component as well. Like checkboxes and radio-buttons, validation happens immediately on input. Not on blur.\n\nFor a complete example, see: [https://zaceno.github.io/hyperapp-form/#components/select](https://zaceno.github.io/hyperapp-form/#components/select)\n\nThere is not yet any support for multi-selectable `select`s\n\n### Buttons\n\nYou've already seen the `\u003cform.button type=\"submit\"\u003e` component in the examples. If you need other buttons for other reasons among the children of your `form.form` component, then you would normally need to make sure to set `type=\"button\"` on them (or they would also submit the form). You'd also need to handle disabling the buttons for submitted forms some other way. Instead you can simply use the `form.button` component with no type, and it will behave as you probably want:\n\n```jsx\n\u003cform.button onclick={DoWhatever}\u003eClick me\u003c/form.button\u003e\n```\n\n## Advanced\n\n### Custom Form Inputs\n\nPerhaps the standard fields aren't enough for you and you'd like to build a more complex type of input. You can do this with the `widget` function.\n\nYou call widget with: `form.widget(name, validator, renderer)` and it will return whatever you return from `renderer`. `renderer` is called with a set of props (computed from the form-props as well as `name` and `validator`) and should return a virtual-node which may use those props.\n\nFor a concrete example have a look at [https://zaceno.github.io/hyperapp-form/#advanced/custominput](https://zaceno.github.io/hyperapp-form/#advanced/custominput)\n\nThe props that the renderer is called with are:\n\n-   `value`: the current value for the given name,\n-   `error`: the current error for the given name,\n-   `Set`: an action which sets the value for the current name (give as payload)\n-   `Validate`: an action which validates the given payload and sets the error maybe for the given name.\n-   `disabled`: boolean if the widget should be disabled (because the form is already submitted)\n\n\u003e Notice you might want want to call the `Set` and `Validate` action in one go. To do that, we offer you the `form.batch` action utility. It's not form related but we use it internally so we might as well let you use it too. It defines one action from several. Dispatching `batch(a1, a2, a3)` is the same as dispatching actions a1, a2, a3 in succession (with the same payload, and no rendering in between).\n\n### Context\n\nThe secret behind how this library works, is that the `form.form` component provides a _context_ to all the children and grandchildren. That is to say, there is an object of computed values and actions available anywhere in the tree below `form.form`. To define a component which can read the context, instead of returning a virtual-node from your component, return a function which returns the virtual-node. That function will be called with the context as argument.\n\n```js\nconst MyFormAwareComponent = props =\u003e context =\u003e html`\u003cdiv\u003e...\u003c/div\u003e`\n```\n\nThe context is an object with the following properties:\n\n-   `values`: all the current values of the form\n-   `SetValues`: action to set all the values of the form\n-   `errors`: all the current errors of the form\n-   `SetErrors`: action to set all the values of the form\n-   `submitted`: wether the form has been submitted or not\n-   `register`: a function for registering validators\n\nThe `register` function is how form widgets can register validators that will be called (reduced) before the form submits. A validator you register here has the signature: `currentErrors =\u003e newErrors`\n\nOne reason for using context could be to implement a more advanced\ntype of form-component that `widget` is not enough for. Another reason would be to implement components that visualize some calculation of the currently entered values as in this example (a box shows the color representation of the currently entered hue, saturation \u0026 lightness values): [https://zaceno.github.io/hyperapp-form/#advanced/custominfo](https://zaceno.github.io/hyperapp-form/#advanced/custominfo)\n\n### Overriding Context\n\nIf you want more control over the widgets of a form, you can modify the context provided by using the `form.provide` function.\n\n```js\nform.provide({\n    register: ...\n    values: ...\n    errors: ...\n    submitted: ...\n    SetValues: ...\n    SetErrors: ...\n},\n [ ...children]\n)\n```\n\nTechnically, `provide` is not specifically for forms, and you could use it to provide any context to any parts of your app. But if you want to control behavior of components from hyperapp-form, you need to provide a context object that looks and behaves like how they expect.\n\nAn example of this can be found here: [https://zaceno.github.io/hyperapp-form/#advanced/custombehavior](https://zaceno.github.io/hyperapp-form/#advanced/custombehavior)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzaceno%2Fhyperapp-form","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzaceno%2Fhyperapp-form","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzaceno%2Fhyperapp-form/lists"}