component.init(app, componentsDir, options);
Returns a Promise
.
Recursively loads components.
Arguments:
- app = Express application instance
- componentsDir = directory to scan for components
- options = options object:
- js = array of additional javascript files to include
- css = array of css files to include
- htmx = htmx library to include. Defaults to "https://unpkg.com/htmx.org@1.9.9"
- favicon = link to a favicon
- link = add link tags to the document head
Example:
const express = require('express');
const component = require('express-htmx-components');
const app = express();
component.init(app,'./components')
.then(() => app.listen(8888))
.catch(err => console.error(err));
Adding global js and css files to your app:
component.init(app, "./components", {
js: [
"https://unpkg.com/htmx.org/dist/ext/json-enc.js",
"https://unpkg.com/htmx.org/dist/ext/alpine-morph.js",
],
css: [
"https://cdn.jsdelivr.net/npm/purecss@3.0.0/build/pure-min.css",
],
});
Overriding default htmx library to include:
component.init(app, "./components", {
htmx: "/static/js/htmx.js",
});
// or
component.init(app, "./components", {
htmx: {
src: "https://unpkg.com/htmx.org@1.9.9/dist/htmx.min.js",
integrity: "sha384-QFjmbokDn2DjBjq+fM+8LUIVrAgqcNW2s0PjAxHETgRn9l4fvX31ZxDxvwQnyMOX",
crossorigin: "anonymous",
}
});
Add a favicon:
component.init(app, "./components", {
favicon: "/static/icon.png",
});
// or
component.init(app, "./components", {
favicon: {
href: "/static/icon.png",
type: "image/png",
}
});
Add a manifest:
component.init(app, "./components", {
link: [
{
rel: 'manifest'
href: "/static/manifest.json",
}
]
});
component.get(path, ... middleware?, componentDefinition)
Returns a Component
.
Defines a htmx component.
Arguments:
- path = URL path for the component
- middleware = optional, zero or more Express or Connect middlewares
- componentDefinition = function defining the component (may be
async
)
The componentDefinition
is just a function that returns an HTML string.
A single object will be passed to it when called containing all the props for the component. Query params, request bodies and path params are all automatically converted to props,
Example:
const component = require("express-htmx-components");
const getHello = component.get("/hello", () => "<h1>Hello World</h1>");
module.exports = {
getHello,
};
const testing = component.get("/testing", ({ n }) => {
return html`
<h1>Number is: ${n}</h1>
`;
});
Calling the component directly:
console.log(testing.html({ n: 100 }));
Or calling from the browser: http://localhost:8888/testing?n=100
Generates:
<h1>Number is: 100</h1>
To use a path parameter you need to pass a prop with the same name:
const testing = component.get("/testing/:n", ({ n }) => {
return html`
<h1>Number is: ${n}</h1>
`;
});
This allows you to call it with: http://localhost:8888/testing/100
A special session
prop is passed into components which is linked to
req.session
:
const testing = component.get("/testing", ({ session }) => {
return html`
<h1>Hello ${session.user.name}</h1>
`;
});
To return a 302
redirect you can pass an additional parameter to your
componentDefinition
to access the redirect()
function:
const testing = component.get("/testing", ({ session }, hx) => {
// ^ extra parameter
if (!session.user) {
return hx.redirect("/login");
}
return html`
<h1>Hello ${session.user.name}</h1>
`;
});
The additional hx
parameter also allows you to read the request headers and
set the response headers using the hx.get()
and hx.set()
functions:
const testing = component.get("/testing", ({}, hx) => {
hx.set("HX-Refresh", true); // set the HX-Refresh header
// get user agent:
return html`
<div>User agent = ${hx.get("User-Agent")}</div>
`;
});
component.post(path, ... middleware?, componentDefinition)
Returns a Component
.
Defines a htmx component. Behaves similar to component.get()
but handles a POST request.
Example:
const component = require("express-htmx-components");
const postHello = component.post("/hello", () => "<h1>Hello World</h1>");
module.exports = {
postHello,
};
Assuming you're using express.urlencoded()
as the body parser, you can access
post body the same way you access query params:
const testingGet = component.get("/testing", () => {
return html`
<div id="theNumber">
<form hx-post="/testing" hx-target="#theNumber">
<input type="text" name="n" />
<button type="submit">Set Number</button>
</form>
</div>
`;
});
const testingPost = component.post("/testing", ({ n }) => {
return html`
<h1>Number is: ${n}</h1>
`;
});
Accessing http://localhost:8888/testing
will call the testingGet
component
but submitting the form will call the testingPost
component.
A files
prop is passed to the component if you use a multipart/form-data
middleware
that can handle file uploads and set either the req.file
or req.files
property.
Currently, express-htmx-components support multer and express-fileupload.
To handle file uploads using multer's upload.single()
or upload.array()
:
const myComponent = component.post("/test-single",
upload.single('foo'),
({ files }) => {
return html`
<div>NAME = ${files[0].originalname}</div>
<div>DATA = ${files[0].buffer.toString('utf8')}</div>
`;
}
);
const ourComponent = component.post("/test-multiple",
upload.array('foo'),
({ files }) => {
return html`
$${files.map((f, idx) => {
html`
<div>
<div>NAME${idx} = ${f.originalname}</div>
<div>DATA${idx} = ${f.buffer.toString('utf8')}</div>
</div>`;
}).join('')}
`;
}
);
With both single()
and array()
express-htmx-components will pass the file or files
into the files
array.
To handle file uploads using express-fileupload:
const post = component.post("/test",
fileUpload(),
({ files }) => {
return html`
<div>NAME = ${files.foo.name}</div>
<div>DATA = ${files.foo.data.toString('utf8')}</div>
`;
}
);
Unlike multer, express-fileupload passes an object of files. The keys of the object are
the form's input name (eg. <input type="file" name="foo">
).
component.put(path, ... middleware?, componentDefinition)
Returns a Component
.
Defines a htmx component. Behaves similar to component.post()
but handles a PUT request.
component.patch(path, ... middleware?, componentDefinition)
Returns a Component
.
Defines a htmx component. Behaves similar to component.post()
but handles a PATCH request.
component.patch(path, ... middleware?, componentDefinition)
Returns a Component
.
Defines a htmx component. Behaves similar to component.get()
but handles a DELETE request.
component.use(path, ... middleware?, componentDefinition)
Returns a Component
.
Defines a htmx component similar to the other component definition methods however this matches all request methods (get/post/put etc.).
Arguments:
- path = URL path for the component
- middleware = optional, zero or more Express or Connect middlewares
- componentDefinition = function defining the component (may be
async
)
To figure out which method was used to call the component an additional property
method
is passed in as a prop:
const testing = component.use("/testing", ({ method, n }) => {
if (method === "POST") {
return html`<h1>${n}</h1>`;
} else {
return html`
<form hx-post="/testing" hx-target="closest div">
<input type="text" name="n" />
<button type="submit">Submit</button>
</form>
`;
}
});
Note that the method is passed in as UPPERCASE.
To prevent XSS vulnerability and also for improve developer experience you should use HTML tagged template strings instead of plain template strings. When used in conjunction with VSCode plugins such as Inline HTML it provides HTML syntax support inside template literals.
The html
tag escapes HTML special characters such as <
to <
to prevent
XSS attacks from user input.
const { html } = require("express-htmx-components/tags");
const data = '<script>alert("HA!")</script>';
console.log(html`Data is ${data}`);
Will output:
Data is <script>alert("HA!")</script>
Since htmx components are just HTML strings the html
tag allows you to insert
raw HTML into the template string using a special $${}
substitution:
const { html } = require("express-htmx-components/tags");
const data = "<h1>Hello</h1>";
console.log(html`
${data}
$${data}
`);
Will output:
<h1>Hello</h1>
<h1>Hello</h1>
The css
tag is mainly for the improved developer experience with usng css
syntax inside template literals. It does not do any additional processing
apart from simply building the string as is:
const { css } = require("express-htmx-components/tags");
const style = css`
#username {
font-size: 14px;
font-weight: bold;
}
`;