Thêm mục Danh sách đặt hàng nhanh vào mã chủ đề

Mục Danh sách đặt hàng nhanh được hỗ trợ trên chủ đề Shopify miễn phí, phiên bản 11.0.0 trở lên. Nếu muốn thêm mục Danh sách đơn hàng nhanh vào cửa hàng, bạn có thể cập nhật chủ đề của cửa hàng lên phiên bản mới nhất.

Nếu không muốn thay đổi hay cập nhật chủ đề thì bạn có thể thêm mã vào chủ đề bằng Liquid hoặc Javascript để hiển thị mục Danh sách đặt hàng nhanh trên trang sản phẩm.

Trước khi cập nhật các tệp chủ đề, đảm bảo rằng bạn nhân bản chủ đề để tạo bản sao lưu.

Thêm mã Liquid Danh sách đặt hàng nhanh

Bạn có thể thêm mã vào các tệp sau trong chủ đề để hỗ trợ mục Danh sách đặt hàng nhanh:

  • main-product.liquid hoặc tương đương

Bước:

  1. Trên trang quản trị Shopify, vào mục Cửa hàng trực tuyến > Chủ đề.
  2. Tìm chủ đề bạn muốn chỉnh sửa, nhấp vào nút ... để mở menu thao tác, sau đó nhấp vào Chỉnh sửa mã.
  3. Mở tệp bạn muốn chỉnh sửa.
  4. Tạo một dòng mới ở cuối tệp, sau đó thêm mã sau:
{% # theme-check-disable %}
{%- assign items_in_cart = cart | line_items_for: product | sum: 'quantity' -%}
{% # theme-check-enable %}

<div class="color-background-1 gradient">
  <quick-order-list
    class="page-width"
    id="quick-order-list"
    data-id="{{ section.id }}"
  >
    <form
      action="{{ routes.cart_update_url }}"
      class="quick-order-list__contents critical-hidden"
      method="post"
      id="QuickOrderList"
    >
      <div class="quick-order-list__container" id="main-variant-items">
        <div class="js-contents">
          <table class="quick-order-list__table">
            <caption class="visually-hidden">
              Quick Order List
            </caption>
            <thead>
              <tr>
                <th class="caption-with-letter-spacing" scope="col">
                  {%- if product.has_only_default_variant -%}
                    Product
                  {%- else -%}
                    Variant
                  {%- endif -%}
                </th>
                <th class="large-up-hide right caption-with-letter-spacing" scope="col">
                  {%- if product.has_only_default_variant -%}
                    Product subtotal
                  {%- else -%}
                    Variant total
                  {%- endif -%}
                </th>
                <th
                  class="quick-order-list__table-heading--wide small-hide medium-hide caption-with-letter-spacing"
                  scope="col"
                >
                  Quantity
                </th>
                <th
                  class="quick-order-list__table-heading--wide small-hide medium-hide caption-with-letter-spacing"
                  scope="col"
                >
                  Price
                </th>
                <th class="small-hide medium-hide right caption-with-letter-spacing" scope="col">
                  {%- if product.has_only_default_variant -%}
                    Product subtotal
                  {%- else -%}
                    Variant total
                  {%- endif -%}
                </th>
              </tr>
            </thead>

            <tbody>
              {%- if product.has_only_default_variant -%}
                  {% # theme-check-disable %}
                  {% assign cart_qty = cart | item_count_for_variant: product.selected_or_first_available_variant.id %}
                  {% # theme-check-enable %}
                  <tr
                    class="variant-item"
                    id="Variant-{{ product.selected_or_first_available_variant.id }}"
                    data-variant-id="{{ product.selected_or_first_available_variant.id }}"
                    data-cart-qty="{{ cart_qty }}"
                  >
                    <td class="variant-item__inner">
                        <div class="variant-item__media">
                          <div class="variant-item__image-container gradient global-media-settings{% unless product.featured_media %} variant-item__image-container--no-img {% endunless %}">
                            {% if product.featured_media %}
                              {%- assign img_height = 43 | divided_by: product.featured_media.aspect_ratio | ceil -%}
                              {{
                                product.featured_media
                                | image_url: width: 86
                                | image_tag:
                                  loading: 'lazy',
                                  fetchpriority: 'low',
                                  decoding: 'async',
                                  class: 'variant-item__image',
                                  width: 43,
                                  height: img_height,
                                  widths: '86',
                                  alt: product.featured_media.alt
                                | escape
                              }}
                            {% endif %}
                          </div>
                        </div>
                      <div class="small-hide medium-hide">
                        <span class="variant-item__name h4 break">{{ product.title | escape }}</span>
                        {%- if product.sku -%}
                          <span class="variant-item__sku break">{{ product.selected_or_first_available_variant.sku | escape }}</span>
                        {%- endif -%}
                      </div>
                    </td>

                    <td class="variant-item__details large-up-hide">
                      <div class="variant-item__info">
                        <span class="variant-item__name h4 break">{{ variant.title | escape }}</span>
                        {%- if product.selected_or_first_available_variant.sku -%}
                          <span class="variant-item__sku break">{{ product.selected_or_first_available_variant.sku | escape }}</span>
                        {%- endif -%}
                      </div>

                      {%- assign item_price = product.selected_or_first_available_variant.price | money -%}
                      {%- if product.selected_or_first_available_variant.compare_at_price -%}
                        <dl class="variant-item__discounted-prices">
                          <dt class="visually-hidden">
                            Regular price
                          </dt>
                          <dd>
                            <s class="variant-item__old-price price price--end">
                              {{ product.selected_or_first_available_variant.compare_at_price | money }}
                            </s>
                          </dd>
                          <dt class="visually-hidden">
                            Sale price
                          </dt>
                          <dd class="price">
                             <span class="price">
                               {{ item_price }}/ea
                             </span>
                          </dd>
                        </dl>
                      {%- else -%}
                          <span class="price">
                            {{ item_price }}/ea
                          </span>
                      {%- endif -%}

                      {%- if product.selected_or_first_available_variant.available and product.selected_or_first_available_variant.unit_price_measurement -%}
                        <div class="unit-price caption">
                          <span class="visually-hidden">Unit price</span>
                          {{ product.selected_or_first_available_variant.unit_price | money }}
                          <span aria-hidden="true">/</span>
                          <span class="visually-hidden">&nbsp;per&nbsp;</span>
                          {%- if product.selected_or_first_available_variant.unit_price_measurement.reference_value != 1 -%}
                            {{- product.selected_or_first_available_variant.unit_price_measurement.reference_value -}}
                          {%- endif -%}
                          {{ product.selected_or_first_available_variant.unit_price_measurement.reference_unit }}
                        </div>
                      {%- endif -%}
                    </td>

                    <td class="variant-item__totals right large-up-hide">
                      {%- render 'loading-overlay' -%}
                      {% comment %} TODO: enable theme-check once `line_items_for` is accepted as valid filter {% endcomment %}
                      {% # theme-check-disable %}
                      <span class="price">{{ cart | line_items_for: product.selected_or_first_available_variant | sum: 'original_line_price' | money }}</span>
                      {% # theme-check-enable %}
                    </td>

                    <td class="variant-item__quantity">
                      <quantity-popover>
                        <div class="variant-item__quantity-wrapper quantity-popover-wrapper variant-item__quantity-wrapper--no-info">
                          <label class="visually-hidden" for="Quantity-{{ variant.id }}">
                            Quantity
                          </label>
                          <div class="quantity-popover-container">
                            {%- if product.selected_or_first_available_variant.available == false -%}
                              <span class="variant-item__sold-out h4"> Sold out </span>
                            {%- else -%}
                              {% comment %} TODO: Remove theme check {% endcomment %}
                              {% # theme-check-disable %}
                              {% assign cart_qty = cart | item_count_for_variant: product.selected_or_first_available_variant.id %}
                              {% # theme-check-enable %}
                              {% render 'quantity-input', variant: product.selected_or_first_available_variant %}
                            {%- endif -%}
                          </div>
                          {%- if cart_qty > 0 -%}
                            <quick-order-list-remove-button
                              id="Remove-{{ product.selected_or_first_available_variant.id }}"
                              data-index="{{ product.selected_or_first_available_variant.id }}"
                            >
                              <a
                                href="{{ product.selected_or_first_available_variant.url_to_remove }}"
                                class="button button--tertiary"
                                aria-label="Remove {{ variant.title }}"
                              >
                                {% render 'icon-remove' %}
                              </a>
                            </quick-order-list-remove-button>
                          {%- endif -%}
                        </div>
                        <div
                          class="variant-item__error large-up-hide"
                          id="Quick-order-list-item-error-mobile-{{ product.selected_or_first_available_variant.id }}"
                          role="alert"
                        >
                          <small class="variant-item__error-text"></small>
                          {% render 'icon-error' %}
                        </div>
                      </quantity-popover>
                    </td>
                    {%- assign item_price = product.selected_or_first_available_variant.price | money -%}
                    <td class="variant-item__price small-hide medium-hide">
                      {%- if variant.compare_at_price -%}
                        <dl class="variant-item__discounted-prices">
                          <dt class="visually-hidden">
                            Regular price
                          </dt>
                          <dd>
                            <s class="variant-item__old-price price price--end">
                              {{ product.selected_or_first_available_variant.compare_at_price | money }}
                            </s>
                          </dd>
                          <dt class="visually-hidden">
                            Sale price
                          </dt>
                          <dd class="price">
                             <span class="price">
                               {{ item_price }}/ea
                             </span>
                          </dd>
                        </dl>
                      {%- else -%}
                         <span class="price">
                           {{ item_price }}/ea
                         </span>
                       {%- endif -%}

                      {%- if product.selected_or_first_available_variant.available and product.selected_or_first_available_variant.unit_price_measurement -%}
                        <div class="unit-price caption">
                          <span class="visually-hidden">Unit price</span>
                          {{ variant.unit_price | money }}
                          <span aria-hidden="true">/</span>
                          <span class="visually-hidden">&nbsp;per&nbsp;</span>
                          {%- if product.selected_or_first_available_variant.unit_price_measurement.reference_value != 1 -%}
                            {{- product.selected_or_first_available_variant.unit_price_measurement.reference_value -}}
                          {%- endif -%}
                          {{ product.selected_or_first_available_variant.unit_price_measurement.reference_unit }}
                        </div>
                      {%- endif -%}
                    </td>
                    <td class="variant-item__totals right small-hide medium-hide">
                      {%- render 'loading-overlay' -%}
                      {% comment %} TODO: enable theme-check once `line_items_for` is accepted as valid filter {% endcomment %}
                      {% # theme-check-disable %}
                      <span class="price">{{ cart | line_items_for: product.selected_or_first_available_variant | sum: 'original_line_price' | money }}</span>
                      {% # theme-check-enable %}
                    </td>
                  </tr>
                  <tr class="small-hide medium-hide hidden desktop-row-error">
                    <td></td>
                    <td>
                      <div class="variant-item__error" id="Quick-order-list-item-error-desktop-{{ variant.id }}" role="alert">
                        <small class="variant-item__error-text"></small>
                        {% render 'icon-error' %}
                      </div>
                    </td>
                    <td></td>
                    <td></td>
                  </tr>
              {%- else -%}
                {%- for variant in product.variants -%}
                {% # theme-check-disable %}
                  {% assign cart_qty = cart | item_count_for_variant: variant.id %}
                 {% # theme-check-enable %}
                  <tr
                    class="variant-item"
                    id="Variant-{{ variant.id }}"
                    data-variant-id="{{ variant.id }}"
                    data-cart-qty="{{ cart_qty }}"
                  >
                    <td class="variant-item__inner">
                        <div class="variant-item__media">
                          <div class="variant-item__image-container gradient global-media-settings{% unless variant.image %} variant-item__image-container--no-img {% endunless %}">
                            {% if variant.image %}
                              {%- assign img_height = 43 | divided_by: variant.image.aspect_ratio | ceil -%}
                              {{
                                variant.image
                                | image_url: width: 86
                                | image_tag:
                                  loading: 'lazy',
                                  fetchpriority: 'low',
                                  decoding: 'async',
                                  class: 'variant-item__image',
                                  width: 43,
                                  height: img_height,
                                  widths: '86',
                                  alt: variant.image.alt
                                | escape
                              }}
                            {% endif %}
                          </div>
                        </div>
                      <div class="small-hide medium-hide">
                        <span class="variant-item__name h4 break">{{ variant.title | escape }}</span>
                        {%- if variant.sku -%}
                          <span class="variant-item__sku break">{{ variant.sku | escape }}</span>
                        {%- endif -%}
                      </div>
                    </td>

                    <td class="variant-item__details large-up-hide">
                      <div class="variant-item__info">
                        <span class="variant-item__name h4 break">{{ variant.title | escape }}</span>
                        {%- if variant.sku -%}
                          <span class="variant-item__sku break">{{ variant.sku | escape }}</span>
                        {%- endif -%}
                      </div>

                      {%- assign item_price = variant.price | money -%}
                      {%- if variant.compare_at_price -%}
                        <dl class="variant-item__discounted-prices">
                          <dt class="visually-hidden">
                            Regular price
                          </dt>
                          <dd>
                            <s class="variant-item__old-price price price--end">
                              {{ variant.compare_at_price | money }}
                            </s>
                          </dd>
                          <dt class="visually-hidden">
                            Sale price
                          </dt>
                          <dd class="price">
                             <span class="price">
                               {{ item_price }}/ea
                             </span>
                          </dd>
                        </dl>
                      {%- else -%}
                          <span class="price">
                            {{ item_price }}/ea
                          </span>
                      {%- endif -%}

                      {%- if variant.available and variant.unit_price_measurement -%}
                        <div class="unit-price caption">
                          <span class="visually-hidden">Unit price</span>
                          {{ variant.unit_price | money }}
                          <span aria-hidden="true">/</span>
                          <span class="visually-hidden">&nbsp;per&nbsp;</span>
                          {%- if variant.unit_price_measurement.reference_value != 1 -%}
                            {{- variant.unit_price_measurement.reference_value -}}
                          {%- endif -%}
                          {{ variant.unit_price_measurement.reference_unit }}
                        </div>
                      {%- endif -%}
                    </td>

                    <td class="variant-item__totals right large-up-hide">
                      {%- render 'loading-overlay' -%}
                      {% comment %} TODO: enable theme-check once `line_items_for` is accepted as valid filter {% endcomment %}
                      {% # theme-check-disable %}
                      <span class="price">{{ cart | line_items_for: variant | sum: 'original_line_price' | money }}</span>
                      {% # theme-check-enable %}
                    </td>

                    <td class="variant-item__quantity">
                      <quantity-popover>
                        <div class="variant-item__quantity-wrapper quantity-popover-wrapper variant-item__quantity-wrapper--no-info">
                          <label class="visually-hidden" for="Quantity-{{ variant.id }}">
                            Quantity
                          </label>
                          <div class="quantity-popover-container">
                            {%- if variant.available == false -%}
                              <span class="variant-item__sold-out h4">Sold out</span>
                            {%- else -%}
                              {% comment %} TODO: Remove theme check {% endcomment %}
                              {% # theme-check-disable %}
                              {% assign cart_qty = cart | item_count_for_variant: variant.id %}
                              {% # theme-check-enable %}
                              {% render 'quantity-input', variant: variant %}
                            {%- endif -%}
                          </div>
                          {%- if cart_qty > 0 -%}
                            <quick-order-list-remove-button
                              id="Remove-{{ variant.id }}"
                              data-index="{{ variant.id }}"
                            >
                              <a
                                href="{{ variant.url_to_remove }}"
                                class="button button--tertiary"
                                aria-label="Remove {{variant.title }}"
                              >
                                {% render 'icon-remove' %}
                              </a>
                            </quick-order-list-remove-button>
                          {%- endif -%}
                        </div>
                        <div
                          class="variant-item__error large-up-hide"
                          id="Quick-order-list-item-error-mobile-{{ variant.id }}"
                          role="alert"
                        >
                          <small class="variant-item__error-text"></small>
                          {% render 'icon-error' %}
                        </div>
                      </quantity-popover>
                    </td>
                    {%- assign item_price = variant.price | money -%}
                    <td class="variant-item__price small-hide medium-hide">
                      {%- if variant.compare_at_price -%}
                        <dl class="variant-item__discounted-prices">
                          <dt class="visually-hidden">
                            Regular price
                          </dt>
                          <dd>
                            <s class="variant-item__old-price price price--end">
                              {{ variant.compare_at_price | money }}
                            </s>
                          </dd>
                          <dt class="visually-hidden">
                            Sale price
                          </dt>
                          <dd class="price">
                             <span class="price">
                               {{ item_price }}/ea
                             </span>
                          </dd>
                        </dl>
                      {%- else -%}
                         <span class="price">
                           {{ item_price }}/ea
                         </span>
                       {%- endif -%}

                      {%- if variant.available and variant.unit_price_measurement -%}
                        <div class="unit-price caption">
                          <span class="visually-hidden">Unit price</span>
                          {{ variant.unit_price | money }}
                          <span aria-hidden="true">/</span>
                          <span class="visually-hidden">&nbsp;per&nbsp;</span>
                          {%- if variant.unit_price_measurement.reference_value != 1 -%}
                            {{- variant.unit_price_measurement.reference_value -}}
                          {%- endif -%}
                          {{ variant.unit_price_measurement.reference_unit }}
                        </div>
                      {%- endif -%}
                    </td>
                    <td class="variant-item__totals right small-hide medium-hide">
                      {%- render 'loading-overlay' -%}
                      {% comment %} TODO: enable theme-check once `line_items_for` is accepted as valid filter {% endcomment %}
                      {% # theme-check-disable %}
                      <span class="price">{{ cart | line_items_for: variant | sum: 'original_line_price' | money }}</span>
                      {% # theme-check-enable %}
                    </td>
                  </tr>
                  <tr class="small-hide medium-hide hidden desktop-row-error">
                    <td></td>
                    <td>
                      <div class="variant-item__error" id="Quick-order-list-item-error-desktop-{{ variant.id }}" role="alert">
                        <small class="variant-item__error-text"></small>
                        {% render 'icon-error' %}
                      </div>
                    </td>
                    <td></td>
                    <td></td>
                  </tr>

                {%- endfor -%}
              {%- endif -%}
            </tbody>
          </table>
        </div>
        <noscript>
          {%- if product.has_only_default_variant or product.variants.size == 1 -%}
            <button type="submit" class="button button--secondary right" formnovalidate form="QuickOrderList">
              Update
            </button>
          {%- endif -%}
        </noscript>
      </div>

      <p class="visually-hidden" id="quick-order-list-live-region-text" aria-live="polite" role="status"></p>
      <p
        class="visually-hidden"
        id="shopping-cart-variant-item-status"
        aria-live="polite"
        aria-hidden="true"
        role="status"
      >
        Loading...
      </p>
    </form>

    {%- if product.has_only_default_variant or product.variants.size == 1 -%}
      <span class="quick-order-list-error">
        {% comment %} Populated by JS {% endcomment %}
      </span>
    {%- else -%}
      <div class="quick-order-list__total gradient" id="quick-order-list-total">
        <div class="quick-order-list-total__info">
          <div class="quick-order-list-total__column small-hide medium-hide">
            <div class="quick-order-list-buttons">
              <a
                href="{{ routes.cart_url }} "
                class="quick-order-list__button button button--secondary small-hide medium-hide"
              >
                <span class="quick-order-list__button-text">View cart</span>
              </a>
              <div class="variant-remove-total">
                {%- render 'loading-overlay' -%}
                <quick-order-list-remove-all-button
                  class="no-js-hidden"
                  data-action="confirm"
                >
                  <button class="button button--tertiary" type="button">
                    {% render 'icon-remove' %}
                    <span class="text-body">Remove all</span>
                  </button>
                </quick-order-list-remove-all-button>
              </div>
            </div>
            <span class="quick-order-list__message caption-large" role="status">
              <span class="quick-order-list__message-icon hidden">{%- render 'icon-checkmark' -%}</span>
              <span class="quick-order-list__message-text"></span>
            </span>
            <span class="quick-order-list-error">
              {% comment %} Populated by JS {% endcomment %}
            </span>
          </div>
          <div class="quick-order-list__total-items">
            <h3>
              {{ items_in_cart }}
            </h3>
            <p class="h5">Total items</p>
          </div>
          <div class="quick-order-list-total__price">
            <noscript>
              <button type="submit" class="button button--secondary" formnovalidate form="QuickOrderList">
                Update
              </button>
            </noscript>
            <div class="totals__product-total">
              <h3 class="totals__subtotal-value">
                {% comment %} TODO: enable theme-check once `line_items_for` is accepted as valid filter {% endcomment %}
                {% # theme-check-disable %}
                {{ cart | line_items_for: product | sum: 'original_line_price' | money }}
                {% # theme-check-enable %}
              </h3>
              <p class="totals__subtotal h5">Product subtotal</p>
            </div>
            <small class="tax-note caption-large rte">
              {%- if cart.taxes_included and shop.shipping_policy.body != blank -%}
                Tax included. <a href="{{ shop.shipping_policy.url }}">Shipping</a> and discounts calculated at checkout.
              {%- elsif cart.taxes_included -%}
                Tax included and shipping and discounts calculated at checkout
              {%- elsif shop.shipping_policy.body != blank -%}
                Taxes, Discounts and <a href="{{ shop.shipping_policy.url }}">shipping</a> calculated at checkout
              {%- else -%}
                Taxes, discounts and shipping calculated at checkout
              {%- endif -%}
            </small>
          </div>
          <div class="quick-order-list-total__column large-up-hide">
            <div class="quick-order-list-buttons">
              <a
                href="{{ routes.cart_url }}"
                class="quick-order-list__button button button--secondary button--full-width"
              >
                <span class="quick-order-list__button-text">View cart</span>
              </a>
              <div class="variant-remove-total">
                {%- render 'loading-overlay' -%}
                <quick-order-list-remove-all-button
                  class="no-js-hidden"
                  data-action="confirm"
                >
                  <button class="button button--tertiary" type="button">
                    {% render 'icon-remove' %}

                    <span class="text-body">Remove all</span>
                  </button>
                </quick-order-list-remove-all-button>
              </div>
            </div>
            <span class="quick-order-list__message caption-large" role="status">
              <span class="quick-order-list__message-icon hidden">{%- render 'icon-checkmark' -%}</span>
              <span class="quick-order-list__message-text"></span>
            </span>
            <span class="quick-order-list-error">
              {% comment %} Populated by JS {% endcomment %}
            </span>
          </div>
        </div>
        <div class="quick-order-list-total__confirmation hidden">
          <span class="text-body">
            Remove all {{ items_in_cart }} items from your cart?
          </span>
          <quick-order-list-remove-all-button
            data-action="remove"
          >
            <button
              class="quick-order-list__button-confirm button button--secondary"
              type="button"
            >
              Remove all
            </button>
          </quick-order-list-remove-all-button>
          <quick-order-list-remove-all-button
            data-action="cancel"
          >
            <button
              class="quick-order-list__button-cancel button button--tertiary"
              type="button"
            >
              Cancel
            </button>
          </quick-order-list-remove-all-button>
        </div>
      </div>
    {%- endif -%}
  </quick-order-list>
  <template id="QuickOrderListErrorTemplate-{{ section.id }}">
    {% render 'icon-error' %}
    <span class="quick-order-list-error-message caption-large" role="alert"></span>
  </template>
</div>
  1. Nhấp vào Save (Lưu).

Thêm mã Javascript Danh sách đặt hàng nhanh

Khi số lượng trong giỏ hàng của một mẫu mã thay đổi, tổng phụ sản phẩm, tổng phụ mẫu mã và trạng thái lỗi có thể xảy ra sẽ được cập nhật. Bạn có thể tìm nạp giá trị đã cập nhật bằng mã Javascript.

Bạn có thể thêm mã vào tệp theme.js hoặc tương đương.

Bước:

  1. Trên trang quản trị Shopify, vào mục Cửa hàng trực tuyến > Chủ đề.
  2. Tìm chủ đề bạn muốn chỉnh sửa, nhấp vào nút ... để mở menu thao tác, sau đó nhấp vào Chỉnh sửa mã.
  3. Mở tệp theme.js.
  4. Tạo một dòng mới ở cuối tệp, sau đó thêm mã sau:
class QuickOrderListRemoveButton extends HTMLElement {
  constructor() {
    super();
    this.addEventListener('click', (event) => {
      event.preventDefault();
      const quickOrderList = this.closest('quick-order-list');
      quickOrderList.updateQuantity(this.dataset.index, 0);
    });
  }
}

customElements.define('quick-order-list-remove-button', QuickOrderListRemoveButton);

class QuickOrderListRemoveAllButton extends HTMLElement {
  constructor() {
    super();
    const allVariants = Array.from(document.querySelectorAll('[data-variant-id]'));
    const items = {}
    let hasVariantsInCart = false;
    this.quickOrderList = this.closest('quick-order-list');

    allVariants.forEach((variant) => {
      const cartQty = parseInt(variant.dataset.cartQty);
      if (cartQty > 0) {
        hasVariantsInCart = true;
        items[parseInt(variant.dataset.variantId)] = 0;
      }
    });

    if (!hasVariantsInCart) {
      this.classList.add('hidden');
    }

    this.actions = {
      confirm: 'confirm',
      remove: 'remove',
      cancel: 'cancel'
    }

    this.addEventListener('click', (event) => {
      event.preventDefault();
      if (this.dataset.action === this.actions.confirm) {
        this.toggleConfirmation(false, true);
      } else if (this.dataset.action === this.actions.remove) {
        this.quickOrderList.updateMultipleQty(items);
        this.toggleConfirmation(true, false);
      } else if (this.dataset.action === this.actions.cancel) {
        this.toggleConfirmation(true, false);
      }
    });
  }

  toggleConfirmation(showConfirmation, showInfo) {
    this.quickOrderList.querySelector('.quick-order-list-total__confirmation').classList.toggle('hidden', showConfirmation);
    this.quickOrderList.querySelector('.quick-order-list-total__info').classList.toggle('hidden', showInfo)
  }
}

customElements.define('quick-order-list-remove-all-button', QuickOrderListRemoveAllButton);

class QuickOrderList extends HTMLElement {
  constructor() {
    super();
    this.cart = document.querySelector('cart-drawer');
    this.actions = {
      add: 'ADD',
      update: 'UPDATE'
    }
    this.quickOrderListId = 'quick-order-list'
    this.variantItemStatusElement = document.getElementById('shopping-cart-variant-item-status');
    const form = this.querySelector('form');

    form.addEventListener('submit', this.onSubmit.bind(this));

    const debouncedOnChange = debounce((event) => {
      this.onChange(event);
    }, ON_CHANGE_DEBOUNCE_TIMER);
    this.addEventListener('change', debouncedOnChange.bind(this));
  }

  cartUpdateUnsubscriber = undefined;

  onSubmit(event) {
    event.preventDefault();
  }

  connectedCallback() {
    this.cartUpdateUnsubscriber = subscribe(PUB_SUB_EVENTS.cartUpdate, (event) => {
      if (event.source === this.quickOrderListId) {
        return;
      }
      // If its another section that made the update
      this.onCartUpdate();
    });
    this.sectionId = this.dataset.id;
  }

  disconnectedCallback() {
    if (this.cartUpdateUnsubscriber) {
      this.cartUpdateUnsubscriber();
    }
  }

  onChange(event) {
    const inputValue = parseInt(event.target.value);
    const cartQuantity = parseInt(event.target.dataset.cartQuantity);
    const index = event.target.dataset.index;
    const name = document.activeElement.getAttribute('name');

    const quantity = inputValue - cartQuantity;

    if (cartQuantity > 0) {
      this.updateQuantity(index, inputValue, name, this.actions.update);
    } else {
      this.updateQuantity(index, quantity, name, this.actions.add);
    }
  }

  onCartUpdate() {
    fetch(`${window.location.pathname}?section_id=${this.sectionId}`)
      .then((response) => response.text())
      .then((responseText) => {
        const html = new DOMParser().parseFromString(responseText, 'text/html');
        const sourceQty = html.querySelector(this.quickOrderListId);
        this.innerHTML = sourceQty.innerHTML;
      })
      .catch(e => {
        console.error(e);
      });
  }

  getSectionsToRender() {
    return [
      {
        id: this.quickOrderListId,
        section: document.getElementById(this.quickOrderListId).dataset.id,
        selector: '.js-contents'
      },
      {
        id: 'cart-icon-bubble',
        section: 'cart-icon-bubble',
        selector: '.shopify-section'
      },
      {
        id: 'quick-order-list-live-region-text',
        section: 'cart-live-region-text',
        selector: '.shopify-section'
      },
      {
        id: 'quick-order-list-total',
        section: document.getElementById(this.quickOrderListId).dataset.id,
        selector: '.quick-order-list__total'
      },
      {
        id: 'CartDrawer',
        selector: '#CartDrawer',
        section: 'cart-drawer'
      }
    ];
  }

  renderSections(parsedState) {
    this.getSectionsToRender().forEach((section => {
      const sectionElement = document.getElementById(section.id);
      if (sectionElement && sectionElement.parentElement && sectionElement.parentElement.classList.contains('drawer')) {
        parsedState.items.length > 0 ? sectionElement.parentElement.classList.remove('is-empty') : sectionElement.parentElement.classList.add('is-empty');

        setTimeout(() => {
          document.querySelector('#CartDrawer-Overlay').addEventListener('click', this.cart.close.bind(this.cart));
        });
      }
      const elementToReplace = sectionElement && sectionElement.querySelector(section.selector) ? sectionElement.querySelector(section.selector) : sectionElement;
      if (elementToReplace) {
        elementToReplace.innerHTML =
          this.getSectionInnerHTML(parsedState.sections[section.section], section.selector);
      }
    }));

  }

  updateMultipleQty(items) {
    this.querySelector('.variant-remove-total .loading-overlay').classList.remove('hidden');

    const body = JSON.stringify({
      updates: items,
      sections: this.getSectionsToRender().map((section) => section.section),
      sections_url: window.location.pathname
    });

    this.updateMessage();
    this.setErrorMessage();

    fetch(`${routes.cart_update_url}`, { ...fetchConfig(), ...{ body } })
      .then((response) => {
        return response.text();
      })
      .then((state) => {
        const parsedState = JSON.parse(state);
        this.renderSections(parsedState);
      }).catch(() => {
        this.setErrorMessage('There was an error while updating your cart. Please try again.');
      })
      .finally(() => {
        this.querySelector('.variant-remove-total .loading-overlay').classList.add('hidden');
      });
  }

  updateQuantity(id, quantity, name, action) {
    this.toggleLoading(id, true);

    let routeUrl = routes.cart_change_url;
    let body = JSON.stringify({
      quantity,
      id,
      sections: this.getSectionsToRender().map((section) => section.section),
      sections_url: window.location.pathname
    });
    let fetchConfigType;
    if (action === this.actions.add) {
      fetchConfigType = 'javascript';
      routeUrl = routes.cart_add_url;
      body = JSON.stringify({
        items: [
          {
            quantity: parseInt(quantity),
            id: parseInt(id)
          }
        ],
        sections: this.getSectionsToRender().map((section) => section.section),
        sections_url: window.location.pathname
      });
    }

    this.updateMessage();
    this.setErrorMessage();

    fetch(`${routeUrl}`, { ...fetchConfig(fetchConfigType), ...{ body } })
      .then((response) => {
        return response.text();
      })
      .then((state) => {
        const parsedState = JSON.parse(state);
        const quantityElement = document.getElementById(`Quantity-${id}`);
        const items = document.querySelectorAll('.variant-item');

        if (parsedState.description || parsedState.errors) {
          const variantItem = document.querySelector(`[id^="Variant-${id}"] .variant-item__totals.small-hide .loading-overlay`);
          variantItem.classList.add('loading-overlay--error');
          this.resetQuantityInput(id, quantityElement);
          if (parsedState.errors) {
            this.updateLiveRegions(id, parsedState.errors);
          } else {
            this.updateLiveRegions(id, parsedState.description);
          }
          return;
        }

        this.classList.toggle('is-empty', parsedState.item_count === 0);

        this.renderSections(parsedState);

        let hasError = false;

        const currentItem = parsedState.items.find((item) => item.variant_id === parseInt(id));
        const updatedValue = currentItem ? currentItem.quantity : undefined;
        if (updatedValue && updatedValue !== quantity) {
          this.updateError(updatedValue, id);
          hasError = true;
        }

        const variantItem = document.getElementById(`Variant-${id}`);
        if (variantItem && variantItem.querySelector(`[name="${name}"]`)) {
          variantItem.querySelector(`[name="${name}"]`).focus();
        }
        publish(PUB_SUB_EVENTS.cartUpdate, { source: this.quickOrderListId, cartData: parsedState });

        if (hasError) {
          this.updateMessage();
        } else if (action === this.actions.add) {
          this.updateMessage(parseInt(quantity))
        } else if (action === this.actions.update) {
          this.updateMessage(parseInt(quantity - quantityElement.dataset.cartQuantity))
        } else {
          this.updateMessage(-parseInt(quantityElement.dataset.cartQuantity))
        }
      }).catch((error) => {
        this.querySelectorAll('.loading-overlay').forEach((overlay) => overlay.classList.add('hidden'));
        this.resetQuantityInput(id);
        console.error(error);
        this.setErrorMessage('There was an error while updating your cart. Please try again.');
      })
      .finally(() => {
        this.toggleLoading(id);
      });
  }

  resetQuantityInput(id, quantityElement) {
    const input = quantityElement ?? document.getElementById(`Quantity-${id}`);
    input.value = input.getAttribute('value');
  }

  setErrorMessage(message = null) {
    this.errorMessageTemplate = this.errorMessageTemplate ?? document.getElementById(`QuickOrderListErrorTemplate-${this.sectionId}`).cloneNode(true);
    const errorElements = document.querySelectorAll('.quick-order-list-error');

    errorElements.forEach((errorElement) => {
      errorElement.innerHTML = '';
      if (!message) return;
      const updatedMessageElement = this.errorMessageTemplate.cloneNode(true);
      updatedMessageElement.content.querySelector('.quick-order-list-error-message').innerText = message;
      errorElement.appendChild(updatedMessageElement.content);
    });
  }

  updateMessage(quantity = null) {
    const messages = this.querySelectorAll('.quick-order-list__message-text');
    const icons = this.querySelectorAll('.quick-order-list__message-icon');

    if (quantity === null || isNaN(quantity)) {
      messages.forEach(message => message.innerHTML = '');
      icons.forEach(icon => icon.classList.add('hidden'));
      return;
    }

    const isQuantityNegative = quantity < 0;
    const absQuantity = Math.abs(quantity);

    const textTemplate = isQuantityNegative
      ? (absQuantity === 1 ? `${absQuantity} item removed` : `${absQuantity} items removed`)
      : (quantity === 1 ? `${absQuantity} item added` : `${absQuantity} items added`)

    messages.forEach((msg) => msg.innerHTML = textTemplate);

    if (!isQuantityNegative) {
      icons.forEach((i) => i.classList.remove('hidden'));
    }

  }

  updateError(updatedValue, id) {
    let message = '';
    if (typeof updatedValue === 'undefined') {
      message = 'There was an error while updating your cart. Please try again.';
    } else {
      message = `You can only add ${updatedValue} of this item to your cart.`;
    }
    this.updateLiveRegions(id, message);
  }

  updateLiveRegions(id, message) {
    const variantItemErrorDesktop = document.getElementById(`Quick-order-list-item-error-desktop-${id}`);
    const variantItemErrorMobile = document.getElementById(`Quick-order-list-item-error-mobile-${id}`);
    if (variantItemErrorDesktop) {
      variantItemErrorDesktop.querySelector('.variant-item__error-text').innerHTML = message;
      variantItemErrorDesktop.closest('tr').classList.remove('hidden');
    }
    if (variantItemErrorMobile) variantItemErrorMobile.querySelector('.variant-item__error-text').innerHTML = message;

    this.variantItemStatusElement.setAttribute('aria-hidden', true);

    const cartStatus = document.getElementById('quick-order-list-live-region-text');
    cartStatus.setAttribute('aria-hidden', false);

    setTimeout(() => {
      cartStatus.setAttribute('aria-hidden', true);
    }, 1000);
  }

  getSectionInnerHTML(html, selector) {
    return new DOMParser()
      .parseFromString(html, 'text/html')
      .querySelector(selector).innerHTML;
  }

  toggleLoading(id, enable) {
    const quickOrderList = document.getElementById(this.quickOrderListId);
    const quickOrderListItems = this.querySelectorAll(`#Variant-${id} .loading-overlay`);

    if (enable) {
      quickOrderList.classList.add('quick-order-list__container--disabled');
      [...quickOrderListItems].forEach((overlay) => overlay.classList.remove('hidden'));
      document.activeElement.blur();
      this.variantItemStatusElement.setAttribute('aria-hidden', false);
    } else {
      quickOrderList.classList.remove('quick-order-list__container--disabled');
      quickOrderListItems.forEach((overlay) => overlay.classList.add('hidden'));
    }
  }
}

customElements.define('quick-order-list', QuickOrderList);
  1. Nhấp vào Save (Lưu).

Bạn đã sẵn sàng bán hàng với Shopify?

Dùng thử miễn phí