Remove specific index element from listView

Hello, I have a dynamic listView in which there is an add button to add new row and a remove button for each row to remove that specific row. Can anyone help me to remove that specific row, how to remove it from listView ? ListView is not loaded from database. Currently I am able to remove the last element only by decreasing row count.
image

Thank you.

1 Like

Hi @Vinkal, have you tried this documentation link?

Hey @TabraizAhmed , Thanks for replying.
Yes I have tried this document, But it is by using the API request, right? Actually I need to remove it just from local for user friendly environment. Like we do in java script, remove an object from an array. I need like that to remove one specific element from listView without using API request.

Hi @Vinkal, I recommend you write a JS query and bind it against your remove button. You will be glad to know that you can access this "i" variable in your JS query. For example, you can access the "i" variable by using {{ i }} in your JS query. Here is the complete guide about Scripting Retool.
Thanks

Hey @TabraizAhmed , I have tried that too, but not getting js function to remove element from listView, If you know which function can I use, please share it. Thanks

Hello, Can anyone found the solution for this?
Thank you

@Vinkal I just had to find a solution to this for a project, and managed to do it pretty directly by using temporary states and JS queries/transformations.

First, I'll briefly describe the approach here:

  1. To add a row, save the listView component values to a temporary state, then increment the listView size, then refill the listView from the temporary state
  2. To delete a row, save the listView component values to a temporary state, then remove the row from the JSON in the temporary state (not from the listView directly), then decrement the listView size, then refill the listView from the temporary state

And, quick note: I'm new to JavaScript, so it's likely some of the efficiency in the code could be improved -- and I'd welcome any feedback on my code

Step 1: Create your components and states

  1. Create a temporary state to store the listView data (tempData), and set the initial value to a simple JSON object which will store the data of your rows. For the form in your screenshot above, it could be simply {"rows": [{"name": null}]} -- note: it's important that the rows are represented as an indexed array of objects, with key-value pairs for each relevant component.
  2. Create a temporary state to store the listView row count (tempNumRows), and set the initial value to 1
  3. Create your listView (listView1) and set Number of Rows to {{ tempNumRows.value }}
  4. Add your text input component (textInput1) to your listView
  5. Add your remove button (buttonRemove) to your listView
  6. Add your add button (buttonAdd) outside of your listView

Now comes the fun scripting!

Step 2: Create your scripts

First, create a new JavaScript transformer (getCurrentData) and enter the following code:

var formData = {{ listView1.data }};

var currentData = {
  "rows": []
};

// loop through the listView data and grab the current value for each component
for (let i = 0; i < formData.length; i++) {
  var name = formData[i]["textInput1"];
  var newRow = {
    "name": name
  };
  
  currentData.rows.push(newRow);
}

return currentData;

Next, create a new JavaScript query (addNewRow) and enter the following code:

function addRow() {
  var currentData = getCurrentData.value;
  
  var emptyRow = {
    "name": null
  };

	currentData.rows.push(emptyRow);
  
  var currentDataSize = Object.keys(currentData.rows).length;
  
  tempData.setValue(currentData);
  
  tempNumRows.setValue(currentDataSize);
}

addRow();

Now, create a new JavaScript query (deleteCurrentRow) and enter the following code:

function deleteRow() {  
  var currentData = getCurrentData.value;
  
  // use the built-in i variable, which resolves to the current row of the button that triggers this code
  currentData.rows.splice(i, 1);
  
  var currentDataSize = Object.keys(currentData.rows).length;
  
  tempData.setValue(currentData);
  
  tempNumRows.setValue(currentDataSize);
}

deleteRow();

Finally, we'll need a script to refill the listView from the temporary state when you're done making changes.

Create a new JavaScript query (refillForm) and enter the following code:

for (let i = 0; i < Object.keys(tempData.value.rows).length; i++) {
  // create a setValue statement for each nested component you need to fill
  textInput1[i].setValue(tempData.value.rows[i].name);
};

Step 3: Set the triggers for your JS code.

  1. In both the addNewRow and deleteCurrentRow queries, set the On Success Trigger after the query runs to refillForm -- this will ensure the form refills properly, and only after the rows have been added/deleted (which, by default will clear the values of the listView components)
  2. Create an Event handler for buttonAdd. Select the following options: Event: Click, Action: Trigger Query, Query: addNewRow
  3. Create an Event handler for buttonRemove. Select the following options: Event: Click, Action: Trigger Query, Query: deleteCurrentRow
  4. Optionally, you could also disable the Remove button when only one row is present by setting Disable when to {{ Object.keys(form.data).length == 1 }}

Hope this helps! Took a little trial and error, but I'm pleased with the results, personally.

10 Likes

Hi @david.mays ,
I really appreciate your efforts and for the help. I will try this for sure.
Thanks a ton!

1 Like

Hey, it's works for me perfectly.
Thanks!

1 Like

Hi @david.mays and @Vinkal ,

Thank you for sharing your thoughts here. The solution is very useful.
In my use case, there's still one thing I couldn't figure out and would like to ask for your advice. The listview needs to be reset (back to 1 row with empty input) whenever the user opens the modal with the listview. I tried to create a javascript to set all the temp states to the initial values and retrigger the refillForm.

tempNumRows.setValue(1);
tempData.setValue({"rows":[{"name":null}]});

refillForm.trigger();

Though the listview row is back to only one row, the value entered in the name input is not cleared. Not sure how to fix the problem.
Please let me know if you have any thoughts on this. Thank you.

hi @doris.l
I think you can clear that input by using javascript as textInput1.setValue('').
I hope this could help you!

Thank you for reaching out.
Regards, Vinkal

Hi @Vinkal,

Thank you for replying. Using setValue to control the components in the listview doesn't work.

@doris.l I'm not sure i this is the best solution, but would it work if you made your script async? Something like:

await tempNumRows.setValue(1);
await tempData.setValue({"rows":[{"name":null}]});

await refillForm.trigger();

This way, refillForm.trigger() is not called until both relevant values are already set.

Curious to know if this solution works for you, keep me posted!

Hi @david.mays,

Thank you so much for the reply. I just tried the method. The actual result is: the row number on the listview turns back to one. The tempData returns to {"rows":[{"name":null}]}. But the value in the first row's textInput still stays even if I manually trigger refillForm.

What components do you have within your listview, and what is your full refillForm script?

Is it possible you did not add setValue statements in refillForm for the various components you have?

Hi David,
I have a select component and a textInput component per row. The refillForm script is here.

for (let i = 0; i < Object.keys(tempData.value.rows).length; i++) {
  // create a setValue statement for each nested component you need to fill
  select1[i].setValue(tempData.value.rows[i].type);
  textInput1[i].setValue(tempData.value.rows[i].note);
};

Oh, okay.

Then I think the value you're setting tempData to is incorrect. You'll need to include the keys of the values you're trying to set in refillForm, something like:

tempNumRows.setValue(1);
tempData.setValue({"rows":[{"type":null, "note":null}]});

refillForm.trigger();

Or, if you find that the value is not set properly before refillForm.trigger(), I think you can try the async method I suggested above, with the updated value in tempData.setValue(...):

await tempNumRows.setValue(1);
await tempData.setValue({"rows":[{"type":null, "note":null}]});

await refillForm.trigger();

Hi David,

Sorry I didn't explain things clearly before. I used the {row:[ {"name":null}]} just to align with the original post discussion. The real codes I'm using is the same as you suggested. I have used the codes with or without await in a new javascript to clear out the inputs in the listView but it didn't work out. :sweat_smile:

tempNumRows.setValue(1);
tempData.setValue({"rows":[{"type":null, "note":null}]});
refillForm.trigger();

Sorry I couldn't be more helpful!

I think you mentioned that the listView is also nested within a Modal, is that right? Does the listView refill properly if you take it out of the modal?

Another thing worth trying is, for this script:

tempNumRows.setValue(1);
tempData.setValue({"rows":[{"type":null, "note":null}]});
refillForm.trigger();

Go to the Advanced Options and check the box that says "Keep variable references inside the query in sync with your app" (see screenshot for an example).

I'm still not sure if this will solve your problem, but I saw the option and think it's worth a shot!

1 Like