Salla Docs
Merchant API
Partner APIs
Storefront
Salla CLI
Salla Docs
    Light Mode

    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

    Product Card

    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.

    ```js class ProductCard extends HTMLElement { connectedCallback() { try { this.product = this.product || JSON.parse(this.getAttribute("product")); } catch (e) { console.error("could not parse product data"); return; }
    //... 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);
    
    Last modified: 3 months ago