diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/astro.config.mjs b/astro.config.mjs index 82ca5f0..08f15ea 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,36 +1,54 @@ -import { defineConfig } from 'astro/config'; +import {defineConfig} from 'astro/config'; import starlight from '@astrojs/starlight'; import expressiveCode from "astro-expressive-code"; // https://astro.build/config export default defineConfig({ - site: 'https://withastro-utils.github.io', - base: '/docs', - integrations: [expressiveCode(), starlight({ - editLink: { - baseUrl: 'https://github.com/withastro-utils/docs/tree/main/', - }, - lastUpdated: true, - favicon: '/favicon.png', - title: 'Astro Utils', - logo: { - src: '/src/assets/logo.png' - }, - social: { - github: 'https://github.com/withastro-utils/utils' - }, - sidebar: [{ - label: 'Guides', - autogenerate: { - directory: 'guides' - } - }, { - label: 'Reference', - autogenerate: { - directory: 'reference' - } - }], - customCss: ['./src/styles/home.css', './src/styles/code-margin.css'] - })] + site: 'https://withastro-utils.github.io', + base: '/docs', + integrations: [expressiveCode(), starlight({ + editLink: { + baseUrl: 'https://github.com/withastro-utils/docs/tree/main/', + }, + lastUpdated: true, + favicon: '/favicon.png', + title: 'Astro Utils', + logo: { + src: '/src/assets/logo.png' + }, + social: { + github: 'https://github.com/withastro-utils/utils' + }, + sidebar: [{ + label: 'Guides', + items: [{ + label: 'Forms', + items: [ + { + label: 'Getting Started', + link: '/guides/forms/getting-started' + }, { + label: 'Data Binding', + link: '/guides/forms/data-binding' + }, { + label: 'JS Helpers', + link: '/guides/forms/js-helpers' + } + ] + }, { + label: 'Context', + link: '/guides/context' + }, { + label: 'Express Endpoints', + link: '/guides/express-endpoints' + }] + }, { + label: 'Reference', + autogenerate: { + directory: 'reference/forms' + } + }], + customCss: ['./src/styles/home.css', './src/styles/code-margin.css'] + })] }); diff --git a/src/assets/change-color/after-clicks.png b/src/assets/change-color/after-clicks.png new file mode 100644 index 0000000..36bcefe Binary files /dev/null and b/src/assets/change-color/after-clicks.png differ diff --git a/src/assets/change-color/initial-state.png b/src/assets/change-color/initial-state.png new file mode 100644 index 0000000..125fc20 Binary files /dev/null and b/src/assets/change-color/initial-state.png differ diff --git a/src/content/docs/reference/context.mdx b/src/content/docs/guides/context.mdx similarity index 100% rename from src/content/docs/reference/context.mdx rename to src/content/docs/guides/context.mdx diff --git a/src/content/docs/guides/forms/data-binding.mdx b/src/content/docs/guides/forms/data-binding.mdx new file mode 100644 index 0000000..ea1d0ea --- /dev/null +++ b/src/content/docs/guides/forms/data-binding.mdx @@ -0,0 +1,145 @@ +--- +title: Binding & Validation +description: Using data binding with Astro Forms +--- + +# Introduction + +With Astro Forms you can easily create forms with data binding and validation. It also provides a simple way to handle form submissions and preserve state between postbacks. + +## Data Binding + +Astro Forms supports two-way data binding. This means that you can bind a form field to a property on `Bind` instance and the value of the property will be updated when the field changes and vice versa. + +### Example + +```astro +--- +import { Bind, BindForm, BButton, BInput } from "@astro-utils/forms/forms.js"; +import Layout from "../layouts/Layout.astro"; + +type Form = { + name: string; + age: number; +} + +const form = Bind
(); + +let showSubmitText: string; +function formSubmit(){ + showSubmitText = `You name is ${form.name}, you are ${form.age} years old. `; + form.age++; +} +--- + + + {showSubmitText} + +

What you name*

+ + +

Enter age*

+ + + Submit +
+
+``` + +In this example, the `Bind` instance is bound to the form fields. +- When the user changes the value of the `name` or `age` fields and submit, the `Bind` instance will be updated. +- The `formSubmit` function will be called when the user clicks the `Submit` button and the form is valid. +- After `formSubmit` the `age` property will be incremented and the `showSubmitText` will be updated. + +## View State + +Astro Forms also supports view state. This means that the values of the form fields will be preserved between postbacks. + +### Example + +```astro +--- +type Form = { + counter: number; +} + +const form = Bind({counter: 0}); + +function incCounter(){ + form.counter++; +} +--- + + + Counter: {form.counter} + + Increase counter + + +``` + +What happens here is that the `counter` property of the `Bind` instance will be incremented every time the user clicks the `Increase counter` button. + +The `Bind` state will persist between postbacks, by storing the data on the client side compressed and __encrypted__. + +### Valid values + +The state of the `Bind` instance must be serlizable, but you can use more then just JSON. + +You can use any valid [SuperJSON](https://github.com/blitz-js/superjson) value in the `Bind` instance. +Meaning also `Date`, `Map`, `URL`, `Set`, `RegExp`, `BigInt`, `undefined` are supported. + + +### Button State + +You can use state per `BButton` component. You can also change the `BButton` props easily each time the button is clicked. + +Special props: +- **state**: any - store state per button +- **extra**: any - store extra data per button (cannot be changed in the `onClick` function) +- **innerText**: string - the text of the button (overides the children) +- **innerHTML**: string - the HTML of the button (overides the children) +- **remove**: boolean - remove the button from the HTML + +```astro +--- +import { BButton, Bind, BindForm } from '@astro-utils/forms/forms.js'; +import Layout from '../layouts/Layout.astro'; + +const colors = ['red', 'green', 'blue', 'yellow', 'purple', 'orange', 'pink', 'black', 'white', 'gray']; + +function changeColor() { + this.state.color++; + + if (this.state.color >= colors.length) { + this.state.color = this.extra; + } + this.style = `color: ${colors[this.state.color]}`; +} +--- + + + + { + colors.map((_, i) => ( + + Button {i} + + )) + } + + +``` + +In this example, the `changeColor` will change the button color each time it is clicked for a different color in the `colors` array. + +If the button is clicked more times then the length of the `colors` array, it will start from the beginning. + +#### Initial state + +![Initial state](../../../../assets/change-color/initial-state.png) + + +#### After some clicks + +![After clicks](../../../../assets/change-color/after-clicks.png) diff --git a/src/content/docs/guides/forms.mdx b/src/content/docs/guides/forms/getting-started.mdx similarity index 83% rename from src/content/docs/guides/forms.mdx rename to src/content/docs/guides/forms/getting-started.mdx index 2e79fe9..98c01df 100644 --- a/src/content/docs/guides/forms.mdx +++ b/src/content/docs/guides/forms/getting-started.mdx @@ -1,5 +1,5 @@ --- -title: Forms - Getting started +title: Getting started description: How to use astro forms --- @@ -8,15 +8,13 @@ import { Tabs, TabItem } from '@astrojs/starlight/components'; # Introduction Astro-Utils Forms is a set of tools to help you build forms in Astro. - +It provides similar functionality to ASP.NET Web Forms, but with a modern approach. Some of the features: - ✅ Client & Server side form validation - ✅ Forms [CSRF](https://developer.mozilla.org/en-US/docs/Glossary/CSRF) protection -- ✅ Data binding -- ✅ File upload (support for multipart forms) -- ✅ Web controls +- ✅ Web Controls (Data Binding) + View State - ✅ JWT session ## Installation @@ -30,6 +28,13 @@ First install the package npm install @astro-utils/forms ``` + + + +```sh +bun i @astro-utils/forms +``` + @@ -60,8 +65,8 @@ export default defineConfig({ integrations: [formDebug] }); ``` -This integration will easy you're debugging by avoiding the browser popups every vite reload. +This integration will simplify your debugging process by eliminating the browser pop-ups during every Vite reload Edit `src/middleware.ts` to add the middleware diff --git a/src/content/docs/guides/forms/js-helpers.mdx b/src/content/docs/guides/forms/js-helpers.mdx new file mode 100644 index 0000000..917698e --- /dev/null +++ b/src/content/docs/guides/forms/js-helpers.mdx @@ -0,0 +1,73 @@ +--- +title: JS Helpers +description: SSR JS for client actions +--- + +# Introduction + +Astro forms ship with some utility functions to handle forms. +Some functionality works by sending the script to the client and running it there. + +You can access them via `Astro.locals.forms`. + +### Controllers + +```ts +class FormsReact { + scriptToRun: string; + overrideResponse: Response | null; + + // server side redirect + redirect(location: string, status?: ValidRedirectStatus): void; + updateSearchParams(): { + search: URLSearchParams; + redirect(status?: ValidRedirectStatus): void; + }; + updateOneSearchParam(key: string, value?: string, status?: ValidRedirectStatus): void; + + // client side redirect + redirectTimeoutSeconds(location: string, timeoutSec = 2): void; + + alert(message: string): void; + consoleLog(...messages: any[]): void; + console(type: keyof Console, ...messages: any[]): void; + callFunction(func: string, ...args: any[]): void; +} +``` + +### Example + +```astro +--- +import { Bind, BindForm, BButton, BInput } from "@astro-utils/forms/forms.js"; +import Layout from "../layouts/Layout.astro"; + +type Form = { + name: string; + age: number; +} + +const form = Bind(); + +let showSubmitText: string; +function formSubmit(){ + showSubmitText = `You name is ${form.name}, you are ${form.age} years old. `; + + // Redirect to home page after 2 seconds + Astro.locals.forms.redirectTimeoutSeconds('/'); +} +--- + + + {showSubmitText} + +

What you name*

+ + +

Enter age*

+ + + Submit +
+
+``` diff --git a/src/content/docs/index.mdx b/src/content/docs/index.mdx index 23c221d..0c68763 100644 --- a/src/content/docs/index.mdx +++ b/src/content/docs/index.mdx @@ -11,7 +11,7 @@ hero: file: ../../assets/logo.png actions: - text: Get started - link: guides/forms/ + link: /docs/guides/forms/getting-started icon: right-arrow variant: primary - text: View on GitHub diff --git a/src/content/docs/reference/formidable.mdx b/src/content/docs/reference/formidable.mdx deleted file mode 100644 index 5191f72..0000000 --- a/src/content/docs/reference/formidable.mdx +++ /dev/null @@ -1,78 +0,0 @@ ---- -title: Formidable -description: Astro-Utils formidable port ---- -import { Tabs, TabItem } from '@astrojs/starlight/components'; - -Allow you to use formidable to parse request body. - -With formidable you can parse multipart forms (file upload). - -## Installation -Install the package by running - - - - -```sh -npm install @astro-utils/formidable -``` - - - - -```sh -pnpm add @astro-utils/formidable -``` - - - - -```sh -yarn add @astro-utils/formidable -``` - - - - -## Using formidable -### Endpoint - -`pages/upload.json.ts` -```ts -import {parseAstroForm, isFormidableFile} from '@astro-utils/formidable'; - -export const post: APIRoute = async ({ request }) => { - const formData: FormData = await parseAstroForm(Astro.request); - let name = 'Not-File' - - const file = formData.get('file'); - if(isFormidableFile(file)){ - name = file.name; - } - - return { - body: name - } -} -``` - -### Astro Page -`pages/index.page` -```astro ---- -import {parseAstroForm, isFormidableFile} from '@astro-utils/formidable'; - -if(Astro.request.method === "POST"){ - const formData: FormData = await parseAstroForm(Astro.request); - - const file = formData.get('my-file'); - if(isFormidableFile(file)){ - console.log('The user upload a file'); - } -} ---- -``` - -## Note -This package is used in `@astro-utils/forms`. You don't need to do anything to enable file upload if you're already using `@astro-utils/forms`. diff --git a/src/content/docs/reference/forms/components.md b/src/content/docs/reference/forms/components.md index d399c98..02188cb 100644 --- a/src/content/docs/reference/forms/components.md +++ b/src/content/docs/reference/forms/components.md @@ -1,5 +1,5 @@ --- -title: Forms - Components +title: Components description: HTML like form for Astro --- @@ -60,7 +60,7 @@ The select component is use to make the user choose value / values The select option - **value** - value to send to the server -- **disabled** - you can not select this option +- **disabled** - you cannot select this option ```astro @@ -78,6 +78,10 @@ Attributes: - **onClick** - function to execute when the button clicked - **connectId** - (optional) name for the button action (auto-generate by default) - **whenFormOK** - execute the function only if there isn't any error in the form +- **as** - change the base element (default `button`, can be a React component) +- **props** - props for the `as` element +- **state**: any - store state per button (must be used with `BindForm`) +- **extra**: any - store extra data per button (cannot be changed in the `onClick` function) ```astro --- @@ -85,9 +89,9 @@ function sayHi(){ console.log('Hi'); } --- - + Say Hi - + ``` ## Form Error diff --git a/src/content/docs/reference/forms/data-binding.md b/src/content/docs/reference/forms/data-binding.md index 86aa434..4e6689c 100644 --- a/src/content/docs/reference/forms/data-binding.md +++ b/src/content/docs/reference/forms/data-binding.md @@ -1,5 +1,5 @@ --- -title: Forms - Data binding +title: Data binding description: Forms data binding --- @@ -7,7 +7,7 @@ description: Forms data binding You use components simpler to HTML5 standard form components. -Behinds the seen, the form is validate and parsed according to the HTML +Behind the seen, the form is validate and parsed according to the HTML ```astro --- diff --git a/src/content/docs/reference/forms/session.md b/src/content/docs/reference/forms/session.md index 62a8d69..d94e8d3 100644 --- a/src/content/docs/reference/forms/session.md +++ b/src/content/docs/reference/forms/session.md @@ -1,5 +1,5 @@ --- -title: Forms - session +title: session description: Forms session ---