如何将产品项添加到购物车,以及如何在始终(重新)计算购物车项的总价的同时将其从购物车中删除?

How does one add a product-item to a shopping-cart and how would one remove it from there while always (re)calculating the cart items' total price?

以下数组包含作为产品项目的对象。单击 Add to cart 按钮应通过 mytable 函数将相关项目呈现为 table 演示文稿,如下所示。

var product = [
  {"name":"jeans","image":"pics/jeans3.jpg","price":500},
  {"name":"hoodie","image":"pics/hoodie.jpg","price":700},
  {"name":"shirt","image":"pics/shirt.jpg","price":450},
  {"name":"sweter","image":"pics/sweter.jpg","price":1100},
  {"name":"trouser","image":"pics/trouser.jpg","price":600},
  {"name":"tshirt","image":"pics/tshirt.jpg","price":250}
];

这是一个循环,它应该根据上面提供的数据创建产品概览(包括事件处理)。

var head = "<div id='main'>";

for (var i in product) {
  head += "<div class='pro'>";
  head += "<h1>" + product[i].name + "</h1>";
  head += "<img src=" + product[i].image + ">";
  head += "<p>" + product[i].price + "</p>";
  head += "<button onclick='mytable(i)'>Add to cart</button>"
  head += "</div>";
}

这是上述代码的其余部分,预计会将产品概述写入文档。还有上面提到的 mytable 函数,它应该创建一个项目购物车演示(包括事件处理)。

head += "</div>";
head += "<div id='cart'> </div>"
document.write(head);

function mytable(i) {
     document.getElementById('cart').innerHTML = "<table border='1'> <tr><th>Product name</th>  <th>Quantity</th>  <th>Price</th> <th>image</th><th><button>Remove Items</button></th></tr></table>";
} 

此外,我想处理购物车商品的移除。对于添加和删除商品的两种情况 to/from 我想计算所有购物车商品的总价的购物车。

如何才能做到这一点?

正在使用的技术...

网页Api

事件委托

语法

Expressions and operators

JavaScript Api / Standard built-in objects

的方法

const productList = [
  { pid: "abcd-0987-WXYZ", name: "jeans", price: 500, image: "https://media.istockphoto.com/photos/blue-denim-picture-id501250332" },
  { pid: "efgh-1234-QRST", name: "hoodie", price: 700, image: "https://media.istockphoto.com/photos/faceless-man-in-hoodie-standing-isolated-on-black-picture-id916306960" },
  { pid: "ijkl-6543-MNOP", name: "shirt", price: 450, image: "https://media.istockphoto.com/photos/men-shirt-for-clothing-isolated-on-white-background-picture-id641319368" },
  { pid: "mnop-5678-IJKL", name: "sweater", price: 1100, image: "https://media.istockphoto.com/photos/minimalistic-rustic-composition-with-stacked-vintage-knitted-easy-picture-id1049751604" },
  { pid: "qrst-2109-EFGH", name: "trouser", price: 600, image: "https://media.istockphoto.com/photos/pants-picture-id168790494" },
  { pid: "wxyz-9012-ABCD", name: "tshirt", price: 250, image: "https://media.istockphoto.com/photos/close-up-of-colorful-tshirts-on-hangers-apparel-background-picture-id1170635789" },
];

function createElementFromMarkup(html) {
  const renderBox = document.createElement('div');
  renderBox.innerHTML = html;
  return renderBox.firstElementChild;
}

function createItemMainView(data) {
  return createElementFromMarkup(`
    <li data-pid="${ data.pid }">
      <h3>${ data.name }</h3>
      <img src="${ data.image }"/>
      <dl><dt>Price</dt><dd>${ data.price }</dd></dl>
      <button data-add-pid="${ data.pid }" data-text="Add to cart">Add to cart</button>
    </li>
  `);
}
function createItemCartView(data) {
  return createElementFromMarkup(`
    <li data-pid="${ data.pid }">
      <h3>${ data.name }</h3>
      <dl><dt>Price</dt><dd>${ data.price }</dd></dl>
      <button data-remove-pid="${ data.pid }">Remove</button>
    </li>
  `);
}

function createShoppingItem(data) {
  return {
    data,
    view: {
      main: createItemMainView(data),
      cart: createItemCartView(data),
    },
    checkout: {
      isInCart: false,
      orderCount: 0,
    },
  };
}

function scrollIntoViewIfNeeded(elmNode) {
  if (elmNode) {
    const whichScrollIntoView = elmNode.scrollIntoViewIfNeeded
      ? 'scrollIntoViewIfNeeded'
      : 'scrollIntoView';
    elmNode[whichScrollIntoView]();
  }
}

function updateShoppingCartTotal(elmCartTotal, shoppingState) {
  const total = Object
    .values(shoppingState)
    .reduce((sum, item) =>
      (sum + ((item.data.price ?? 0) * (item.checkout.orderCount ?? 0))), 0
    );
  elmCartTotal.textContent = (total === 0) ? '' : total;
}

function updateCartItemPriceView(elmPrice, price, orderCount) {
  elmPrice.textContent = (orderCount >= 2)
    ? `${ price } x ${ orderCount }`
    : price;
}
function updateAddButtonItemCount(elmButton, orderCount) {
  const { text: buttonText } = elmButton.dataset;

  elmButton.textContent = (orderCount >= 1)
    ? `${ buttonText } (${ orderCount })`
    : buttonText;
}

function updateOrderCounts(pid, context) {
  const {
    target: { elmMainOverview, elmCartOverview, elmCartTotal },
    state: shoppingState,
  } = context;

  const shoppingItem = shoppingState[pid];
  const orderCount = shoppingItem?.checkout?.orderCount ?? 0;

  const elmButton = elmMainOverview
    .querySelector(`[data-add-pid="${ pid }"]`);
  const elmPrice = (orderCount >= 1) && elmCartOverview
    .querySelector(`[data-pid="${ pid }"] dd`);

  if (elmButton) {
    updateAddButtonItemCount(elmButton, orderCount);
  }
  if (elmPrice) {
    updateCartItemPriceView(elmPrice, shoppingItem?.data?.price, orderCount);
  }
  updateShoppingCartTotal(elmCartTotal, shoppingState);
}

function handleAddToCartWithBoundTargetAndState(evt) {
  const target = evt.target.closest('[data-add-pid]');
  if (target) {

    const { addPid: pid } = target.dataset;
    const {
      target: { elmCartOverview },
      state: shoppingState,
    } = this;

    const item = shoppingState[pid];
    if (item) {
      if (item.checkout.isInCart === false) {

        elmCartOverview.appendChild(item.view.cart.cloneNode(true));

        item.checkout.isInCart = true;
      }
      item.checkout.orderCount += 1;

      scrollIntoViewIfNeeded(
        elmCartOverview.querySelector(`[data-pid="${ pid }"]`)
      );
      updateOrderCounts(pid, this);
    }
    // console.log('Add To Cart :: pid ...', pid);
  }
  console.log('Add To Cart :: evt.target ...', evt.target);
}
function handleRemoveFromCartWithBoundTargetAndState(evt) {
  const target = evt.target.closest('[data-remove-pid]');
  if (target) {

    const { removePid: pid } = target.dataset;
    const {
      target: { elmMainOverview, elmCartOverview },
      state: shoppingState,
    } = this;

    const item = shoppingState[pid];
    if (item) {

      const selector = `[data-pid="${ pid }"]`;

      scrollIntoViewIfNeeded(elmMainOverview.querySelector(selector));
      elmCartOverview.querySelector(selector)?.remove();

      elmMainOverview
        .querySelector(`[data-add-pid="${ pid }"]`).focus?.();

      item.checkout.isInCart = false;
      item.checkout.orderCount = 0;

      updateOrderCounts(pid, this);
    }
    // console.log('Remove From Cart :: pid ...', pid);
  }
  console.log('Remove From Cart :: evt.target ...', evt.target);
}

function main() {
  const shoppingState = productList
    .map(createShoppingItem)
    .reduce((state, item) =>
      Object.assign(state, { [item.data.pid]: item }),
      Object.create(null)
    );
  console.log({ shoppingState })

  const elmMainOverview = document
    .querySelector('[data-product-overview]');
  const elmShoppingCart = document
    .querySelector('[data-shopping-cart]');

  const elmCartOverview = elmShoppingCart
    ?.querySelector('[data-cart-overview]');
  const elmCartTotal = elmShoppingCart
    ?.querySelector('[data-cart-total]');

  const handlerContext = {
    target: {
      elmMainOverview,
      elmCartOverview,
      elmCartTotal,
    },
    state: shoppingState,
  };
  elmMainOverview.addEventListener('click',
    handleAddToCartWithBoundTargetAndState.bind(handlerContext)
  );
  elmCartOverview.addEventListener('click',
    handleRemoveFromCartWithBoundTargetAndState.bind(handlerContext)
  );
  // initially render product list from shopping state.
  Object
    .values(shoppingState)
    .forEach(item =>
      elmMainOverview.appendChild(item.view.main.cloneNode(true))
    );
}
main();
* {
  margin: 0;
  padding: 0;
}
ul, li {
  list-style: none;
}
li {
  position: relative;
  margin-bottom: 5px;
  padding: 5px;
}
li:hover {
  background-color: #eee;
}
h3, dl, button {
  font-size: 12px;
}
img {
  max-height: 54px;
  max-width: 72px;
}
dl::after {
  clear: left;
}
dl dt {
  float: left;
}
dl dd::before {
  content: ': ';
}
button {
  position: absolute;
  right: 5px;
  bottom: 5px;
}
button:hover {
  cursor: pointer;
}
button:target, button:focus, button:focus-within {
  outline: 1px solid #06f;
}

main {
  position: relative;
  max-width: 25%;
}
#mini-cart {
  position: fixed;
  right: 60%;
  top: 0;
  min-width: 14%;
  height: 100%;
  overflow-y: scroll;
  font-size: 12px;
}
#mini-cart button {
  position: unset;
}

.as-console-wrapper {
  min-height: 100%!important;
  width: 60%;
  top: 0;
  left: auto!important;
  right: 0;
}
<main>
  <ul data-product-overview>
  </ul>
</main>

<section id="mini-cart" data-shopping-cart>
  <a href="#mini-cart">
    Mini Cart
    <output data-cart-total></output>
  </a>
  <ul data-cart-overview>
  </ul>
</section>