JavaScript Design Patterns for Web Apps

Undo with Memento Pattern

Maximiliano Firtman

Maximiliano Firtman

Independent Consultant
JavaScript Design Patterns for Web Apps

Check out a free preview of the full JavaScript Design Patterns for Web Apps course

The "Undo with Memento Pattern" Lesson is part of the full, JavaScript Design Patterns for Web Apps course featured in this preview video. Here's what you'd learn in this lesson:

Max uses the Memento design pattern to add an "undo" feature. An array to manage the history of the application is added. Items are pushed and popped from the array as the data changes.

Preview
Close

Transcript from the "Undo with Memento Pattern" Lesson

[00:00:00]
>> Maximiliano Firtman: What if we want to implement undo? Say when I press Ctrl+C and I wanna go back to the previous list. Let's try to do that. Again, without design patterns, it will be really complicated. But now it's not going to be so bad. So for that, we are going to implement that memento design pattern that we mentioned before.

[00:00:25]
Again, even if you don't remember the name of the pattern, it doesn't matter, just remember the solution. So to do that, then we're going to create another file, memento.js, memento.js, there we are. And here, we're going to export one object that is going to be a singleton in the term of an object.

[00:00:49]
We can call this the TodoHistory.
>> Maximiliano Firtman: I'm going to start with an empty array. And typically, you have push, you receive the current state, and you have pop, that returns on a state, okay? That's kind of the deal always. So to push, it's just, I will check if I do receive a state, just in case, I'm going to push it into my history, this.history.push.

[00:01:16]
And here is something important, so before adding, let me add something here, but then I will change it, okay? So, I will leave a mark there. Then pop, if we don't have something, actually you will see later that we need two things. I need to pop twice. You will understand why [LAUGH] in a second,
>> Maximiliano Firtman: Because the last state is actually the current state.

[00:01:45]
Actually, I need to take out the current state and also the previous one, okay? You can implement this in another way. So I'm returning the other one. That makes sense? The current one, out, I take the previous one, and I wanna take that previous one and convert that into my new current one.

[00:02:06]
So why I added the mark here, flag here? I should do flag. So this is not gonna work completely, why? So what is the state? Let's try to understand what is the state. The state is gonna be my list, okay, my list. My list is a set, remember that?

[00:02:27]
So what is a set? So a set, it's actually a list of pointers to different objects of type todo items, okay? So let's say, this is my current situation. I have four Todos, and I remove one, okay, I remove one. See, I remove one, I'm deleting one, which is okay.

[00:02:55]
The problem that we have is that when we are saving the state, we are not cloning the contents. So which means, if you change one of these pointers, you're changing not just that state, but all the previous states that you have. If you have ever played with React, with Redux, or with other data framework libraries, you have already deal with this.

[00:03:28]
In this case, when you are changing the state, you need to clone the whole structure, okay? For our example, it will work, I mean, this will work. But in the future, if we have a way to edit the text and change the text, the problem is that the undo will not work.

[00:03:50]
Because when I'm changing the third one, it will check the third one for all the historical lists that we have because all the sets are pointing to the same object. So every time I'm pushing into the history, I need to clone the whole thing. If you don't understand exactly what I'm doing, don't worry.

[00:04:08]
But actually, I need to do something like this, creating a new set based on the current state, a new parenthesis here. So here I'm taking the current state, if you're coming from Redux, probably you have used this. You're spreading the contents of the state in a new array, and we then are creating a new set.

[00:04:29]
So I'm always creating a new set when I'm saving this, okay? Anyway, have in mind that if I don't do that, it will work anyway in our example as it is right now. Okay, so the question is when to push the new state. When do you think is a good moment to push a new state in this memento's collection?

[00:04:58]
>> Speaker 2: On add or on delete.
>> Maximiliano Firtman: On add or on delete, yeah. Do we have any other better, I mean, it's okay, but we might have a better way that will also embrace add and delete, the observer pattern notify. We can add another observer. For when the list changes, I will save the new state, as we did for a storage, something like this.

[00:05:29]
So we don't have yet the todo items here, so I need to import that, remember.js. So we'll say, okay, you know what? For TodoItems, get the instance, the singleton. I'm going to add an observer. And I'm going to say, I want to talk to my TodoHistory, and we will push the state.

[00:05:55]
What's the state? Well, actually, let me create the variable,
>> Maximiliano Firtman: For the instance, so I don't need to access this. The getter twice, it's not a big deal anyway. But TodoItem,
>> Maximiliano Firtman: So the current state is the current set. Okay, so addObserver, this goes like this. Yep, so I'm adding a new observer, pushing into the history.

[00:06:31]
And all the logic for saving the state is here.
>> Maximiliano Firtman: So it's just that. The only thing that is left yet is, okay, we need to communicate this with the UI somehow to trigger an undo operation, okay, that makes sense? And also we need maybe to add here in app.js another, if checking for Ctrl+C, if you're on a Mac, you can add also Cmd+C.

[00:07:06]
But anyway, if you want, Mac users will be comfortable with Cmd+C. But anyway, let's do Ctrl+C for now. But here, should I talk directly to my memento object, from my history object from here?
>> Maximiliano Firtman: Can I? Yes, I can, definitely. So I can go and talk to my pop, okay, I can do that.

[00:07:34]
But we have a command design pattern. Isn't it a better idea to create a new command? Because now it's Ctrl+C, but maybe in the future, I have a button on the screen as well for undo. So it's much better to decouple the implementation of the action from the invoker, and that's actually the command pattern.

[00:07:56]
And we already have a command, so the only thing I need is to add another command for undo. So undo, then another case here, and we will implement what to do here, Commands.UNDO, break, like so. And actually, what I need to do is, I need to take the previousList from TodoHistory.pop();.

[00:08:32]
And if I do have one, for some reason, let's say that there is no one because there is nothing in the history or something like that. Well, I will call todoList.replaceList. Remember, we did that replaceList, it's replacing the list with this one.
>> Maximiliano Firtman: So it's important if we have a command executor or something, that is, every new operation that we have in the app, just go through a command.

[00:09:02]
So then you can add multiple entry points, multiple ways to code that command, does it make sense? Yep? So then what we need to do here is what? Well, create a command and execute it what is going to be UNDO.
>> Maximiliano Firtman: There is no argument in this case.

[00:09:25]
So cross your fingers and let's see what happens, yeah.
>> Speaker 2: I think at the bottom of memento, it's todoList, not todoItem, but I might be wrong.
>> Maximiliano Firtman: Probably, well, actually, it's probably going to work anyway. But you're right that the name that I use should be todoList. I mean, it won't work because it's just a variable name, but that one you said?

[00:09:50]
This is the list, not the item. So you were right.
>> Speaker 2: But on line 18, it's not TodoItem.getInstance, right?
>> Maximiliano Firtman: Here, yeah.
>> Speaker 2: Okay, yeah.
>> Maximiliano Firtman: Yeah, you're right, so everything is todoList, not todoItem, thank you.
>> Maximiliano Firtman: So now let's add another one, or let's delete one. So let's see, I'm not sure if it's gonna work.

[00:10:18]
That's right, I'm going to delete lunch. I'm going to press Ctrl+C. We have an error, okay, but something happens. TodoHistory is not defined. Typically, when you see that, it's an import issue. So let's go to command.js. And here, it's using TodoHistory, and it's not imported. And of course, this is coming from another module.

[00:10:43]
So let's add the import with the .js, okay? Refresh, well, now, TodoList is not defined, let me see. 18 of memento, it's here, TodoList. Maybe because when we change this, we didn't change the import, okay, cool, refresh. Okay, well, it disappeared, okay, I deleted that. So let's add lunch, okay?

[00:11:15]
Ctrl+C, whoop, let me finish the workshop. Ctrl+C, gets back. Ctrl+C, we have a problem there probably. It's deleting more than one for some reason. But lunch works, dinner works, also, when we have some errors in the middle, with local storage, it's always a good idea because sometimes we end up with some invalid states.

[00:11:48]
To go back here to Application > Storage, so DevTools > Application > Storage > Clear site data. So let's start from scratch, okay? So we cleared everything, so now we refresh and we start from scratch, okay, just in case. Well, lunch, dinner, by the way, Cmd+C, Cmd+C no, Ctrl+C, sorry, [LAUGH] I can go back here, I can add.

[00:12:15]
Wherever I said no, I don't want this. Ctrl+C, for asdf it's working, okay? So implementing undo is typically, if you don't use design patterns, seems like a very difficult thing, right? But here, it was just a matter of, I don't know, three minutes. And I'm explaining this, if not in one minute, two minutes, in one, two minutes, you can have a full new feature that seems complex, but thanks to using design patterns, it's actually simple.

[00:12:45]
So for now, this is our final version. There are much more things we can improve. There are better ways to do a lot of stuff. Right now, we are re-rendering the whole UL every time instead of reusing DOM elements, so that we can improve performance. So if we are not changing, so what I'm saying is, if I'm adding a new task now, asdf, lunch, dinner, and sdf are going to be recreated in the DOM, and there is no need for that.

[00:13:14]
I mean, you don't see any problem because we are not talking about 10,000 elements, so it's fast enough. But anyway, it's still not the best performance probably, but it works. And for a lot of apps, it's good enough, to be honest, okay? But it was just a way to prove the usage of many design patterns.

[00:13:34]
Just remember that there are many more, we just covered the surface of those patterns. So there are probably more than 50 patterns that applies to, so far, design that we haven't used. So there are more to learn.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now