Introduction
Hey there! Welcome to the component-oriented world of development 😃
If you aren't already using VueJS, React or something similar, this article is for you.
What are we building?
A simple product list with a cart.
Products
Cart
We could improve this product and cart list with many niceties:
- Remove products form the cart via a button
- Generate a total sum
- Filter products based on a price range
But the core problem we tackle here is not a nice list of extra features, the hard stuff lies within how to actually get started with this. It is a shift in paradigm.
What you are used to as developer that used jQuery in his day-to-day business, is that you use IDs, call functions when you listen to an event and set classes. Compared to what is natural to someone using React or similar, is that the template is just a tool of mirroring the state.
In our example, the state looks like this:
let products = [
{
name: 'Product 1',
selected: false,
price: 10.5,
},
{
name: 'Product 2',
selected: true,
price: 4.5,
},
{
name: 'Product 3',
selected: false,
price: 5,
},
]
Feel free to extend your product with whatever properties you need. categories, variants, urls, etc.
If you have trouble finding the source of your data and how to retrieve it in an appropriate format, check out wireup your frontend. A place where I try to help and find a way in any territory for you to get your data .
It is important to get the data in JSON format for web-based renderings If your only experience is a PHP server-side rendered list of items, the problem quickly becomes visible: in order to make the list dynamic, you would need to pick your data from the DOM. There is a better way than manually writing the HTML, that works for a variable amount of elements. We can automate that, without even using IDs. What you want is to think about state and which data has to be displayed where. So given your example, you have an array of products:
So what we need to do, is to read out the checked
state of an input associated with the product and keep it in sync with the correct products selected
property.
TIP
Front-end frameworks such as VueJS make this extremely easy, compared to vanilla JS, because keeping state in sync with the DOM is a difficult task. If you want to make it render efficiently, you should also avoid using settings innerHTML
, but for our example, it is okay. The right way is to use document.createElement
but that is too cumbersome for this demo.
The HTML
Straightforward, if we use JS to populate the cart and products, all we need is a container:
<h2>Products</h2>
<div class="products"></div>
<h2>Cart</h2>
<div class="cart"></div>
The Templates
So you want to re-use a snippet of HTML for your product, that you populate with the state of your product object.
Product
<div class="product">
<div class="product-info">Product 1 - 3.40 $</div>
<input type="checkbox">
</div>
So now we need a way to render this snippet per product. We can put that into a function and use tagged templates to render that.
All your products variables and the array index are available here. This gets populated when rendering.
function getProductHTML(product, index) {
return `
<div class="product product-${index}">
<div class="product-info">${product.name} - ${product.price} $</div>
<input type="checkbox" ${product.selected ? 'checked' : ''}>
</div>
`
}
This snippet reflects also the selected state. That means that if the product has selected
set to true
it will have the checkbox shown as checked.
This function can be used in combination when refreshing the state:
// lightweight version of jQuery to select DOM elements
let $ = document.querySelector.bind(document)
let $$ = document.querySelectorAll.bind(document)
// render products into the products container - we have to call this whenever the state changes
$('.products').innerHTML = products.map(getProductHTML).join(' ')
The cart
Now comes the tricky part.
We want to put the product into the cart whenever we check the checkbox, and remove it when the checkbox is unselected again. Rather than manually mounting the the product into the .cart
container, we just do a filter and render the result.
function setCartHTML() {
$('.cart').innerHTML = products
.filter(product => product.selected)
.map(product => `<div class="product">${product.name}</div>`)
.join('')
}
Wiring up together everything
Now to hook up the checkbox with the cart and the products, we need to add an eventListener for the input
event of the checkbox, to update the list of products and the cart:
function refreshProducts() {
// set HTML
$('.products').innerHTML = products.map(getProductHTML).join(' ')
// add eventlistener for checkbox changes.
// input catches both click, space and related events that change the checked state
products.map((product, index) => {
$(`.product-${index}`).addEventListener('input', event => {
product.selected = event.target.checked
refreshProducts()
setCartHTML()
})
})
}
Don't forget to initially render the state and set up eventlisteners
refreshProducts()
setCartHTML()
Conclusion
The paradigm shift from declarative to imperative might take some time, but learning how to map the data to the visual part, takes away a lot of headache. Especially when you come from a PHP background, you are used to first render the products and everything else is just an afterthought. Instead of retrieving the data from the DOM, you can directly obtain dataformats that javascript can easily handle.
That's all!
How do you charter unknown server territory?
Don't get stuck connecting forms and servers.
Get free on-spot advice delivered to your inbox.