Website: Product Configurator and Bootstrap 4 (#15965)
- Refactored Homepage with customisable Hero Section
- New Homepage Section to add content on Homepage as cards or using Custom HTML
- Products page at "/all-products" with customisable filters
- Item Configure dialog to find an Item Variant filtered by attribute values
- Contact Us dialog on Item page
- Customisable Item page content using the Website Content field
diff --git a/erpnext/templates/includes/address_row.html b/erpnext/templates/includes/address_row.html
index bfc035a..dadd2df 100644
--- a/erpnext/templates/includes/address_row.html
+++ b/erpnext/templates/includes/address_row.html
@@ -1,12 +1,12 @@
-<div class="web-list-item">
- <a href="/addresses?name={{ doc.name | urlencode }}" class="no-decoration">
+<div class="web-list-item mb-3">
+ <a href="/addresses?name={{ doc.name | urlencode }}" class="no-underline text-reset">
<div class="row">
- <div class="col-xs-3">
+ <div class="col-3">
<span class="indicator {{ "red" if doc.address_type=="Office" else "green" if doc.address_type=="Billing" else "blue" if doc.address_type=="Shipping" else "darkgrey" }}">{{ doc.address_title }}</span>
</div>
- <div class="col-xs-2"> {{ _(doc.address_type) }} </div>
- <div class="col-xs-2"> {{ doc.city }} </div>
- <div class="col-xs-5 text-right small text-muted">
+ <div class="col-2"> {{ _(doc.address_type) }} </div>
+ <div class="col-2"> {{ doc.city }} </div>
+ <div class="col-5 text-right small text-muted">
{{ frappe.get_doc(doc).get_display() }}
</div>
</div>
diff --git a/erpnext/templates/includes/cart.js b/erpnext/templates/includes/cart.js
index 51be954..983898b 100644
--- a/erpnext/templates/includes/cart.js
+++ b/erpnext/templates/includes/cart.js
@@ -16,41 +16,33 @@
bind_events: function() {
shopping_cart.bind_address_select();
shopping_cart.bind_place_order();
+ shopping_cart.bind_request_quotation();
shopping_cart.bind_change_qty();
+ shopping_cart.bind_change_notes();
shopping_cart.bind_dropdown_cart_buttons();
},
bind_address_select: function() {
- $(".cart-addresses").find('input[data-address-name]').on("click", function() {
- if($(this).prop("checked")) {
- var me = this;
+ $(".cart-addresses").on('click', '.address-card', function(e) {
+ const $card = $(e.currentTarget);
+ const address_fieldname = $card.closest('[data-fieldname]').attr('data-fieldname');
+ const address_name = $card.closest('[data-address-name]').attr('data-address-name');
- // uncheck other shipping or billing addresses:
- if ( $(this).is('input[data-fieldname=customer_address]') ) {
- $('input[data-fieldname=customer_address]').not(this).prop('checked', false);
- } else {
- $('input[data-fieldname=shipping_address_name]').not(this).prop('checked', false);
- }
-
- return frappe.call({
- type: "POST",
- method: "erpnext.shopping_cart.cart.update_cart_address",
- freeze: true,
- args: {
- address_fieldname: $(this).attr("data-fieldname"),
- address_name: $(this).attr("data-address-name")
- },
- callback: function(r) {
- if(!r.exc) {
- $(".cart-tax-items").html(r.message.taxes);
- }
+ return frappe.call({
+ type: "POST",
+ method: "erpnext.shopping_cart.cart.update_cart_address",
+ freeze: true,
+ args: {
+ address_fieldname,
+ address_name
+ },
+ callback: function(r) {
+ if(!r.exc) {
+ $(".cart-tax-items").html(r.message.taxes);
}
- });
- } else {
- return false;
- }
+ }
+ });
});
-
},
bind_place_order: function() {
@@ -59,12 +51,18 @@
});
},
+ bind_request_quotation: function() {
+ $('.btn-request-for-quotation').on('click', function() {
+ shopping_cart.request_quotation(this);
+ });
+ },
+
bind_change_qty: function() {
// bind update button
$(".cart-items").on("change", ".cart-qty", function() {
var item_code = $(this).attr("data-item-code");
var newVal = $(this).val();
- shopping_cart.shopping_cart_update(item_code, newVal);
+ shopping_cart.shopping_cart_update({item_code, qty: newVal});
});
$(".cart-items").on('click', '.number-spinner button', function () {
@@ -82,7 +80,21 @@
}
input.val(newVal);
var item_code = input.attr("data-item-code");
- shopping_cart.shopping_cart_update(item_code, newVal);
+ shopping_cart.shopping_cart_update({item_code, qty: newVal});
+ });
+ },
+
+ bind_change_notes: function() {
+ $('.cart-items').on('change', 'textarea', function() {
+ const $textarea = $(this);
+ const item_code = $textarea.attr('data-item-code');
+ const qty = $textarea.closest('tr').find('.cart-qty').val();
+ const notes = $textarea.val();
+ shopping_cart.shopping_cart_update({
+ item_code,
+ qty,
+ additional_notes: notes
+ });
});
},
@@ -150,7 +162,32 @@
.html(msg || frappe._("Something went wrong!"))
.toggle(true);
} else {
- window.location.href = "/orders/" + encodeURIComponent(r.message);
+ window.open('/orders/' + encodeURIComponent(r.message), '_blank');
+ window.location.reload();
+ }
+ }
+ });
+ },
+
+ request_quotation: function(btn) {
+ return frappe.call({
+ type: "POST",
+ method: "erpnext.shopping_cart.cart.request_for_quotation",
+ btn: btn,
+ callback: function(r) {
+ if(r.exc) {
+ var msg = "";
+ if(r._server_messages) {
+ msg = JSON.parse(r._server_messages || []).join("<br>");
+ }
+
+ $("#cart-error")
+ .empty()
+ .html(msg || frappe._("Something went wrong!"))
+ .toggle(true);
+ } else {
+ window.open('/printview?doctype=Quotation&name=' + r.message, '_blank');
+ window.location.reload();
}
}
});
diff --git a/erpnext/templates/includes/cart/address_card.html b/erpnext/templates/includes/cart/address_card.html
new file mode 100644
index 0000000..c91723e
--- /dev/null
+++ b/erpnext/templates/includes/cart/address_card.html
@@ -0,0 +1,12 @@
+<div class="card address-card h-100">
+ <div class="check" style="position: absolute; right: 15px; top: 15px;">
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>
+ </div>
+ <div class="card-body">
+ <h5 class="card-title">{{ address.name }}</h5>
+ <p class="card-text text-muted">
+ {{ address.display }}
+ </p>
+ <a href="/addresses?name={{address.name}}" class="card-link">{{ _('Edit') }}</a>
+ </div>
+</div>
diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html
index 7bd9256..2c90f8c 100644
--- a/erpnext/templates/includes/cart/cart_address.html
+++ b/erpnext/templates/includes/cart/cart_address.html
@@ -1,26 +1,141 @@
{% from "erpnext/templates/includes/cart/cart_macros.html" import show_address %}
-<div class="row">
- {% if addresses|length == 1%}
- {% set select_address = True %}
- {% endif %}
- <div class="col-sm-6">
- <div class="h6 text-uppercase">{{ _("Shipping Address") }}</div>
- <div id="cart-shipping-address" class="panel-group"
- data-fieldname="shipping_address_name">
- {% for address in shipping_addresses %}
- {{ show_address(address, doc, "shipping_address_name", select_address) }}
- {% endfor %}
- </div>
- <a class="btn btn-default btn-sm" href="/addresses">
- {{ _("Manage Addresses") }}</a>
- </div>
- <div class="col-sm-6">
- <div class="h6 text-uppercase">{{ _("Billing Address") }}</div>
- <div id="cart-billing-address" class="panel-group"
- data-fieldname="customer_address">
- {% for address in billing_addresses %}
- {{ show_address(address, doc, "customer_address", select_address) }}
- {% endfor %}
- </div>
+
+{% if addresses | length == 1%}
+ {% set select_address = True %}
+{% endif %}
+
+<div class="mb-3" data-section="shipping-address">
+ <h6 class="text-uppercase">{{ _("Shipping Address") }}</h6>
+ <div class="row no-gutters" data-fieldname="shipping_address_name">
+ {% for address in shipping_addresses %}
+ <div class="mr-3 mb-3 w-25" data-address-name="{{address.name}}" {% if doc.shipping_address_name == address.name %} data-active {% endif %}>
+ {% include "templates/includes/cart/address_card.html" %}
+ </div>
+ {% endfor %}
</div>
</div>
+<div class="mb-3" data-section="billing-address">
+ <h6 class="text-uppercase">{{ _("Billing Address") }}</h6>
+ <div class="row no-gutters" data-fieldname="customer_address">
+ {% for address in billing_addresses %}
+ <div class="mr-3 mb-3 w-25" data-address-name="{{address.name}}" {% if doc.customer_address == address.name %} data-active {% endif %}>
+ {% include "templates/includes/cart/address_card.html" %}
+ </div>
+ {% endfor %}
+ </div>
+</div>
+<div class="custom-control custom-checkbox">
+ <input type="checkbox" class="custom-control-input" id="input_same_billing" checked>
+ <label class="custom-control-label" for="input_same_billing">{{ _('Billing Address is same as Shipping Address') }}</label>
+</div>
+<button class="btn btn-outline-primary btn-sm mt-3 btn-new-address">{{ _("Add a new address") }}</button>
+
+<script>
+frappe.ready(() => {
+ $(document).on('click', '.address-card', (e) => {
+ const $target = $(e.currentTarget);
+ const $section = $target.closest('[data-section]');
+ $section.find('.address-card').removeClass('active');
+ $target.addClass('active');
+ });
+
+ $('#input_same_billing').change((e) => {
+ const $check = $(e.target);
+ toggle_billing_address_section(!$check.is(':checked'));
+ });
+
+ $('.btn-new-address').click(() => {
+ const d = new frappe.ui.Dialog({
+ title: __('New Address'),
+ fields: [
+ {
+ label: __('Address Title'),
+ fieldname: 'address_title',
+ fieldtype: 'Data',
+ reqd: 1
+ },
+ {
+ label: __('Address Type'),
+ fieldname: 'address_type',
+ fieldtype: 'Select',
+ options: [
+ 'Billing',
+ 'Shipping'
+ ],
+ reqd: 1
+ },
+ {
+ label: __('Address Line 1'),
+ fieldname: 'address_line1',
+ fieldtype: 'Data',
+ reqd: 1
+ },
+ {
+ label: __('Address Line 2'),
+ fieldname: 'address_line2',
+ fieldtype: 'Data'
+ },
+ {
+ label: __('City/Town'),
+ fieldname: 'city',
+ fieldtype: 'Data',
+ reqd: 1
+ },
+ {
+ label: __('State'),
+ fieldname: 'state',
+ fieldtype: 'Data'
+ },
+ {
+ label: __('Pin Code'),
+ fieldname: 'pincode',
+ fieldtype: 'Data'
+ },
+ {
+ label: __('Country'),
+ fieldname: 'country',
+ fieldtype: 'Data',
+ reqd: 1
+ },
+ ],
+ primary_action_label: __('Save'),
+ primary_action: (values) => {
+ frappe.call('erpnext.shopping_cart.cart.add_new_address', { doc: values })
+ .then(r => {
+ d.hide();
+ window.location.reload();
+ });
+ }
+ })
+
+ d.show();
+ });
+
+ function setup_state() {
+ const shipping_address = $('[data-section="shipping-address"]')
+ .find('[data-address-name][data-active]').attr('data-address-name');
+
+ const billing_address = $('[data-section="billing-address"]')
+ .find('[data-address-name][data-active]').attr('data-address-name');
+
+ $('#input_same_billing').prop('checked', shipping_address === billing_address).trigger('change');
+
+ if (!shipping_address && !billing_address) {
+ $('#input_same_billing').prop('checked', true).trigger('change');
+ }
+
+ if (shipping_address) {
+ $(`[data-section="shipping-address"] [data-address-name="${shipping_address}"] .address-card`).addClass('active');
+ }
+ if (billing_address) {
+ $(`[data-section="billing-address"] [data-address-name="${billing_address}"] .address-card`).addClass('active');
+ }
+ }
+
+ setup_state();
+
+ function toggle_billing_address_section(flag) {
+ $('[data-section="billing-address"]').toggle(flag);
+ }
+});
+</script>
diff --git a/erpnext/templates/includes/cart/cart_items.html b/erpnext/templates/includes/cart/cart_items.html
index 65b81d9..ca5744b 100644
--- a/erpnext/templates/includes/cart/cart_items.html
+++ b/erpnext/templates/includes/cart/cart_items.html
@@ -1,31 +1,42 @@
-{% from "erpnext/templates/includes/order/order_macros.html" import item_name_and_description %}
-{% from "erpnext/templates/includes/order/order_macros.html" import item_name_and_description_cart %}
-
{% for d in doc.items %}
-<div class="row checkout">
- <div class="col-sm-8 col-xs-6 col-name-description">
- {{ item_name_and_description(d) }}
- </div>
- <div class="col-sm-2 col-xs-3 text-right col-qty">
- <span style="display: inline-block">
- <div class="input-group number-spinner">
- <span class="input-group-btn">
- <button class="btn btn-default cart-btn" data-dir="dwn">
- –</button>
- </span>
- <input class="form-control text-right cart-qty"
- value = "{{ d.get_formatted('qty') }}"
- data-item-code="{{ d.item_code }}">
- <span class="input-group-btn">
- <button class="btn btn-default cart-btn" data-dir="up" style="margin-left:-2px;">
- +</button>
- </span>
- </div>
+<tr data-name="{{ d.name }}">
+ <td>
+ <div class="font-weight-bold">
+ {{ d.item_name }}
+ </div>
+ <div>
+ {{ d.item_code }}
+ </div>
+ {%- set variant_of = frappe.db.get_value('Item', d.item_code, 'variant_of') %}
+ {% if variant_of %}
+ <span class="text-muted">
+ {{ _('Variant of') }} <a href="{{frappe.db.get_value('Item', variant_of, 'route')}}">{{ variant_of }}</a>
</span>
- </div>
- <div class="col-sm-2 col-xs-3 text-right col-amount">
- {{ d.get_formatted("amount") }}
- <p class="text-muted small item-rate">{{ _("Rate") }} {{ d.get_formatted("rate") }}</p>
- </div>
-</div>
-{% endfor %}
\ No newline at end of file
+ {% endif %}
+ <div class="mt-2">
+ <textarea data-item-code="{{d.item_code}}" class="form-control" rows="2" placeholder="{{ _('Add notes') }}">{{d.additional_notes or ''}}</textarea>
+ </div>
+ </td>
+ <td class="text-right">
+ <div class="input-group number-spinner">
+ <span class="input-group-prepend d-none d-sm-inline-block">
+ <button class="btn btn-outline-secondary cart-btn" data-dir="dwn">–</button>
+ </span>
+ <input class="form-control text-right cart-qty border-secondary" value="{{ d.get_formatted('qty') }}" data-item-code="{{ d.item_code }}">
+ <span class="input-group-append d-none d-sm-inline-block">
+ <button class="btn btn-outline-secondary cart-btn" data-dir="up">+</button>
+ </span>
+ </div>
+ </td>
+ {% if cart_settings.enable_checkout %}
+ <td class="text-right">
+ <div>
+ {{ d.get_formatted('amount') }}
+ </div>
+ <span class="text-muted">
+ {{ _('Rate:') }} {{ d.get_formatted('rate') }}
+ </span>
+ </td>
+ {% endif %}
+</tr>
+{% endfor %}
diff --git a/erpnext/templates/includes/footer/footer_extension.html b/erpnext/templates/includes/footer/footer_extension.html
index 23a6a34..8cf3081 100644
--- a/erpnext/templates/includes/footer/footer_extension.html
+++ b/erpnext/templates/includes/footer/footer_extension.html
@@ -1,11 +1,14 @@
{% if not hide_footer_signup %}
-<div class='input-group input-group-sm pull-right footer-subscribe'>
- <input class="form-control" type="text" id="footer-subscribe-email"
- placeholder="{{ _('Your email address') }}...">
- <span class='input-group-btn'>
- <button class="btn btn-default" type="button"
- id="footer-subscribe-button">{{ _("Get Updates") }}</button>
- </span>
+<div class="input-group">
+ <input type="text" class="form-control border-secondary"
+ id="footer-subscribe-email"
+ placeholder="{{ _('Your email address...') }}"
+ aria-label="{{ _('Your email address...') }}"
+ aria-describedby="footer-subscribe-button">
+ <div class="input-group-append">
+ <button class="btn btn-outline-secondary"
+ type="button" id="footer-subscribe-button">{{ _("Get Updates") }}</button>
+ </div>
</div>
<script>
diff --git a/erpnext/templates/includes/footer/footer_powered.html b/erpnext/templates/includes/footer/footer_powered.html
index e9d5f56..faf5e92 100644
--- a/erpnext/templates/includes/footer/footer_powered.html
+++ b/erpnext/templates/includes/footer/footer_powered.html
@@ -1,2 +1 @@
-<a href="https://erpnext.com?source=website_footer" target="_blank" class="text-muted">
- Powered by ERPNext</a>
+<a href="https://erpnext.com?source=website_footer" target="_blank" class="text-muted">Powered by ERPNext</a>
diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html
index 863d48e..2d27915 100644
--- a/erpnext/templates/includes/macros.html
+++ b/erpnext/templates/includes/macros.html
@@ -1,7 +1,4 @@
{% macro product_image_square(website_image, css_class="") %}
-{% if website_image -%}
- <meta itemprop="image" content="{{ frappe.utils.quoted(website_image) | abs_url }}"></meta>
-{%- endif %}
<div class="product-image product-image-square
{% if not website_image -%} missing-image {%- endif %} {{ css_class }}"
{% if website_image -%}
@@ -11,12 +8,8 @@
{% endmacro %}
{% macro product_image(website_image, css_class="") %}
- <div class="product-image {% if not website_image -%} missing-image {%- endif %} {{ css_class }}">
- {% if website_image -%}
- <a href="{{ frappe.utils.quoted(website_image) }}">
- <img itemprop="image" src="{{ frappe.utils.quoted(website_image) | abs_url }}" class="img-responsive">
- </a>
- {%- endif %}
+ <div class="border text-center rounded h-100 {{ css_class }}" style="overflow: hidden;">
+ <img itemprop="image" class="website-image h-100 w-100" src="{{ frappe.utils.quoted(website_image or 'no-image.jpg') | abs_url }}">
</div>
{% endmacro %}
@@ -33,3 +26,35 @@
{%- endif %}
</div>
{% endmacro %}
+
+{% macro render_homepage_section(section) %}
+
+{% if section.section_based_on == 'Custom HTML' and section.section_html %}
+ {{ section.section_html }}
+{% elif section.section_based_on == 'Cards' %}
+<section class="container my-5">
+ <h3>{{ section.name }}</h3>
+
+ <div class="row">
+ {% for card in section.section_cards %}
+ <div class="col-md-{{ section.column_value }} mb-4">
+ <div class="card h-100 justify-content-between">
+ {% if card.image %}
+ <div class="website-image-lazy" data-class="card-img-top h-100" data-src="{{ card.image }}" data-alt="{{ card.title }}"></div>
+ {% endif %}
+ <div class="card-body">
+ <h5 class="card-title">{{ card.title }}</h5>
+ <p class="card-subtitle mb-2 text-muted">{{ card.subtitle or '' }}</p>
+ <p class="card-text">{{ card.content | truncate(140, True) }}</p>
+ </div>
+ <div class="card-body flex-grow-0">
+ <a href="{{ card.route }}" class="card-link">{{ _('More details') }}</a>
+ </div>
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+</section>
+{% endif %}
+
+{% endmacro %}
\ No newline at end of file
diff --git a/erpnext/templates/includes/navbar/navbar_items.html b/erpnext/templates/includes/navbar/navbar_items.html
index faf8adf..4daf0e7 100644
--- a/erpnext/templates/includes/navbar/navbar_items.html
+++ b/erpnext/templates/includes/navbar/navbar_items.html
@@ -1,12 +1,10 @@
{% extends 'frappe/templates/includes/navbar/navbar_items.html' %}
{% block navbar_right_extension %}
- <li class="shopping-cart hidden">
- <div class="cart-icon">
- <a class="dropdown-toggle" href="#" data-toggle="dropdown" id="navLogin">
- {{ _("Cart") }} <span class="badge-wrapper" id="cart-count"></span>
- </a>
- <div id="cart-overlay" class="dropdown-menu shopping-cart-menu"></div>
- </div>
+ <li class="shopping-cart cart-icon hidden">
+ <a href="/cart" class="nav-link">
+ {{ _("Cart") }}
+ <span class="badge badge-primary" id="cart-count"></span>
+ </a>
</li>
{% endblock %}
\ No newline at end of file
diff --git a/erpnext/templates/includes/order/order_macros.html b/erpnext/templates/includes/order/order_macros.html
index c2dff8c..da4fb8c 100644
--- a/erpnext/templates/includes/order/order_macros.html
+++ b/erpnext/templates/includes/order/order_macros.html
@@ -9,7 +9,9 @@
</div>
<div class="col-xs-8 col-sm-10">
{{ d.item_code }}
- <div class="text-muted small item-description">{{ d.description }}</div>
+ <div class="text-muted small item-description">
+ {{ html2text(d.description) | truncate(140) }}
+ </div>
</div>
</div>
{% endmacro %}
@@ -25,14 +27,14 @@
{{ d.item_name|truncate(25) }}
<div class="input-group number-spinner">
<span class="input-group-btn">
- <button class="btn btn-default cart-btn" data-dir="dwn">
+ <button class="btn btn-light cart-btn" data-dir="dwn">
–</button>
</span>
<input class="form-control text-right cart-qty"
value = "{{ d.get_formatted('qty') }}"
data-item-code="{{ d.item_code }}">
<span class="input-group-btn">
- <button class="btn btn-default cart-btn" data-dir="up">
+ <button class="btn btn-light cart-btn" data-dir="up">
+</button>
</span>
</div>
diff --git a/erpnext/templates/includes/order/order_taxes.html b/erpnext/templates/includes/order/order_taxes.html
index 462d77d..1d26700 100644
--- a/erpnext/templates/includes/order/order_taxes.html
+++ b/erpnext/templates/includes/order/order_taxes.html
@@ -1,24 +1,32 @@
{% if doc.taxes %}
-<div class="row tax-net-total-row">
- <div class="col-xs-6 text-right">{{ _("Net Total") }}</div>
- <div class="col-xs-6 text-right">
- {{ doc.get_formatted("net_total") }}</div>
-</div>
+<tr>
+ <td class="text-right" colspan="2">
+ {{ _("Net Total") }}
+ </td>
+ <td class="text-right">
+ {{ doc.get_formatted("net_total") }}
+ </td>
+</tr>
{% endif %}
+
{% for d in doc.taxes %}
{% if d.base_tax_amount > 0 %}
-<div class="row tax-row">
- <div class="col-xs-6 text-right">{{ d.description }}</div>
- <div class="col-xs-6 text-right">
- {{ d.get_formatted("base_tax_amount") }}</div>
-</div>
+<tr>
+ <td class="text-right" colspan="2">
+ {{ d.description }}
+ </td>
+ <td class="text-right">
+ {{ d.get_formatted("base_tax_amount") }}
+ </td>
+</tr>
{% endif %}
{% endfor %}
-<div class="row tax-grand-total-row">
- <div class="col-xs-6 text-right text-uppercase h6 text-muted">{{ _("Grand Total") }}</div>
- <div class="col-xs-6 text-right">
- <span class="tax-grand-total bold">
- {{ doc.get_formatted("grand_total") }}
- </span>
- </div>
-</div>
+
+<tr>
+ <th class="text-right" colspan="2">
+ {{ _("Grand Total") }}
+ </th>
+ <th class="text-right">
+ {{ doc.get_formatted("grand_total") }}
+ </th>
+</tr>
diff --git a/erpnext/templates/includes/product_page.js b/erpnext/templates/includes/product_page.js
deleted file mode 100644
index ef69e20..0000000
--- a/erpnext/templates/includes/product_page.js
+++ /dev/null
@@ -1,215 +0,0 @@
-// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-// License: GNU General Public License v3. See license.txt
-
-frappe.ready(function() {
- window.item_code = $('[itemscope] [itemprop="productID"]').text().trim();
- var qty = 0;
-
- frappe.call({
- type: "POST",
- method: "erpnext.shopping_cart.product_info.get_product_info_for_website",
- args: {
- item_code: get_item_code()
- },
- callback: function(r) {
- if(r.message) {
- if(r.message.cart_settings.enabled) {
- $(".item-cart, .item-price, .item-stock").toggleClass("hide", (!!!r.message.product_info.price || !!!r.message.product_info.in_stock));
- }
- if(r.message.cart_settings.show_price) {
- $(".item-price").toggleClass("hide", false);
- }
- if(r.message.cart_settings.show_stock_availability) {
- $(".item-stock").toggleClass("hide", false);
- }
- if(r.message.product_info.price) {
- $(".item-price")
- .html(r.message.product_info.price.formatted_price_sales_uom + "<div style='font-size: small'>\
- (" + r.message.product_info.price.formatted_price + " / " + r.message.product_info.uom + ")</div>");
-
- if(r.message.product_info.in_stock==0) {
- $(".item-stock").html("<div style='color: red'> <i class='fa fa-close'></i> {{ _("Not in stock") }}</div>");
- }
- else if(r.message.product_info.in_stock==1) {
- var qty_display = "{{ _("In stock") }}";
- if (r.message.product_info.show_stock_qty) {
- qty_display += " ("+r.message.product_info.stock_qty+")";
- }
- $(".item-stock").html("<div style='color: green'>\
- <i class='fa fa-check'></i> "+qty_display+"</div>");
- }
-
- if(r.message.product_info.qty) {
- qty = r.message.product_info.qty;
- toggle_update_cart(r.message.product_info.qty);
- } else {
- toggle_update_cart(0);
- }
- }
- }
- }
- })
-
- $("#item-add-to-cart button").on("click", function() {
- frappe.provide('erpnext.shopping_cart');
-
- erpnext.shopping_cart.update_cart({
- item_code: get_item_code(),
- qty: $("#item-spinner .cart-qty").val(),
- callback: function(r) {
- if(!r.exc) {
- toggle_update_cart(1);
- qty = 1;
- }
- },
- btn: this,
- });
- });
-
- $("#item-spinner").on('click', '.number-spinner button', function () {
- var btn = $(this),
- input = btn.closest('.number-spinner').find('input'),
- oldValue = input.val().trim(),
- newVal = 0;
-
- if (btn.attr('data-dir') == 'up') {
- newVal = parseInt(oldValue) + 1;
- } else if (btn.attr('data-dir') == 'dwn') {
- if (parseInt(oldValue) > 1) {
- newVal = parseInt(oldValue) - 1;
- }
- else {
- newVal = parseInt(oldValue);
- }
- }
- input.val(newVal);
- });
-
- $("[itemscope] .item-view-attribute .form-control").on("change", function() {
- try {
- var item_code = encodeURIComponent(get_item_code());
-
- } catch(e) {
- // unable to find variant
- // then chose the closest available one
-
- var attribute = $(this).attr("data-attribute");
- var attribute_value = $(this).val();
- var item_code = find_closest_match(attribute, attribute_value);
-
- if (!item_code) {
- frappe.msgprint(__("Cannot find a matching Item. Please select some other value for {0}.", [attribute]))
- throw e;
- }
- }
-
- if (window.location.search == ("?variant=" + item_code) || window.location.search.includes(item_code)) {
- return;
- }
-
- window.location.href = window.location.pathname + "?variant=" + item_code;
- });
-
- // change the item image src when alternate images are hovered
- $(document.body).on('mouseover', '.item-alternative-image', (e) => {
- const $alternative_image = $(e.currentTarget);
- const src = $alternative_image.find('img').prop('src');
- $('.item-image img').prop('src', src);
- });
-});
-
-var toggle_update_cart = function(qty) {
- $("#item-add-to-cart").toggle(qty ? false : true);
- $("#item-update-cart")
- .toggle(qty ? true : false)
- .find("input").val(qty);
- $("#item-spinner").toggle(qty ? false : true);
-}
-
-function get_item_code() {
- var variant_info = window.variant_info;
- if(variant_info) {
- var attributes = get_selected_attributes();
- var no_of_attributes = Object.keys(attributes).length;
-
- for(var i in variant_info) {
- var variant = variant_info[i];
-
- if (variant.attributes.length < no_of_attributes) {
- // the case when variant has less attributes than template
- continue;
- }
-
- var match = true;
- for(var j in variant.attributes) {
- if(attributes[variant.attributes[j].attribute]
- != variant.attributes[j].attribute_value
- ) {
- match = false;
- break;
- }
- }
- if(match) {
- return variant.name;
- }
- }
- throw "Unable to match variant";
- } else {
- return window.item_code;
- }
-}
-
-function find_closest_match(selected_attribute, selected_attribute_value) {
- // find the closest match keeping the selected attribute in focus and get the item code
-
- var attributes = get_selected_attributes();
-
- var previous_match_score = 0;
- var previous_no_of_attributes = 0;
- var matched;
-
- var variant_info = window.variant_info;
- for(var i in variant_info) {
- var variant = variant_info[i];
- var match_score = 0;
- var has_selected_attribute = false;
-
- for(var j in variant.attributes) {
- if(attributes[variant.attributes[j].attribute]===variant.attributes[j].attribute_value) {
- match_score = match_score + 1;
-
- if (variant.attributes[j].attribute==selected_attribute && variant.attributes[j].attribute_value==selected_attribute_value) {
- has_selected_attribute = true;
- }
- }
- }
-
- if (has_selected_attribute
- && ((match_score > previous_match_score) || (match_score==previous_match_score && previous_no_of_attributes < variant.attributes.length))) {
- previous_match_score = match_score;
- matched = variant;
- previous_no_of_attributes = variant.attributes.length;
-
-
- }
- }
-
- if (matched) {
- for (var j in matched.attributes) {
- var attr = matched.attributes[j];
- $('[itemscope]')
- .find(repl('.item-view-attribute .form-control[data-attribute="%(attribute)s"]', attr))
- .val(attr.attribute_value);
- }
-
- return matched.name;
- }
-}
-
-function get_selected_attributes() {
- var attributes = {};
- $('[itemscope]').find(".item-view-attribute .form-control").each(function() {
- attributes[$(this).attr('data-attribute')] = $(this).val();
- });
- return attributes;
-}