3

I'm trying to write a Lightning Web Component (yay, first one).

Still in the "proof of concept; can I get user input to propagate around the page?" stage.

I have a List<MyApexWrapperClass> whose records I'd like to serve as rows of an HTML table (and whose @AuraEnabled properties such as value_for_freeform_column I'd like to have serve as the columnar cell values).

I got the List<MyApexWrapperClass into a JavaScript controller helloworld.js property wrapped_records as follows just fine:

import { LightningElement, track } from 'lwc';
import lwcMagic from '@salesforce/apex/MyWrapperFetcher.lwcMagic';

export default class helloworld extends LightningElement {
    @track wrapped_records;

    doMagic() {
        lwcMagic()
            .then(result => {
                this.wrapped_records = result;
                this.error = undefined;
            })
            .catch(error => {
                this.error = error;
                this.wrapped_records = undefined;
            });
    }

}

I'm working with a situation where I've got @AuraEnabled properties of an Apex class serving as the basis of a JavaScript controller variable I called wrapped_records (note that that's plural -- I'm fetching from an Apex method that returns a List<MyWrapperClass>). Then between <tbody>...</tbody> tags I have a <template for:each={wrapped_records} for:item="w_record"> tagset where there's a <tr key={w_record.unique_name}> within, and td's within those. In the td's, I am trying to do something along the following lines:

<lightning-input 
  type="text" 
  label="Need to enter a manual value?" 
  variant="label-hidden"
  value={w_record.value_for_freeform_column}
  placeholder="type here..."
>
</lightning-input>
You entered:  <em>{w_record.value_for_freeform_column}</em>

The full HTML template helloworld.html is as follows:

<template>
    <template if:false={wrapped_records}>
        <lightning-button
            label="Do The Magic"
            onclick={doMagic}
        ></lightning-button>
    </template>
    <template if:true={wrapped_records}>
        <table class="slds-table slds-table_bordered slds-border_left slds-border_right">
            <thead>
                <tr class="slds-line-height_reset">
                    <th class="" scope="col">
                        <div class="slds-truncate" title="Unique Name">Unique Name</div>
                    </th>
                    <th class="" scope="col">
                        <div class="slds-truncate" title="Freeform">Freeform</div>
                    </th>
                </tr>
            </thead>
            <tbody>
                <template for:each={wrapped_records} for:item="w_record">
                    <tr key={w_record.unique_name}>
                        <th data-label="UniqueName" scope="row" key={w_record.unique_name}>
                            <div class="slds-truncate" title={w_record.unique_name}><b><u>{w_record.unique_name}</u></b></div>
                        </th>
                        <td data-label="Freeform" title={w_record.unique_name} key={w_record.unique_name}>
                            <div class="slds-truncate">
                                <lightning-input 
                                    type="text" 
                                    label="Need to enter a manual value?" 
                                    variant="label-hidden"
                                    value={w_record.value_for_freeform_column}
                                    placeholder="type here..."
                                >
                                </lightning-input>
                                You entered:  <em>{w_record.value_for_freeform_column}</em>
                            </div>
                        </td>
                    </tr>
                </template>
            </tbody>
        </table>
    </template>
    <template if:true={error}>
        There was an error.
    </template>
</template>

So far, my resulting table looks like this:

| Unique Name | Freeform                                       |
| ----------- | ---------------------------------------------- |
| ----------- | ---------------------------------------------- |
| ABC         | (Editable input box displaying "Hello World!") |
|             | You entered: Hello World!                      |
| ----------- | ---------------------------------------------- |
| JKL         | (Editable input box displaying "Hello World!") |
|             | You entered: Hello World!                      |
| ----------- | ---------------------------------------------- |
| XYZ         | (Editable input box displaying "Hello World!") |
|             | You entered: Hello World!                      |
| ----------- | ---------------------------------------------- |

(Note: a MyApexWrapperClass object always instantiates with a value_for_freeform_column value of Hello World!)


What I'd like to be able to do is backspace out the "Hello World!" in row "JKL" and type "Goodbye," and as I type, the "You entered:" immediately underneath it updates and displays, You entered: Goodbye when I finish typing.

| Unique Name | Freeform                                       |
| ----------- | ---------------------------------------------- |
| ----------- | ---------------------------------------------- |
| ABC         | (Editable input box displaying "Hello World!") |
|             | You entered: Hello World!                      |
| ----------- | ---------------------------------------------- |
| JKL         | (Editable input box displaying "Goodbye")      |
|             | You entered: Goodbye                           |
| ----------- | ---------------------------------------------- |
| XYZ         | (Editable input box displaying "Hello World!") |
|             | You entered: Hello World!                      |
| ----------- | ---------------------------------------------- |

I read https://lwc.dev/guide/html_templates#handle-user-input, but it seems to presume that there's a globally scoped variable used as the value for your <lightning-input> (e.g. in this.name = event.target.value;).

My <lightning-input>'s value, however, is a temporary loop reference to a piece of an array variable ({w_record.value_for_freeform_column}).

Is there some sort of code I could write that would let me write a handleInput(event)-like JavaScript controller function capable of assigning event.target.value to such a locally-scoped-in-the-HTML variable?

(Note: I'll want any edits to a property of w_record to either update the corresponding piece of wrapped_records or to be easy to fetch later, since in the bigger picture, my HTML table is a way of rendering a complex data entry form whose final values will need to be sent back to Apex as part of some complex DML operations when the user clicks "all done.")

1

As suggested by sfdcfox you can use, data attributes,

if you dont wanna use data attribute you can use lightning-input name attribute instead.

<template for:each={wrapped_records} for:item="w_record">
                    <tr key={w_record.unique_name}>
                        <th data-label="UniqueName" scope="row" key={w_record.unique_name}>
                            <div class="slds-truncate" title={w_record.unique_name}><b><u>{w_record.unique_name}</u></b></div>
                        </th>
                        <td data-label="Freeform" title={w_record.unique_name} key={w_record.unique_name}>
                            <div class="slds-truncate">
                                <lightning-input name={w_record.unique_name}
                                    type="text" 
                                    label="Need to enter a manual value?" 
                                    variant="label-hidden" onchange={updateFreeFormColum}
                                    value={w_record.value_for_freeform_column}
                                    placeholder="type here..."
                                >
                                </lightning-input>
                                You entered:  <em>{w_record.value_for_freeform_column}</em>
                            </div>
                        </td>
                    </tr>
                </template>

JS:

import { LightningElement, track, api } from 'lwc';

export default class App extends LightningElement {
    @track wrapped_records = [{unique_name:'ABC' , value_for_freeform_column : 'Hello World'} ,
     {unique_name:'JKL' ,value_for_freeform_column : 'Hello World'}  , 
     {unique_name:'XYZ' , value_for_freeform_column : 'Hello World'}];

     updateFreeFormColum(event){
        console.log(event.target.name);

        var foundElement = this.wrapped_records.find(function(element){
            return element.unique_name === event.target.name;
        });        
        foundElement.value_for_freeform_column = event.target.value;

     }
}

Playground Link: https://developer.salesforce.com/docs/component-library/tools/playground/K88RmBKA6/1/edit

  • Thanks -- followup question. Is accessing the equivalent of element.unique_name inside the .find() function going to need to be different when wrapped_records comes out of the result of an Apex returning a List<SomeObject> than when it's written as a JSON literal like in your example? The code works, then element.unique_name goes undefined if I try to wire back in the Apex. It looks like it's buried deep in some sort of Proxy and originalTarget structure? – k.. Nov 13 '19 at 17:23
  • its a proxy right, used for caching, if you use console.log(JSON.stringify(this.wrapped_records)); you will see your records in JSON format – Pranay Jaiswal Nov 13 '19 at 17:27
  • 1
    My errors flipping between the literal-defined wrapped_records and the Apex-populated version ended up being some unrelated typos ... thanks for the stringify() tip! – k.. Nov 13 '19 at 19:08
3

Here's a Playground that demonstrates how to set an onchange handler like what you're asking for:


import { LightningElement, track } from 'lwc';

export default class App extends LightningElement {
    @track rows;
    connectedCallback() {
        this.rows = [{id:0,value:''},{id:1,value:''},{id:2,value:''}];
    }
    handleChange(event) {
        this.rows[event.target.closest('[data-key]').dataset.key].value = event.target.value;
    }
}

<template>
    <template for:each={rows} for:item="item">
        <div key={item.id} data-key={item.id}>
            <input onchange={handleChange} value={item.value} /><br />
            "{item.value}"
        </div>
    </template>
</template>

Basically, we assign the key to an item we can inspect (data-key), then use that to update the array in the controller.

  • What's the connectedCallback() for, and what exactly are you setting with 0, 1, and 2? – k.. Nov 13 '19 at 16:25
  • @k.. connectedCallback is a special method that is called when a component is loaded. The values 0, 1, and 2 are just index values in to the array. You could do it other ways, too, like by a record Id, then you'd need to use Array.prototype.find instead. This is just a simple self-contained example. – sfdcfox Nov 13 '19 at 16:34
  • I got Pranay's variation working a little faster than this one made sense to me at my skill level for JavaScript, so I accepted that one, but I appreciate you being so fast on the ball -- also great information to know, thanks! – k.. Nov 13 '19 at 19:09

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.