I have already posts on this subject.
Here is a reminder
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mithril.js E-commerce - State/Action Pattern</title>
<!-- Mithril Library -->
<script src="https://unpkg.com/mithril/mithril.js"></script>
<style>
body { font-family: system-ui, sans-serif; margin: 0; background: #f4f4f4; }
header {
background: #2c3e50; color: white; padding: 1rem 2rem;
display: flex; justify-content: space-between; align-items: center;
position: sticky; top: 0; z-index: 10;
}
.container { max-width: 1100px; margin: 2rem auto; padding: 0 1rem; }
.products-grid {
display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1.5rem;
}
.card {
background: white; border-radius: 8px; overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.1); display: flex; flex-direction: column;
}
.card img { width: 100%; height: 200px; object-fit: cover; }
.card-body { padding: 1rem; flex-grow: 1; }
.card-footer { padding: 1rem; border-top: 1px solid #eee; }
button {
background: #3498db; color: white; border: none; padding: 0.6rem 1rem;
border-radius: 4px; cursor: pointer; width: 100%; font-weight: bold;
}
button:hover { background: #2980b9; }
button.secondary { background: #e74c3c; margin-top: 5px; }
/* Cart Sidebar */
.cart-overlay {
position: fixed; top: 0; right: 0; width: 350px; height: 100%;
background: white; box-shadow: -5px 0 15px rgba(0,0,0,0.1);
transform: translateX(100%); transition: 0.3s ease-in-out;
z-index: 20; padding: 1.5rem; display: flex; flex-direction: column;
}
.cart-overlay.open { transform: translateX(0); }
.cart-item { display: flex; justify-content: space-between; margin-bottom: 1rem; border-bottom: 1px solid #eee; padding-bottom: 0.5rem; }
.close-cart { background: #95a5a6; margin-bottom: 1rem; }
</style>
</head>
<body>
<script>
/**
* 1. STATE FACTORIES
*/
const ProductState = () => ({
list: [],
loading: false
});
const CartState = () => ({
items: [],
isOpen: false
});
/**
* 2. ACTION FACTORIES (The Logic)
*/
const ProductActions = (state) => ({
load: async () => {
state.loading = true;
try {
const res = await m.request("https://dummyjson.com/products?limit=12");
state.list = res.products;
} finally {
state.loading = false;
}
}
});
const CartActions = (state) => ({
toggle: () => state.isOpen = !state.isOpen,
add: (product) => {
const existing = state.items.find(item => item.id === product.id);
if (existing) {
existing.qty++;
} else {
state.items.push({ ...product, qty: 1 });
}
},
remove: (id) => {
state.items = state.items.filter(item => item.id !== id);
}
});
/**
* 3. COMPONENTS (The View)
*/
// Helper to calculate total price
const calcTotal = (items) => items.reduce((sum, item) => sum + (item.price * item.qty), 0);
const ProductCard = (product, actions) =>
m(".card", [
m("img", { src: product.thumbnail }),
m(".card-body", [
m("h3", product.title),
m("p", { style: "color: #7f8c8d; font-size: 0.9rem" }, product.description.substring(0, 60) + "..."),
m("strong", `$${product.price}`)
]),
m(".card-footer", [
m("button", { onclick: () => actions.cart.add(product) }, "Add to Cart")
])
]);
const CartDrawer = (state, actions) =>
m(".cart-overlay", { class: state.cart.isOpen ? "open" : "" }, [
m("button.close-cart", { onclick: actions.cart.toggle }, "Close Cart"),
m("h2", "Your Shopping Cart"),
state.cart.items.length === 0 ? m("p", "Cart is empty...") :
state.cart.items.map(item =>
m(".cart-item", [
m("div", [
m("div", { style: "font-weight: bold" }, item.title),
m("small", `$${item.price} x ${item.qty}`)
]),
m("button.secondary", {
style: "width: auto; padding: 2px 8px",
onclick: () => actions.cart.remove(item.id)
}, "Remove")
])
),
m("hr"),
m("h3", `Total: $${calcTotal(state.cart.items)}`),
m("button", { onclick: () => alert("Proceeding to Checkout!") }, "Checkout")
]);
/**
* 4. APP INITIALIZATION (Mitosis / Factory Setup)
*/
const App = () => {
// Instantiate our states
const pState = ProductState();
const cState = CartState();
// Instantiate our actions, binding them to the states
const pActions = ProductActions(pState);
const cActions = CartActions(cState);
// Initial API call
pActions.load();
return {
view: () => m("div", [
// Header
m("header", [
m("h1", "JS Store"),
m("button", {
style: "width: auto",
onclick: cActions.toggle
}, `Cart (${cState.items.length})`)
]),
// Main Content
m(".container", [
pState.loading
? m("h2", "Loading Products...")
: m(".products-grid", pState.list.map(p => ProductCard(p, { cart: cActions })))
]),
// Sidebar
CartDrawer({ cart: cState }, { cart: cActions })
])
};
};
// Mount the app to the body
m.mount(document.body, App);
</script>
</body>
</html>
You can test it here MitosisPattern
Top comments (0)