Handle forms (no-JS)
This is a full example of how to handle forms in Gracile. Server-only handling, no JS needed.
📄 /src/routes/form.ts
import { defineRoutefunction defineRoute<GetHandlerData extends R.HandlerDataHtml = undefined, PostHandlerData extends R.HandlerDataHtml = undefined, CatchAllHandlerData extends R.HandlerDataHtml = undefined, StaticPathOptions extends R.StaticPathOptionsGeneric | undefined = undefined, RouteContext extends R.RouteContextGeneric = {
...;
}>(options: {
handler?: StaticPathOptions extends object ? never : R.Handler<CatchAllHandlerData> | {
GET?: R.Handler<GetHandlerData>;
POST?: R.Handler<PostHandlerData>;
QUERY?: R.Handler<Response>;
PUT?: R.Handler<Response>;
PATCH?: R.Handler<Response>;
DELETE?: R.Handler<Response>;
HEAD?: R.Handler<Response>;
OPTIONS?: R.Handler<Response>;
} | undefined;
staticPaths?: () => R.MaybePromise<StaticPathOptions[]> | undefined;
prerender?: boolean | undefined;
document?: R.DocumentTemplate<RouteContext> | undefined;
template?: R.BodyTemplate<RouteContext> | undefined;
}): (RouteModule: typeof R.RouteModule) => R.RouteModuleDefines a file-based route for Gracile to consume.
} from '@gracile/gracile/route';
import { htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
} from 'lit';
import { documentconst document: () => ServerRenderedTemplate } from '../document.js';
// -----------------------------------------------------------------------------
let achievementsDblet achievementsDb: {
name: string;
coolnessFactor: number;
}[] = [{ namename: string : 'initial record', coolnessFactorcoolnessFactor: number : 5 }];
Just a bit of optional setup, for easier refactoring and to keep a bird-eye view.
const FORMconst FORM: {
readonly fields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
};
readonly actions: {
...;
};
} = {
fieldsfields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
} : {
actionaction: "action" : 'action',
achievementNameachievementName: "achievement_name" : 'achievement_name',
coolnessFactorcoolnessFactor: "coolness_factor" : 'coolness_factor',
filterByNamefilterByName: "filter_by_name" : 'filter_by_name',
},
actionsactions: {
readonly deleteAll: "delete_all";
readonly add: "add";
} : {
deleteAlldeleteAll: "delete_all" : 'delete_all',
addadd: "add" : 'add',
},
} as consttype const = {
readonly fields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
};
readonly actions: {
...;
};
} ;
// -----------------------------------------------------------------------------
export default defineRoutedefineRoute<Response | {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
}, Response | {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
...;
};
} | {
...;
}, undefined, undefined, {
...;
}>(options: {
...;
}): (RouteModule: typeof RouteModule) => RouteModuleDefines a file-based route for Gracile to consume.
({
Order matters! Handlers return inferred props. for doc/page afterward.
handlerhandler?: Handler<undefined> | {
GET?: Handler<Response | {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
}> | undefined;
... 6 more ...;
OPTIONS?: Handler<...> | undefined;
} | undefinedA function or an object containing functions named after HTTP methods.
A handler can return either a standard Response that will terminate the
request pipeline, or any object to populate the current route template
and document contexts.
: {
GETGET?: Handler<Response | {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
}> | undefined : (contextcontext: {
url: URL;
params: Parameters;
request: Request;
locals: Gracile.Locals;
responseInit: ResponseInit;
} ) => {
const filterByNameconst filterByName: string | null = contextcontext: {
url: URL;
params: Parameters;
request: Request;
locals: Gracile.Locals;
responseInit: ResponseInit;
} .urlurl: URL .searchParamsURL.searchParams: URLSearchParams .getURLSearchParams.get(name: string): string | nullReturns the first value associated to the given search parameter.
(
FORMconst FORM: {
readonly fields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
};
readonly actions: {
...;
};
} .fieldsfields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
} .filterByNamefilterByName: "filter_by_name" ,
);
if (filterByNameconst filterByName: string | null ) {
const filteredconst filtered: {
name: string;
coolnessFactor: number;
}[] = achievementsDblet achievementsDb: {
name: string;
coolnessFactor: number;
}[] .filterArray<{ name: string; coolnessFactor: number; }>.filter(predicate: (value: {
name: string;
coolnessFactor: number;
}, index: number, array: {
name: string;
coolnessFactor: number;
}[]) => unknown, thisArg?: any): {
name: string;
coolnessFactor: number;
}[] (+1 overload)Returns the elements of an array that meet the condition specified in a callback function.
((achievementachievement: {
name: string;
coolnessFactor: number;
} ) =>
achievementachievement: {
name: string;
coolnessFactor: number;
} .namename: string .includesString.includes(searchString: string, position?: number): booleanReturns true if searchString appears as a substring of the result of converting this
object to a String, at one or more positions that are
greater than or equal to position; otherwise, returns false.
(filterByNameconst filterByName: string ),
);
return { achievementsachievements: {
name: string;
coolnessFactor: number;
}[] : filteredconst filtered: {
name: string;
coolnessFactor: number;
}[] , filterByNamefilterByName: string | null } as consttype const = {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string;
} ;
}
Clean-up empty search parameter
if (filterByNameconst filterByName: string | null === '') {
contextcontext: {
url: URL;
params: Parameters;
request: Request;
locals: Gracile.Locals;
responseInit: ResponseInit;
} .urlurl: URL .searchParamsURL.searchParams: URLSearchParams .deleteURLSearchParams.delete(name: string, value?: string): voidDeletes the given search parameter, and its associated value, from the list of all search parameters.
(FORMconst FORM: {
readonly fields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
};
readonly actions: {
...;
};
} .fieldsfields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
} .filterByNamefilterByName: "filter_by_name" );
return Responsevar Response: {
new (body?: BodyInit | null, init?: ResponseInit): Response;
prototype: Response;
error(): Response;
json(data: any, init?: ResponseInit): Response;
redirect(url: string | URL, status?: number): Response;
}This Fetch API interface represents the response to a request.
.redirectfunction redirect(url: string | URL, status?: number): Response (contextcontext: {
url: URL;
params: Parameters;
request: Request;
locals: Gracile.Locals;
responseInit: ResponseInit;
} .urlurl: URL );
}
return { achievementsachievements: {
name: string;
coolnessFactor: number;
}[] : achievementsDblet achievementsDb: {
name: string;
coolnessFactor: number;
}[] , filterByNamefilterByName: string | null } as consttype const = {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
} ;
},
POSTPOST?: Handler<Response | {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
}> | undefined : async (contextcontext: {
url: URL;
params: Parameters;
request: Request;
locals: Gracile.Locals;
responseInit: ResponseInit;
} ) => {
const formDataconst formData: FormData = await contextcontext: {
url: URL;
params: Parameters;
request: Request;
locals: Gracile.Locals;
responseInit: ResponseInit;
} .requestrequest: Request .formDataBody.formData(): Promise<FormData> ();
const actionconst action: string | undefined = formDataconst formData: FormData .getFormData.get(name: string): FormDataEntryValue | null (FORMconst FORM: {
readonly fields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
};
readonly actions: {
...;
};
} .fieldsfields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
} .actionaction: "action" )?.toStringfunction toString(): stringReturns a string representation of a string.
();
switch (actionconst action: string | undefined ) {
case FORMconst FORM: {
readonly fields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
};
readonly actions: {
...;
};
} .actionsactions: {
readonly deleteAll: "delete_all";
readonly add: "add";
} .addadd: "add" :
{
const nameconst name: string | undefined = formDataconst formData: FormData .getFormData.get(name: string): FormDataEntryValue | null (FORMconst FORM: {
readonly fields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
};
readonly actions: {
...;
};
} .fieldsfields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
} .achievementNameachievementName: "achievement_name" )?.toStringfunction toString(): stringReturns a string representation of a string.
();
const coolnessFactorconst coolnessFactor: string | undefined = formDataconst formData: FormData
.getFormData.get(name: string): FormDataEntryValue | null (FORMconst FORM: {
readonly fields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
};
readonly actions: {
...;
};
} .fieldsfields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
} .coolnessFactorcoolnessFactor: "coolness_factor" )
?.toStringfunction toString(): stringReturns a string representation of a string.
();
Basic form data shape validation.
if (nameconst name: string | undefined && coolnessFactorconst coolnessFactor: string | undefined && nameconst name: string .lengthString.length: numberReturns the length of a String object.
>= 3) {
achievementsDblet achievementsDb: {
name: string;
coolnessFactor: number;
}[] .pushArray<{ name: string; coolnessFactor: number; }>.push(...items: {
name: string;
coolnessFactor: number;
}[]): numberAppends new elements to the end of an array, and returns the new length of the array.
({
namename: string ,
coolnessFactorcoolnessFactor: number : Numbervar Number: NumberConstructor
(value?: any) => numberAn object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.
(coolnessFactorconst coolnessFactor: string ),
});
} else {
contextcontext: {
url: URL;
params: Parameters;
request: Request;
locals: Gracile.Locals;
responseInit: ResponseInit;
} .responseInitresponseInit: ResponseInitLet you mutate the downstream page response.
It doesn't take effect if you're returning the
response yourself before (within your request handler).
.statusResponseInit.status?: number | undefined = 400;
We want the user data to be repopulated in the page after a failed `POST`.
return {
successsuccess: false : false,
messagemessage: "Wrong form input." : 'Wrong form input.',
achievementsachievements: {
name: string;
coolnessFactor: number;
}[] : achievementsDblet achievementsDb: {
name: string;
coolnessFactor: number;
}[] ,
payloadpayload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
} : { namename: string | undefined , coolnessFactorcoolnessFactor: string | undefined },
} as consttype const = {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} ;
}
}
break;
case FORMconst FORM: {
readonly fields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
};
readonly actions: {
...;
};
} .actionsactions: {
readonly deleteAll: "delete_all";
readonly add: "add";
} .deleteAlldeleteAll: "delete_all" :
achievementsDblet achievementsDb: {
name: string;
coolnessFactor: number;
}[] = [];
break;
default:
contextcontext: {
url: URL;
params: Parameters;
request: Request;
locals: Gracile.Locals;
responseInit: ResponseInit;
} .responseInitresponseInit: ResponseInitLet you mutate the downstream page response.
It doesn't take effect if you're returning the
response yourself before (within your request handler).
.statusResponseInit.status?: number | undefined = 422;
return {
successsuccess: false : false,
messagemessage: "Unknown form action." : 'Unknown form action.',
achievementsachievements: {
name: string;
coolnessFactor: number;
}[] : achievementsDblet achievementsDb: {
name: string;
coolnessFactor: number;
}[] ,
} as consttype const = {
readonly success: false;
readonly message: "Unknown form action.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
} ;
}
Using the "POST/Redirect/GET" pattern to avoid duplicate form submissions
return Responsevar Response: {
new (body?: BodyInit | null, init?: ResponseInit): Response;
prototype: Response;
error(): Response;
json(data: any, init?: ResponseInit): Response;
redirect(url: string | URL, status?: number): Response;
}This Fetch API interface represents the response to a request.
.redirectfunction redirect(url: string | URL, status?: number): Response (contextcontext: {
url: URL;
params: Parameters;
request: Request;
locals: Gracile.Locals;
responseInit: ResponseInit;
} .urlurl: URL , 303);
},
},
documentdocument?: DocumentTemplate<{
url: URL;
props: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
};
params: Parameters;
}> | undefinedA function that returns a server only template.
Route context is provided at runtime during the build.
: (contextcontext: {
url: URL;
props: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
};
params: Parameters;
} ) => documentfunction document(): ServerRenderedTemplate (contextcontext: {
url: URL;
props: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
};
params: Parameters;
} ),
templatetemplate?: BodyTemplate<{
url: URL;
props: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
};
params: Parameters;
}> | undefinedA function that returns a server only or a Lit client hydratable template.
Route context is provided at runtime during the build.
: (contextcontext: {
url: URL;
props: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
};
params: Parameters;
} ) => htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
`
<h1>Achievements manager</h1>
<form method="post">
<input
type="hidden"
name=${FORMconst FORM: {
readonly fields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
};
readonly actions: {
...;
};
} .fieldsfields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
} .actionaction: "action" }
value=${FORMconst FORM: {
readonly fields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
};
readonly actions: {
...;
};
} .actionsactions: {
readonly deleteAll: "delete_all";
readonly add: "add";
} .addadd: "add" }
/>
<input
type="text"
name=${FORMconst FORM: {
readonly fields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
};
readonly actions: {
...;
};
} .fieldsfields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
} .achievementNameachievementName: "achievement_name" }
value=${contextcontext: {
url: URL;
props: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
};
params: Parameters;
} .propsprops: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
} .POSTtype POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
} ?.payloadpayload?: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
} | undefined ?.namename: string | undefined ?? ''}
required
/>
<input
type="number"
value=${contextcontext: {
url: URL;
props: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
};
params: Parameters;
} .propsprops: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
} .POSTtype POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
} ?.payloadpayload?: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
} | undefined ?.coolnessFactorcoolnessFactor: string | undefined ?? 1}
name=${FORMconst FORM: {
readonly fields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
};
readonly actions: {
...;
};
} .fieldsfields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
} .coolnessFactorcoolnessFactor: "coolness_factor" }
/>
<footer>
<button>Add an achievement</button>
${contextcontext: {
url: URL;
props: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
};
params: Parameters;
} .propsprops: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
} .POSTtype POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
} ?.successsuccess: false === false
? htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
`
<strong>Something went wrong!</strong>
${contextcontext: {
url: URL;
props: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
};
params: Parameters;
} .propsprops: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
} .POSTtype POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
} ?.messagemessage: "Wrong form input." | "Unknown form action."
? htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
` <strong>${contextcontext: {
url: URL;
props: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
};
params: Parameters;
} .propsprops: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
} .POSTtype POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
} .messagemessage: "Wrong form input." | "Unknown form action." }</strong> `
: null}
`
: null}
</footer>
</form>
<hr />
<form>
<input
type="text"
name=${FORMconst FORM: {
readonly fields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
};
readonly actions: {
...;
};
} .fieldsfields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
} .filterByNamefilterByName: "filter_by_name" }
value=${contextcontext: {
url: URL;
props: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
};
params: Parameters;
} .propsprops: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
} .GETtype GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
} ?.filterByNamefilterByName: string | null ?? ''}
/>
<button>Filter by name</button>
</form>
<ul>
${(contextcontext: {
url: URL;
props: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
};
params: Parameters;
} .propsprops: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
} .GETtype GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
} || contextcontext: {
url: URL;
props: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
};
params: Parameters;
} .propsprops: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
} .POSTtype POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
} )?.achievementsachievements: {
name: string;
coolnessFactor: number;
}[] ?.mapArray<{ name: string; coolnessFactor: number; }>.map<TemplateResult<1>>(callbackfn: (value: {
name: string;
coolnessFactor: number;
}, index: number, array: {
name: string;
coolnessFactor: number;
}[]) => TemplateResult<1>, thisArg?: any): TemplateResult<...>[]Calls a defined callback function on each element of an array, and returns an array that contains the results.
(
(achievementachievement: {
name: string;
coolnessFactor: number;
} ) => htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
`
<li>
<!-- -->
<em>${achievementachievement: {
name: string;
coolnessFactor: number;
} .coolnessFactorcoolnessFactor: number }</em> -
<strong>${achievementachievement: {
name: string;
coolnessFactor: number;
} .namename: string }</strong>
</li>
`,
)}
</ul>
<hr />
<form method="post">
<input
type="hidden"
name=${FORMconst FORM: {
readonly fields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
};
readonly actions: {
...;
};
} .fieldsfields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
} .actionaction: "action" }
value=${FORMconst FORM: {
readonly fields: {
readonly action: "action";
readonly achievementName: "achievement_name";
readonly coolnessFactor: "coolness_factor";
readonly filterByName: "filter_by_name";
};
readonly actions: {
...;
};
} .actionsactions: {
readonly deleteAll: "delete_all";
readonly add: "add";
} .deleteAlldeleteAll: "delete_all" }
/>
<button>Delete all</button>
</form>
<hr />
<footer>
<pre>${JSONvar JSON: JSONAn intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
.stringifyJSON.stringify(value: any, replacer?: (number | string)[] | null, space?: string | number): string (+1 overload)Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
({ propsprops: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
} : contextcontext: {
url: URL;
props: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
};
params: Parameters;
} .propsprops: {
GET: {
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly filterByName: string | null;
};
POST: {
readonly success: false;
readonly message: "Wrong form input.";
readonly achievements: {
name: string;
coolnessFactor: number;
}[];
readonly payload: {
readonly name: string | undefined;
readonly coolnessFactor: string | undefined;
};
} | {
...;
};
} }, null, 2)}</pre>
</footer>
`,
});