Customize Magento page builder product slider
Customize Magento page builder product slider
Oct 7, 2022 12:00 AM (6 months ago)

# Customize Magento page builder product slider

Recently I had to modify page builder product slider and add extra settings for how much slides it can show on desktop/tablet/mobile devices. So after short review, to achieve this you need to change quite a bit of files. I created a module for this, but I will list all the fiels that need to be updated.

So gole was to add extra fields to PB product slider, for each of the view. Most of the changes where done in XML, as this is where all configuration for PB components is placed.

This chnages in XML-s will help us to add extra 3 fields components configuration. Filds that we need are:

  • Slides to show Desktop view

  • Slides to show Tablet view

  • Slides to show Mobile view

vendor/magento/module-page-builder/view/adminhtml/pagebuilder/content_type/products.xml
vendor/magento/module-page-builder/view/adminhtml/ui_component/pagebuilder_products_carousel_form.xml

products.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/content_type.xsd">
    <type name="products">
        <appearances>
            <appearance name="carousel"
                        preview_template="Magento_PageBuilder/content-type/products/grid/preview"
                        master_template="Magento_PageBuilder/content-type/products/grid/master"
                        reader="Magento_PageBuilder/js/master-format/read/configurable">
                <form>pagebuilder_products_carousel_form</form>
                <elements>
                    <element name="main">
                        <attribute name="slides_to_show_desktop" source="data-slides-to-show-desktop"/>
                        <attribute name="slides_to_show_tablet" source="data-slides-to-show-tablet"/>
                        <attribute name="slides_to_show_mobile" source="data-slides-to-show-mobile"/>
                    </element>
                </elements>
            </appearance>
        </appearances>
    </type>
</config>


pagebuilder_products_carousel_form.xml

<?xml version="1.0" encoding="UTF-8"?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd" extends="pagebuilder_products_form">
    <fieldset name="settings" sortOrder="30">
        <field name="slides_to_show_desktop" sortOrder="10" formElement="input">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="default" xsi:type="number">1</item>
                </item>
            </argument>
            <settings>
                <dataType>number</dataType>
                <label translate="true">Slides to show Desktop view</label>
                <additionalClasses>
                    <class name="admin__field-small">true</class>
                </additionalClasses>
                <dataScope>slides_to_show_desktop</dataScope>
                <validation>
                    <rule name="required-entry" xsi:type="boolean">true</rule>
                </validation>
            </settings>
        </field>
        <field name="slides_to_show_tablet" sortOrder="20" formElement="input">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="default" xsi:type="number">1</item>
                </item>
            </argument>
            <settings>
                <dataType>number</dataType>
                <label translate="true">Slides to show Tablet view</label>
                <additionalClasses>
                    <class name="admin__field-small">true</class>
                </additionalClasses>
                <dataScope>slides_to_show_tablet</dataScope>
                <validation>
                    <rule name="required-entry" xsi:type="boolean">true</rule>
                </validation>
            </settings>
        </field>
        <field name="slides_to_show_mobile" sortOrder="30" formElement="input">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="default" xsi:type="number">1</item>
                </item>
            </argument>
            <settings>
                <dataType>number</dataType>
                <label translate="true">Slides to show Mobile view</label>
                <additionalClasses>
                    <class name="admin__field-small">true</class>
                </additionalClasses>
                <dataScope>slides_to_show_mobile</dataScope>
                <validation>
                    <rule name="required-entry" xsi:type="boolean">true</rule>
                </validation>
            </settings>
        </field>
    </fieldset>
</form>

Now the only thing left is to override default component JS as mixin will not do here. So in module we can create requirejs-config.js

[Vendor] / [Module] / view / base / requirejs - config.js;

and config for override

var config = {
  map: {
    "*": {
      "Magento_PageBuilder/js/content-type/products/appearance/carousel/widget":
        "[Vendor]_[Module]/js/content-type/slider/appearance/default/product-carousel-override",
    },
  },
};

and in product-carousel-override.js

define([
  "jquery",
  "underscore",
  "matchMedia",
  "Magento_PageBuilder/js/utils/breakpoints",
  "Magento_PageBuilder/js/events",
  "slick",
], function ($, _, mediaCheck, breakpointsUtils, events) {
  "use strict";

  /**
   * Build slick
   *
   * @param {jQuery} $carouselElement
   * @param {Object} config
   */
  function buildSlick($carouselElement, config) {
    /**
     * Prevent each slick slider from being initialized more than once which could throw an error.
     */
    if ($carouselElement.hasClass("slick-initialized")) {
      $carouselElement.slick("unslick");
    }

    config.slidesToScroll = config.slidesToShow;
    $carouselElement.slick(config);
  }

  /**
   * Initialize slider.
   *
   * @param {jQuery} $element
   * @param {Object} slickConfig
   * @param {Object} breakpoint
   */
  function initSlider($element, slickConfig, breakpoint) {
    var productCount = $element.find(".product-item").length,
      $carouselElement = $($element.children()),
      centerModeClass = "center-mode",
      carouselMode = $element.data("carousel-mode"),
      currentBreakpoint = "",
      slidesToShow = breakpoint.options.products[carouselMode]
        ? breakpoint.options.products[carouselMode].slidesToShow
        : breakpoint.options.products.default.slidesToShow;

    slickConfig.slidesToShow = parseFloat(slidesToShow);
    currentBreakpoint = breakpointsUtils.buildMedia(breakpoint.conditions);

    if (
      $element.data("slidesToShowDesktop") &&
      currentBreakpoint == "(min-width: 1024px)"
    ) {
      slickConfig.slidesToShow = parseFloat(
        $element.data("slidesToShowDesktop")
      );
    }
    if (
      ($element.data("slidesToShowTablet") &&
        currentBreakpoint == "(max-width: 1024px) and (min-width: 768px)") ||
      currentBreakpoint == "(max-width: 768px) and (min-width: 640px)"
    ) {
      slickConfig.slidesToShow = parseFloat(
        $element.data("slidesToShowTablet")
      );
    }
    if (
      $element.data("slidesToShowMobile") &&
      currentBreakpoint == "(max-width: 640px)"
    ) {
      slickConfig.slidesToShow = parseFloat(
        $element.data("slidesToShowMobile")
      );
    }

    if (
      carouselMode === "continuous" &&
      productCount > slickConfig.slidesToShow
    ) {
      $element.addClass(centerModeClass);
      slickConfig.centerPadding = $element.data("center-padding");
      slickConfig.centerMode = true;
    } else {
      $element.removeClass(centerModeClass);
      slickConfig.infinite = $element.data("infinite-loop");
    }

    buildSlick($carouselElement, slickConfig);
  }

  return function (config, element) {
    var $element = $(element),
      $carouselElement = $($element.children()),
      currentViewport = config.currentViewport,
      currentBreakpoint = config.breakpoints[currentViewport],
      slickConfig = {
        autoplay: $element.data("autoplay"),
        autoplaySpeed: $element.data("autoplay-speed") || 0,
        arrows: $element.data("show-arrows"),
        dots: $element.data("show-dots"),
      };

    _.each(config.breakpoints, function (breakpoint) {
      mediaCheck({
        media: breakpointsUtils.buildMedia(breakpoint.conditions),

        /** @inheritdoc */
        entry: function () {
          initSlider($element, slickConfig, breakpoint);
        },
      });
    });

    //initialize slider when content type is added in mobile viewport
    if (currentViewport === "mobile") {
      initSlider($element, slickConfig, currentBreakpoint);
    }

    // Redraw slide after content type gets redrawn
    events.on("contentType:redrawAfter", function (args) {
      if ($carouselElement.closest(args.element).length) {
        $carouselElement.slick("setPosition");
      }
    });

    events.on("stage:viewportChangeAfter", function (args) {
      var breakpoint = config.breakpoints[args.viewport];

      initSlider($element, slickConfig, breakpoint);
    });
  };
});