ListView / Modify Values and save

Hi all,
I've tried now several approaches to modify items in a ListView component and be able to save back the edited items, but i can not make it work.

Simple example: I have a table with two columns id + new_text
I created a listview component and populated the content with the query:
select id,new_text
from table_data
order by id asc

Result:

Now what i want to achieve is, if value AAA is changed to ZZZ, then i want to run an update and store the id: 1 with value for new_text ZZZ back to database.

But somehow, even if i change the value in the listview item, the corresponding jsonExplorer for displaying the listview data seems not to change.

Where do i find the data with the changed values, so that i can take that for the bulk update for example?

Any help greatly appreciated, took me some hours now to look for dozen of tutorials and topics but did not find anything useful.

I saw that there is something to use a "tempStorage", but a step-by-step tutorial on how to do this would be great.

Thanks!

1 Like

Here is how i solved it, it may be valuable for others as well facing same requirements

Assume we have a simple table like this:
image

We want to display the items in a listview component and make the content of column "new_text" editable.

Then here is step-by-step guide:

  1. Create query1 with select id, new_text from table_data.

  2. Create new variable, name it variable2 (or whatever you like) and set initial value to {{ query1.data }}

  3. Create a ListView Component, as DataSource set the variable2, primary key should be the unique id field of a table

  1. I have added a JSONExplorer component to see the live state of variable2. Without changes made to the input field (i called it new_text), the JSON looks like this.

  1. This is how my repeatable "new_text" input field is configured:

  2. Now we need to pay attention to the event handlers, add a "change" event like this:
    6.1. key path: we add a new array of objects called "changeSet" and add the changes at position "i"


    6.2. Value: id is the table name field (important because later this needs to be mapped when doing the bulk update. new_text.id resolves to "new_text" and new_text.value resolves to the changed value of that text_input field.
    For example i added a "2" to "finally"

  3. in the variable2 object, there is now a new object called "changeSet"

  4. Now we can use this object to pass it to an bulk-update statement to change values in our table like so:

Now you could link the update to a Button and it would update the values in the table only if that button is being pressed.

EDIT: If typing the values fast in the input fields, there is a lag-behind issue. I think in need to solve it by firing the "set value" handler only, if typing is finished or the input field is not in focus anymore.

EDIT2: Solved it by setting event handler event to "Blur" instead of "Change". Now the value can be edited and be saved to the changeSet only after leaving the input field.

I hope that helps, have a good start to the new year everyone!

Thank you for this post; very helpful. Did you use new list component or legacy one? While using new component I get an error that I can not reference list values outside the list due to performance. Did you ran into this issue and did you maybe found a workaround?

I think I understand now; as you were setting the variable value within the list event handler you avoided this problem?

I used the new list component. The workaround is to use the variable which was created before. If initialized with the default value of the resource query, then it contains basically the same attributes and values like the list component.

first, impressive and thanks for taking the time to write that all up. I had question though, I was hoping you might know or had noticed the answer to while figuring all this out.

when you add changeSet to variable2, does this trigger a re-draw on the listview since the data source has been changed even though the value of "chageSet" isn't used by the listview? Maybe this is just with the old listview, but I was fairly sure that if you call .set() or .setIn() on a variable and no values actually change, it still triggers a re-draw.
if this is the case, i think you could use {{ query1.data }} as the Data Source for listView1 it would prevent the extra render. you'd then be able to just use variable2 as an array of objects that store the index and new_value for each change ([{id: 0, new_text:"stuff"},...]. then for the update query you could set Watched Inputs to {{ variable2.value }} and add a Success Event Handler to trigger query1. This would also be more in line with the Declarative Programming that Retool is for and suggests here which is extended with the Reactive Programming that React.js is basically designed for and now I'm wondering if it's named after (I guess technically it's MVVM, but whatever - nerd semantics are forever changing lol).

I think I encountered the same issue, but after losing a loooot of time :sob:, I figured out a very simple and unintuitive solution :sweat_smile:, a bit different from the one above. Hoping it saves you some time!

Short version: don't refer to listview, refer to the component directly to use any changeset.

Long version: below is the documentation that I would have loved to read before using the component :innocent:

Using a component's changeset (inside listview's scope)

Limits

  • Listview only exposes its data source's data (initial data), it does not expose the actual data from its children (components nested inside the listview, like forms, editable text etc.)
  • When you add any editable component inside a list view, you'll likely set-it up to use Listview data source, and very very likely edit the data... But when you update the data outside of Listview data source, you'll break data consistency, because...

Listview.data[i]componentInsideListView.data :see_no_evil:

Solution
Never use Listview.data, Listview.data{{i}}, {{item}} or {{i}}, directly use child.data like form.data or editableText.value with editable components.
And don't use GUI event handlers without specifically referring to child.data or child.value.

Why? if you refer to anything from listview, you're just referring to the data source. But if you use the component.data, as it's inside the component's scope, and not in list view's scope, you'll get the right data.

Querying a component's changeset (outside listview's scope)

Limit

  • Components inside Listviews are not accessible outside of the List view scope... so you can't actually call them in other queries or components :joy:

Solution

  • Use event handler + additional scope to expose the component real data.

  • Inside your update query, refer to the additional scope key created (it will throw an error as additional scope keys are only created at runtime :smiling_face_with_tear: for now at least...)

  • Don't forget to refresh the listview data source on success...

    • I guess we could specify ~ OnSuccess: form.clear() in additionalScope, but I couldn't understand the subtilities of it in my use case... with my query also having OnSuccess feature, and a form also having OnSuccess clear form...

ie: inside a form or an editable text that is inside a listview, create an event handler, choose script, pass the form.data through additional scope.

query.trigger({
  additionalScope: {
    formChangeSet: form.data
  }
})

In your query, just refer to {{formChangeSet}} to use the form real data, and on success, trigger your data source refresh.

2 Likes

I think your solution is more clean.

But still, for me it seems to have a performance issue when typing into the text fields. Not sure of the reasons but i replicated my requirements using your solution and still experience performance issues.

Any idea how i can find out what causes the lags when typing?

PS @seb : How did you create the screenshots with blurred fields?

1 Like

Glad it helped @whynot :slight_smile:

If it lags when you type, it could mean that something is triggered whenever a key is stroked.
From what I read of your solution, it may come from your event handler on your text component. OnChange for a text component is triggered every time you press a key.
Check that by triggering a confetti onChange, best, trigger a confetti for each action triggered thereafter (utils.confetti()).
I'm pretty confident Retool has a built-in performance feature somewhere but it's less fun for sure.

If you see too much confetti, you're triggering too much VS what you need I guess. And the workflow (sum of queries and stuff triggered like your variables etc.) may be too resource-intensive also.

Potential solutions: type more slowly ;), use a manual trigger (a button, a form), reduce the trigger frequency using debounce/throttle options, simplify the workflow...

Have a great day!

PS: I used Shottr for the screens, it's a free inde app (but awesome so I took a license), ideal for Retool as there is an option to blur only text etc.

There are performance/lag issues when editing values in a listview. It's hard to notice, but when users click on the component and entering changes, there's 100th of a ms delay. This is more noticable when bouncing between many editable components in a listview or typing fast / longer text.

Here is an example of what I mean:

I'd suggest opening the browser dev tool and going to the network tab. then start typing really fast in one of those areas. this lets you visually see if a bunch of queries are being triggered while typing (typing faster would be more queries ran, slowing down the web app).

did you try OPs solution from 'EDIT2'? he changed the event handler to "Blur" insted of "Change", reducing the number of times the query gets called.

For example, a text input which updates on every user input might use an expensive underlying query. Instead of calling this query on every keystroke, use Debounce to run the query only when the user stops typing.

@seb @whynot
you could also try using debounce. it will let the first call to run the query run immediately then pause for the specified ms before letting the query fire again. doing it this way, you can still update the source while the user types just not as often (and in reality, there's no real need to update the source after every key press)

related links:

1 Like

Hey all, thanks for the detailed study of List Views...

I just want to add something that can be useful: if you change inputs inside the listview, the inputed values overwrite (visually) the values of the variable. It may be the case that these won't differ, but if you want to edit the variable some other way or delete an item, you will stumble with this pain... You delete one item in the variable but the value shown in the input stays...

Here is what I did, I think it is similar but added form.clear() after each line of code on the "change" script: