javascript 如何在自定义Lightning数据表上保存更新的嵌入式选择列表?

k3fezbri  于 2023-05-21  发布在  Java
关注(0)|答案(1)|浏览(118)

bounty还有6天到期。此问题的答案有资格获得+200声望奖励。Mr.DevEng正在寻找典型答案

我正在使用一个“自定义数据表”的解决方案,以修改数据表中的选择列表值。项目代码可参考此处:https://live.playg.app/play/picklist-in-lightning-datatable
我已经进行了更改,以便可以从自定义对象中检索数据:Payment__c,我正在尝试修改Payment_Status__c字段的选择列表值。我的“调试”方法是在更新过程中创建大量的console.log语句来验证数据。Picklist值当前是硬编码的(尚未弄清楚如何从SF动态拉取)。对单个单元格的内联编辑工作正常,我也能够保存这些值(尽管只有在我执行手动页面刷新时才能反映更改)。选择列表选择正在工作,但我无法将当前选择的选择列表值保存到数据表中。
我认为,picklist选择更改的预期触发器事件-'valueselect'没有被激发,并且当进行新的picklist选择时,handleSelection方法没有接收到此事件。
Salesforce上使用的lightning组件是c-customDatatableDemo:

customDatatableDemo.js
import { LightningElement, track, wire } from 'lwc';
import getPayments from '@salesforce/apex/PaymentController.getPayments';
import saveRecords from '@salesforce/apex/PaymentController.saveRecords';
import { updateRecord } from 'lightning/uiRecordApi';
import { refreshApex } from '@salesforce/apex';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';

export default class CustomDatatableDemo extends LightningElement {
    @track data = [];
    //have this attribute to track data changed
    //with custom picklist or custom lookup  
    @track draftValues = [];

    @track lastSavedData = [];

    connectedCallback() { 
        this.columns = [
            {
            label: 'Name',
            fieldName: 'Name',
            editable: false
        }, {
            label: 'Invoice Number',
            fieldName: 'Invoice_Number__c',
            editable: true
        }, {
            label: 'Invoice Amount',
            fieldName: 'Invoice_Amount__c',
            type: 'currency',
            editable: true
        }, {
            label: 'Invoice Date',
            fieldName: 'Invoice_Date__c',
            type: 'date',
            editable: true
        }, {
            label: 'Payment Status',
            fieldName: 'Payment_Status__c',
            type: 'picklist',
            typeAttributes:
            {
                placeholder: 'Choose Status',
                options: [
                    { label: 'Needs to Be Paid', value: 'Needs to Be Paid' },
                    { label: 'Issued', value: 'Issued' },
                    { label: 'Voided', value: 'Voided' },
                ] // List of Payment Status picklist options
                , value: {fieldName: 'Payment_Status__c' } // default value for picklist
                , context: {fieldName: 'Id' } // binding Payment Id with context variable to be returned back
            }
            },
            {
                label: 'Description', fieldName: 'Work_Description__c', type: 'text', editable: true
            }];
    
        //  Get Payments data
        getPayments()
        .then(result => {
            this.data = result;
            this.error = undefined;
        })
        .catch(error => {
            this.error = error;
            this.data = undefined;
        })

        // Save last saved copy
        this.lastSavedData = JSON.parse(JSON.stringify(this.data));
    }   

    updateDataValues(updateItem) {
        console.log('START--updateDataValues()');
        let copyData = [... this.data];
        copyData.forEach(item => {
            if (item.Id === updateItem.Id) {
                for (let field in updateItem) {
                    console.log('updateDataValues() item.Id = ' + JSON.stringify(updateItem.Id));
                    console.log('updateItem[field] = ' + JSON.stringify(updateItem[field]));
                    item[field] = updateItem[field];
                    console.log('UPDATED--item[field] = ' + updateItem[field]);
                }
            }
        });

        //write changes back to original data
        this.data = [...copyData];
        console.log('this.data = ' + JSON.stringify(this.data));
        let tempData = [...this.data];
        //console.log('tempData = ' + JSON.stringify(tempData));
        console.log('END--updateDataValues()');
    }

    updateDraftValues(updateItem) {
        console.log('START--updateDraftValues()');
        console.log('stringify draft updateItem = ' + JSON.stringify(updateItem));
        let draftValueChanged = false;
        let copyDraftValues = [...this.draftValues];
        //store changed value to do operations
        //on save. This will enable inline editing &
        //show standard cancel & save button
        let i = 0;
        copyDraftValues.forEach(item => {
            if (item.Id === updateItem.Id) {
                i++;
                console.log('i = ' + i);
                for (let field in updateItem) {
                    console.log(i + '. UpdateDraftValues--item id if selected...item Id = ' + item.Id + ' & item value = ' + item.value);
                    item[field] = updateItem[field];
                    console.log('item[field] = ' + updateItem[field]);
                }
                draftValueChanged = true;
                console.log('draftValueChanged = TRUE');
            }
        });

        //draftValueChanged = true;

        if (draftValueChanged) {
            console.log('YESdraftValueChanged');
            console.log('copyDraftValues = ' + JSON.stringify(copyDraftValues));
            this.draftValues = [...copyDraftValues];
            //console.log('draftValues = ' + JSON.stringify(draftValues));
        } else {
            console.log('NOdraftValue!Changed');
            this.draftValues = [...copyDraftValues, updateItem];
            let testDraftValues = {... this.draftValues};
            console.log('JSON.stringify(testDraftValues) = ' + JSON.stringify(testDraftValues));
        }
        console.log('STOP--updateDraftValues()')
    }

    //listener handler to get the context and data
    //updates datatable
    picklistChanged(event) {
        console.log('START--picklistChanged()');
        console.log('EVENT type - ' + event.type);
        event.stopPropagation();
        let dataReceived = event.detail.data;
        
        let updatedItem = { ...dataReceived };
        console.log('picklistChanged()...updatedItem = ' + JSON.stringify(updatedItem));
        this.updateDraftValues(updatedItem);
        this.updateDataValues(updatedItem);
        /* console.log('event.value = ' + event.value);
        this.value = event.target.value;
        event.stopPropagation();
        let dataReceived = event.detail.data;
        let updatedItem = { ...dataReceived };
        console.log('updatedItem.context ' + updatedItem.context);
        console.log('updatedItem.value ' + updatedItem.value);
        console.log('updatedItem = ' + JSON.stringify(updatedItem));
        this.updateDraftValues(updatedItem);
        this.updateDataValues(updatedItem);
        console.log('picklistChanged() = ' + JSON.stringify(updatedItem)); */
        console.log('STOP--picklistChanged()');
    }

    handleSelection(event) {
        console.log('START--handleSelection()');
        this.updateDraftValues(event.detail.draftValues[0]);
        console.log('this.updateDraftValues(event.detail.draftValues[0]);')
        /* event.stopPropogation();
        let dataReceived = event.detail.data;
        let updatedItem = { ...dataReceived };
        this.updateDraftValues(updatedItem);
        this.updateDraftValues(updatedItem); */
        console.log('STOP--handleSelection() = ' + JSON.stringify(updatedItem));
    }

    //handler to handle cell changes & update values in draft values
    handleCellChange(event) {
        console.log('START--handleCellChange()');
        console.log('handleCellChange');
        this.updateDraftValues(event.detail.draftValues[0]);
        console.log('handleCellChange value = ' + JSON.stringify(this.updateDraftValues));
        console.log('END--handleCellChange()');
    }

    handleSave(event) {
        if (event.type === 'picklistchanged'){

        }
        console.log('START--handleSave');
        console.log('Updated items = ', this.draftValues);
        // save last saved copy
        this.lastSavedData = JSON.parse(JSON.stringify(this.data));
        console.log('this.lastSavedData = ' + JSON.stringify(this.lastSavedData));

        this.fldsItemValues = event.detail.draftValues;
        console.log('this.fldsItemValues = ' + JSON.stringify(this.fldsItemValues));

        const inputsItems = this.fldsItemValues.slice().map(draft => {
            const fields = Object.assign({}, draft);
            console.log('JSON.stringify() fields ' + JSON.stringify(fields));
            return { fields };
        });

       // Show toast after successful update
        const promises = inputsItems.map(recordInput => updateRecord(recordInput));
        Promise.all(promises).then(res => {
            this.dispatchEvent(
                new ShowToastEvent({
                    title: 'Success',
                    message: 'Records Updated Successfully!!',
                    variant: 'success'
                })
            );
            this.fldsItemValues = [];
            return this.refresh();
        }).catch(error => {
            this.dispatchEvent(
                new ShowToastEvent({
                    title: 'Error',
                    message: 'An Error Occured!!',
                    variant: 'error'
                })
            );
        }).finally(() => {
            // Clear draft values
            this.draftValues = [];
        });

        
        // Refresh the window after successful save
        //window.open('url','_self');
        //document.location.reload(true);
        
        //cmp.find("table-component-id").set("v.draftValues", null);
        console.log('END--handleSave');
    }

    handleCancel(event) {
        //remove draftValues & revert data changes
        this.data = JSON.parse(JSON.stringify(this.lastSavedData));
        this.draftValues = [];
    }

    async refresh() {
        console.log('async refresh');
        await refreshApex(this.data);
        //this.connectedCallback();
    }
}


customDatatableDemo.html

<template>
    <lightning-card title="Invoicing" icon-name="custom:custom17">
        <div class="slds-var-m-around_medium">
            <template if:true={data}>
                <c-custom-data-table
                object-api-name="Payment__c"
                    key-field="Id"
                    data={data}
                    value=""
                    show-row-number-column
                    columns={columns}
                    onpicklistchanged={picklistChanged}
                    onvalueselect={handleSelection}
                    draft-values={draftValues}
                    oncellchange={handleCellChange}
                    onsave={handleSave}
                    oncancel={handleCancel}>
                </c-custom-data-table>
                <template if:true={data.error}></template>
            </template>
        </div>
    </lightning-card>
    <p>Selected value is: {value}</p>
</template>


customDataTable.js

import LightningDatatable from 'lightning/datatable';
//import the template so that it can be reused
import DatatablePicklistTemplate from './picklist-template.html';
import { loadStyle } from 'lightning/platformResourceLoader';
import CustomDataTableResource from '@salesforce/resourceUrl/CustomDataTable';

export default class CustomDataTable extends LightningDatatable {
    static customTypes = {
        picklist: {
            template: DatatablePicklistTemplate,
            typeAttributes: ['label', 'placeholder', 'options', 'value', 'context'],
        },
    
    };

    constructor() {
        super();
        Promise.all([
            loadStyle(this, CustomDataTableResource),
        ]).then(() => {})
    }
}



picklist-template.html (Same folder as customDataTable)
<template>
    <c-datatable-picklist label={typeAttributes.label} value={typeAttributes.value}
        placeholder={typeAttributes.placeholder} options={typeAttributes.options} context={typeAttributes.context}>
    </c-datatable-picklist>
</template>


datatablePicklist.js
import { LightningElement, api, track } from 'lwc';

export default class DatatablePicklist extends LightningElement {
    @api label;
    @api placeholder;
    @api options;
    @api value;
    @api context;

    handleChange(event) {
        //show the selected value on UI
        this.value = event.detail.value;

        //fire event to send context and selected value to the data table
        this.dispatchEvent(new CustomEvent('picklistchanged', {
            composed: true,
            bubbles: true,
            cancelable: true,
            detail: {
                data: { context: this.context, value: this.value }
            }
        }));
    }
}


datatablePicklist.html
<template>
    <div class="picklist-container">
        <lightning-combobox name="picklist" label={label} value={value} placeholder={placeholder} options={options}
        onchange={handleChange}></lightning-combobox>}
    </div>
</template>


lwcEditSaveRow.js

import { LightningElement, wire, track } from 'lwc';
import getAccounts from '@salesforce/apex/lwcEditSaveRowCtrl.getAccounts';
import { updateRecord } from 'lightning/uiRecordApi';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { refreshApex } from '@salesforce/apex';

const columns = [
    {
        label: 'Name',
        fieldName: 'Name',
        type: 'text',
    }, {
        label: 'Phone',
        fieldName: 'Phone',
        type: 'phone',
        editable: true,
    }, {
        label: 'Industry',
        fieldName: 'Industry',
        type: 'text',
        editable: true,
    }, {
        label: 'Type',
        fieldName: 'Type',
        type: 'text',
        editable: true
    }, {
        label: 'Description',
        fieldName: 'Type',
        type: 'text',
        editable: true
    }
    
];
export default class LwcEditSaveRow extends LightningElement {
    columns = columns;
    @track accObj;
    fldsItemValues = [];

    @wire(getAccounts)
    cons(result) {
        this.accObj = result;
        if (result.error) {
            this.accObj = undefined;
        }
    };

    saveHandleAction(event) {
        this.fldsItemValues = event.detail.draftValues;
        const inputsItems = this.fldsItemValues.slice().map(draft => {
            const fields = Object.assign({}, draft);
            return { fields };
        });

       
        const promises = inputsItems.map(recordInput => updateRecord(recordInput));
        Promise.all(promises).then(res => {
            this.dispatchEvent(
                new ShowToastEvent({
                    title: 'Success',
                    message: 'Records Updated Successfully!!',
                    variant: 'success'
                })
            );
            this.fldsItemValues = [];
            return this.refresh();
        }).catch(error => {
            this.dispatchEvent(
                new ShowToastEvent({
                    title: 'Error',
                    message: 'An Error Occured!!',
                    variant: 'error'
                })
            );
        }).finally(() => {
            this.fldsItemValues = [];
        });
    }

   
    async refresh() {
        await refreshApex(this.accObj);
    }
}

lwcEditSaveRow.html

<template>
    <lightning-card>
        <div class="slds-m-around_medium">
        <h3 class="slds-text-heading_medium"><lightning-icon icon-name="custom:custom84" size="small"></lightning-icon> <strong style="color:#270086; font-size:13px; margin-right:5px;"> How to inline Edit/Save Rows With Lightning Datatable in Lightning Web Component (LWC) </strong></h3>
        <br/><br/>
        <template if:true={accObj.data}>
            <lightning-datatable key-field="Id" 
            data={accObj.data} 
            columns={columns} 
            onsave={saveHandleAction}
            draft-values={fldsItemValues} 
            hide-checkbox-column 
            show-row-number-column>
           </lightning-datatable>
        </template>
        <br/>
         <br/>
   <!--Start RelatedTopics Section-->
<div style="border:1px #ddd solid; padding:10px; background:#eee; margin:40px 0;">
            
            <p data-aura-rendered-by="435:0"><img src="https://www.w3web.net/wp-content/uploads/2021/05/thumbsUpLike.png" width="25" height="25" style="vertical-align:top; margin-right:10px;" data-aura-rendered-by="436:0"><strong data-aura-rendered-by="437:0"><span style="font-size:16px; font-style:italic; display:inline-block; margin-right:5px;">Don't forget to check out:-</span><a href="https://www.w3web.net/" target="_blank" rel="noopener noreferrer" style="text-decoration:none;" data-aura-rendered-by="440:0">An easy way to learn step-by-step online free Salesforce tutorial, To know more Click  <span style="color:#ff8000; font-size:18px;" data-aura-rendered-by="442:0">Here..</span></a></strong></p>
 
            <br/><br/>
            <p data-aura-rendered-by="435:0"><img src="https://www.w3web.net/wp-content/uploads/2021/07/tickMarkIcon.png" width="25" height="25" style="vertical-align:top; margin-right:10px;" data-aura-rendered-by="436:0"><strong data-aura-rendered-by="437:0"><span style="font-size:17px; font-style:italic; display:inline-block; margin-right:5px; color:rgb(255 128 0);">You May Also Like →</span> </strong></p>
            <div style="display:block; overflow:hidden;"> 
                <div style="width: 50%; float:left; display:inline-block">
                    <ul style="list-style-type: square; font-size: 16px; margin: 0 0 0 54px; padding: 0;"> 
                        <li><a href="https://www.w3web.net/lwc-get-set-lightning-checkbox-value/" target="_blank" rel="noopener noreferrer">How to get selected checkbox value in lwc</a></li>
                        <li><a href="https://www.w3web.net/display-account-related-contacts-in-lwc/" target="_blank" rel="noopener noreferrer">how to display account related contacts based on AccountId in lwc</a></li>
                        <li><a href="https://www.w3web.net/create-lightning-datatable-row-actions-in-lwc/" target="_blank" rel="noopener noreferrer">how to create lightning datatable row actions in lwc</a></li>
                        <li><a href="https://www.w3web.net/if-and-else-condition-in-lwc/" target="_blank" rel="noopener noreferrer">how to use if and else condition in lwc</a></li>
                        <li><a href="https://www.w3web.net/get-selected-radio-button-value-and-checked-default-in-lwc/" target="_blank" rel="noopener noreferrer">how to display selected radio button value in lwc</a></li>
                    </ul>
            </div>
 
            <div style="width: 50%; float:left; display:inline-block">
                    <ul style="list-style-type: square; font-size: 16px; margin: 0 0 0 54px; padding: 0;"> 
                        <li><a href="https://www.w3web.net/display-account-related-contacts-lwc/" target="_blank" rel="noopener noreferrer">display account related contacts based on account name in lwc</a></li>
                        <li><a href="https://www.w3web.net/create-lightning-datatable-row-actions-in-lwc/" target="_blank" rel="noopener noreferrer">how to insert a record of account Using apex class in LWC</a></li>
                        <li><a href="https://www.w3web.net/fetch-picklist-values-dynamic-in-lwc/" target="_blank" rel="noopener noreferrer">how to get picklist values dynamically in lwc</a></li>
                        <li><a href="https://www.w3web.net/edit-save-and-remove-rows-dynamically-in-lightning-component/" target="_blank" rel="noopener noreferrer">how to edit/save row dynamically in lightning component</a></li>
                        <li><a href="https://www.w3web.net/update-parent-object-from-child/" target="_blank" rel="noopener noreferrer">update parent field from child using apex trigger</a></li>
                    </ul>
                </div>
               <div style="clear:both;"></div> 
               <br/>
                <div class="youtubeIcon">
                    <a href="https://www.youtube.com/channel/UCW62gTen2zniILj9xE6LmOg" target="_blank" rel="noopener noreferrer"><img src="https://www.w3web.net/wp-content/uploads/2021/11/youtubeIcon.png" width="25" height="25" style="vertical-align:top; margin-right:10px;"/> <strong>TechW3web:-</strong> To know more, Use this <span style="color: #ff8000; font-weight: bold;">Link</span> </a>
                </div>
    </div>
 
</div>
 
  <!--End RelatedTopics Section-->
    </div>
    </lightning-card>
</template>

在datatable中更改和保存非picklist值的示例:Changing and saving non-picklist value
更改和保存选择列表值的示例:Changing and saving picklist value (1)
控制台输出的最后一位:Changing and saving picklsit value (2)
由于这是我第一次使用Lightning Web组件,我将非常感谢您提供的任何帮助。先谢谢你了。

enyaitl3

enyaitl31#

我能够让这个自定义数据表组件为我正在开发的东西工作,以有效地将记录绑定为行。这里我注意到两个不同之处:

  • 我没有为组件的HTML声明包括onValueselect和oncellchange方法,只有onpicklistchanged。
  • 您可能希望使用自定义的服务器端控制器方法,而不是内置的uiRecordApi->updateRecord方法。我觉得这可能不适合数据表中的行数据。在我的实现中,我可以简单地将draftValues传递给服务器端方法,该方法具有sObject列表的单个参数:
async saveRecords(event){
     const updatedFields = event.detail.draftValues;
     this.draftValues = [];
     this.showSpinner = true
     try{
         await saveRelatedRecords({sObjs: updatedFields}) // server side save
             .then((result) => {
                 this.updateMessage = result;
             })

         this.showSpinner = false;
         if(this.updateMessage == 'success'){
             this.showToast('Success', 'Record(s) Updated', 'success');
         } else{
             this.showToast('Error', this.updateMessage, 'error');
         }
         await refreshApex(this._wiredRecordData);
         this.unsavedData = this.records;
     } catch (error) {
         this.showSpinner = false;
         this.showToast('Error while updating or refreshing records', error.body.message, 'error');
     }
 }

关于你的数据没有刷新,我建议你连接你的getPayments方法,这样你就可以总是从服务器端得到一个新的集合,而不是试图跟踪和维护客户端的数据更改,比如:

_wiredRecordData;
@wire(getRelatedRecords)
relatedRecords(getRecsResult){
    const { data, error } = getRecsResult;
    this._wiredRecordData = getRecsResult;
    if(data){
        this.records = data;
    }
}

然后在保存方法中成功吐司之后,可以使用await refreshApex(_wiredRecords);和lwc knows根据连接重新渲染(或者至少我认为它是这样工作的)。
最后,这里是服务器端控制器方法,我必须动态生成列标题信息,因此不需要将其硬编码到组件中。但是,我确实需要修改一下自定义组件,以包含一个新的“fieldapi”属性,以便客户端方法知道在onpicklistchanged操作期间为更改的值设置什么字段。

@AuraEnabled(cacheable=true)
public static String getColumnHeaders(String sObjAPI, String fieldAPIs)
{
    List<ColumnHeaderInfo> colHeaders = new List<ColumnHeaderInfo>();
    Schema.DescribeSObjectResult sObjDesc = Schema.getGlobalDescribe().get(sObjAPI).getDescribe();
    Boolean objIsUpdateable = sObjDesc.isUpdateable();
    Map<String, Schema.SObjectField> objFields =  sObjDesc.fields.getMap();
    for(String field: fieldAPIs.split(','))
    {
        if(!objFields.keySet().contains(field.toLowerCase())){
            continue;
        }
        Schema.DescribeFieldResult fieldDesc = objFields.get(field).getDescribe();
        if(!fieldDesc.isAccessible()){
            continue;
        }
        ColumnHeaderInfo colHeader = new ColumnHeaderInfo();
        colHeader.label = fieldDesc.getLabel();
        colHeader.fieldName = fieldDesc.getName();
        colHeader.editable = fieldDesc.isUpdateable() && sObjDesc.isUpdateable();
        colHeader.type_x  = fieldDesc.getType().name().toLowerCase();
        
        if(colHeader.type_x == 'picklist')
        {
            colHeader.type_x = 'picklist';
            List<Schema.PicklistEntry> picklistValues = fieldDesc.getPicklistValues();
            colHeader.typeAttributes = new TypeAttributes();
            colHeader.typeAttributes.options = new List<Option>();
            for(Schema.PicklistEntry ple: picklistValues)
            {
                Option opt = new Option();
                opt.value = ple.getValue();
                opt.label = ple.getLabel();
                colHeader.typeAttributes.options.add(opt);
            }
            colHeader.typeAttributes.context = new FieldName();
            colHeader.typeAttributes.context.fieldName = 'Id';
            colHeader.typeAttributes.value = new FieldName();
            colHeader.typeAttributes.value.fieldName = fieldDesc.getName();
            colHeader.typeAttributes.fieldapi = fieldDesc.getName();
        }
        // multi-select picklist fields not supported so make them read-only
        if(colHeader.type_x == 'multipicklist'){
            colHeader.editable = false;
        }
        colHeaders.add(colHeader);
    }

    return JSON.serialize(colHeaders).replaceAll('type_x', 'type');
}

private class ColumnHeaderInfo
{
    public String label;
    public String fieldName;
    public String type_x;
    public Boolean editable;
    public TypeAttributes typeAttributes;
}

private class TypeAttributes
{
    public String placeholder;
    public List<Option> options;
    public FieldName value;
    public FieldName context;
    public FieldName label;
    public FieldName tooltip;
    public String fieldapi;
    public String target;
}

private class Option
{
    public String label;
    public String value;
}

private class FieldName
{
    public String fieldName;
}

相关问题