Strategy for updating displayed entity

Consider a form with a list box displaying an entity selection and a subform that displays the currently selected entity. Here is a screen shot of part of it to give you the idea.

temp|570x369

This form may be open on multiple clients at the same time to the same record. I've worked out how to manage the different save configurations. Now I need to work on updating the forms of users when the record is changed by someone else.

One idea is the use On timer with something like .diff() and refresh the record. But that’s a problem if a user happens to be editing it. Perhaps they are writing a note in the text field. Poof - the refresh wipes their work away. Another problem is this generates a lot of network chatter.

Another idea is using On loosing focus as the trigger to check .diff(). Less chatter and doesn’t disturb the user. But if the user doesn’t do anything there is no update.

I guess On Activate would be the best option - refresh what you need and no network chatter. But there’s no update unless the user changes windows. Still not bad.

Am I missing some push option where 4D is tracking the content of displayed entities?

I contemplated this for a while, and ended up with checking for conflicts upon a save, and displaying a conflict resolution screen to the user, something along the lines of

And the code to manage is quite simple

You could expand the functionality and allow a change in each of the fields in the conflict resolution screen, but I didn’t take it there yet…

In your scenario you have two independent “data sets”. An entity selection used in the list box to show some records - and an entity to show and edit ONE record.

I would never refresh the one record currently being entered.
You could consider to check if something was entered and refresh if not, but what if the user just spend a while to read carefully a comment and then decides to add/modify it, while you just updated it in the background? If the text is long enough (or if there are enough fields), they might not notice.
Ok, this could be different for simple records with only some value fields.
For me: updating the entry area should be well thought through…

For updating the listbox:
4D does not keep track of all displayed data in all clients.
It is not only about displaying data in a list box, it is also data used in popup menus, in charts, anywhere. Not only in a 4D Client, more and more data is requested these days via REST server.

But if you want to update the data regularly, you could use on timer and then call:
form.students.refresh()

https://doc.4d.com/4Dv18R3/4D/18-R3/entitySelectionrefresh.305-4960731.en.html
(example 2)

Note: requires 4D v18 R3 or newer.

Just an information to keep in mind if its could help: if the subform container is focusable, when the container get the focus, the subform event On activate is triggered…

I’m not completely clear about your use case and I don’t seem to have sufficient access to view your screenshot. Are you suggesting that uncommitted edits should be displayed to all users? I would be hesitant to display the actual characters as they are typed to all users unless this is a very specialized case. Instead there are a couple of models which could be considered:

  1. Most messaging apps implement some form of typing indicator. An ellipsis bubble or “someone is typing” prompt could be useful to show that an edit is underway. In that case your On timer approach would be appropriate. That way you could ‘time out’ an edit which is started but never completed. However, your case is not a messaging app, and all users are, in fact, editing the same record, rather than serially appending entries in a chat log, so this doesn’t really address your case.

  2. The “Auto-save” and “Track Changes” features of many word processors (Google Docs, Microsoft Word etc) combine to provide something that sounds like your case. While a full fledged change tracking system might beyond the scope of this project it might be reasonable to add an indicator to show that new content is available with the option to view and merge it.

HTH,

Tom Benedict

Here is a second attempt at uploading the screenshot:
temp_1
That seems to work better.

Hey Lahav,
The saving part isn’t the issue right now. I think I have that worked out. What I’m trying to do is work out a way that changes saved somewhere else propagate to the other users. In this case it would literally be a situation where two or three folks are on Slack looking at this form. One of them says they will make an agreed on change. Others are busy and when they look back don’t see the change. Did that person make it?

For folks like us popping up a dialog of the various edits and such is just cool. It turns out for regular people not so much.

This is sort of a by-product of optimistic locking I think. Or unintended consequence. Being able to save changes field by field is great but managing this aspect of the display a little more involved.

Because if I refresh the one record being entered it ‘breaks’ the association with the record in the list? In this case I really can’t avoid reloading the detail record because while the display doesn’t look very complex, and there are only a few fields, there is some complex code in the trigger that can update the values of fields displayed. .reload() was the only way I could get those fields to display current values after the save completed. But I see the value of also reloading the entity in the listbox.

Hmm. I will try that. I had abandoned using subform object events because the subform doesn’t generate, in v17.4, an On Bound Variable Change when the reference is changed. It seems like it should.

Tom - no, not at all. But once those edits are saved I am trying to get the displayed values to update.

The typing indicator would be a cool feature but right now 4D doesn’t have any mechanism we can use to do it. You would have to build some sort of publish/subscribe system. (David Adams did such a thing and presented in on 4DMethod. Frankly I couldn’t follow what he was doing when I looked through the code.) But at that point it’s just way too complicated. A major feature of ORDA, for me, is it is generally simplifying my coding life.

I was intrigued by Meteor for a while because they had (maybe still do) a platform that would push changes to clients instead of clients having to pull updates. They had publish/subscribe scheme that monitored the database journal to recognize changes and then ‘publish’ them to the ‘subscribers’. The network chatter was minimal because the ‘publisher’ was only sending out messages when something changed. I’m simplifying but that’s the basic idea. That also made it easy to do what I am trying to work out.

Actually doing something like what you had I have in a similar case status window. I use an on timer to perform a periodic refresh. This way if a change in the hot interesting data occurs the window refreshes with the current data.

To deal with some of the potential conflicts I have has especially when I have related tables I use transactions. This way If I need to create a big complex mess between multiple tables it causes no permanent harm. When I create the record I save the record upon creation. Then I can add all the relationships I want. If the user aborts I just rollback the transactions and its all gone. No harm done. But if they commit then all the changes propagate out.

In the example there is the organization but there are child tables with relationships to this org table. I can add all the items I want to the child tables and have no problems.

START TRANSACTION
$newRecord:=ds.AB_Organization.new()
$newRecord.save()  // save the base record so that we can add child elements.
DIALOG([AB_Organization];"EditOrganizationRecord";$newRecord)
If (OK=1)
	$newRecord.save()
	VALIDATE TRANSACTION
Else 
	CANCEL TRANSACTION
End if 

Hey Eric,
Yeah, a solid approach in many situations. The On timer approach won’t work in this case though. I’m using optimistic locking so no transaction (edits are saved as they are entered in each field and there are limited users with authority to make changes). And as Thomas points out if a user happens to be typing in the note field when the On timer event fires they loose their input.

Hi Kirk,

What about firing off an EXECUTE ON CLIENT to all of the other users when a user updates a record?

What do you mean by “different save configurations”?

Also, what about the situation where editing occurs on an unposted invoice where individual invoice line items are being edited (ie, added, removed; or quantity, pricing changed)?

Hi Jeremy,

Using ORDA you have a few options for saving. Take a look at this blog post, I found it very helpful.

Well that would be absolutely the wrong place to implement this scheme. When it comes to financial data you have to go with hard locks, in my opinion.

This isn’t financial data. And it fits the work flow for these folks. To be honest it’s also my first foray into actually deploying optimistic locking on a live project. This is a low use, low danger situation. I’ve already learned a lot quickly about how to implement it. That’s sort of what this is about - the optimistic lock lets you do some cool things but there’s this display issue…

Hi Peter,
How do you know when to send the command? Who to send it too? That’s really the heart of this isn’t it?

And it brings you right back to some sort of publish/subscribe scheme. I’m beginning to wonder if such a scheme hasn’t already been discussed somewhere along the line. To roll your own would be extremely cumbersome at the least and probably not very performant. But if it were baked into 4D…

Hi,

Just for information, instead of using the .diff() member method to check if the entity in memory differs from the one in database, you can use entity.getStamp() to compare the stamps of the 2 entities.

1 Like

Hi Marie-Sophie,

That’s a good point. You know, I could use On Timer to check the stamps and display some sort of tell tale or graphic element to indicate something has changed. That would notify the user and leave the control with them.

Hi Kirk,
You could send the command whenever a record is saved. It could be sent to every client and they could “decide” what to do based on what record(s) was being displayed on their client at that time.

It’s not so much publish/subscribe, but more like an event. The event is sent to every client and it’s up to them how/if they react to the event.

Peter,
True. I’ve used a scheme like that in other cases and it would be OK here because this is a small database. A solution like that doesn’t scale well or work well if the database is running over WAN. The milliseconds of delay over WAN start to add up. And it can really up the amount of network chatter. I’m really using this as an opportunity to try to find a solution that doesn’t require all that network chatter.

I have worked out a primitive implementation of the idea Marie-Sophie suggested here:

If this were a v18 project I think I could build a subform widget that could be dropped onto a form and initialized with the entity to monitor and from there manage everything. In v17 I couldn’t make that work. So I went with a solution specific to the form. That is a button named delta_entity and labeled “Refresh”. I enabled the On Timer event and set it for 1 second.
image
It’s invisible by default.

The form method manages displaying it:

If (Form.e=Null)
	$entityDelta:=False
Else 
	$entityDelta:=(Form.e.getStamp()#ds.Student_Class.get(Form.e.UUID).getStamp())
End if 

OBJECT SET VISIBLE(*;"delta_entity";$entityDelta)

Form.e is the entity, if one is selected. The button code is simply Form.e.refresh().

It works great so far catching changes on records right away. On this form I save each time a change is made so the user never has lots of unsaved changed. And this lets the On Timer go by without interrupting the user during input.

The only part I wish I could reduce is the
ds.Student_Class.get(Form.e.UUID).getStamp()
That’s going to run once a second from every workstation that has this form open. But perhaps it’s not such a big load? Perhaps the getStamp() method is optimized in a way to reduces the network load? As I say this is fine for small databases but I have doubts it would be good on an installation with hundreds of simultaneous users.