This tutorial will guide you through adding image / photo inputs to your form and then saving them as parts of a sealious collection.
1. Create a sealious app (if you haven’t already)
git clone http://hub.sealcode.org/diffusion/PLAY/sealious-playground.git my-app
cd my-app
npm install
docker-compose up -d
2. Start the app
npm run watch
Go to localhost:8080 in your browser and confirm that the application is working
3. Add a collection that contains image fields
In a separate container, run npx sealgen add collection
. It will ask you about the name of the collection - let’s name it photos
:
Then, edit open the src/back/collections/photos.ts
in your IDE and create two fields: photo
and description
:
// src/back/collections/photos.ts
import { Collection, FieldTypes, Policies } from "sealious";
export default class Photos extends Collection {
fields = {
photo: new FieldTypes.Image(),
description: new FieldTypes.Text(),
};
defaultPolicy = new Policies.Public();
}
4. Add a form with an image input
Run npx sealgen add-route
, and add an add photo
route. Make it a form:
Then, go to the src/back/routes/add-photo.form.ts
file and make some modifications.
Change the fields
Change the const fields
value to the following:
const fields = {
photo: new Fields.File(true),
description: new Fields.SimpleFormField(true),
};
This is going to register the fields in the form, but it’s not going to change how what inputs the form displays to the user. We change that by setting the `controllers property of the class:
import { imageRouter } from "../image-router.js";
///...
export default new (class SomeFormForm extends Form<typeof fields, void> {
//...
controls = [
new Controls.Photo(fields.photo, imageRouter),
new Controls.SimpleInput(fields.description),
];
async validateValues(): Promise<{ valid: boolean; error: string }> {
// no validation for now
return { valid: true, error: "" };
}
Now, visit the form to see the effect:
If you fill the form and submit it…
…not much is going to happen yet. Now it’s time to do something with the values provided by the user.
5. Process the submitted values
With the onSubmit
handler, we can run any logic we want with the provided values. Here, we’re going to add a new item to the photos
collection we created in step 3.
//...
import { hasShape, predicates } from "@sealcode/ts-predicates";
import {File as SealiousFile} from "sealious";
//...
export default new (class SomeFormForm extends Form<typeof fields, void> {
//...
async onSubmit(ctx: Context) {
const data = await this.getParsedValues(ctx);
const photo = data.photo;
if (
!hasShape(
{
new: predicates.maybe(predicates.instanceOf(SealiousFile)),
old: predicates.maybe(predicates.instanceOf(SealiousFile)),
},
photo
)
) {
throw new Error("Expected 'photo' to be a file");
}
await ctx.$app.collections.photos.create(ctx.$context, {
photo: photo.new,
description: data.description,
});
return;
}
Now when you go to http://localhost:8080/add-photo/, fill the form, and press submit
, a new item is going to be added to the photos
collection. You can see the newly added items by visiting http://localhost:8080/api/v1/collections/photos?format[photo]=url
Summary
And that’s it! We’ve added a form that handles image upload to store it in a field of a sealious collection. Here’s the full content of the photos
collection file and of the form itself, for reference:
// src/back/collections/photos.ts
import { Collection, FieldTypes, Policies } from "sealious";
export default class Photos extends Collection {
fields = {
photo: new FieldTypes.Image(),
description: new FieldTypes.Text(),
};
defaultPolicy = new Policies.Public();
}
// src/back/routes/add-photo.form.ts
import type { Context } from "koa";
import type { FormData } from "@sealcode/sealgen";
import { Form, Fields, Controls, fieldsToShape } from "@sealcode/sealgen";
import html from "../html.js";
import { imageRouter } from "../image-router.js";
import { hasShape, predicates } from "@sealcode/ts-predicates";
import { File as SealiousFile } from "sealious";
export const actionName = "SomeForm";
const fields = {
photo: new Fields.File(true),
description: new Fields.SimpleFormField(true),
};
export const SomeFormShape = fieldsToShape(fields);
export default new (class SomeFormForm extends Form<typeof fields, void> {
defaultSuccessMessage = "Formularz wypełniony poprawnie";
fields = fields;
controls = [
new Controls.Photo(fields.photo, imageRouter),
new Controls.SimpleInput(fields.description),
];
async validateValues(): Promise<{ valid: boolean; error: string }> {
return { valid: true, error: "" };
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async canAccess(_: Context) {
return { canAccess: true, message: "" };
}
async onSubmit(ctx: Context) {
const data = await this.getParsedValues(ctx);
const photo = data.photo;
if (
!hasShape(
{
new: predicates.maybe(predicates.instanceOf(SealiousFile)),
old: predicates.maybe(predicates.instanceOf(SealiousFile)),
},
photo
)
) {
throw new Error("Expected 'photo' to be a file");
}
await ctx.$app.collections.photos.create(ctx.$context, {
photo: photo.new,
description: data.description,
});
return;
}
async render(ctx: Context, data: FormData, show_field_errors: boolean) {
return html(ctx, "SomeForm", await super.render(ctx, data, show_field_errors));
}
})();