Product Card
The <salla-product-card>
web component is used to contain content and actions about a single product in a card display mode. The component incorporates options for displaying images, hovering over buttons, product information, and more.
Example
Usage
<!-- Basic Product Card component usage -->
<salla-product-card
minimal="false"
special="true">
</salla-product-card>
This JS web component can be targeted for styling by its .s-product-card
class. Following is a complete source code for customizing this component:
.s-product-card {
&-entry {}
&-image {
&::before {
font-family: "sallaicons";
content: "\ec1f" !important;
}
}
&-vertical {}
&-horizontal {}
&-fit-height {}
&-special {}
&-full-image {}
&-minimal {}
&-donation {}
&-shadow {}
&-out-of-stock {}
&-wishlist-btn {}
&-content {
&-main {}
&-sub {}
&-footer {}
&-title {}
&-subtitle {}
&-pie {
&-svg {
circle {
transition: stroke-dashoffset 1s linear;
-webkit-transition: stroke-dashoffset 1s linear;
-moz-transition: stroke-dashoffset 1s linear;
-ms-transition: stroke-dashoffset 1s linear;
-o-transition: stroke-dashoffset 1s linear;
stroke: #E8EDF2;
stroke-width: 2px;
stroke-linecap: round;
fill: none
}
&-base {}
&-bar {
stroke: var(--color-primary) !important;
stroke-dasharray: 100 100;
stroke-dashoffset: 100
}
}
}
// for special card
&-extra-padding {}
}
&-donation-input {}
}
Properties
Property | Attribute | Description | Type | Default |
---|---|---|---|---|
Full Image | full-image |
Whether or not to show the full image on the card. | boolean |
undefined |
Hide Add Button | hide-add-btn |
Whether or not to hide the "add to cart" button. | boolean |
undefined |
Horizontal | horizontal |
Whether or not to show the product card as Horizontal card. | boolean |
undefined |
Special | special |
Whether or not to show the product card as Special card. | boolean |
undefined |
Minimal | minimal |
Whether or not to show the product card as Minimal card. | boolean |
undefined |
Product | product |
The product's data. | string |
undefined |
Shadow On Hover | shadow-on-hover |
Whether or not to support the effect of shadow on hover. | boolean |
undefined |
Show Quantity | show-quantity |
Whether or not to show quantity. | boolean |
undefined |
Slots
Theslots
makes it customizable to modify certain labels, such as add-to-cart-label
.
Slot | Description |
---|---|
"add-to-cart-label" |
The add to cart text label |
Custom Salla Product Card Component
:::tip[Note]
The above mentioned content represents the default salla-product-card
component. If you want to further customize the component and build your own product card component, please read further in this section
:::
The developer can fully customize cards which comes within the Product Lists when calling the component, by utilizing the custom-salla-product-card
phrase. That allows developers to create their own custom product cards that is returned when the salla-products-list
is called.
The following code is about defining a custom HTML element called custom-salla-product-card
that extends the functionality of a standard HTML element.
The custom element is defined using the customElements
object provided by the browser, and is given the name custom-salla-product-card
that Salla has pre-deifned. This element is then assigned to the class variable, say ProductCard
, which extends the HTMLElement
class.
The ProductCard
class has two methods: connectedCallback
and render
. The connectedCallback
method is called automatically by the browser when the element is added to the DOM, and the render
method is used to generate the content for the custom element.
:::info[Information]
📚 Learn more about Custom Components Mozilla and Salla Developer Portal, as well as Theme Raed.
//... your initiate using this.product
// this.render() is called in salla.onReady to ensure
// rendering occurs after the theme finishes loading.
salla.onReady(() => this.render());
}
render() {
//another logic if you want...
this.innerHTML = <div class="product-card">${this.product.name} - ${salla.money(this.product.price)}</div>
;
}
}
customElements.define("custom-salla-product-card", ProductCard);
</Tab>
<Tab title="Example">
```js
class ProductCard extends HTMLElement {
constructor(){
super()
}
connectedCallback(){
// Parse product data
this.product = this.product || JSON.parse(this.getAttribute('product'));
if (window.app?.status === 'ready') {
this.onReady();
} else {
document.addEventListener('theme::ready', () => this.onReady() )
}
}
onReady(){
this.fitImageHeight = salla.config.get('store.settings.product.fit_type');
salla.wishlist.event.onAdded((event, id) => this.toggleFavoriteIcon(id));
salla.wishlist.event.onRemoved((event,id) => this.toggleFavoriteIcon(id, false));
this.placeholder = salla.url.asset(salla.config.get('theme.settings.placeholder'));
this.getProps()
// Get page slug
this.source = salla.config.get("page.slug");
// If the card is in the landing page, hide the add button and show the quantity
if (this.source == "landing-page") {
this.hideAddBtn = true;
this.showQuantity = true;
}
salla.lang.onLoaded(() => {
// Language
this.remained = salla.lang.get('pages.products.remained');
this.donationAmount = salla.lang.get('pages.products.donation_amount');
this.startingPrice = salla.lang.get('pages.products.starting_price');
this.addToCart = salla.lang.get('pages.cart.add_to_cart');
this.outOfStock = salla.lang.get('pages.products.out_of_stock');
// re-render to update translations
this.render();
})
this.render()
}
initCircleBar() {
let qty = this.product.quantity,
total = this.product.quantity > 100 ? this.product.quantity * 2 : 100,
roundPercent = (qty / total) * 100,
bar = this.querySelector('.s-product-card-content-pie-svg-bar'),
strokeDashOffsetValue = 100 - roundPercent;
bar.style.strokeDashoffset = strokeDashOffsetValue;
}
toggleFavoriteIcon(id, isAdded = true) {
document.querySelectorAll('.s-product-card-wishlist-btn[data-id="' + id + '"]').forEach(btn => {
app.toggleElementClassIf(btn, 's-product-card-wishlist-added', 'not-added', () => isAdded);
app.toggleElementClassIf(btn, 'pulse-anime', 'un-favorited', () => isAdded);
});
}
formatDate(date) {
let d = new Date(date);
return `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`;
}
getProductBadge() {
if (this.product.promotion_title) {
return `<div class="s-product-card-promotion-title">${this.product.promotion_title}</div>`
}
if (this.showQuantity && this.product?.quantity) {
return `<div
class="s-product-card-quantity">${this.remained} ${salla.helpers.number(this.product?.quantity)}</div>`
}
if (this.showQuantity && this.product?.is_out_of_stock) {
return `<div class="s-product-card-out-badge">${this.outOfStock}</div>`
}
return '';
}
getPriceFormat(price) {
if (!price || price == 0) {
return salla.config.get('store.settings.product.show_price_as_dash')?'-':'';
}
return salla.money(price);
}
getProductPrice() {
let price = '';
if (this.product.is_on_sale) {
price = `<div class="s-product-card-sale-price">
<h4>${this.getPriceFormat(this.product.sale_price)}</h4>
<span>${this.getPriceFormat(this.product?.regular_price)}</span>
</div>`;
}
else if (this.product.starting_price) {
price = `<div class="s-product-card-starting-price">
<p>${this.startingPrice}</p>
<h4> ${this.getPriceFormat(this.product?.starting_price)} </h4>
</div>`
}
else{
price = `<h4 class="s-product-card-price">${this.getPriceFormat(this.product?.price)}</h4>`
}
return price;
}
getAddButtonLabel() {
if (this.product.status === 'sale' && this.product.type === 'booking') {
return salla.lang.get('pages.cart.book_now');
}
if (this.product.status === 'sale') {
return salla.lang.get('pages.cart.add_to_cart');
}
if (this.product.type !== 'donating') {
return salla.lang.get('pages.products.out_of_stock');
}
// donating
return salla.lang.get('pages.products.donation_exceed');
}
getProps(){
/**
* Horizontal card.
*/
this.horizontal = this.hasAttribute('horizontal');
/**
* Support shadow on hover.
*/
this.shadowOnHover = this.hasAttribute('shadowOnHover');
/**
* Hide add to cart button.
*/
this.hideAddBtn = this.hasAttribute('hideAddBtn');
/**
* Full image card.
*/
this.fullImage = this.hasAttribute('fullImage');
/**
* Minimal card.
*/
this.minimal = this.hasAttribute('minimal');
/**
* Special card.
*/
this.isSpecial = this.hasAttribute('isSpecial');
/**
* Show quantity.
*/
this.showQuantity = this.hasAttribute('showQuantity');
}
render(){
this.classList.add('s-product-card-entry');
this.setAttribute('id', this.product.id);
!this.horizontal && !this.fullImage && !this.minimal? this.classList.add('s-product-card-vertical') : '';
this.horizontal && !this.fullImage && !this.minimal? this.classList.add('s-product-card-horizontal') : '';
this.fitImageHeight && !this.isSpecial && !this.fullImage && !this.minimal? this.classList.add('s-product-card-fit-height') : '';
this.isSpecial? this.classList.add('s-product-card-special') : '';
this.fullImage? this.classList.add('s-product-card-full-image') : '';
this.minimal? this.classList.add('s-product-card-minimal') : '';
this.product?.donation? this.classList.add('s-product-card-donation') : '';
this.shadowOnHover? this.classList.add('s-product-card-shadow') : '';
this.product?.is_out_of_stock? this.classList.add('s-product-card-out-of-stock') : '';
this.innerHTML = `
<div class="${!this.fullImage ? 's-product-card-image' : 's-product-card-image-full'}">
<a href="${this.product?.url}">
<img class="s-product-card-image-${salla.url.is_placeholder(this.product?.image?.url)
? 'contain'
: this.fitImageHeight
? this.fitImageHeight
: 'cover'} lazy"
src=${this.placeholder}
alt=${this.product?.image?.alt}
data-src=${this.product?.image?.url || this.product?.thumbnail}
/>
${!this.fullImage && !this.minimal ? this.getProductBadge() : ''}
</a>
${this.fullImage ? `<a href="${this.product?.url}" class="s-product-card-overlay"></a>`:''}
${!this.horizontal && !this.fullImage ?
`<salla-button
shape="icon"
fill="outline"
color="light"
name="product-name-${this.product.id}"
aria-label="Add or remove to wishlist"
class="s-product-card-wishlist-btn animated "
onclick="salla.wishlist.toggle(${this.product.id})"
data-id="${this.product.id}">
<i class="sicon-heart"></i>
</salla-button>` : ``
}
</div>
<div class="s-product-card-content">
${this.isSpecial && this.product?.quantity ?
`<div class="s-product-card-content-pie">
<span>
<b>${salla.helpers.number(this.product?.quantity)}</b>
${this.remained}
</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -1 36 34" class="s-product-card-content-pie-svg">
<circle cx="16" cy="16" r="15.9155" class="s-product-card-content-pie-svg-base" />
<circle cx="16" cy="16" r="15.9155" class="s-product-card-content-pie-svg-bar" />
</svg>
</div>`
: ``}
<div class="s-product-card-content-main ${this.isSpecial ? 's-product-card-content-extra-padding' : ''}">
<h3 class="s-product-card-content-title">
<a href="${this.product?.url}">${this.product?.name}</a>
</h3>
${this.product?.subtitle && !this.minimal ?
`<p class="s-product-card-content-subtitle">${this.product?.subtitle}</p>`
: ``}
</div>
${this.product?.donation && !this.minimal && !this.fullImage ?
`[<salla-progress-bar donation=${this.product?.donation} />
<div class="s-product-card-donation-input">
${this.product?.donation?.can_donate ?
`[<label htmlFor="donation-amount">${this.donationAmount} <span>*</span></label>,
<input
type="text"
onInput="${e => {
salla.helpers.inputDigitsOnly(e.target);
this.addBtn.donatingAmount = (e.target).value;
}}"
id="donation-amount"
name="donating_amount"
class="s-form-control"
placeholder="${this.donationAmount}" />]`
: ``}
</div>]`
: ''}
<div class="s-product-card-content-sub ${this.isSpecial ? 's-product-card-content-extra-padding' : ''}">
${this.getProductPrice()}
${this.product?.rating?.stars && !this.minimal ?
`<div class="s-product-card-rating">
<i class="sicon-star2"></i>
<span>${this.product.rating.stars}</span>
</div>`
: ``}
</div>
${this.isSpecial && this.product.discount_ends
? `<salla-count-down date="${this.formatDate(this.product.discount_ends)}" end-of-day=${true} boxed=${true}
labeled=${true} />`
: ``}
${!this.hideAddBtn ?
`<div class="s-product-card-content-footer gap-2">
<salla-add-product-button fill="outline" width="wide"
product-id="${this.product.id}"
product-status="${this.product.status}"
product-type="${this.product.type}">
${this.product.status == 'sale' ?
`<i class="text-[16px] sicon-${ this.product.type == 'booking' ? 'calendar-time' : 'shopping-bag'}"></i>` : ``
}
${this.product.add_to_cart_label ? this.product.add_to_cart_label : this.getAddButtonLabel() }
</salla-add-product-button>
${this.horizontal || this.fullImage ?
`<salla-button
shape="icon"
fill="outline"
color="light"
id="card-wishlist-btn-${this.product.id}-horizontal"
aria-label="Add or remove to wishlist"
class="s-product-card-wishlist-btn animated"
onclick="salla.wishlist.toggle(${this.product.id})"
data-id="${this.product.id}">
<i class="sicon-heart"></i>
</salla-button>`
: ``}
</div>`
: ``}
</div>
`
// re-init favorite icon
if (!salla.config.isGuest()){
salla.storage.get('salla::wishlist', []).forEach(id => this.toggleFavoriteIcon(id));
}
document.lazyLoadInstance?.update(this.querySelectorAll('.lazy'));
if (this.product?.quantity && this.isSpecial) {
this.initCircleBar();
}
}
}
customElements.define('custom-salla-product-card', ProductCard);