{"id":15616696,"url":"https://github.com/saibotsivad/jsonapi-svelte-form","last_synced_at":"2025-03-29T15:21:37.165Z","repository":{"id":57285638,"uuid":"407312148","full_name":"saibotsivad/jsonapi-svelte-form","owner":"saibotsivad","description":"JSON:API based forms, built with Svelte.","archived":false,"fork":false,"pushed_at":"2022-05-10T16:19:04.000Z","size":134,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-24T02:12:51.906Z","etag":null,"topics":["form","json-api","jsonapi","svelte"],"latest_commit_sha":null,"homepage":"https://saibotsivad.github.io/jsonapi-svelte-form/","language":"Svelte","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/saibotsivad.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-09-16T20:54:54.000Z","updated_at":"2022-02-23T18:23:12.000Z","dependencies_parsed_at":"2022-09-07T13:13:25.588Z","dependency_job_id":null,"html_url":"https://github.com/saibotsivad/jsonapi-svelte-form","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saibotsivad%2Fjsonapi-svelte-form","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saibotsivad%2Fjsonapi-svelte-form/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saibotsivad%2Fjsonapi-svelte-form/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saibotsivad%2Fjsonapi-svelte-form/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/saibotsivad","download_url":"https://codeload.github.com/saibotsivad/jsonapi-svelte-form/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246201155,"owners_count":20739707,"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":["form","json-api","jsonapi","svelte"],"created_at":"2024-10-03T07:21:06.689Z","updated_at":"2025-03-29T15:21:37.142Z","avatar_url":"https://github.com/saibotsivad.png","language":"Svelte","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JSON:API Svelte Form\n\nTooling for building forms in Svelte for JSON:API backends.\n\nIf you use Svelte to build business webapps, and those webapps interact with [JSON:API](https://jsonapi.org/), than you've probably thought about building some tooling to help make it easier to build good forms, without so much boilerplate.\n\n## How to Use\n\nTo import the Svelte components (documented below):\n\n```js\nimport {\n\tField,\n\tFieldSetter,\n\tForm,\n\tFormCreate,\n\tFormRemove,\n\tMakeDiff,\n\tRelationship\n} from 'jsonapi-svelte-form'\n```\n\nTo import the mapper functions:\n\n```js\nimport { saving, error, saved } from 'jsonapi-svelte-form/mapper'\n```\n\n## The Big Idea\n\nBuild a form with a couple Svelte component wrappers:\n\n```html\n\u003cscript\u003e\n\timport { Form, Field } from 'jsonapi-svelte-form'\n\texport let form // a specially formed object\n\u003c/script\u003e\n\u003cForm bind:form let:remove let:create\u003e\n\tInside your form, bind a property to an input element:\n\t\u003cField bind:form let:set let:value id=\"id001\" keypath={[ 'attributes', 'color' ]}\u003e\n\t\t\u003cinput type=\"text\" {value} on:input={event =\u003e set(event.target.value)} /\u003e\n\t\u003c/Field\u003e\n\u003c/Form\u003e\n```\n\nNow, you can bind values using JSON Pointer paths.\n\nThere's a demo [here](https://saibotsivad.github.io/jsonapi-svelte-form/), or as a [Svelte REPL](https://svelte.dev/repl/ca6db8ec270d4f5c9f8cd679592e8441?version=3.43.0), to see how to use the tooling.\n\n## Data Structure\n\nWhen you get a response from a JSON:API server, you map it to a `JsonApiForm` object, typically using the `load` function:\n\n```js\nimport { load } from 'jsonapi-svelte-form/mapper'\nconst fetchVehicle = () =\u003e fetch('/api/v1/vehicles/id001')\n\t.then(response =\u003e response.json())\n\t.then(load)\n```\n\nThat structure of that object looks like this:\n\n```js\nconst JsonApiForm = {\n\t// This is the data you'll mutate with your form:\n\tdata: {\n\t\tid001: {\n\t\t\tid: 'id001',\n\t\t\ttype: 'car',\n\t\t\tattributes: { /* ... */ }\n\t\t}\n\t},\n\t// A copy is kept around that isn't allowed to change...\n\toriginal: {\n\t\tid001: {\n\t\t\tid: 'id001',\n\t\t\ttype: 'car',\n\t\t\tattributes: { /* ... */ }\n\t\t}\n\t},\n\t// ...that way the changes between the two can be calculated:\n\tchanges: {\n\t\tid001: [\n\t\t\t{\n\t\t\t\top: 'add',\n\t\t\t\t// (Note: the changes are JSON Patch objects, but\n\t\t\t\t// the path is the array of accessors, instead of\n\t\t\t\t// the escaped string.)\n\t\t\t\tpath: [ 'attributes', 'color' ],\n\t\t\t\tvalue: 'red'\n\t\t\t}\n\t\t]\n\t},\n\t// If there are errors on a response, they can be mapped to this error\n\t// object, which is a map to each resource:\n\terrors: {\n\t\tid001: {\n\t\t\tattributes: {\n\t\t\t\tcolor: [\n\t\t\t\t\t// Each object is a valid JSON:API error object:\n\t\t\t\t\t// https://jsonapi.org/format/#error-objects\n\t\t\t\t\t{\n\t\t\t\t\t\tcode: 'TheServerErrorCode',\n\t\t\t\t\t\ttitle: 'Human-readable summary.',\n\t\t\t\t\t\t// etc.\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t}\n\t}\n}\n```\n\n## Field Component\n\nWhen the `set` function of the `Field` component is called, e.g.:\n\n```html\n\u003cField bind:form let:set let:value id=\"id001\" keypath={[ 'attributes', 'color' ]}\u003e\n\t\u003cinput type=\"text\" {value} on:input={event =\u003e set(event.target.value)} /\u003e\n\u003c/Field\u003e\n```\n\nthe component updates the appropriate `form.data` property, and then updates the `form.changes` list by doing a diff against the `form.original` property.\n\n\u003e **Note:** your component is responsible for handling the difference between undefined and empty-string.\n\nIn the example above, when the input element is made to be empty, the default `event.target.value` is the empty string, so the `form.data` property would be set to the empty string. This matters when calculating the `changes` list for the `form` object: if the property was originally undefined and a change event is emitted where `value` is the empty string, the `form.changes` list will not be empty.\n\nOne way to handle that difference is simply:\n\n```html\n\u003cinput ... on:input={event =\u003e set(event.target.value || undefined)} /\u003e\n```\n\nThis may be wanted or unwanted behaviour, so it is left up to your implementation to handle the difference.\n\n## Field Component API\n\nRequired properties to set on the `Field` component:\n\n* `form: JsonApiForm` - The form object needs to be bound for the reactivity to work.\n* `id: String` - The resource identifier to bind to.\n* `keypath: String | Array\u003cString|Number\u003e` - Either the JSON Pointer string, e.g. `\"/path/to/thing\"` or\n  the list, e.g. `[ \"path\", \"to\", \"thing\" ]`. (Note: using the string incurs a performance penalty, since\n  it will be converted to a list in the `Field` component.)\n\nOptional properties:\n\n* `debounceMillis: Integer` (default: `15`) - On every change, the diff between original and updated is calculated. This can get very expensive, and since it blocks the UI, it can cause the form to feel very jerky if many changes are made quickly. To counteract this, there is a debounce on the diff calculation, and you can modify the debounce delay with this property.\n\nEmitted events:\n\n**change** - Emitted after an object has been updated and the diff has been calculated. It emits an object with these properties.\n\n* `id: String` - The resource identifier.\n* `keypath: Array\u003cString\u003e` - The JSON Pointer accessor tokens.\n* `value: any` - The set value.\n\nSlot properties:\n\n* `value: any` - The value located at the resources keypath, or undefined.\n* `errors: Array\u003cJsonApiError\u003e` - The list of errors, or an empty list.\n* `set: Function` - Call this with the updated value, when it changes.\n* `disabled: Boolean` - A convenient property which is true if the form is in the `saving` or `loading` state.\n\n## Form Component\n\nThe `Form` component is responsible for handling creating and removing resources, so at the root of your form you'd have something like:\n\n```html\n\u003cForm bind:form let:remove let:create on:create on:remove\u003e\n\t\u003cbutton on:click={() =\u003e create(resource)}Create\u003c/button\u003e\n\u003c/Form\u003e\n```\n\nNew resources are placed on the `form.data` object, with an id generated using a configurable prefix (by default `GID`) and an incrementing counter, e.g. `GID1` will be the id of the first generated resource.\n\nThe `create` function requires new resources to have their relationship defined, so e.g. on a car form you might make a `wheel` resource, but that relationship would need to be defined.\n\n## Form Component Api\n\nRequired properties to set on the `Form` component:\n\n* `form: JsonApiForm` - The form object needs to be bound for the reactivity to work.\n\nOptional properties:\n\n* `prefix: String` (default: `GID`) - The prefix used on the identifiers of created resources.\n* `suffix: String` (default: blank string) - The suffix used on the identifiers of created resources.\n\nSlot properties:\n\n* `create: Function` - Used to create a new resource with a generated identifier. Call with\n  an object containing these properties:\n  * `relId: String` - The identifier of the resource to add this to, as a relationship.\n  * `relName: String` - The relationship accessor name of the relationship.\n  * `isArray: Boolean` (optional) - Set to true if the relationship is an array style.\n  * `type: String` - The type of the resource to create.\n  * `resource: Object` - The initial state of the created resource, with `id` and `type` set, e.g. `resource = { attributes, meta }`.\n* `remove: Function` - Used to remove a resource. Call with an object containing these properties:\n  * `id: String` - The identifier of the resource to remove.\n  * `type: String` - The type of the resource to remove.\n* `disabled: Boolean` - A convenient property which is true if the form is in the `saving` or `loading` state.\n\n## License\n\nPublished and released under the [Very Open License](http://veryopenlicense.com).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaibotsivad%2Fjsonapi-svelte-form","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsaibotsivad%2Fjsonapi-svelte-form","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaibotsivad%2Fjsonapi-svelte-form/lists"}