How to sync UI with changes in virtual cart?

Viewed 75

I am learning some front end design patterns, and I keep reading about how in a shopping cart example, you want to have a virtual shopping cart, and then have the UI listen for changes to it, and then reflect it on the page. But I have not found good examples of exactly how to "listen for changes and reflect it" particularly where I'm essentially syncing changes in multiple places.

Below I took a stab at a most rudimentary system. The code WORKS (fiddle here), however, it seems wildly brittle (especially since reality could be far more complex than this with a lot more things to have to match to find the right cart row. The changePriceInUI and removeFromUI functions, for example, rely on matchers, because I've learned that item.row can only reference 1 jQuery object in either Cart page or Total page. That's why the code that's commented out, if used, would only affect the UI on cart-body in the Total page. But the code that's commented out is what makes most sense to me in terms of most accurately working with items in a cart and syncing changes.

For example, I can imagine item being a Class where each time the price is changed, there's a setter function that updates the UI (but again, item.row is only 1 row). But I guess not? Would love to hear how shopping carts are built to sync up changes. Other learning resources welcome!

cart = []
cart_body = $(".cart-body")

function addItem(name, price) {
  item = {}
  item.name = name
  item.price = price  
  item.row = $("<tr class='item-row' data-name='"+name+"' data-price='"+price+"'><td class='name'>" + name + "</td><td class='price'>" + price + "</td></tr>")
  cart.push(item)
  addToUI(item.row)
}

function addToUI(item_row) {
  cart_body.append(item_row)
}

function removeItem(name,price) {
  var length = cart.length
    for (var i = 0; i < length; i++ ) {
    if (cart[i].name == name && cart[i].price == price) { 
      item_whose_row_to_remove = cart[i]
      cart.splice(i,1)
      break
    }
  }
  removeFromUI(item_whose_row_to_remove)
}

function removeFromUI(item) {
  // item.row.remove()
  cart_body.find(".item-row[data-name='"+item.name+"'][data-price='"+item.price+"']").remove()
}

function changeItemPrice(name,new_price) {
  var length = cart.length
    for (var i = 0; i < length; i++ ) {
    if (cart[i].name == name) { 
      old_price = cart[i].price
      cart[i].price = new_price
      item_whose_price_to_update = cart[i]
      break
    }
  }
  changePriceInUI(item_whose_price_to_update, old_price)
}

function changePriceInUI(item, old_price) {
  // item.row.find(".price").empty().append(item.price)
  cart_body.find(".item-row[data-name='"+item.name+"'][data-price='"+old_price+"']").find(".price").empty().append(item.price)
}


addItem("Ball", 1)
addItem("Stick",2)
removeItem("Ball",1)
changeItemPrice("Stick",3)

1 Answers

Here is one example: https://jsfiddle.net/4tq9a6h3/

I created a ShoppingCart class, which owns a reference to a HTML table, as well as an array of shopping cart items. The class handles updating the HTML table internally whenever you call the addItem-method to add an item to the cart. The UI can never be out of sync with the cartItems-array, since the cartItems-array is the "primary source of truth" of which the UI updates are based upon, when the array contents change.

Note that this is missing a lot of optimizations and features. Normally you would not empty the whole HTML table when simply adding a new item for example, because there is no reason to, as the existing table rows will be the same as the existing array elements.

Similarly how the addItem works, you could implement removeItem and changePrice -methods and just call this.render() in the end again.

Second note that it is probably not a good idea to code something like this by yourself for production, there are better ways. This example is to answer your question about how you'd virtually decouple data and UI and have the UI updated when the data updates (and how to not rely on HTML elements when you'd like to see what items the user has in cart - you'd just check what the cartItems array contains instead of reading the HTML table rows). The other important thing to takeaway from this is that the cartItems-array is at the center of everything. Every update to UI is done by reflecting what the cartItems-array contains and never the other way around.

Furthermore one could probably utilize Custom Events for example, in order to trigger the UI updates (calling the render method) after cartItems-array changes and not having to remember calling it "manually".

Requested additions: https://jsfiddle.net/qr0avhwe/2/

Related