1

I have a Lightning Component that displays some Categories and the Offers per Category. I want to achieve "Search" in those Categories at first and then at offers. So, what did i try to do:

  1. Have an attribute to store all the info set by Apex during initialization
  2. Have another attribute to display all the Categories and Offers (Iterate)
  3. Based on user's search value, dynamically change the second attribute. Whenever the search string is empty, use the initial attribute to "restore" all the Categories and Offers. Problem: It seems like the 1st attribute, although it is not manipulated through my js code, it seems like it "loses" its value. For example, user searches with "123" which returns no results and i do "component.set" to the second attribute to show zero results. Then user "clears" the search bar and when i use in my Controller the 1st attribute, its value is [] as it is in the second one!

Code below: CMP

<!-- @Author   :   Christos Kolonis & George Galaios
     @Date     :   24/03/2021
     @Desc     :   Lightning component that shows the available bundled categories and offers for the current user
-->

<aura:component controller="el_bundledOffersController" implements="lightning:availableForFlowScreens,force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global">
    
    <!-- Register Events -->
    <aura:handler name="sampleComponentEvent" event="c:el_OfferMemberConfigurationEvent" action="{!c.handleComponentEvent}"/>
    
    
    <!--Attributes Definitions -->
    <!-- Basket Id -->
    <aura:attribute name="basketId" type="String"/>
    <aura:attribute name="mapList" type="List" />
    <aura:attribute name="initialMapList" type="List" />
    <aura:attribute name="value" type="String" default=""/>
    <aura:attribute name="selectedOfferId" type="String"/>
    <aura:attribute name="offerSelected" type="Boolean" default="false"/>
    <aura:attribute name="nextClicked" type="Boolean" default="false"/>
    
    <!--GG-->
    <!-- attribute for storing the value of the String searched by the user for the Catalogue-->
    <aura:attribute name="searchCatalogueString" type="Boolean" default="false"/>
    <!--GG-->
    
    <!-- attribute for storing current Account -->
    <aura:attribute name="currentAccount" type="String" />
    <!-- attribute for offermembers -->
    <aura:attribute name="offerMembersList" type="List" />
    <!-- Loading spinner Attribute -->
    <aura:attribute name="spinner" type="boolean" default="false"/>
    <!-- Attribute to gey out background -->
    <aura:attribute name="isModalOpen" type="boolean"/>
    <!-- Attribute with stored offers when user presses add -->
    <aura:attribute name="storedSelectedOffer" type="List"/>
    <!-- End of Attributes Definitions -->
    
    <!--loading spinner start... style=Brand Medium (blue dots)-->
    <aura:if isTrue="{!v.spinner}">
        <div aura:id="spinnerId" class="slds-spinner_container">
            <div class="slds-spinner--brand  slds-spinner slds-spinner--large slds-is-relative" role="alert">
                <span class="slds-assistive-text">Loading</span>
                <div class="slds-spinner__dot-a"></div>
                <div class="slds-spinner__dot-b"></div>
            </div>
        </div>
    </aura:if>
    <!-- Loading spinner end--> 
    
    <!-- Define Handlers -->
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>  
    
    <!-- End of Handlers Definition -->
    <!-- Bundled Offers Catalogue Section -->
    <aura:if isTrue="{!not(v.nextClicked)}">
        <lightning:card footer="" title="{!$Label.c.el_Bundled_Offers_Catalogue}">
            <!--Search Offer-->
            <div class="slds-size_1-of-2">
                    <lightning:input type="search"
                                     aura:id="searchinput"
                                     required="{!v.required}"
                                     fieldLevelHelp="{!v.fieldLevelHelp}"
                                     value="{!v.searchCatalogueString}"
                                     onchange="{!c.searchOffer}"
                                     placeholder="Search..."
                                     class="leftspace"/>
                </div>
            <!--End of Search Offer-->
            <lightning:layout>
                <lightning:layoutItem padding="around-small">
                    <aura:iteration items="{!v.mapList}" var="map">
                        <lightning:accordion allowMultipleSectionsOpen="true">
                            <lightning:accordionSection
                                                        name="{!map.category.Id}"
                                                        label="{!map.category.Name}">
                                <aura:set attribute="body">
                                    <aura:iteration items="{!map.offers}" var="offer"> 
                                        <ui:inputCheckbox class="myCheckbox" aura:id="offer" name="{!offer.value}" label="{!offer.label}" change="{!c.onCheck}"/>   
                                    </aura:iteration>
                                </aura:set>
                            </lightning:accordionSection>
                        </lightning:accordion>
                    </aura:iteration>
                    <!-- Button to move to the Configuration of the Members -->
                </lightning:layoutItem>
                
            </lightning:layout>
            
        </lightning:card>
        <div class="slds-col slds-size_1-of-1">
            <div class="slds-p-top_medium"></div>
        </div> 
        <lightning:button variant="brand" label="{!$Label.c.el_Next}" title="{!$Label.c.el_Next}"
                          onclick="{!c.handleNext}" disabled="{!not(v.offerSelected)}" class="slds-float_right"/>
    </aura:if>

Controller JS

// initCmp.js
({
    //Function on Components Load...
    doInit: function(component,conrtoller,helper) {
        component.set("v.spinner", true); //Set Loading Spinner to True
        // Call the server-side controller to get Eligible Bundled Offers and Categories
        var action = component.get("c.getEligibleBundledOffersCategories");
        action.setParams({ basketId : component.get("v.basketId") });
        // Create a callback that is executed after the server-side action returns
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                // set the mapList with the value returned from the server
                component.set("v.mapList", response.getReturnValue());
                component.set("v.initialMapList", response.getReturnValue());
                component.set("v.spinner", false); //Set Loading Spinner to False
            }
            else if (state === "INCOMPLETE") {
                // do something
            }
                else if (state === "ERROR") {
                    var errors = response.getError();
                    if (errors) {
                        if (errors[0] && errors[0].message) {
                            console.log("Error message: " + 
                                        errors[0].message);
                        }
                    } else {
                        console.log("Unknown error");
                    }
                }
        });
        $A.enqueueAction(action);
    },

//@ggalaios: Function when user searches for Bundled Offer
    searchOffer: function(component,conrtoller,helper) {
        component.set("v.spinner", true); //Set Loading Spinner to True
        var offersCategories = component.get("v.initialMapList"); //get the list of the returned by APEX Categories & Offers
        //alert('Start and OfferCategories are: ' +JSON.stringify(offersCategories));
        
        //alert('Start and OfferCategories are: ' +JSON.stringify(offersCategories));
        //If searchSpec null or empty...
        if(!component.get("v.searchCatalogueString") || component.get("v.searchCatalogueString").length === 0) {
            //if searchSpec has a value
            //no actions needed...
        } else {
            //Iterate through each Category
            for (var i=0; i < offersCategories.length; i++) {
                //If Category Name included as a substring, do not change anything
                console.log('Categ. name is: ' +offersCategories[i].category.Name);
                
                if(offersCategories[i].category.Name.includes(component.get("v.searchCatalogueString"))) {
                    //alert('Category included in searchSpec!!!');
                    //No actions needed...
                } else {
                    //alert('Category not included in searchSpec');
                    if (offersCategories[i].offers.length > 0) {
                        for (var j=0; j<offersCategories[i].offers.length; j++) {
                            if (offersCategories[i].offers.length > 0) {
                                //alert('length of offers is: ' +offersCategories[i].offers.length);
                                if(offersCategories[i].offers[j].label.includes(component.get("v.searchCatalogueString"))) {
                                    //alert('searchSpec included in offer name' +offersCategories[i].offers[j].label);
                                } else {
                                    //alert('splice ' +offersCategories[i].offers[j].label);
                                    offersCategories[i].offers.splice(j,1); //Remove Offer from the search results
                                    j=j-1; //reduce j by 1, since the length of the Offers is reduced
                                }
                            }
                        }
                        if(offersCategories[i].offers.length===0) {
                            //alert('Splice categ');
                            offersCategories.splice(i,1); //Remove Category from the Search results
                            i=i-1;
                        }
                    } else {
                        offersCategories.splice(i,1); //Remove Category from the Search results
                        i=i-1;
                    }
                }
                
            }
        }
        component.set("v.mapList", offersCategories);
        component.set("v.spinner", false); //Set Loading Spinner to False
    },
    
});

The problem is that the attribute "initialMapList" although i never do a component.set from the js side, it gets manipulated! and the crazy thing is that when i try to iterate through this list in the cmp code (html), it gets displayed properly!!! Don't know what i am missing here... Any ideas?

5

Try to change

component.set("v.mapList", response.getReturnValue());
component.set("v.initialMapList", response.getReturnValue());

by

component.set("v.mapList", response.getReturnValue());
component.set("v.initialMapList", JSON.parse(JSON.stringify(response.getReturnValue())));

If that worked, your problem was that you had two variables referencing the same area of memory, so when you touched one, you touched the other.

With the JSON.parse/stringify you create a deep-clone of the result (actually, you convert whatever you return into a string and back to a new object / array)

Please don't use it if you return a Simple type (something different from an Object / Array, such as String, Decimal, etc). Either way, you would not need to "copy" those, because they won't have this problem ;)

4
  • I know what you're talking about, and you know what you're talking about, but you should explain why this matters so they know what you're talking about. – sfdcfox Apr 15 at 20:50
  • I was editing it now ;) – Sergio Alcocer Apr 15 at 20:51
  • An array would have this same problem. Only immutable primitives don't. – Phil W Apr 15 at 21:42
  • Thank you very much guys :) Solution work and explanation was amazing. Totally understood the issue. – Giorgos Galaios Apr 16 at 6:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.