تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Background Sync Overview0:00

Service workers are fantastic pieces of technology. They allow us to cache things. We can handle offline states. We can have notifications, but they can also sync data in the background, which is really useful when our application is offline. It's not, well, yeah, it is. You'll see. So the idea is when our application is offline, we of course tell the user, you're offline,

So the idea is when our application is offline, we of course tell the user, you're offline, and it's on them, or at least we could take the approach and say that it's on them if they make whatever change that they're gonna make and then try to submit that form. Of course, it says you're offline because we told you you're offline, but we could make that experience so much better. We could make it so that if they are offline, if they submit the form,

We could make it so that if they are offline, if they submit the form, we can actually save that form data inside of a database inside of the browser. It's called IndexedDB. It's an object data store, basically, which, well, you'll see here in this episode. But the idea will be then we can store all of these requests in this IndexedDB, and then whenever our application is back online,

all of these requests in this IndexedDB, and then whenever our application is back online, our service worker will then sync those requests back to the server so that the user doesn't really lose any data. Ideally, we would do this for every form within our application, but we are going to focus on the form for managing our readings because it is essentially the same form. If we are editing or if we are creating a new reading,

Targeting the Readings Form1:39

because it is essentially the same form. If we are editing or if we are creating a new reading, it's all the same form. If we take a look in our code, so we are inside of the views here. So this is the show view for the meter. We can see that it is including this readings form. And if we take a look at the edit view for a reading, it is including the same thing. So it's the same exact form.

it is including the same thing. So it's the same exact form. The only difference is the form element itself. And of course, for the edit, we're using the method directive to tell Laravel that this is supposed to be a put request, which that's something that we need to take into account. And we could go way into the weeds with this. Well, it's not necessarily weeds. It's more into the forest

Well, it's not necessarily weeds. It's more into the forest because we're gonna take the more simple approach in that if the user is online, we're just gonna submit the form and everything's fine. If they are offline, we are going to store it inside of indexed DB so that it can then be later synced back to the server when the application is back online. The other approach would be to just handle everything with JavaScript, which gives us a lot more control

The other approach would be to just handle everything with JavaScript, which gives us a lot more control because what if there's a server error or there are other things that can go wrong than just being offline. Well, with JavaScript and handling the form submission requests with JavaScript, we can handle those things. But JavaScript is involved. So what we are going to do is this.

Creating Alpine Component3:12

But JavaScript is involved. So what we are going to do is this. We are going to create an Alpine component called simply reading form. And all we are going to do is handle the submit event and we'll call a method called submit form. I mean, that's exactly what it is. We need to do the same thing inside of the edit view and that's gonna be that. So it's the same form.

and that's gonna be that. So it's the same form. There's just a few little differences, but for the most part, this is gonna work just fine. So we're gonna be done there. Let's close those. Let's create that component, shouldn't we? So let's go to our resources.js. We have our components. Let's create our new file called reading form.js.

We have our components. Let's create our new file called reading form.js. And this is going to start off like a lot of our other components. I say a lot exactly like all of the other components in that we are going to listen for the Alpine init event so that we can then say Alpine data. It was called reading form. Then we will have our function that returns our object. And we have that.

Then we will have our function that returns our object. And we have that. So now that we have that, let's open up the app.js file so that we can include that. Ideally, we would be doing this just for the pages where we need this component, but this is gonna be fine for our needs. So with that in place, we now have this reading form components. And as I said, we could go completely into JavaScript

we now have this reading form components. And as I said, we could go completely into JavaScript so that we would have the models for the form fields and things like that, but we're gonna keep things a little more simple in that we're gonna have this online property, which we will initialize with navigator.online. Then we will have our init method, in which case we will call the add event listener on the window for the online event

in which case we will call the add event listener on the window for the online event so that we can set this online to true. And we'll do the same thing, but for offline. And we will set that value to false so that we have that submit form method so that we will get the event object. We will check. If we are online, then great. There's nothing that we need to do.

Handling Offline Submission5:37

If we are online, then great. There's nothing that we need to do. We will just submit the form and everything is gonna be fine. However, if we are offline, well, things get a little more interesting because the first thing we need to do is called prevent default. Because if we're offline, we don't want to try to submit the form.

Because if we're offline, we don't want to try to submit the form. That's not gonna work at all. So we're going to prevent the form from being submitted so that we can get the data from the form. Now, we could, once again, do this in a variety of different ways. I'm gonna be a little lazy and I'm gonna create a new form data object so that we can pass in the element of this component,

and I'm gonna create a new form data object so that we can pass in the element of this component, which is our form element. You know, we could create those properties, set them up as models for the form fields. That takes a little bit of time. This takes less time because now we can say payload and we just need the keys to be the names of the form fields, in which case that is form data.get value.

to be the names of the form fields, in which case that is form data.get value. And then we have a noted at, I believe that's for the date. And then we have notes and then that's that. So an easy way to get the data from the form and we're done. But we need more than just that because we are going to replay these requests. So we need to know where the request was going.

because we are going to replay these requests. So we need to know where the request was going. We need to go know the HTTP method that was gonna be used. And all of this is gonna be stored inside of a record in our indexed DB. So we can go ahead and just create our record. We're just gonna call it item for the lack of a better word. And we need a couple of things. We need an ID, which we can call crypto

And we need a couple of things. We need an ID, which we can call crypto and then call the random UUID. That's gonna give us a random ID that we can use. Then we need to know the end point that we were sending this request to. Well, we have that. That is part of the form. That is the action attributes on our form. But then we need the method.

That is the action attributes on our form. But then we need the method. What type of HTTP request were we making? Again, that's part of the form. That is the method attribute. But as I mentioned for an edit request, that's actually using a put method. This isn't gonna work for us. That is still a post because a browser for whatever reason

That is still a post because a browser for whatever reason can only do get and post. You would think in 2025 or 26, whenever you're viewing this, that we would be able to do other types of requests natively in the browser. We can't, have to rely on JavaScript, but that's okay. So we would do this instead. So we will first try to get the underscore method field.

So we would do this instead. So we will first try to get the underscore method field. If that doesn't exist, then we will use the method attribute. But that's really it. We need the ID. We need the end point where we were sending this request. We need the HTTP type. Now we just need to save the payload. And it would be nice to have,

Now we just need to save the payload. And it would be nice to have, to know when this was created. Because ideally we would replay these requests in the order in which they were made. So we'll have this created at, which will be a new date. And then we will call to isostring. So that is essentially what we are going to store inside of the database. So that we can replay those requests

inside of the database. So that we can replay those requests and get those sent to the server so that the data in the payload can be saved or whatever. So that means we need to save to the database. But we also need to register sync on the service worker, which is what we will do next. But then the very last thing, let's reset the form. You know, because that would be a nice UI thing to do. You know, the user types in their data,

You know, because that would be a nice UI thing to do. You know, the user types in their data, they hit the submit button. It saves that information behind the scenes. And then, you know, it clears out the form. The idea being that the user is going to be able to click on the form. The idea being that, hey, you can do some more if you want. For the edit, I'm not necessarily sure if that's what we want to do.

For the edit, I'm not necessarily sure if that's what we want to do. So let's do this. If the item method is post, let's do this in lowercase. If it's post, then we will reset. So we'll call to lower case. If it's post, then we will reset the form. And yeah, that will be good. Okay, so that's what we will do

And yeah, that will be good. Okay, so that's what we will do whenever we try to submit the form. If we're online, great. We just do it like we normally would. But if we are offline, we prevent the default so that we can get the form data and build this payload so that we can store it inside of index DB that has an ID. We store the endpoint, the HTTP method, and when it was created,

Registering Background Sync10:41

We store the endpoint, the HTTP method, and when it was created, then we will save to the database, register the background sync on the service worker, and then reset the form if it is a post. Okay, we need to mark that as async because we will then have async function register sync because we have to register the background sync with our service worker. And it would be a good thing to make sure

with our service worker. And it would be a good thing to make sure that the browser supports service worker. So if service worker is in navigator and sync manager is in window, then we want to try to get the registration for our service worker. We've done this many times. And then we will try to await registration.sync.register. And we need a name here.

And then we will try to await registration.sync.register. And we need a name here. So we're just gonna call it outbox sync. No, sync outbox. I'm gonna call it outbox because I'm thinking kind of like an email client. If we're offline, we can still use that email client. We can still create new emails. We can not send them, but we can queue them in the outbox so that whenever the email client is back online,

We can not send them, but we can queue them in the outbox so that whenever the email client is back online, it sends the items in the outbox and then everything's gonna work. So we're gonna call this outbox and we are going to try to register that. Now, if it fails, well, we need to warn that background sync registration failed. So right here, we will await register sync

Building IndexedDB Outbox12:20

registration failed. So right here, we will await register sync and then we're good. So now it's just a matter of saving to the database. So let's create a module. This isn't necessarily a component. So let's do this. Inside of JS, we will create, let's call it storage. And then we will have outbox.js. Yeah, I like that.

And then we will have outbox.js. Yeah, I like that. Now, when it comes to working with indexed DB, we need the DB name. We can call this whatever we want. I'm gonna call it field logger because it makes sense to use the name of the application in my opinion. Then we need the DB version and then we need the outbox store.

Then we need the DB version and then we need the outbox store. This is gonna be the name of, you can think of it as the table really. And I'm just gonna call it outbox. And the first thing that we need to do is open up a connection, if you will, to the database. So let's have a function called open DB and we're gonna return a new promise with the resolve reject.

and we're gonna return a new promise with the resolve reject. The very first thing that we are going to do is call indexed DB. As the same implies, this is the object that we use to interact with the indexed DB inside of the browser. And there's an open method. We need to pass in the DB name and the DB version. Now, if this database doesn't exist,

We need to pass in the DB name and the DB version. Now, if this database doesn't exist, it's gonna create it, then it will open it. If it does exist, it's gonna open it. And then we have our connection or, well, yeah, we have our connection. Now, just like with Laravel, whenever we define a database or we create a database, you know, things like that, we need to run migrations. We need to define the structure of our database

you know, things like that, we need to run migrations. We need to define the structure of our database and run the migrations. We essentially need to do the same thing here. We will handle the on upgrade needed event. And this is going to give us the opportunity to run our migration. So we get the DB from the event object. It is the target of the event. And we need to check if our outbox is listed

It is the target of the event. And we need to check if our outbox is listed in the object store names property. If it contains our DB store, no, that's not DB store, outbox store, or rather, if it doesn't contain that, then we want to create that object store. So we will do that using DB create object store. We will pass in the object store value, but then we will need to pass in an options object because we need to define some things

but then we will need to pass in an options object because we need to define some things such as the key for this object store. We do that with the key path property, and then we give it a name, ID. So this ID here is directly linked to this ID that we create here. Okay, so this is going to create our objects or our outbox store keying it with ID, but then we can also create an index.

or our outbox store keying it with ID, but then we can also create an index. And I think it would be a good idea to create an index on created at. So we have the name, the key path, and then we'll say unique false. So if our outbox doesn't exist, we're going to create it. It's going to have an ID for the key. Then we index it based upon created at, and we're good to go there.

Then we index it based upon created at, and we're good to go there. So that now, if our request is successful, then well, we resolve passing in the result of the request, which is our database. However, if there was an error, we need to reject with request error. That is how we are going to open up a database, an indexed DB, but that's only opening the database. We need to write data into the database too.

an indexed DB, but that's only opening the database. We need to write data into the database too. And this, we want to export. So we'll export, it's not async, it's function, save to outbox. Yes, it is async. We have to open up the database. So save to outbox, and we will get the item that we want to save. So that we will open DB.

and we will get the item that we want to save. So that we will open DB. And then from there, we're going to return a new promise with resolve and reject. And our interaction with the database is based upon transactions. So let's create a transaction for our outbox store, and then the mode for this transaction, which is going to be read write. And then we are going to get our data store

which is going to be read write. And then we are going to get our data store using the transaction, and we will pass in outbox store. So that once we have the transaction, once we have our data store, then we're going to try to put our item into that store, but this could succeed, this could fail. So we're going to store that. So that if it is successful, then hooray, we resolve.

So we're going to store that. So that if it is successful, then hooray, we resolve. There's really nothing to resolve, so we're done. But if it errors, then we want to not resolve, we want to reject request.error. So that now here inside of our reading form component, we can import save to outbox from, and we need to go back so that we can go to storage and then outbox.js. So that we can use that save to outbox right here.

and then outbox.js. So that we can use that save to outbox right here. We will await save to outbox passing in the item, and that's it. Now, of course, this is just the first part of this. We're saving it into the database so that we can then sync that back up to the server when it comes time. We can, however, test this and make sure that we are at least saving something to the database.

We can, however, test this and make sure that we are at least saving something to the database. Let's go back to the browser. Let's open up the developer tools. Let's do a hard refresh. Now, if we scroll on down, we see that IndexedDB doesn't have anything there. So if we go offline and if we change the value and if we click updates, we can see that IndexedDB changed here.

and if we click updates, we can see that IndexedDB changed here. So if we expand this, we have field logger. That's the database that we created. Then we can see that there's this outbox, which is the data store, the object store that we created. We can see that we have this key ID right here. It's the same value as the ID property in the value payload, basically. So we can see that we have the ID, we have created at,

in the value payload, basically. So we can see that we have the ID, we have created at, we have the endpoint, we have the method and the payload. So we have everything that we need in order to replay that request whenever we are back online. And we will look at how to do that in the next episode.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟