function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Kent LichtyKent Lichty 

How to pass a class wrapper with Identifier from LWC datatable to Apex on Save?

I am fairly new to Salesforce and really new to learning about LWC, and my organization has tasked me with what I think is a difficult challenge. Because sometimes the (poor) definition of sObjects does NOT make them conducive to user maintenance, I have been tasked to develop a maintenance application (in the form of an LWC datatable) which makes it easier for users to enter/change prices  (expressed in 6 different currencies)  for a given product. In order to do this I needed to create a class wrapper which combines properties from a Product sObject and a Price sObject. Then I need to be able to SAVE those changed prices back into the appropriate Price record, and that is the difficult part with which I am struggling.  But I would think that this would be a common use case.

And while there is a LOT of information about DISPLAYING class wrappers in a datatable (which is quite simple) there is virtually no information about sending the list of modified properties back to an imperative Apex program which could then do the record updating, and that is what I am hoping that somebody has figured out how to do.

I think I am very close to being able to achieve this, and am hoping that somebody can help me get to the finish line.  Here is the approach I am taking:

For what it’s worth, here is my wrapper class which is keyed by a “product” code and then has the associated prices in 6 different currencies, along with some other information about the product.

        public String product{get;set;}
        public String productFamily {get;set;}
        public String productLine {get;set;}
        public String productSubLine {get;set;}
        public String productCategory {get;set;}
        public Decimal priceCad {get;set;
        public Decimal priceEur {get;set;} 
        public Decimal priceGbp {get;set;}
        public Decimal priceInr {get;set;}
        public Decimal priceJpy {get;set;}
        public Decimal priceUsd {get;set;}

And here is a fragment of my displayed list just to amplify the concept; it just shows 2 currency amounts instead of all 6 just to make it clearer.
User-added imageSo I just want to use the same “draft values” approach that is standard for the datatable component, as below:

User-added image
And here is the standard “Save” function (from another project; NOT this one) which I am sure most of you have seen, and which uses the uiRecordApi to implement, without any Apex call. 
handleSave(event) {
        const recordInputs =  event.detail.draftValues.slice().map(draft => {
            const fields = Object.assign({}, draft);
            return { fields };
        });

But in my application I know that I need to imperatively call my Apex class to handle this update, and I can do that.  I am currently only passing back the JSONified values of the “draftValues” and the entire list itself, just to see how that works:

handleSave(event) {
        var draftValuesStr = JSON.stringify(event.detail.draftValues);
        var wrapList = JSON.stringify(this.pricesList);
        updatePriceRecords({updateObjStr: draftValuesStr, wrapList: wrapList});
      }

And this does bring back the “draft” values perfectly for me, as you can see below:

[{"priceUsd":"456.00","priceEur":"765.67","Id":"row-3"}]

And my Apex could handle the price updates if ONLY it had some unique ID (like the product number, in my case), so that is what I am asking how to do.  All the string contains as an ‘ID’ is the row ID, which is not very helpful.  I know that I can bring back the ENTIRE list, and then be able to associate the row ID with the appropriate record in the list and get the product number that way, but that is clearly a horribly inefficient idea, and there has got to be a better way. 

I also considered somehow persisting my list into a CACHE (like a user setting?) so I could retrieve the original list that way, but that does not sound like a good idea either.

Also, I am thinking that I could somehow iterate through the JSONified draft values and insert the product ID appropriately into that list, but I am not sure how I would go about grabbing the product ID out of selected draft record.


If anybody could help me out with this I would really appreciate it.  It seems to me that this would be a very common use case out there.

Thanks very much.
​​​​​​​



 
Abhishek BansalAbhishek Bansal
Hi Kent,

You need to add one more variable in your wrapper class like below:
public Boolean isSelected{get;set;}

Now map the checkboxes with this property so that you can get to know which record is selected. You can pass the complete wrapper list in your apex class and based on this isSelected property you can figure out which product is selecetd. In this way you will also get the unique code.

Let me know if you need any further help on this.

Thanks,
Abhishek Bansal.
Gmail: abhibansal2790@gmail.com
Skype: abhishek.bansal2790
Phone: +917357512102
Kent LichtyKent Lichty
That is awesome! Just what I was looking for ... but let me run another idea by you; please be honest and let me know what you think: I would make a Map object that contains the ROW ID as the key (just sequentially numbering my wrapper class), and then a list of an object consisting of: the currency code and the particular record ID of that currency/price. I would then persist that map in the SESSION CACHE (which I have never used before). Then when my JSON list of the draft values comes back, I could use the row ID in the returned JSON to retrieve the record from the cache and update it using its natural ID. Would that not work? In that way, I would not have to return the list back to my apex. Like I said, I have never used the session cache, and was just wondering what you knew about it. Thanks again! I really appreciate your help.
Abhishek BansalAbhishek Bansal
Yes you can go with this option as well but the best way to map the selected records is to have a boolean variable in the wrapper class and than use this variable to identify the selected records. Wrapper class is designed mainly for this purpose only.
You can contact me for any help on my gmail or skype.
Kent LichtyKent Lichty
Abhishek, I want to try your suggested technique first, but I don't understand when you say "map the checkboxes with this property." Could you please clarify? I don't want my users to have to manually click on the checkbox in the list row to enable this function, but to just click the "Save" button. Thanks again.
Abhishek BansalAbhishek Bansal
Map means to bind the checkbox value with the Boolean variable that we created in our wrapper class.
If user are not going to select them manually then what is the use of showing them in page? Just want to understand what you are doing with these checkboxes.
Kent LichtyKent Lichty
Hi Abishek; I am doing nothing with the check boxes at all, and I apologize if I gave you that impression. I will remove them when I put this into production, as I do not want my users to have to explicitly mark a check box unless that is an absolute requirement. As explained in my above email, here is what I want for the user's experience: 1: Like for any editable field in a datatable, the user can make changes to the 6 price fields. When they tab out of a field after making a change, the "Save" button becomes available at the bottom of the page. 2: When the "Save" button is pressed, I want it to function like the "standard" save/record update behavior for a datatable, but of course I understand that I need to call my apex method imperatively and cannot use the automatic LDS functionality like I could for a normal SObject. 3: My javascript converts the "draft" values JSON to a string and sends it back to my Apex method, which is exactly what I want. Now I just need to determine and update the appropriate price record that corresponds to the row number and the applicable price field (USD, GBP, etc). The complexity is using the row number only to be able to tie back to the appropriate record IDs (in the price object) that need updating. 4: I think I can solve the problem by persisting a Map of indices in the session cache that can be retrieved for this purpose. However, if you can think of a better way to handle this, I appreciate your expertise and advice. Please know how much I appreciate your assistance, and I apologize if I did not explain my use case clearly.
Abhishek BansalAbhishek Bansal
Hi Kent,

If you are not using checkboxes than you can follow the below process:
  1. Instead of having a boolean variable, create one String variable like -> public String productId {get;set;}
  2. Store id of the respective product which should be unique across the rows.
  3. Now in your list that you are sending to your apex controller, send this Id also.
  4. Based on this Id you can update your records.
Kent, may be I am not able to answer you beacuse I have not seen your page. Its better we can connect on a call and resolve all of them at once. Let me know your thoughts on this.

Thanks,
Abhishek Bansal.
Kent LichtyKent Lichty
Hello Abishek; Sorry I am so slow in responding, I have been out of town for a few days. Using the session cache I am able to get my program to work correctly, and I appreciate your help with that. The only remaining issue that I have is that I cannot seem to get the refreshApex() logic to work so that the previously selected editable cells would be cleared out when they are saved, so they are constantly being reselected, unless I refresh the page manually. Here is what I mean: [image: highlight.png] After the user presses the "Save" button (it is the system generated "Save" button for datatables) and the cell value is saved, I want the yellow highlighting to be removed so that the user can continue to add/change other values and then press the "Save" button again without having to manually refresh the page after each page. I assume that the refreshApex() function would do this for me, but I cannot seem to get that to work. The relevant parts of my javascript and HTML code are below. I would truly appreciate any assistance that you could give me about how to force the page to refresh after the Save function completes. *import { LightningElement, wire, track } from 'lwc';import getPriceRecords from '@salesforce/apex/FikeStdPriceBookController.getPriceRecords';import updatePriceRecords from '@salesforce/apex/FikeStdPriceBookController.updatePriceRecords';import { refreshApex } from '@salesforce/apex';import { ShowToastEvent } from 'lightning/platformShowToastEvent';export default class StdPriceBookWorkBench extends LightningElement {@track productCategorySearchKey = '';@track productFamilySearchKey = '';@track productLineSearchKey = '';@track productSubLineSearchKey = '';@track productSearchKey = '';@track productDescriptionSearchKey = '';@track error;@track pricesList;@wire(getPriceRecords, {productCategorySearchKey: '$productCategorySearchKey', productFamilySearchKey: '$productFamilySearchKey', productLineSearchKey: '$productLineSearchKey', productSubLineSearchKey: '$productSubLineSearchKey', productSearchKey: '$productSearchKey', productDescriptionSearchKey: '$productDescriptionSearchKey'})* *wiredTargets({ error, data }) { if (data) { this.pricesList = data; } else if (error) { this.error = error; } }* // This is the standard handleSave function, in which I think the problem is to be found. *handleSave(event) { var draftValuesStr = JSON.stringify(event.detail.draftValues); updatePriceRecords({updateObjStr: draftValuesStr}) .then(result => { this.dispatchEvent( new ShowToastEvent({ title: 'Success', message: result + ' price records have been added or updated.', variant: 'success' }) ); //End of dispatchEvent this.draftValues = []; refreshApex(this.getPriceRecords); }).catch(error => { // Handle error }); }* *Here is the datatable in my HTML File:* * {error} *