Item Alternate Image Features in e-commerce site. (#15044)
* [ADD] Added Item Alternate Image Features in e-commerce site.
* [IMP] Improved Code for Item Alternate Image Feature.
* [IMP] Improved code for Item alternative image functionality.
* Remove .css file and make a build for erpnext-web.css
* Cleanup styling for item alternate image, also add delegated handler
* Spaces -> Tabs
* Spaces -> Tabs in item.html
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 999ece9..7bf948e 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -21,7 +21,7 @@
app_include_js = "assets/js/erpnext.min.js"
app_include_css = "assets/css/erpnext.css"
web_include_js = "assets/js/erpnext-web.min.js"
-web_include_css = "assets/erpnext/css/website.css"
+web_include_css = "assets/css/erpnext-web.css"
doctype_js = {
"Communication": "public/js/communication.js",
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
index ed4ebab..75809ce 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -1,49 +1,52 @@
{
- "css/erpnext.css": [
- "public/less/erpnext.less",
- "public/less/hub.less"
- ],
- "js/erpnext-web.min.js": [
- "public/js/website_utils.js",
- "public/js/shopping_cart.js"
- ],
- "js/erpnext.min.js": [
- "public/js/conf.js",
- "public/js/utils.js",
- "public/js/queries.js",
- "public/js/sms_manager.js",
- "public/js/utils/party.js",
- "public/js/templates/address_list.html",
- "public/js/templates/contact_list.html",
- "public/js/controllers/stock_controller.js",
- "public/js/payment/payments.js",
- "public/js/controllers/taxes_and_totals.js",
- "public/js/controllers/transaction.js",
- "public/js/pos/pos.html",
- "public/js/pos/pos_bill_item.html",
- "public/js/pos/pos_bill_item_new.html",
- "public/js/pos/pos_selected_item.html",
- "public/js/pos/pos_item.html",
- "public/js/pos/pos_tax_row.html",
- "public/js/pos/customer_toolbar.html",
- "public/js/pos/pos_invoice_list.html",
- "public/js/payment/pos_payment.html",
- "public/js/payment/payment_details.html",
- "public/js/templates/item_selector.html",
- "public/js/templates/employees_to_mark_attendance.html",
- "public/js/utils/item_selector.js",
- "public/js/help_links.js",
- "public/js/agriculture/ternary_plot.js",
- "public/js/templates/item_quick_entry.html",
- "public/js/utils/item_quick_entry.js",
- "public/js/utils/customer_quick_entry.js",
- "public/js/education/student_button.html",
- "public/js/education/assessment_result_tool.html",
- "public/js/hub/hub_factory.js"
- ],
- "js/item-dashboard.min.js": [
- "stock/dashboard/item_dashboard.html",
- "stock/dashboard/item_dashboard_list.html",
- "stock/dashboard/item_dashboard.js"
- ]
+ "css/erpnext.css": [
+ "public/less/erpnext.less",
+ "public/less/hub.less"
+ ],
+ "js/erpnext-web.min.js": [
+ "public/js/website_utils.js",
+ "public/js/shopping_cart.js"
+ ],
+ "css/erpnext-web.css": [
+ "public/less/website.less"
+ ],
+ "js/erpnext.min.js": [
+ "public/js/conf.js",
+ "public/js/utils.js",
+ "public/js/queries.js",
+ "public/js/sms_manager.js",
+ "public/js/utils/party.js",
+ "public/js/templates/address_list.html",
+ "public/js/templates/contact_list.html",
+ "public/js/controllers/stock_controller.js",
+ "public/js/payment/payments.js",
+ "public/js/controllers/taxes_and_totals.js",
+ "public/js/controllers/transaction.js",
+ "public/js/pos/pos.html",
+ "public/js/pos/pos_bill_item.html",
+ "public/js/pos/pos_bill_item_new.html",
+ "public/js/pos/pos_selected_item.html",
+ "public/js/pos/pos_item.html",
+ "public/js/pos/pos_tax_row.html",
+ "public/js/pos/customer_toolbar.html",
+ "public/js/pos/pos_invoice_list.html",
+ "public/js/payment/pos_payment.html",
+ "public/js/payment/payment_details.html",
+ "public/js/templates/item_selector.html",
+ "public/js/templates/employees_to_mark_attendance.html",
+ "public/js/utils/item_selector.js",
+ "public/js/help_links.js",
+ "public/js/agriculture/ternary_plot.js",
+ "public/js/templates/item_quick_entry.html",
+ "public/js/utils/item_quick_entry.js",
+ "public/js/utils/customer_quick_entry.js",
+ "public/js/education/student_button.html",
+ "public/js/education/assessment_result_tool.html",
+ "public/js/hub/hub_factory.js"
+ ],
+ "js/item-dashboard.min.js": [
+ "stock/dashboard/item_dashboard.html",
+ "stock/dashboard/item_dashboard_list.html",
+ "stock/dashboard/item_dashboard.js"
+ ]
}
diff --git a/erpnext/public/css/website.css b/erpnext/public/css/website.css
deleted file mode 100644
index 9ac9aac..0000000
--- a/erpnext/public/css/website.css
+++ /dev/null
@@ -1,270 +0,0 @@
-.web-long-description {
- font-size: 18px;
- line-height: 200%;
-}
-.web-page-content {
- margin-bottom: 30px;
-}
-.item-stock {
- margin-bottom: 10px !important;
-}
-.product-link {
- display: block;
- text-align: center;
-}
-@media (max-width: 767px) {
- .product-image {
- height: 0px;
- padding: 0px 0px 100%;
- overflow: hidden;
- }
-}
-.product-image-square {
- width: 100%;
- height: 0;
- padding: 50% 0px;
- background-size: cover;
- background-repeat: no-repeat;
- background-position: center top;
-}
-.product-image.missing-image {
- width: 100%;
- height: 0;
- padding: 50% 0px;
- background-size: cover;
- background-repeat: no-repeat;
- background-position: center top;
- position: relative;
- background-color: #EBEFF2;
-}
-.product-image.missing-image .octicon {
- font-size: 32px;
- color: #d1d8dd;
-}
-.product-search {
- margin-bottom: 15px;
-}
-@media (max-width: 767px) {
- .product-search {
- width: 100%;
- }
-}
-.borderless td,
-.borderless th {
- border-bottom: 1px solid #EBEFF2;
- padding-left: 0px !important;
- line-height: 1.8em !important;
-}
-.item-desc {
- border-top: 2px solid #EBEFF2;
- padding-top: 10px;
-}
-.featured-products {
- border-top: 1px solid #EBEFF2;
-}
-.transaction-list-item .indicator {
- font-weight: inherit;
- color: #8D99A6;
-}
-.transaction-list-item .transaction-time {
- margin-top: 5px;
-}
-.transaction-subheading .indicator {
- font-weight: inherit;
- color: #8D99A6;
-}
-.order-container {
- margin: 50px 0px;
-}
-.order-container .order-item-header .h6 {
- padding: 7px 15px;
-}
-.order-container .order-items {
- margin: 30px 0px 0px;
-}
-.order-container .order-item-table {
- margin: 0px -15px;
-}
-.order-container .order-item-header {
- border-bottom: 1px solid #d1d8dd;
-}
-.order-container .order-image-col {
- padding-right: 0px;
-}
-.order-container .order-image {
- max-width: 55px;
- max-height: 55px;
- margin-top: -5px;
-}
-.order-container .order-taxes {
- margin-top: 30px;
-}
-.order-container .order-taxes .row {
- margin-top: 15px;
-}
-.order-container .tax-grand-total-row {
- padding-top: 15px;
- padding-bottom: 30px;
-}
-.order-container .tax-grand-total {
- display: inline-block;
- font-size: 16px;
- font-weight: bold;
- margin-top: 5px;
-}
-.cart-container {
- margin: 50px 0px;
-}
-.cart-container .checkout {
- margin-bottom: 15px;
-}
-.cart-container .cart-item-header .h6 {
- padding: 7px 15px;
-}
-.cart-container .cart-items {
- margin: 30px 0px 0px;
-}
-.cart-container .cart-item-table {
- margin: 0px -15px;
-}
-.cart-container .cart-item-header {
- border-bottom: 1px solid #d1d8dd;
-}
-.cart-container .cart-image-col {
- padding-right: 0px;
-}
-.cart-container .cart-image {
- max-width: 55px;
- max-height: 55px;
- margin-top: -5px;
-}
-.cart-container .cart-taxes {
- margin-top: 30px;
-}
-.cart-container .cart-taxes .row {
- margin-top: 15px;
-}
-.cart-container .tax-grand-total-row {
- border-top: 1px solid #d1d8dd;
- padding-top: 15px;
-}
-.cart-container .cart-addresses {
- margin-top: 50px;
-}
-.cart-items-dropdown .cart-dropdown,
-.item_name_dropdown {
- display: none;
-}
-.cart-dropdown-container {
- width: 400px;
- padding: 15px;
-}
-.cart-dropdown-container .item-price {
- display: block !important;
- padding-bottom: 10px;
-}
-.cart-dropdown-container .cart-item-header {
- border-bottom: 1px solid #d1d8dd;
-}
-.cart-dropdown-container .cart-items-dropdown {
- max-height: 350px;
-}
-.cart-dropdown-container .cart-items-dropdown .cart-dropdown {
- display: block;
- margin-top: 15px;
-}
-.cart-dropdown-container .item_name_dropdown {
- display: block;
-}
-.cart-dropdown-container .item-description,
-.cart-dropdown-container .cart-items .checkout,
-.cart-dropdown-container .item_name_and_description {
- display: none;
-}
-.cart-dropdown-container .checkout-btn {
- padding-bottom: 25px;
-}
-.cart-dropdown-container .col-name-description {
- margin-bottom: 8px;
-}
-.number-spinner {
- width: 100px;
- margin-top: 5px;
-}
-.cart-btn {
- border-color: #ccc;
-}
-.cart-qty {
- text-align: center;
-}
-.product-list-link .row {
- border-bottom: 1px solid #EBEFF2;
-}
-.product-list-link .row:hover {
- background-color: #fafbfc;
-}
-.product-list-link .row > div {
- padding-top: 15px;
- padding-bottom: 15px;
-}
-.product-list-link:first-child .row {
- border-top: 1px solid #EBEFF2;
-}
-.item-group-nav-buttons {
- margin-top: 15px;
-}
-.footer-subscribe .btn-default {
- background-color: transparent;
- border: 1px solid #d1d8dd;
-}
-@media (min-width: 992px) {
- .footer-subscribe {
- max-width: 350px;
- }
-}
-.item-group-content {
- margin-top: 30px;
-}
-.product-image-img {
- border: 1px solid #EBEFF2;
- border-radius: 3px;
-}
-.product-text {
- border-top: 1px solid #EBEFF2;
- padding: 15px;
- word-wrap: break-word;
- height: 75px;
- display: block;
- /* Fallback for non-webkit */
- display: -webkit-box;
- max-width: 100%;
- margin: 0 auto;
- -webkit-line-clamp: 3;
- -webkit-box-orient: vertical;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-.product-image-wrapper {
- padding-bottom: 40px;
-}
-.duration-bar {
- display: inline-block;
- color: white;
- background: #8FD288;
- padding: 3px;
-}
-.duration-invisible {
- visibility: hidden;
-}
-.duration-value {
- float: right;
-}
-.bar-outer-text {
- color: #8FD288;
- background: none;
- float: none;
- border: none;
-}
-.bom-spec {
- margin-bottom: 20px;
-}
diff --git a/erpnext/public/less/website.less b/erpnext/public/less/website.less
index 14d033c..448bd3b 100644
--- a/erpnext/public/less/website.less
+++ b/erpnext/public/less/website.less
@@ -1,7 +1,4 @@
-@border-color: #d1d8dd;
-@light-border-color: #EBEFF2;
-@text-muted: #8D99A6;
-@light-bg: #fafbfc;
+@import "variables.less";
.web-long-description {
font-size: 18px;
@@ -21,6 +18,12 @@
text-align: center;
}
+
+.product-image img {
+ max-height: 500px;
+ margin: 0 auto;
+}
+
@media (max-width: 767px) {
.product-image {
height: 0px;
@@ -221,7 +224,7 @@
.cart-items-dropdown .cart-dropdown {
display:block;
- margin-top:15px;
+ margin-top:15px;
}
.item_name_dropdown {
@@ -342,4 +345,14 @@
.bom-spec {
margin-bottom: 20px;
-}
\ No newline at end of file
+}
+
+// For Item Alternate Image
+.item-alternative-image {
+ padding: 5px;
+ margin-bottom: 5px;
+
+ &:hover {
+ border-color: @brand-primary;
+ }
+}
diff --git a/erpnext/templates/generators/item.html b/erpnext/templates/generators/item.html
index 0e09f58..3220722 100644
--- a/erpnext/templates/generators/item.html
+++ b/erpnext/templates/generators/item.html
@@ -8,108 +8,130 @@
{% block page_content %}
{% from "erpnext/templates/includes/macros.html" import product_image %}
-<div class="item-content" style="margin-top:20px;">
+<div class="item-content">
<div class="product-page-content" itemscope itemtype="http://schema.org/Product">
<div class="row">
- <div class="col-sm-6">
+ <div class="row">
{% if slideshow %}
- {% include "templates/includes/slideshow.html" %}
- {% else %}
- {{ product_image(website_image, "product-full-image") }}
- {% endif %}
- </div>
- <div class="col-sm-6" style="padding-left:20px;">
- <h2 itemprop="name" style="margin-top: 0px;">{{ item_name }}</h2>
-
- <p class="text-muted">
- {{ _("Item Code") }}: <span itemprop="productID">{{ variant and variant.name or name }}</span></p>
- <br>
- <div class="item-attribute-selectors">
- {% if has_variants %}
- {% for d in attributes %}
- {% if attribute_values[d.attribute] -%}
- <div class="item-view-attribute {% if (attribute_values[d.attribute] | len)==1 -%} hidden {%- endif %}"
- style="margin-bottom: 10px;">
- <h6 class="text-muted">{{ _(d.attribute) }}</h6>
- <select class="form-control"
- style="max-width: 140px"
- data-attribute="{{ d.attribute }}">
- {% for value in attribute_values[d.attribute] %}
- <option value="{{ value }}"
- {% if selected_attributes and selected_attributes[d.attribute]==value -%}
- selected
- {%- elif disabled_attributes and value in disabled_attributes.get(d.attribute, []) -%}
- disabled
- {%- endif %}>
- {{ _(value) }}
- </option>
- {% endfor %}
- </select>
+ {% set slideshow_items = frappe.get_list(doctype="Website Slideshow Item", fields=["image"], filters={ "parent": doc.slideshow }) %}
+ <div class="col-md-1">
+ {%- for slideshow_item in slideshow_items -%}
+ {% set image_src = slideshow_item['image'] %}
+ {% if image_src %}
+ <div class="item-alternative-image border">
+ <img src="{{ image_src }}" height="50" weight="50" />
</div>
- {%- endif %}
- {% endfor %}
{% endif %}
+ {% endfor %}
</div>
- <br>
- <div style="min-height: 100px; margin: 10px 0;">
- <div itemprop="offers" itemscope itemtype="http://schema.org/Offer">
- <h4 class="item-price hide" itemprop="price"></h4>
- <div class="item-stock hide" itemprop="availability"></div>
+ <div class="col-md-5">
+ <div class="item-image">
+ {% set first_image = slideshow_items[0]['image'] %}
+ {{ product_image(first_image, "product-full-image") }}
</div>
- <div class="item-cart hide">
- <div id="item-spinner">
- <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="1">
- <span class="input-group-btn">
- <button class="btn btn-default cart-btn" data-dir="up" style="margin-left:-2px;">
+ </div>
+ {% else %}
+ <div class="col-md-6">
+ {{ product_image(website_image, "product-full-image") }}
+ </div>
+ {% endif %}
+ <div class="col-sm-6">
+ <h2 itemprop="name">{{ item_name }}</h2>
+ <p class="text-muted">
+ {{ _("Item Code") }}: <span itemprop="productID">{{ variant and variant.name or name }}</span>
+ </p>
+ <br>
+ <div class="item-attribute-selectors">
+ {% if has_variants and attributes %}
+ {{ attributes }}
+ {#
+
+ {% for d in attributes %}
+ {% if attribute_values[d.attribute] -%}
+ <div class="item-view-attribute {% if (attribute_values[d.attribute] | len)==1 -%} hidden {%- endif %}"
+ style="margin-bottom: 10px;">
+ <h6 class="text-muted">{{ _(d.attribute) }}</h6>
+ <select class="form-control"
+ style="max-width: 140px"
+ data-attribute="{{ d.attribute }}">
+ {% for value in attribute_values[d.attribute] %}
+ <option value="{{ value }}"
+ {% if selected_attributes and selected_attributes[d.attribute]==value -%}
+ selected
+ {%- elif disabled_attributes and value in disabled_attributes.get(d.attribute, []) -%}
+ disabled
+ {%- endif %}>
+ {{ _(value) }}
+ </option>
+ {% endfor %}
+ </select>
+ </div>
+ {%- endif %}
+ {% endfor %}
+
+ #}
+ {% endif %}
+ </div>
+ <br>
+ <div>
+ <div itemprop="offers" itemscope itemtype="http://schema.org/Offer">
+ <h4 class="item-price hide" itemprop="price"></h4>
+ <div class="item-stock hide" itemprop="availability"></div>
+ </div>
+ <div class="item-cart hide">
+ <div id="item-spinner">
+ <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="1">
+ <span class="input-group-btn">
+ <button class="btn btn-default cart-btn" data-dir="up" style="margin-left:-2px;">
+</button>
- </span>
- </div>
- </span>
- </div>
- <div id="item-add-to-cart">
- <button class="btn btn-primary btn-sm">
- {{ _("Add to Cart") }}</button>
- </div>
- <div id="item-update-cart" style="display: none;">
- <a href="/cart" class='btn btn-sm btn-default'>
- <i class='octicon octicon-check'></i>
- {{ _("View in Cart") }}</a>
+ </span>
+ </div>
+ </span>
+ </div>
+ <div id="item-add-to-cart">
+ <button class="btn btn-primary btn-sm">
+ {{ _("Add to Cart") }}</button>
+ </div>
+ <div id="item-update-cart" style="display: none;">
+ <a href="/cart" class='btn btn-sm btn-default'>
+ <i class='octicon octicon-check'></i>
+ {{ _("View in Cart") }}</a>
+ </div>
</div>
</div>
</div>
</div>
- </div>
- <div class="row item-website-description" style="margin-top:30px; margin-bottom:20px">
- <div class="col-md-12">
- <div class="h6 text-uppercase">{{ _("Description") }}</div>
- <div itemprop="description" class="item-desc">
- {{ web_long_description or description or _("No description given") }}
- </div>
- </div>
- </div>
-
- {% if website_specifications -%}
- <div class="row item-website-specification" style="margin-top: 40px">
- <div class="col-md-12">
- <div class="h6 text-uppercase">{{ _("Specifications") }}</div>
-
- <table class="table borderless" style="width: 100%">
- {% for d in website_specifications -%}
- <tr>
- <td class="text-muted" style="width: 30%;">{{ d.label }}</td>
- <td>{{ d.description }}</td>
- </tr>
- {%- endfor %}
- </table>
+ <div class="row item-website-description margin-top">
+ <div class="col-md-12">
+ <div class="h6 text-uppercase">{{ _("Description") }}</div>
+ <div itemprop="description" class="item-desc">
+ {{ web_long_description or description or _("No description given") }}
+ </div>
+ </div>
</div>
+ {% if website_specifications -%}
+ <div class="row item-website-specification margin-top">
+ <div class="col-md-12">
+ <div class="h6 text-uppercase">{{ _("Specifications") }}</div>
+
+ <table class="table">
+ {% for d in website_specifications -%}
+ <tr>
+ <td class="text-muted" style="width: 30%;">{{ d.label }}</td>
+ <td>{{ d.description }}</td>
+ </tr>
+ {%- endfor %}
+ </table>
+ </div>
+ </div>
+ {%- endif %}
</div>
- {%- endif %}
</div>
</div>
<script>
diff --git a/erpnext/templates/includes/product_page.js b/erpnext/templates/includes/product_page.js
index 798a6cf..ef69e20 100644
--- a/erpnext/templates/includes/product_page.js
+++ b/erpnext/templates/includes/product_page.js
@@ -109,6 +109,13 @@
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) {