eta-orionis's avatar

How to support undo/redo in a Laravel app?

Hello, I'm wondering about how to best support "undo/redo" in a Laravel app, ideally with the Livewire or Inertia stacks (but also in principle).

I am familiar with the Command pattern, with Soft Deletes in Laravel, as well as with how to implement undo/redo in a SPA, but I can't seem to find any best practices or resources when it comes to actually implementing undo/redo with Laravel in a multiuser application. Specifically:

  • where and how to save the command history (backend? frontend? both? in the database? in the session?)
  • how to keep front-end and back-end state in sync, without requiring a full page reload every time the state changes?
  • how to best deal with changes that have side effects, such as undo/redo the upload of a file or the sending of a message?
  • how to do all of the above without needing to "reinvent the UI layer", using e.g. LiveWire or Inertia?
  • does it even make sense to consider LiveWire or Inertia, or is it better to have the frontend as a completely independent SPA and use Laravel only for a backend API, with no UI, views, etc?

Thanks!

0 likes
8 replies
martinbean's avatar

@erion Livewire or Inertia has no bearing here. They’re libraries for interaction and have nothing to do with business logic or persistence.

You’re right in that you’ll want to use the command pattern. You don’t give any context on what it is you want to allow the user to undo or redo though, so can’t really offer any specific advice.

Actions you allow the user to perform, you can represent using commands. You’ll need to persist these commands somewhere in a linear fashion. So when a user undoes something, you’ll need to move some pointer back in this linear list of commands, and redoing obviously moves the pointer to the next one (if there is one).

The command pattern is used a lot in video game programming to allow undo/redo. This post has a good overview on how to use the command pattern for this, albeit in the context of video games and not the web, but you can take the lesson and apply it to your scenario: https://gameprogrammingpatterns.com/command.html#undo-and-redo

eta-orionis's avatar

Thanks for your answer Martin!

Nystrom's "Game Patterns" is one of my favourites, but unfortunately doesn't help here. Applications live and die by how good their user interaction is. Anyone writing a modern Laravel web app must deal with some kind of interaction libraries - i.e. Inertia, Livewire, Vue, etc. Because undo and redo are purely interaction features, it means we have to deal with them in the context of these libraries too.

I know how to implement undo and redo in a pure SPA where the whole application is in the front end. What I am stumbling on is the mixture of Laravel (backend) + Vue/Inertia/LiveWire/whatever (frontend) + Laravel or Inertia routing. The division of undo/redo logic and state among all these parts eludes me.

Let's say I'm doing a ToDo App with the usual CRUD operations plus undo/redo for the last 50 changes.

Nobody likes a full page refresh every time a ToDo is marked as done, so there has to be some kind of rich frontend (Vue/React/LiveWire/whatever) that sends requests to the Laravel backend.

Whenever users want to undo/redo, it takes too long to go ask the server "hey what was the last thing the user did?" and wait for the answer. That means the front-end has to remember at least some undo history.

You also don't want to reload the whole page from the server upon "undo", because 1) the user gets confused, 2) there is some UI state that can't/shouldn't be saved on the server. For example: which field of a ToDo was focused, where was the page scroll, which tab in a tab-view is selected, etc. So we have back-end state (the todos + the session) AND front-end state (the todos and other UI-specific state). Which of them is the authoritative state? Is the undo/redo history part of the frontend state or backend state?

The frontend and backend states need to be kept in sync. How?

Let's say the user finishes editing a ToDo in single-view and is taken to the list-view of all ToDos. (This is a different Vue component). The user now remembers "Oops, I edited the wrong ToDo" and presses ctrl-z to undo. For the user's mental concept, "undo" means go back to the previous view as well, i.e. to the single-todo view. How do we know which view and which component was used for the action that was just "undone"? Where do we save this component? Is this information part of the command, part of the backend state, or part of the frontend state? How to restore this state in the frontend when the user has a network problem and refreshes the browser page completely (ctrl-R or shift-ctrl-R)?

These considerations are solved easily for a pure SPA, because there is only one Application state - the frontend one. (The SPA backend APIs are, in a sense, just a persistence layer). That is not the case with any of the Laravel UI solutions -- at least as much as I know. It would be great to have some help and best practices on how to tackle undo/redo with Laravel and its UI solutions. The problem will become more and more important as rich web applications replace old-style ones and as autosaving becomes the norm.

Thanks!

martinbean's avatar

Nystrom's "Game Patterns" is one of my favourites, but unfortunately doesn't help here.

@erion It literally explains using undo/redo, though? You’ve just got to implement it in the context of a web app and your use case and not a video game. Unfortunately, because again you can’t give an actual example and instead theoretically ones, I can’t give a specific answer.

All undo/redo is, is persisting operations somewhere (memory, disk, database, etc). You then have a pointer, that points to a “frame” and the state/context of your thing at that point in time. Undoing moves the pointer back, redoing moves it forward. It’s up to you to decide:

  1. Where you store this information (i.e. memory, disk, database, etc).
  2. Applying and reversing the changes based on the information in your command objects.

We can’t make these decisions for you. These are things you need to decide and aren’t going to be given to you in a nice, easy-to-follow tutorial, because they’re specific to your application and use case.

Also, these decisions are nothing to do with Livewire or Inertia. They’re just different technology choices. People were implementing undo/redo for a long time before both Livewire and Inertia came along.

eta-orionis's avatar

Thanks @martinbean for your answers and willingness to help! I appreciate it, though I'm afraid we are misunderstanding each other.

I know Nystrom's Command pattern in all its details, I know how to implement undo/redo in pure SPAs, and I know how to persist information in multiple locations. If I were to do a pure SPA for the frontend and use Laravel only as an API backend, I would not even need to ask for help. My problem is that I would like to leverage Laravel's UI facilities, not make a pure SPA on top of it.

I don't agree that the decisions have nothing to do with Inertia/Livewire. Inertia frontend, for example, gets its initial state from the server when the page first loads, then synchronizes (parts of) this state with the server upon user actions. To perform undo/redo I must either change the backend state, or change Inertia's frontend state, and make sure to sync them both. None of these options are trivial, because Inertia is a mix of client-side and server-side paradigms, it likes to manage its own state, and it doesn't like meddling with it. Of course I could add Vuex on top, but then there are three states to manage at the same time (and if you google for Inertia+Vuex you will find exactly 0 useful results). I'm not familiar enough with Livewire internals, but I don't see why it should be different.

I unfortunately am under NDA and can't describe the concrete system I'm working on, but this is a good enough approximation:

  • a standard "ToDo list" Web2.0 App using Laravel+Inertia+Vue
  • undo/redo for the last 50 actions
  • different views/different routes for a single ToDo vs. for the list of all ToDos
  • SPA-like user experience (no full page reloads are allowed after the initial visit).
  • max delay allowed between user action and UI reaction is 50 ms
  • App should be usable on a 3G connection (i.e. delay for querying the server varies 100-1000 ms)

If you don't mind sharing how you would approach the undo/redo implementation in that scenario, I'd be happy to know your thoughts.

martinbean's avatar

@erion You’re asking questions only you can answer. Where you store the state is up to you.

I unfortunately am under NDA and can't describe the concrete system I'm working on… If you don't mind sharing how you would approach the undo/redo implementation

So you won’t share your use case, but you want me to give an exact run-down of how I would implement it…? I’m not here to architect projects for free I’m afraid.

jlrdw's avatar

@erion you realize the front end is actually nothing, right. No different than using notepad in Windows.

I can type a sentence, but nothing til I hit save. When I say nothing, I mean just inputs, text areas, etc there on a browser waiting to be acted upon. So naturally any "remembering" what was previously typed in an input (just example) would have to be held somewhere.

State when using web based applications are a back end (or temp memory). The browser is basically just an image of "what was".

Bring up a web page here on forum, once loaded, it's in your browser, you aren't even looking live any more, until you click or somehow interact.

Kinda hard to explain, but leave the computer for five minutes, come back, hit refresh, usually there are changes, like a new post added. The browser only holds dead info, and new info waiting to be sent.

Edit:

The closest to your description would be a client server setup, like Visual Foxpro shared over several terminals over a network. But a desktop application like that is far different than a browser. There are other examples like a winforms app communicating with a remote sql server. It's also possible to have a C++ application communicate with a remote server.

Besides gaming as mentioned, I haven't really seen a big need for undo. I have done business apps including logistics software for a large trucking company, and hundreds of loads entered weekly, but an "undo" was never needed. Of course desktop apps have it, like peachtree accounting had a standard "undo".

But just some thoughts on it, I suppose it would be good for some folks to have an undo.

Of course in a textarea browsers have an undo anyway.

eta-orionis's avatar

Thanks @jlrdw for your answer and willingness to help! Your advice sounds fine for a Desktop/Web1.0 approach, but unfortunately I don't see how that approach would work in a Web2.0 app, like I'm trying to achieve.

The front end in Web2.0 apps is unfortunately not "nothing", nor does it hold only "dead info". Think of Google Docs, Confluence, Jira, Notion, Medium, or any other big one. When the network goes down you can still work with them; changes get uploaded later to the server. To me this means that the information in the browser -- i.e. frontend state -- is very much alive and even has higher precedence than the server.

With regard to "nothing happens until you hit save", notice again that all the web apps I mentioned perform automatic saves every 5-10 seconds. There is often not even a "save" button to click on.

In my work I always have the need for undo; you could even say it is required by law :) The ISO9241-110 Usability standard (which is the basis of the EU, UK, and US laws on Usability and Accessibility), requires that users are "able to control direction and speed of the interaction", which often means the system must have undo/redo functionality.

jlrdw's avatar

@erion I agree web 2.0 is great, but you only cache data offline, you are not actually working on an online app. Portions yes but cached. Whereas the foxpro example was live and just example.

Things like Google drive (I use myself) when viewing only is still static until acted upon. And yes the click save was example, a script can auto save as well.

You just need to find some tutorials on this, medium has some like:

https://medium.com/swlh/how-to-make-your-web-apps-work-offline-be6f27dd28e

Bottom line, you cannot work online if the internet is down, but cached data (whatever type) is uploaded, blended as needed when back online.

A browser has nothing to do with back end content, except displaying it, and giving a means to type, fill in, do things with the static "seen" data, then you can of course send it to server (back end).

Times like this I miss my old MSDOS and Dbase 3 days..... That was the good ole days.

Edit: But the undo redo should be fairly easy to do while online, it's a matter of storing the last few items. But how much undo is needed? I wouldn't think too much. I would think either github has packages to help with this, or there has to be some in-depth tutorials on it.

Please or to participate in this conversation.