Check out a free preview of the full Electron, v3 course
The "Saving Unsaved File Changes" Lesson is part of the full, Electron, v3 course featured in this preview video. Here's what you'd learn in this lesson:
Steve walks through comparing the current file to the previously saved file to detect if there are unsaved changes. The save status of the file can then be utilized for other UI features, such as triggering a save dialog when a user closes the window.
Transcript from the "Saving Unsaved File Changes" Lesson
[00:00:00]
>> So now we want to see if we have unsaved changes right, and this is part of the reason we kind of save the content whenever we go and store a current file. And so that allows us to kind of know what the last time we either originally opened the file.
[00:00:18]
Or the last time we saved the file that's currently kind of getting set in that case, that's why we do it all on one function. Again, the actual specifics of your app might call for a slightly different approach, but in the context of what we're working with, this works.
[00:00:32]
And so we could say, is changed has changes, which one do we like with has changes or has changed?
>> Has changes.
>> Yeah, has changes good, cuz that's what I internally had decided too and I was gonna have to just be really polite if you'd picked is changed, so that's cool.
[00:00:54]
In this case, we can just basically say this is gonna be a super easy function, which is yeah, current file content is that the same as the content. So far, we've been just sending messages and getting the response. To be clear, we have been doing that because I tend to prefer it versus that kind of invoke, which I said before.
[00:01:16]
Allows you almost get a promise, blurs the fact that they're sending a message receiving you basically just get a promise that returns. But I will show it to you in this case because I think it makes total sense, I want to say, does this have changes? Check the one we have in memory in the main process and let me know.
[00:01:34]
But I find that bound send to a response, I always find out that, there's actually seven different ways I want to open a file or five different ways I want to save a file. And I tend to want to decouple the requests and response from each other. But in this case of like, yo I got some content is the same as you have in memory that feels safe to demonstrate this with.
[00:02:02]
So instead of IPC main on, we're gonna use IPC main.handle, and that is basically saying, hey I'm gonna return the result, and that's gonna be a promise on the render a process. Under the hood, this is simply just making a Bi-directional send and listener and stuff along those lines.
[00:02:28]
Which is why i find myself often decoupling them, so it becomes an async function, obviously. Personally care about the event, the event object, and this is something I don't tend to use, but I just wanna point it out. I'll show you over in this one where I'm using an on.
[00:02:50]
I don't tend to use this, but this is another option that gets a very similar result, which is that reply. It's basically the same way I was getting the browser window and I was doing the browser window.webcontents.send, right? Event.reply does that for you as well, the only reason you haven't seen me use that so far.
[00:03:13]
Is i've been passing that browser window to the dialogs and doing a bunch of other stuff with it before I send a message back. Event.reply is the same as taking event.sender and sending a message back to it. I just happen to be doing other stuff with that browser window outside of that event handler, so I am not using it.
[00:03:34]
But it is an option available to you that I don't tend to use at all that often, but you might have a use case for it that I don't right, so yeah. A lot of what I'm building right now is taking our various SDKs for the products that I work on and building a UI.
[00:03:53]
The same way that GitHub has got that GUI for the git stuff, so I'm more decoupled and passing stuff around a lot more at what I'm building currently. But it could be useful to you and is simpler in a lot of cases, so here we just want to say has changes for the content, that will look at the one in memory.
[00:04:10]
And I would love it if that would just return back right, to say like, yo are there changes here or not. So this will be IPC main.handle instead of on that is going to be the difference, on is sending on or just sending forget and hope the listener is there.
[00:04:26]
Handle, to your question wherever you mistype the channel name you will get yelled at. Because it is looking for that one and trying to get a response from it as nothing catches it, you will get yelled at, right. So handle is more of a giving, it still promises it's still asynchronous, but it feels a little bit more I'm not sending to a totally different process and waiting to hear a message back.
[00:04:47]
You're just saying, I'm going to invoke this and I would like the response in the next line of code, please and so let's get a sense of how that works. And so we can start to determine whether or not the file has changed. Cuz then we'll hide or show the save button based on that, and then we'll also set the document editing status as well with that.
[00:05:11]
So yeah, so detecting unsaved changes, now we need to also call this to see, cuz the DOM knows what it currently has it's unsaved. And we don't know right now, we have no access to the browser from here, we can only ask for things. In fact, we can't even send message, if you had that browser window, you can send a message to it and get a reply.
[00:05:32]
But generally speaking, we'll have the DOM on an input or whatever, I'll try to check, awesome. So we go back to our preload script, which is our bridge between will do check for changes has changes check for changes, right? Yeah, you could also watch on the file system and do this file has changed on the file system, there's a bunch of stuff that you could do.
[00:05:57]
As well check for changes. Check for unsaved changes. Check for unsaved changes, here, we're going to do the IPC render check for unsaved changes. I don't know if that's what I called it over here, I think we just called it has changes, right? Has changes and now you can see that we hit invoke instead of send, invoke is basically send a message, wait for the response, right?
[00:06:31]
And then return that response back into this process as well. So here we can just say, we can just really return the result we can even that's an extra line, but let's start with just console logging it as well. Cool and I will do something with that information in a second, we'll just start by console logging it as a sanity check.
[00:06:57]
Before I get too far down the coding rabbit hole here and I do need to add that over here as well. And that will return back a promise that makes total sense, we gotta send in the content though, right? That's probably what I was missing, do we send in content?
[00:07:16]
Yeah, we gotta get that. Cuz that was the first argument that we send in content awesome, I'm just verifying that to make sure, yep, we send in content as a string. We check, we return the Boolean, perfect awesome, so now we're doing that here as well. We're also gonna console log it along the way and now it's available on that window.api and we can see it here too.
[00:07:46]
So now we hopefully, really on the input that's already an async function, we can do, Check for unsaved changes with that markdown, and what's the issue? Expected 0 arguments got 1. Content string in the type, and so yeah we should have that in place now, so we will check that upon input, and let's get a sense.
[00:08:24]
I wonder I don't need to wait on the rendering of the markdown on that response, that's going to cause lag in my app. You are still on the hook for performance, everything you know about web performance and JavaScript performance applies here as well, you are not off the hook.
[00:08:43]
I'm not going to go into that rant right now though, we'll save performance talk for our closing rant and we'll have this in place. Awesome, so it's changed, right? True, true, true, true, true, false, right? Cuz it was an empty string to begin with, so now we know if the file has changed or not, which is super cool.
[00:09:08]
And so we can go ahead and begin to do various things based on that information. And let's go ahead and open a file as well just to verify that this all works as expected all right, currently changed, unchanged, changed, unchanged. So now we can do two things based on that, we can go ahead and say, Elements.save mark down button disabled is, So disabled should be the opposite of has changes, right?
[00:09:54]
And we can get rid of that line where I disable it always. Cool. The fun part of Vite in memory, we still have the same file in memory, but then Vite did the hot module reload, so that's fun, cool. So on off, on off, on off which is when did before I wasn't messing too much with the nodemon stuff.
[00:10:23]
Things moving out from under with me multiple processes stresses me out. And I knew that if I turned all that stuff on, I would have one moment of deep confusion in front of all of you. For a very simple thing of one process restarted when I didn't expect it to, sweet.
[00:10:37]
So now we show and hide that button based on the changes really what we need to do is now represent that in the window. Right and then we've got a fairly good, I might leave you to set the revert button as well. In fact, that might be a thing that we do as an extension exercise at some point.
[00:10:54]
But we've got the ability to represent it now in the UI, we just want to also represent it in the main process. You could argue whether or not it should be handled here or not, you can make it its own function, I don't care what you do. So I'd have to pass in the whole browser window in there, so what if I just said in this, Changed cuz that will be a boolean and then we can say, we'll return that changed and we'll say, Event.
[00:11:46]
We can't type like that in there, that's silly, we'll get the browser window again. And if it is changed, we'll do the browserWindow.setDocumentEdited to whether or not it's changed. And so now we should get that full kinda OS integration there as well. And you can kind of see that small, subtle little dot there, which is Mac OS's way of telling you.
[00:12:24]
And I'll leave a little bit this is an exercise to y'all along with revert, is that you do get like abilities to listen for events on the window closed. So now in your main process, you know if that given window has an edited status, right? And you can listen for a closed event and you could do stuff, are you sure?
[00:12:42]
Right, and those all kind of leave as little extra, all those little nuances are the I think the harder part of building an app is just getting all those feel things right. But generally speaking, electron has given you almost everything available to do that, you don't have to do any kind of crazy stuff like reinvent your own title bar.
[00:13:04]
A lot of electron apps you see do reinvent their own title bar, right like VS code has done it right behind me, the GitHub app has. But they're still using the built in macOS close button same for windows and stuff along those lines as well.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops