Adding cursor pagination to Novum

This guide will walk you through adding cursor pagination in Novum.

Instead of relying on session storage for product information for actions like adding, swapping, updating or updating product and rendering upsells, the new code fetches products when needed.

In the add/swap product flow, pagination and product search will be dynamic, so you always have the latest product information. This is especially beneficial for stores with a large number of products. Each section is separated by file. In each section, the old code is displayed, followed by an example of the updated code.

📘

Platform:

  • Shopify Checkout Integration
  • Recharge Checkout on Shopify
  • BigCommerce Integration
  • Recharge Checkout on BigCommerce

Before you start

  • Pagination is available natively on theme engine version 3.0.0. Upgrade the customer portal to the latest version to take advantage of the latest bug fixes and updates. See the Recharge changelog for more details.
  • You must have a Recharge Pro account to implement this solution.
  • If you’re using a version of Novum made before July 12th, 2021 and have made prior customizations, you will need to update your code manually.
  • Follow the instructions in Customizing the Novum customer portal theme to access the theme code.

_edit-subscription.js

In the function swapProductHandler, is not relying on session storage to get information about the product, but dynamically retrieving it. It also sets some properties for pagination.

Old code

function swapProductHandler(event, source = "cancellation-flow") {  
   event.preventDefault();

   ReCharge.Novum.backBtn.setAttribute("style", "visibility: visible");  
   // remove event where backBtn would lead to swap search that was placed in function swapProductDetailsHandler  
   ReCharge.Novum.backBtn.removeEventListener("click", swapProductHandler, false);  
   ReCharge.Novum.backBtn.removeEventListener("click", cancelSubscriptionFlow, false);

   // add event where backBtn points to edit product  
   if (source === "cancellation-flow") {  
       ReCharge.Novum.backBtn.addEventListener("click", cancelSubscriptionFlow);  
   } else {  
       ReCharge.Novum.backBtn.addEventListener("click", editProduct);  
   }

   ReCharge.Novum.sidebarHeading.innerHTML = `{{ 'cp_select_product' | t }}`;  
   ReCharge.Novum.sidebarContent.innerHTML = `{% include '_render_products.html' %}`;  
   let products = JSON.parse(sessionStorage.getItem("rc_products"));

   let firstPageProducts = [...products].filter(prod =>  
       prod.subscription_defaults &&  
       prod.subscription_defaults.charge_interval_frequency == prod.subscription_defaults.order_interval_frequency_options[0]  
   );

   ReCharge.Novum.Helpers.renderProducts(firstPageProducts.slice(0, 6), "swap");

   const input = document.getElementById("rc_search");  
   input.setAttribute("placeholder", `{{ 'cp_search_product_to_swap' | t }}`);  
   input.addEventListener("keyup", (evt) => ReCharge.Novum.Helpers.searchProductsHandler(evt, 'swap'));  
}

New code

async function swapProductHandler(event, source = "cancellation-flow") {  
   event.preventDefault();

   ReCharge.Novum.backBtn.setAttribute("style", "visibility: visible");  
   // remove event where backBtn would lead to swap search that was placed in function swapProductDetailsHandler  
   ReCharge.Novum.backBtn.removeEventListener("click", swapProductHandler, false);  
   ReCharge.Novum.backBtn.removeEventListener("click", cancelSubscriptionFlow, false);

   // add event where backBtn points to edit product  
   if (source === "cancellation-flow") {  
       ReCharge.Novum.backBtn.addEventListener("click", cancelSubscriptionFlow);  
   } else {  
       ReCharge.Novum.backBtn.addEventListener("click", editProduct);  
   }

   ReCharge.Novum.sidebarHeading.innerHTML = `{{ 'cp_select_product' | t }}`;  
   ReCharge.Novum.sidebarContent.innerHTML = `{% include '_render_products.html' %}`;

   const schema = ReCharge.Schemas.products.search('', 6, 1, true);  
   const data =  await ReCharge.Actions.getProducts(6, schema);  
   let productsToRender = ReCharge.Novum.Utils.isOnetimesEnabled(data.products);  
   productsToRender = ReCharge.Novum.Utils.isPrepaidProduct(productsToRender);

   ReCharge.Novum.Pagination.currentAddPage = 1;  
   ReCharge.Novum.Pagination.type = 'add';  
   ReCharge.Novum.Helpers.renderProducts(productsToRender, 'swap');  
   ReCharge.Novum.isSwap = true;

   const input = document.getElementById("rc_search");  
   input.setAttribute("placeholder", `{{ 'cp_search_product_to_swap' | t }}`);  
   input.addEventListener("keyup", (evt) => ReCharge.Novum.Helpers.searchProductsHandler(evt, 'swap'));  
}

The function swapProductDetailsHandler dynamically fetches a specific product and displays its information.

Old code

function swapProductDetailsHandler(event) {  
   event.preventDefault();

   ReCharge.Novum.backBtn.addEventListener("click", swapProductHandler);

   const subscription = ReCharge.Novum.subscription;  
   let products = JSON.parse(sessionStorage.getItem("rc_products"));  
   const productId = event.target.dataset.productId;

   const productToSwap = products.find(  
       product => product.shopify_details.shopify_id == productId  
   );  
   const { shopify_id } = productToSwap.shopify_details.variants[0];

   ReCharge.Novum.sidebarHeading.innerHTML = `{{ 'title_swap_product' | t }}`;  
   ReCharge.Novum.sidebarContent.innerHTML = `{% include '_swap_product_details.html' %}`;

   const productContainer = document.querySelector(  
       ".rc_swap_product_details_container"  
   );

   let actionUrl = ReCharge.Endpoints.swap_subscription_url(subscription.id);  
   productContainer.setAttribute("action", actionUrl);

   let redirect_url = ReCharge.Endpoints.update_subscription_url(subscription.id);  
 productContainer.innerHTML = `       <input type="hidden" name="redirect_url" value="${redirect_url}">
       ${
           ReCharge.Novum.store.external_platform === 'big_commerce'
               ?`<input type="hidden" name="external_product_id" value="${productToSwap.shopify_product_id}">\``              :`  
       }  
       <input type="hidden" name="shopify_variant_id" value="${shopify_id}">  
       ${ReCharge.Novum.Helpers.renderSubscriptionProductInfo(  
           productToSwap,  
           ReCharge.Novum.Helpers.getDisplayPrice(productToSwap)  
       )}  
       ${ReCharge.Novum.Helpers.renderDeliveryOptions(productToSwap)}  
       <div id="product_variant_container">  
           <p class="text-font-14">{{ 'cp_variants' | t }}</p>  
           <ul id="product_options_container"></ul>  
       </div>  
       <button
           type="submit"
           class="rc_btn text-uppercase title-bold"
       >  
           {{ 'button_swap_product' | t }}  
       </button>
`;
ReCharge.Novum.Helpers.renderVariants(productToSwap);

 // Trigger the variant change callback to ensure correct price display  
 ReCharge.Novum.Helpers.triggerVariantUpdate();

 // Add handler for subscription/otp creation  
 document  
     .querySelector('#subscriptionSwapForm')  
     .addEventListener(  
         'submit',  
         (e) => ReCharge.Novum.Utils.createProduct(e, productToSwap.shopify_details.shopify_id, 'swap')  
     );  
}

New code

async function swapProductDetailsHandler(event)  
{  
event.preventDefault();

ReCharge.Novum.backBtn.addEventListener("click", swapProductHandler);

const subscription = ReCharge.Novum.subscription;  
const productId = event.target.dataset.productId;  
const schema = ReCharge.Schemas.products.getProduct(productId);  
const data = await ReCharge.Actions.getProducts(6, schema);  
const productToSwap = data.products[0];  
const  
{  
  shopify_id  
} = productToSwap.shopify_details.variants[0];

ReCharge.Novum.sidebarHeading.innerHTML = `{{ 'title_swap_product' | t }}`;  
ReCharge.Novum.sidebarContent.innerHTML = `{% include '_swap_product_details.html' %}`;

const productContainer = document.querySelector(  
  ".rc_swap_product_details_container"  
);

let actionUrl = ReCharge.Endpoints.swap_subscription_url(subscription.id);  
productContainer.setAttribute("action", actionUrl);

let redirect_url = ReCharge.Endpoints.update_subscription_url(subscription.id);

productContainer.innerHTML = `       <input type="hidden" name="redirect_url" value="${redirect_url}">
     ${
         ReCharge.Novum.store.external_platform === 'big_commerce'
             ?`<input type="hidden" name="external_product_id" value="${productToSwap.shopify_product_id}">\`  
             : \`\`  
     }  
     <input type="hidden" name="shopify_variant_id" value="${shopify_id}">

   

${ReCharge.Novum.Helpers.renderSubscriptionProductInfo(  
       productToSwap,  
       ReCharge.Novum.Helpers.get@@DisplayPrice(productToSwap)  
   )}  
   ${ReCharge.Novum.Helpers.renderDeliveryOptions(productToSwap)}

   <div id="product_variant_container">
```
<p class="text-font-14">{{ 'cp_variants' | t }}</p>
           <ul id="product_options_container"></ul>
       </div>
       <button
           type="submit"
           class="rc_btn text-uppercase title-bold"
       >
           {{ 'button_swap_product' | t }}
       </button>
   `;

  ReCharge.Novum.Helpers.renderVariants(productToSwap);

  // Trigger the variant change callback to ensure correct price display  
  ReCharge.Novum.Helpers.triggerVariantUpdate();

  // Add handler for subscription/otp creation  
  document  
    .querySelector('#subscriptionSwapForm')  
    .addEventListener(  
      'submit',  
      (e) => ReCharge.Novum.Utils.createProduct(  
        e,  
        productToSwap.shopify_details.shopify_id,  
        'swap',  
        productToSwap.shopify_details.variants  
      )  
    );  
}

return productsToRender;  
}

return productsToRender;  
}

_helpers.js

The function updateVariant filters products fetched asynchronously, instead of relying on session storage.

Replace this line of code:

const product = products.find(  
    prod => prod.shopify_details.shopify_id == productId  
 );

With this:

const product = ReCharge.Novum.products.find(  
   prod => prod.shopify_details.shopify_id == productId  
 );

The method searchProductsHandler, refactors this code section and uses ReCharge.Schemas, so schemas are centralized and easier to modify. Pagination is also updated.

Old code

searchProductsHandler: async function(evt, action = 'add') {  
   if (evt.keyCode === 13) {  
       evt.preventDefault();
const baseSource = {{ settings | json }}.customer_portal.onetime.enabled
       ? `"base_source": "shopify"`
       : `"base_source": "store_settings"`;


   const productsContainer = document.querySelector('.rc_product_list_container');
   const searchValue = evt.target.value;

   const schema =  `{ "products": { "title": "${searchValue.toLowerCase()}", ${baseSource} } }`;

   try {
       const url = `${ReCharge.Endpoints.request_objects()}&schema=${schema}`;
       const response = await axios(url);

       ReCharge.Novum.Helpers.validateResponseData(response.data, 'products');

       let products = response.data.products;

       if (!Array.isArray(response.data.products)) {
           products = [response.data.products];
       }

       if (action === 'swap') {
           products = products.filter(prod => {
               if (prod.subscription_defaults) {
                   const {
                       charge_interval_frequency,
                       order_interval_frequency_options
         } = prod.subscription_defaults;
                   if (
                       order_interval_frequency_options.length === 1 &&
                       Number(order_interval_frequency_options[0]) !== charge_interval_frequency
                   ) {
                       return;
                   }
                   return prod;
               }
           });
       }

       if (
           products &&
           products.length
       ) {
           productsContainer.innerHTML = ``;
           ReCharge.Novum.Helpers.renderProducts(products, action);
       } else {
           productsContainer.innerHTML = `{{ "cp_no_products_found" | t }}`;
       }
   } catch (error) {
       console.error(error);
       productsContainer.innerHTML = `{{ "cp_no_products_found" | t }}`;
   }
  }  
}

New code

searchProductsHandler: async function(ev, action = 'add') {  
       if (ev.keyCode === 13) {  
           ev.preventDefault();
 const searchQuery = ev.target.value
           .trim()
           .toLowerCase()
           .replace(/"/g, '')
       ;
       const isSwap = action === 'add'
           ? false
            : true
       ;
       const schema = ReCharge.Schemas.products.search(searchQuery, 6, 1, isSwap);

       const data = await ReCharge.Actions.getProducts(6, schema);

       // Remove OTPs for Swap feature
       let productsToRender = ReCharge.Novum.Utils.isOnetimesEnabled(data.products);
       if (isSwap) {
           productsToRender = ReCharge.Novum.Utils.isPrepaidProduct(productsToRender);
       }
       ReCharge.Novum.Helpers.renderProducts(productsToRender, action);

       ReCharge.Novum.Pagination.updatePagination(); 
   }
}

The method renderUpsells was converted to an asynchronous function and fetches products instead of relying on session storage. Pagination has been initialized.

Old code

renderUpsells: function(type = 'upsell', products, currentPage = 1, productsPerPage = 12) {  
   const container = document.querySelector('#rc\_\_upsells--container');  
   // Hide loader  
   let loader = document.querySelector('#upsells--loader');  
   if (loader) {  
       loader.setAttribute('style', 'display: none;');  
   }

   let productCards = '';  
   let productsToRender = ReCharge.Novum.Utils.isOnetimesEnabled(products);

   ReCharge.Novum.Helpers.renderPagination(productsToRender, currentPage, type, productsPerPage);

   if (products.length) {  
       productCards = productsToRender  
           .slice(0, 12)  
  .map(product => {  
               let imageSrc = ReCharge.Novum.Utils.getImageUrl(product);  
               let price = ReCharge.Novum.Helpers.getDisplayPrice(product);
         const { shopify_id, handle, title } = product.shopify_details;

           return `
                   <li class="js-toggle-card text-center rc_element_wrapper rc_single_product_card-wrapper" id="product_${shopify_id}">
                       <div class="js-card js-card-${shopify_id}">
                           <div class="rc_image_container">
                               <img src="${imageSrc}" alt="${handle}">
                           </div>
                           <p
                               class="text-font-14 title-bold upsells-title ${title.replace('Auto renew', '').trim().length > 45 ? 'upsell_text--clip' : 'upsell_text--center'}"
                           >
                               ${title.replace('Auto renew', '')}
                           </p>
                           <p>
                               ${ReCharge.Novum.Utils.getCurrency()}${Number(price).toFixed(2)}
                           </p>
                           <button
                               class="rc_btn--secondary text-uppercase title-bold upsell-btn-mobile"
                               data-product-id="${shopify_id}"
                               onclick="ReCharge.Novum.Helpers.toggleUpsellsButtons(event)"
                           >
                               {{ 'cp_add_render_upsells' | t }}
                           </button>
                       </div>
                       ${ReCharge.Novum.Helpers.renderUpsellButtons(product)}
                   </li>
               `;
       }).join('');
        }
   if (container) {  
       container.innerHTML = `${productCards}`;  
   }  
}

New code

renderUpsells: async function(products) {  
       const container = document.querySelector('#rc\_\_upsells--container');  
       // Hide loader  
       let loader = document.querySelector('#upsells--loader');  
       if (loader) {  
           loader.setAttribute('style', 'display: none;');  
       }
  let productCards = '';
   let productsToRender = ReCharge.Novum.Utils.isOnetimesEnabled(products);

   if (productsToRender.length) {
       productCards = productsToRender
           .slice(0, 12)
           .map(product => {
               let imageSrc = ReCharge.Novum.Utils.getImageUrl(product);
               let price = ReCharge.Novum.Helpers.getDisplayPrice(product);

               const { shopify_id, handle, title } = product.shopify_details;

               return `
                       <li class="js-toggle-card text-center rc_element_wrapper rc_single_product_card-wrapper" id="product_${shopify_id}">
                           <div class="js-card js-card-${shopify_id}">
                               <div class="rc_image_container">
                                   <img src="${imageSrc}" alt="${handle}">
                               </div>

                               <p
                                   class="text-font-14 title-bold upsells-title ${title.replace('Auto renew', '').trim().length > 45 ? 'upsell_text--clip' : 'upsell_text--center'}"
                               >
                                   ${title.replace('Auto renew', '')}
                               </p>

                               <p>
                                   ${ReCharge.Novum.Utils.getCurrency()}${Number(price).toFixed(2)}
                               </p>

                               <button
                                   class="rc_btn--secondary text-uppercase title-bold upsell-btn-mobile"
                                   data-product-id="${shopify_id}"
                                   onclick="ReCharge.Novum.Helpers.toggleUpsellsButtons(event)"
                               >
                                   {{ 'cp_add_render_upsells' | t }}
                               </button>
                           </div>
                           ${ReCharge.Novum.Helpers.renderUpsellButtons(product)}
                       </li>
                   `;
           }).join('');
   }

   if (container) {
       container.innerHTML = `${productCards}`;
       ReCharge.Novum.Pagination.renderInitialPagination();
   }
  }

We converted the method addUpsellHandler to an asynchronous function so it retrieves the specific product that will be added.

Old code

addUpsellHandler: function(evt) {  
   evt.preventDefault();

   const chosenOption = evt.target.value;  
   const parentElement = evt.target.closest('.rc_upsells-btns');  
   const productId = parentElement.dataset.productId;  
   const products = JSON.parse(sessionStorage.getItem('rc_products'));  
   const settings = {{ settings | json }};  
   const chosenProduct = products.find(product => product.shopify_details.shopify_id == productId);  
   const subscription = ReCharge.Novum.subscription;  
   let url = chosenOption.includes('one-time') ? "{{ onetime_list_url }}" : "{{ subscription_list_url }}";  
   let isInStock;

   if (chosenProduct.shopify_details.variants.length > 1) {  
       ReCharge.Novum.sidebarHeading.innerHTML = `{{ "cp_add_product_label" | t }}`;  
       ReCharge.Novum.sidebarContent.innerHTML = `{% include '_add_product_details.html' %}`;
     let productContainer = document.querySelector('.rc_add_product_details_container');

   productContainer.innerHTML += `
       <input type="hidden" name="shopify_variant_id" value="${chosenProduct.shopify_details.variants[0].shopify_id}">
       ${ReCharge.Novum.store.external_platform === 'big_commerce'
           ? `<input type="hidden" name="external_product_id" value="${chosenProduct.shopify_details.shopify_id}">`
           : ``
       }
       <input type="hidden" name="next_charge_scheduled_at" id="next_charge_scheduled_at" value="${subscription.next_charge_scheduled_at}" required >
       <input type="hidden" name="address_id" value="${subscription.address_id}" required>
       <input type="hidden" name="redirect_url" value="{{ schedule_url }}">
       ${!chosenOption.includes('one-time') ? '' :
           `<input type="hidden" name="properties[add_on]" value="True">
           <input type="hidden" name="properties[add_on_subscription_id]" value="${subscription.id}">
           `
       }
       ${ReCharge.Novum.Helpers.renderSubscriptionProductInfo(
           chosenProduct,
           ReCharge.Novum.Helpers.getDisplayPrice(chosenProduct)
       )}
       ${chosenOption.includes('one-time') ? '' :
           `<div id="product_schedule_container">
               ${ReCharge.Novum.Helpers.renderDeliveryOptions(chosenProduct)}
           </div>`
       }
       <div id="product_variant_container">
           <p class="text-font-14">{{ 'cp_variants' | t }}</p>
           <ul id="product_options_container"></ul>
       </div>
       <button type="submit" class="rc_btn text-uppercase title-bold">
           ${evt.target.value}
       </button>
   `;

   ReCharge.Novum.Helpers.renderVariants(chosenProduct);
   document.querySelector('#subscriptionNewForm').setAttribute('action', url);

   // Trigger the variant change callback to ensure correct price display
   ReCharge.Novum.Helpers.triggerVariantUpdate();

   // Add handler for subscription/otp creation
   document
       .querySelector('#subscriptionNewForm')
       .addEventListener(
           'submit',
           (e) => ReCharge.Novum.Utils.createProduct(e, chosenProduct.shopify_details.shopify_id)
       );

   ReCharge.Novum.toggleSidebar();
        } else {  
       isInStock = ReCharge.Novum.Utils.checkInventory(chosenProduct.shopify_details.variants[0]);  
       if (isInStock) {  
           evt.target.value = `{{ 'cp_processing_message' | t }}`;  
           evt.target.disabled = true;  
           let postUrl = 'create_onetime';  
           const data = {  
               address_id: subscription.address_id,  
               external_product_id: chosenProduct.shopify_details.shopify_id,  
               shopify_variant_id: chosenProduct.shopify_details.variants[0].shopify_id,  
               quantity: 1,  
               next_charge_scheduled_at: subscription.next_charge_scheduled_at,  
               "properties[add_on]": true,  
               "properties[add_on_subscription_id]": subscription.id,  
                    if (chosenOption.includes('subscription')) {
           postUrl = 'list_subscriptions_url';

           data.order_interval_frequency = chosenProduct.subscription_defaults.order_interval_frequency_options[0];
           data.charge_interval_frequency = chosenProduct.subscription_defaults.order_interval_frequency_options.length > 1
             ? chosenProduct.subscription_defaults.order_interval_frequency_options[0]
             : chosenProduct.subscription_defaults.charge_interval_frequency
           ;
           data.order_interval_unit = chosenProduct.subscription_defaults.order_interval_unit;
       }

       data.redirect_url = "{{ schedule_url }}";

       ReCharge.Actions.put(postUrl, null, data, chosenProduct.title);
   } else {
       evt.target.value = `{{ 'cp_out_of_stock' | t }}`;
       evt.target.disabled = true;
       ReCharge.Toast.addToast(`{{ 'cp_toast_error' | t }}`, `{{ 'cp_product_out_of_stock' | t }}`);
   }
 }  
}
           }

New code

addUpsellHandler: async function(evt) {  
       evt.preventDefault();
  const chosenOption = evt.target.value;
   const parentElement = evt.target.closest('.rc_upsells-btns');
   const productId = parentElement.dataset.productId;
   const settings = {{ settings | json }};

   const schema = ReCharge.Schemas.products.getProduct(productId);
   const data =  await ReCharge.Actions.getProducts(6, schema);
   const chosenProduct = data.products[0];


   const subscription = ReCharge.Novum.subscription;
   let url = chosenOption.includes('one-time') ? "{{ onetime_list_url }}" : "{{ subscription_list_url }}";
   let isInStock;

   if (chosenProduct.shopify_details.variants.length > 1) {
       ReCharge.Novum.sidebarHeading.innerHTML = `{{ "cp_add_product_label" | t }}`;
       ReCharge.Novum.sidebarContent.innerHTML = `{% include '_add_product_details.html' %}`;

       let productContainer = document.querySelector('.rc_add_product_details_container');

       productContainer.innerHTML += `
           <input type="hidden" name="shopify_variant_id" value="${chosenProduct.shopify_details.variants[0].shopify_id}">
           ${ReCharge.Novum.store.external_platform === 'big_commerce'
               ? `<input type="hidden" name="external_product_id" value="${chosenProduct.shopify_details.shopify_id}">`
               : ``
           }
           <input type="hidden" name="next_charge_scheduled_at" id="next_charge_scheduled_at" value="${subscription.next_charge_scheduled_at}" required >
           <input type="hidden" name="address_id" value="${subscription.address_id}" required>
           <input type="hidden" name="redirect_url" value="{{ schedule_url }}">

           ${!chosenOption.includes('one-time') ? '' :
               `<input type="hidden" name="properties[add_on]" value="True">
               <input type="hidden" name="properties[add_on_subscription_id]" value="${subscription.id}">
               `
           }

           ${ReCharge.Novum.Helpers.renderSubscriptionProductInfo(
               chosenProduct,
               ReCharge.Novum.Helpers.getDisplayPrice(chosenProduct)
           )}

           ${chosenOption.includes('one-time') ? '' :
               `<div id="product_schedule_container">
                   ${ReCharge.Novum.Helpers.renderDeliveryOptions(chosenProduct)}
               </div>`
           }

           <div id="product_variant_container">
               <p class="text-font-14">{{ 'cp_variants' | t }}</p>
               <ul id="product_options_container"></ul>
           </div>
           <button type="submit" class="rc_btn text-uppercase title-bold">
               ${evt.target.value}
           </button>
       `;

       ReCharge.Novum.Helpers.renderVariants(chosenProduct);
       document.querySelector('#subscriptionNewForm').setAttribute('action', url);

       // Trigger the variant change callback to ensure correct price display
       ReCharge.Novum.Helpers.triggerVariantUpdate();

       // Add handler for subscription/otp creation
       document
           .querySelector('#subscriptionNewForm')
           .addEventListener(
               'submit',
               (e) => ReCharge.Novum.Utils.createProduct(
                       e,
                       chosenProduct.shopify_details.shopify_id,
                       'create',
                       chosenProduct.shopify_details.variants
                   )
           );

       ReCharge.Novum.toggleSidebar();
   } else {
       isInStock = ReCharge.Novum.Utils.checkInventory(chosenProduct.shopify_details.variants[0]);
       if (isInStock) {
           evt.target.value = `{{ 'cp_processing_message' | t }}`;
           evt.target.disabled = true;
           let postUrl = 'create_onetime';
           const data = {
               address_id: subscription.address_id,
               external_product_id: chosenProduct.shopify_details.shopify_id,
               shopify_variant_id: chosenProduct.shopify_details.variants[0].shopify_id,
               quantity: 1,
               next_charge_scheduled_at: subscription.next_charge_scheduled_at,
               "properties[add_on]": true,
               "properties[add_on_subscription_id]": subscription.id,
           }

           if (chosenOption.includes('subscription')) {
               postUrl = 'list_subscriptions_url';

               data.order_interval_frequency = chosenProduct.subscription_defaults.order_interval_frequency_options[0];
               data.charge_interval_frequency = chosenProduct.subscription_defaults.order_interval_frequency_options.length > 1
                 ? chosenProduct.subscription_defaults.order_interval_frequency_options[0]
                 : chosenProduct.subscription_defaults.charge_interval_frequency
               ;
               data.order_interval_unit = chosenProduct.subscription_defaults.order_interval_unit;
           }

           data.redirect_url = "{{ schedule_url }}";

           ReCharge.Actions.put(postUrl, null, data, chosenProduct.title);
       } else {
           evt.target.value = `{{ 'cp_out_of_stock' | t }}`;
           evt.target.disabled = true;
           ReCharge.Toast.addToast(`{{ 'cp_toast_error' | t }}`, `{{ 'cp_product_out_of_stock' | t }}`);
       }
   }
}

Method renderProducts was refactored and does not accept params currentPage and productsPerPage. Additional check for products was added. Pagination was initialized.

Old code

renderProducts: function(products, type, currentPage=1, productsPerPage=6) {  
   ReCharge.Novum.Helpers.renderPagination(products, currentPage, type, productsPerPage);

   const productsContainer = document.querySelector('.rc_product_list_container');

   products.forEach(product => {
       let otpPrice = product.shopify_details.variants[0].price;
   let subPrice = product.shopify_details.variants[0].price;

   if (product.subscription_defaults) {
       const hasDiscount = product.discount_amount && product.discount_amount !== 0 || false;
       if (hasDiscount) {
           if (product.discount_type == 'percentage') {
               subPrice *= 1 - product.discount_amount / 100;
           } else {
               subPrice -= product.discount_amount;
           }
       }
   }

   const { title, shopify_id } = product.shopify_details;

   productsContainer.innerHTML += `
       <li class="rc_product_card border-light text-center rc_single_product_card-wrapper" id="product_${shopify_id}">
           <div class="rc_image_container">
               <img src="${ReCharge.Novum.Utils.getImageUrl(product)}" alt="${product.title}" class="rc_img__sidebar" height="100px" width="100px">
           </div>
           <p class="product-title title-bold text-font-14 ${title.trim().length > 35 ? 'upsell_text--clip' : 'upsell_text--center'}">
               ${title}
           </p>
           <p>
               {% include '_onetime-icon.svg' %}
               ${ReCharge.Novum.Utils.getCurrency()}${Number(otpPrice).toFixed(2)}
               <svg class="vertical-divider" width="1" height="9" fill="none"><path d="M.962 8.553H.234V.125h.728v8.428z" fill="var(--color-dark-green)"/></svg>
               {% include '_subscription-icon.svg' %}
               ${ReCharge.Novum.Utils.getCurrency()}${Number(subPrice).toFixed(2)}
           </p>
           <button
               class="rc_btn text-uppercase title-bold view-product-button"
               data-product-id="${shopify_id}"
           >
               {{ 'cp_select_button' | t }}
           </button>
       </li>
   `;
      });

   let handler = ReCharge.Novum.Utils.addProductDetailsHandler;

   if (type === 'swap') {  
       handler = swapProductDetailsHandler;  
   }

   document.querySelectorAll('.view-product-button')  
       .forEach(button => {  
           button.addEventListener('click', handler)  
       });  
}

New code

renderProducts: function(products, type = 'add') {  
       const productsContainer = document.querySelector('.rc_product_list_container');  
       productsContainer.innerHTML = '';
if (!products.length) {
       return productsContainer.innerHTML = `{{ "cp_no_products_found" | t }}`;
   }

   products.forEach(product => {
       let otpPrice = product.shopify_details.variants[0].price;
       let subPrice = product.shopify_details.variants[0].price;

       if (product.subscription_defaults) {
           const hasDiscount = product.discount_amount && product.discount_amount !== 0 || false;
           if (hasDiscount) {
               if (product.discount_type == 'percentage') {
                   subPrice *= 1 - product.discount_amount / 100;
               } else {
                   subPrice -= product.discount_amount;
               }
           }
       }

       const { title, shopify_id } = product.shopify_details;

       productsContainer.innerHTML += `
           <li class="rc_product_card border-light text-center rc_single_product_card-wrapper" id="product_${shopify_id}">
               <div class="rc_image_container">
                   <img src="${ReCharge.Novum.Utils.getImageUrl(product)}" alt="${product.title}" class="rc_img__sidebar" height="100px" width="100px">
               </div>

               <p class="product-title title-bold text-font-14 ${title.trim().length > 35 ? 'upsell_text--clip' : 'upsell_text--center'}">
                   ${title}
               </p>

               <p>
                   {% include '_onetime-icon.svg' %}
                   ${ReCharge.Novum.Utils.getCurrency()}${Number(otpPrice).toFixed(2)}
                   <svg class="vertical-divider" width="1" height="9" fill="none"><path d="M.962 8.553H.234V.125h.728v8.428z" fill="var(--color-dark-green)"/></svg>
                   {% include '_subscription-icon.svg' %}
                   ${ReCharge.Novum.Utils.getCurrency()}${Number(subPrice).toFixed(2)}
               </p>
               <button
                   class="rc_btn text-uppercase title-bold view-product-button"
                   data-product-id="${shopify_id}"
                    {{ 'cp_select_button' | t }}
               </button>
           </li>
       `;
   });

   let handler = ReCharge.Novum.Utils.addProductDetailsHandler;

   if (type === 'swap') {
       handler = swapProductDetailsHandler;
   }

   document.querySelectorAll('.view-product-button')
       .forEach(button => {
           button.addEventListener('click', handler)
       })
   ;

   ReCharge.Novum.Pagination.renderInitialPagination();
  }

Methods renderPagination and goToPageHandler were deleted as they’re no longer needed in the new flow.

We refactored fetchProductsRetentionStrategiesOrders method. Checks for external_platform and whether store allows for adding a product were deleted. We also deleted the render Onetimes flow.

Remove the following code

if (store.external_platform === 'big_commerce') {  
    schema = `{ "products": { "limit": 250, "page": 1 }, "retention_strategies": {  "sort_by":"id-asc" }, "orders": { "status": "SUCCESS" } }`;  
 }

// render Onetimes  
let container = document.querySelector('#rc\_\_upsells--container');  
const upsellWrapper = document.querySelector(".upsells--wrapper") || null;  
if (container && mappedProducts.length > 0 && ReCharge.Novum.settings.customer_portal.subscription.add_product) {  
    ReCharge.Novum.Helpers.renderUpsells("upsell", mappedProducts);  
} else {  
    if (upsellWrapper !== null) {  
        upsellWrapper.innerHTML = ReCharge.Utils.renderNoProductsLayout();  
    }  
}

// Check if store allows adding product  
if (settings.customer_portal.subscription.add_product) {  
    const addProductBtn = document.querySelector('.js-add-product-btn');  
    if (addProductBtn != null) {  
        ReCharge.Novum.Helpers.showElement(addProductBtn);  
    }  
}

Method fetchProducts was refactored and check for external_platform has been deleted.

Remove the following code

if (store.external_platform === 'big_commerce') {  
   schema = `{ "products": { "limit": 250, "page": ${page} } }`;  
}

_pagination.css

This is a new file. Styles for pagination were isolated into a separate file to make editing CSS easier.

  1. Create a new file named _pagination.css
  2. Paste the following code to the new file
body#recharge-novum #recharge-te .rct_pagination\_\_container {  
   display: flex;  
   justify-content: center;  
   align-items: center;  
 }

 body#recharge-novum #recharge-te .rct_pagination**prev,  
 body#recharge-novum #recharge-te .rct_pagination**next {  
   display: flex;  
   justify-content: center;  
   align-items: center;  
   border: 1px solid var(--primary-color);  
   padding: 10px;  
   height: 40px;  
   width: 40px;  
   border-radius: 50%;  
   font-size: 18px;  
   color: var(--primary-color);  
 }

 body#recharge-novum #recharge-te .rct_pagination**prev:hover,  
 body#recharge-novum #recharge-te .rct_pagination**next:hover {  
   cursor: pointer;  
 }

 body#recharge-novum #recharge-te .rct_pagination\_\_page {  
   margin: 0 6px;  
   padding: 8px;  
 }

 body#recharge-novum #recharge-te .rct_pagination\_\_page--current {  
   font-weight: 700;  
   color: var(--primary-color);  
   margin: 0px 10px;  
 }

 body#recharge-novum #recharge-te .rct_pagination\_\_container--hidden {  
   display: none;  
 }

 body#recharge-novum #recharge-te .rct_pagination**prev--disabled,  
 body#recharge-novum #recharge-te .rct_pagination**next--disabled {  
   opacity: 0.5;  
   pointer-events: none;  
 }

_pagination.html

We created a new HTML file to facilitate pagination.

  1. Create a file named _pagination.html
  2. Paste the following code in the new file
<style>
   {% include '_pagination.css' %}
</style>

<div class="rct_pagination__container rct_pagination__container--add rct_pagination__container--hidden">
   <span
       class="rct_pagination__prev rct_pagination__prev--add rct_pagination__prev--disabled"
       onclick="ReCharge.Novum.Pagination.previousPageHandler(event)"
       data-handler-type="add"
   >
       <i class="fas fa-chevron-left"></i>
   </span>
   <span
       class="rct_pagination__page rct_pagination__current--add rct_pagination__page--current"
       data-pagination-current-page
       data-handler-type="add"
   >
       1
   </span>
   <span
       class="rct_pagination__next rct_pagination__next--add "
       onclick="ReCharge.Novum.Pagination.nextPageHandler(event)"
       data-handler-type="add"
   >
       <i class="fas fa-chevron-right"></i>
   </span>
</div>

_recharge.js

Check for adding a product has been deleted. Initial pagination setup has been added.

Remove the following code

// Check if store allows adding a product  
 if(settingsStore.customer_portal.subscription.add_product) {  
   const addProductBtn = document.querySelector('.js-add-product-btn');  
   const rc_products = JSON.parse(sessionStorage.getItem('rc_products')) || null;  
   if (addProductBtn && rc_products && rc_products.length) {  
       ReCharge.Novum.Helpers.showElement(addProductBtn);  
   }  
 }

Inside ReCharge.Novum.toggleSidebar, at the very end, add the following code:

 if (ReCharge.Novum.Pagination.type === 'upsell') {  
     ReCharge.Novum.Pagination.updateBtnProps('container');  
     ReCharge.Novum.Pagination.updateBtnProps('prev');  
     ReCharge.Novum.Pagination.updateBtnProps('next');  
     ReCharge.Novum.Pagination.updateBtnProps('current');  
     ReCharge.Novum.Pagination.limit = 12;  
 }

_render_products.html

Instead of an HTML unordered list, include this snippet for pagination HTML structure.

Replace the following code

<ul class="pagination_buttons_container">
 </ul>

With the following code

{% include '\_pagination.html' %}

_scripts.js

We've added a new method to ReCharge.Actions called getProducts. It is responsible for fetching products whenever needed. Users will always have the latest version of a product using this asynchronous function.

Add the following code to ReCharge.Actions

getProducts: async function(limit = 6, productsSchema = null, url = null, isSwap = false) {  
       const schema = productsSchema || ReCharge.Schemas.products.list(limit);
  let dataUrl = attachQueryParams(`
       ${ReCharge.Endpoints.request_objects()}&schema=${schema}`
   );

   ReCharge.Novum.Pagination.limit = limit;

   if (url) {
       dataUrl = url;
       if (isSwap) {
           dataUrl = url.replace(/%22base_source%22%3A%20%22store_settings%22%2C%20/, '%20%22exclude_prepaids%22:%20true%20,');
       }
   }

   try {
       const response = await axios(dataUrl);
       ReCharge.Novum.products = response.data.products;
       if (
           response.data.meta &&
           response.data.meta.products
       ) {
           ReCharge.Novum.meta = response.data.meta.products;
           if (limit === 12) {
               ReCharge.Novum.upsellMeta = response.data.meta.products;
               ReCharge.Novum.Pagination.limit = 12;
           } else if (limit === 6) {
               ReCharge.Novum.addMeta = response.data.meta.products;
               ReCharge.Novum.Pagination.limit = 6;
           }
       }
       if (
           response.data.products &&
           !Array.isArray(response.data.products)
       ) {
           ReCharge.Novum.products = [response.data.products];
           response.data.products = [response.data.products];
       }

       return response.data;
   } catch(error) {
       console.error(error);
   } finally {
       delete window.locked;
   } 
}

New schemas have also been added to the products object in ReCharge.Schemas. These schemas are now centralized and easier to modify.

Add the following code to ReCharge.Schemas

products: {  
       list(limit = 6, page = 1) {  
           return `{ "products": { "base_source": "store_settings", "limit": ${limit}, "page": ${page} } }`;  
       },  
       search(query, limit = 6, page = 1, isSwap = false) {  
           let title = \``;
           let excludePrepaids = `, "exclude_prepaids": true`;
           let productTypes = `, "product_types": "subscription"\`;
             if (query.length > 0) {
           title = `"title": "${query}",`;
       }

       if (!isSwap) {
           excludePrepaids = ``;
           productTypes = ``;
       }

       return `{ "products": { ${title} "base_source": "store_settings", "limit": ${limit}, "page": ${page} ${excludePrepaids} ${productTypes} } }`;
   },
   getProduct(id) {
       return `{ "products": { "shopify_product_id": ${id} } }`;
   }
  }

subscriptions.js

We've converted the function addProductHandler into an asynchronous function so it can dynamically fetch products, instead of relying on filtering products from session storage. These products are rendered in the sidebar. Pagination has been initialized.

Old code

// Open modal to show all products  
function addProductHandler(evt) {  
   evt.preventDefault();

   // Hide back button  
   ReCharge.Novum.backBtn.setAttribute('style', 'visibility: hidden');

   ReCharge.Novum.sidebarHeading.innerHTML = `{{ 'cp_select_product' | t }}`;  
   ReCharge.Novum.sidebarContent.innerHTML = `{% include '_render_products.html' %}`;

   let products = JSON.parse(sessionStorage.getItem('rc_products'));  
   let productsToRender = ReCharge.Novum.Utils.isOnetimesEnabled(products);

   ReCharge.Novum.Helpers.renderProducts(productsToRender.slice(0, 6), 'add');

   let input = document.getElementById('rc_search');  
   input.addEventListener('keyup', ReCharge.Novum.Helpers.searchProductsHandler);

   ReCharge.Novum.toggleSidebar();  
}

New code

// Open modal to show all products  
async function addProductHandler(evt) {  
   evt.preventDefault();

   // Hide back button  
   ReCharge.Novum.backBtn.setAttribute('style', 'visibility: hidden');

   ReCharge.Novum.sidebarHeading.innerHTML = `{{ 'cp_select_product' | t }}`;  
   ReCharge.Novum.sidebarContent.innerHTML = `{% include '_render_products.html' %}`;

   const data =  await ReCharge.Actions.getProducts(6);  
   const productsToRender = ReCharge.Novum.Utils.isOnetimesEnabled(data.products);

   ReCharge.Novum.Pagination.currentAddPage = 1;  
   ReCharge.Novum.Pagination.limit = 6;  
   ReCharge.Novum.Pagination.type = 'add';  
   ReCharge.Novum.Helpers.renderProducts(productsToRender, 'add');

   let input = document.getElementById('rc_search');  
   input.addEventListener('keyup', ReCharge.Novum.Helpers.searchProductsHandler);

   ReCharge.Novum.toggleSidebar();  
}

Function renderAddProductDetails has been slightly modified with an additional argument passed to the createProduct function.

Old code

// Add handler for subscription/otp creation  
document  
   .querySelector('#subscriptionNewForm')  
   .addEventListener(  
       'submit',  
       (e) => ReCharge.Novum.Utils.createProduct(e, product.shopify_details.shopify_id)  
   );

New code

// Add handler for subscription/otp creation  
 document  
     .querySelector('#subscriptionNewForm')  
     .addEventListener(  
         'submit',  
         (e) => ReCharge.Novum.Utils.createProduct(  
             e,  
             product.shopify_details.shopify_id,  
             'create',  
             product.shopify_details.variants  
         )  
     );

utils.js

The method isOnetimesEnabled has been modified slightly.

Old code

isOnetimesEnabled: function(products) {  
       let productsToRender;  
       const storeSettings = {{ settings | json }};
if (storeSettings.customer_portal.onetime.enabled) {
       productsToRender = [...products];
   } else {
       productsToRender = [...products].filter(
           prod => prod.subscription_defaults && prod.subscription_defaults.storefront_purchase_options !== 'onetime_only'
       );
   }

   return productsToRender;
}

New code

isOnetimesEnabled: function (products)  
{  
  let productsToRender;  
  const storeSettings =  
    {{ settings | json }};

  if (storeSettings.customer_portal.onetime.enabled)  
  {  
    productsToRender = products;  
  }  
  else  
  {  
    productsToRender = products.filter(  
      prod => prod.subscription_defaults && prod.subscription_defaults.storefront_purchase_options !== 'onetime_only'  
    );  
  }  
  return productsToRender;  
}

A new method was added to ReCharge.Novum.Utils named isPrepaidProduct . This new method is responsible for checking if a product is sold as a prepaid or not.

New code

isPrepaidProduct: function (products)  
{  
  return products.filter(prod =>  
  {  
    if (prod.subscription_defaults)  
    {  
      if (prod.subscription_defaults.order_interval_frequency_options.length === 1)  
      {  
        return prod.subscription_defaults.charge_interval_frequency === Number(prod.subscription_defaults.order_interval_frequency_options[0])  
      }
        return products;
} })  
}

Method addProductDetailsHandler was converted to an asynchronous function. It fetches the specific product, instead of relying on filtering products from session storage.

Old code

addProductDetailsHandler: function (evt)  
{  
  evt.preventDefault();

  const productId = evt.target.dataset.productId;  
  let product = JSON.parse(sessionStorage.getItem("rc_products")).find(  
    prod => prod.shopify_details.shopify_id == productId  
  );

  ReCharge.Novum.sidebarHeading.innerHTML = `{{ 'cp_edit_details' | t }}`;  
  ReCharge.Novum.sidebarContent.innerHTML = `{% include '_add_product_details.html' %}`;

  renderAddProductDetails(product);  
}

New code

addProductDetailsHandler: async function (ev)  
{  
  ev.preventDefault();

  const productId = ev.target.dataset.productId;  
  const schema = ReCharge.Schemas.products.getProduct(productId);  
  const data = await ReCharge.Actions.getProducts(6, schema);

  ReCharge.Novum.sidebarHeading.innerHTML = `{{ 'cp_edit_details' | t }}`;  
  ReCharge.Novum.sidebarContent.innerHTML = `{% include '_add_product_details.html' %}`;

  renderAddProductDetails(data.products[0]);  
}

A new parameter has been added to the method createProduct named variants .

Old code

createProduct: async function(evt, shopifyId, message = 'create') {

New code

createProduct: async function(evt, shopifyId, message = 'create', variants) {

Code for filtering products from session storage has been deleted.

Remove the following code:

const products = JSON.parse(sessionStorage.getItem("rc_products"));  
const chosenProduct = products.find(  
   prod => prod.shopify_details.shopify_id === shopifyId  
);

Remove the following code:

const chosenVariant = chosenProduct.shopify_details.variants.find(  
     variant => variant.shopify_id == data["shopify_variant_id"]  
   );

Replace it with:

const chosenVariant = variants.find(  
   variant => variant.shopify_id == data["shopify_variant_id"]  
 );  
We added a new method called ReCharge.Novum.Pagination.

We added a new method called ReCharge.Novum.Pagination.

Add the following code in _utils.js file:

ReCharge.Novum.Pagination = {  
   currentUpsellPage: 1,  
   currentAddPage: 1,  
   type: 'add',  
   limit: 6,  
   hasPrevMeta: function(type) {  
       if (type === 'add') {  
           return (  
               ReCharge.Novum.addMeta &&  
               ReCharge.Novum.addMeta.previous  
           )  
       }
     return (
       ReCharge.Novum.upsellMeta &&
       ReCharge.Novum.upsellMeta.previous
   )
      },  
   hasNextMeta: function(type) {  
       if (type === 'add') {  
           return (  
               ReCharge.Novum.addMeta &&  
               ReCharge.Novum.addMeta.next  
           )  
       }
     return (
       ReCharge.Novum.upsellMeta &&
       ReCharge.Novum.upsellMeta.next
   )
      },  
   previousPageHandler: function(ev) {  
       const handlerType = ev.target.closest('[data-handler-type]').dataset.handlerType;
      if (this.hasPrevMeta(handlerType)) {
       if (handlerType === 'add') {
           url = ReCharge.Novum.addMeta.previous;
           this.currentAddPage > 1
               ? this.currentAddPage -= 1
               : ''
           ;
       } else {
           url = ReCharge.Novum.upsellMeta.previous;
           this.currentUpsellPage > 1
               ? this.currentUpsellPage -= 1
               : ''
           ;
       }
       this.goToPageHandler(
           handlerType,
           ev,
           url
       );
   }
      },  
   nextPageHandler: function(ev) {  
       const handlerType = ev.target.closest('[data-handler-type]').dataset.handlerType;  
       if (this.hasNextMeta(handlerType)) {  
           let url = '';  
           if (handlerType === 'add') {  
               url = ReCharge.Novum.addMeta.next;  
               this.currentAddPage += 1;  
           } else {  
               url = ReCharge.Novum.upsellMeta.next;  
               this.currentUpsellPage += 1;  
           }  
           this.goToPageHandler(  
               handlerType,  
               ev,  
               url  
           );  
       }  
   },  
   goToPageHandler: async function(handlerType, ev, url) {  
       this.disableButtons(handlerType);  
       this.type = handlerType;
     if (handlerType === 'upsell') {
       const data =  await ReCharge.Actions.getProducts(12, null, url);
       ReCharge.Novum.Helpers.renderUpsells(data.products);
   } else {
       let isSwap = false;
       let type = 'add';
       if (ReCharge.Novum.isSwap) {
           isSwap = true;
           ev.target.closest('[data-handler-type]').dataset.handlerType;
           type = 'swap';
       }
       const data =  await ReCharge.Actions.getProducts(6, null, url, isSwap);
       ReCharge.Novum.Helpers.renderProducts(data.products, type);
   }
  
   const page = handlerType === 'upsell'
       ? this.currentUpsellPage
       : this.currentAddPage;

   this.updateButtonState(handlerType);

   this.updateCurrentPageNumber(page, handlerType);
      },  
   disableButtons: function(type) {  
       document  
           .querySelector(`.rct_pagination__prev--${type}`)  
           .classList.add('rct_pagination\_\_prev--disabled');
     document
       .querySelector(`.rct_pagination__next--${type}`)
       .classList.add('rct_pagination__next--disabled');
     },  
   enableButtons: function(type) {  
       document  
           .querySelector(`.rct_pagination__prev--${type}`)  
           .classList.remove('rct_pagination\_\_prev--disabled');
        document
       .querySelector(`.rct_pagination__next--${type}`)
       .classList.remove('rct_pagination__next--disabled'); 
     },  
   updateButtonState(type) {  
       const prevBtnAction = this.hasPrevMeta(type) ? 'remove' : 'add';  
       const nextBtnAction = this.hasNextMeta(type) ? 'remove' : 'add';
        document
       .querySelector(`.rct_pagination__prev--${type}`)
       .classList[prevBtnAction]('rct_pagination__prev--disabled');

   document
       .querySelector(`.rct_pagination__next--${type}`)
       .classList[nextBtnAction]('rct_pagination__next--disabled');
        },  
   updateCurrentPageNumber: function(page = null, type) {  
       if (ReCharge.Novum.isSwap) {  
           document  
               .querySelector(`.rct_pagination__current--${type}`)  
               .innerText = page;  
           return;  
       }
      return document
       .querySelector(`.rct_pagination__current--${type}`)
       .innerText = page;
     },  
   toggle: function(shouldShow = null) {  
       if (shouldShow) {  
           return document  
               .querySelector(`.rct_pagination__container--${this.type}`)  
               .classList.remove('rct_pagination\_\_container--hidden');  
       }
   document
       .querySelector(`.rct_pagination__prev--${type}`)
       .classList[prevBtnAction]('rct_pagination__prev--disabled');

   document
       .querySelector(`.rct_pagination__next--${type}`)
       .classList[nextBtnAction]('rct_pagination__next--disabled');
  document
       .querySelector(`.rct_pagination__container--${this.type}`)
       .classList.add('rct_pagination__container--hidden');
},  
   updatePagination: function() {  
       this.currentAddPage = 1;  
       this.updateButtonState('add');  
       this.updateCurrentPageNumber(this.currentAddPage, 'add');  
       if (!this.hasNextMeta(this.type)) {  
          return this.toggle();  
       }  
   },  
   renderInitialPagination: function() {  
       let page = this.currentAddPage;  
       if (ReCharge.Novum.Pagination.type === 'upsell') {  
           page = this.currentUpsellPage  
       }
       if (
       page === 1 &&
       !this.hasNextMeta(this.type)
   ) {
       this.toggle();
   } else {
       this.toggle(true);
   }
      },  
   updateBtnProps: function(type) {  
       let btn = document.querySelector(`.rct_pagination__${type}--add`);  
       if (btn) {  
           btn.classList.remove(`rct_pagination__${type}--add`);  
           btn.classList.add(`rct_pagination__${type}--upsell`);  
           btn.dataset.handlerType = 'upsell';  
       }  
   }  
}

subscription.html

The old HTML structure for the pagination container was replaced with the included pagination snippet.

Old code

 <ul class="rc__upsells--pagination_buttons_container"> </ul>

New code

   {% include '_pagination.html' %}

For the add product button, an addition check has been added to check if the store allows adding a product.

Old code

\< button  
class = "rc_btn border-light text-uppercase title-bold js-add-product-btn"  
style = "display: none;"  
onclick = "addProductHandler(event);" >  
  {{ 'Add_product' | t }}  
</button>

New code

{% if settings.customer_portal.subscription.add_product%}  
  <button
    class="rc_btn border-light text-uppercase title-bold"
      onclick="addProductHandler(event);"
     >  
       {{ 'cp_add_product_label' | t }}  
   </button>  
{% endif %}

In the script tag the handler has been converted to an asynchronous function, and initial setup for pagination has been added.

Old code

document.addEventListener("DOMContentLoaded", () =>  
{  
  ReCharge.Novum.Helpers.fetchChargesOnetimes();

  const rcProducts = JSON.parse(sessionStorage.getItem('rc_products')) || null;  
  if (rcProducts)  
  {  
    if (rcProducts.length && ReCharge.Novum.settings.customer_portal.subscription.add_product)  
    {  
      ReCharge.Novum.Helpers.renderUpsells('upsell', rcProducts);  
    }  
    else  
    {  
      const upsellWrapper = document.querySelector(".upsells--wrapper") || null;  
      upsellWrapper.innerHTML = ReCharge.Utils.renderNoProductsLayout();  
    }  
  }  
});

New code

document.addEventListener("DOMContentLoaded", async () =>  
{  
  ReCharge.Novum.Helpers.fetchChargesOnetimes();

  if (ReCharge.Novum.settings.customer_portal.subscription.add_product)  
  {  
    const data = await ReCharge.Actions.getProducts(12);  
    ReCharge.Novum.Pagination.type = 'upsell';  
    ReCharge.Novum.Pagination.updateBtnProps('container');  
    ReCharge.Novum.Pagination.updateBtnProps('prev');  
    ReCharge.Novum.Pagination.updateBtnProps('next');  
    ReCharge.Novum.Pagination.updateBtnProps('current');  
    ReCharge.Novum.Helpers.renderUpsells(data.products);  
  }  
  else  
  {  
    const upsellWrapper = document.querySelector(".upsells--wrapper") || null;  
    upsellWrapper.innerHTML = ReCharge.Utils.renderNoProductsLayout();  
  }  
});

Need Help? Contact Us