Introducing Inline Editing0:00
Let's warm up with a simple inline editing component. You know these smooth interfaces which look like they are read only but you can click anywhere and start editing. There are no text areas, no borders, no save buttons and everything is autosave. The user also gets a feedback and this disappears after a few seconds making it look seamless. This interaction is very similar to Google Docs. So let's build this using Livewire. Let's start with creating a Livewire component. It's a full page component called Meeting Notes. Let's find it.
Preparing Component Data0:44
It's a full page component called Meeting Notes. Let's find it. I have already created the meeting model right here with the title, date and notes. I also have the meetings table with title, date, notes again. And I have a seeder with a single meeting. That's the one we are going to be editing. I have migrated and seeded the database. So let's get going. This is the new single file component. So let's add a title attribute to add a title to the page.
This is the new single file component. So let's add a title attribute to add a title to the page. Let's just say inline editing. Let me import this. And right here, let's declare some public properties. We need meeting, we need the title and we need the notes. Let's also get the very first meeting note right here. Down here, I'm just going to paste the meeting title and notes, everything styled. So let's see what this looks like. So this is what we want the end result to look like.
Converting to Textareas1:47
So let's see what this looks like. So this is what we want the end result to look like. Only thing is we want to make this editable. Right now, this is a H1 and this is probably a paragraph down here. So let's change the heading and paragraph to text area. H1 would become text area. And let me remove this and instead add the title as using wire model. And while we are at it, let's also add live and debounce because we want it to autosave. And yes, this is the title. Let's also add the placeholder which says click here to add title in case it's not added.
And yes, this is the title. Let's also add the placeholder which says click here to add title in case it's not added. And we'll do the same to the paragraph as well. We have the placeholder and wire model. Now this obviously looks like a text area, which we don't want. And it's also not full width. Let's change that using some utility classes, which would be WFull. And tailwind already removes the borders, but let's still add border zero to just make sure. We will also be adding focus outline none, focus ring zero, and we will be adding resize
sure. We will also be adding focus outline none, focus ring zero, and we will be adding resize none just to make this resize handle disappear. Let me copy the same and paste it for the other text area as well. Right here. And we don't need this white space pre-wrap that was when it was a paragraph. Now this looks better. Only thing is this text area has two rows and here also there is a scroll bar and even if you add the number of rows here, it will not auto expand. That's something we will solve in a minute.
Adding Autosave Logic3:36
if you add the number of rows here, it will not auto expand. That's something we will solve in a minute. Before that, let's make this autosave using Livewire. So scroll up and add the updated function, updated property, and if it's either title or notes, update the meeting. So if we add another line here that says new task and I refresh the page, it stays just that we are not able to see that without scrolling. So to solve the scrolling issue, we want the text area to resize based on the content within. There's actually a single utility class in Tailwind for this, which is, let me add it
within. There's actually a single utility class in Tailwind for this, which is, let me add it here so you can immediately see the effect, which is field sizing content. Now you see, we don't see any scroll bar. You can start typing as many lines, it will auto resize. And if you add the same thing right here, field sizing content, you will see that now it occupies a single row as well. The only problem with this is that it's not supported in all browsers, especially it's not supported in Firefox and probably some versions of the major browsers, which is why we cannot use this.
Auto-Resizing Textareas4:48
not supported in Firefox and probably some versions of the major browsers, which is why we cannot use this. You can leave it here or remove it. Let's use AlpineJS to fix this issue. So now turn this text area into an Alpine component using XData. Here we're going to have a resize method, which will first set the element style height to auto, and then it will set it to the scroll height, which means the height of the content within. Now we just have to run this on XInit and whenever there's an input. I'll copy all of this and put it into the other text area as well, down here.
Now we just have to run this on XInit and whenever there's an input. I'll copy all of this and put it into the other text area as well, down here. Notice that this is immediately working, except we see two rows for this text area. So let me simply add rows equals one so that it starts with one row. Now it looks like it's working, but the moment I try and type something, it goes back to its previous state. That's because every time I type something, Livewire is re-rendering this text area and the height goes back to what it was before. To solve this problem, we need to add wire ignore self to both the text areas so that this particular component is ignored while re-rendering.
Building Save Indicators6:10
To solve this problem, we need to add wire ignore self to both the text areas so that this particular component is ignored while re-rendering. Now let me refresh and try again. You see, I can type as many lines and this is the exact behavior we need. This is great. Part of our task is done, but now there's no user feedback. Let's add an indicator to the right top corner where it says the note is saving while the progress is on and then it shows saved and finally disappears. Here we have the notes heading. So let's use a div with absolute positioning to absolute and then write six to add a saving
Here we have the notes heading. So let's use a div with absolute positioning to absolute and then write six to add a saving indicator here with six padding. Right here, I'm going to paste the SVG and saving text and it appears. Let's also add text SM. It's a little too big. All right. This is fine. We have to show it only when it is saving. So I will wrap this in another div and add wire loading with a little delay so that we
We have to show it only when it is saving. So I will wrap this in another div and add wire loading with a little delay so that we don't keep seeing it when the network request is too small and also add the wire target title or notes. So we want to show this even while the title is getting edited or the notes. Okay. Let me type something. And since the network request is too small, you're not able to see any loading indicator. So to just simulate a slow request, I'm going to add sleep for maybe two seconds. And here if we start typing, now you see the indicator.
So to just simulate a slow request, I'm going to add sleep for maybe two seconds. And here if we start typing, now you see the indicator. That's great. So the moment it is saved, it would also be nice to see saved for a few seconds. Let me remove this. And right here, I will add another div. Let me copy this, paste it down here and add the saved indicator within this. So this is the check icon and the saved text. Only thing is, instead of showing it while it's loading, we will remove it while it's loading.
Only thing is, instead of showing it while it's loading, we will remove it while it's loading. But this appears all the time. Even if you just open the notes, it appears. This is not something we want. So every time the notes or the title is saved, let's dispatch an event from Livewire. Right here, this dispatch notes saved. Now let's turn this entire div into an Alpine component so that we can have the saved state, which is set to false. And here we listen to the event notes saved on the entire window and set saved equals
which is set to false. And here we listen to the event notes saved on the entire window and set saved equals true. Based on this state, let's show or hide the check. So scroll down to the bottom, enter this div right here. Let's just add X show equals saved. Oh no, we don't want any of the transitions. I will remove that. So now let me refresh. As you can see on page load, we don't see the saved indicator.
So now let me refresh. As you can see on page load, we don't see the saved indicator. But the moment we start typing, it appears and it stays there. We want it to disappear after a few seconds for which let's add the timeout. Saved equals true. Set timeout. Saved equals false after, let me say, three seconds so we can see the demo. Once again, doesn't appear on page load. Let me start making some changes. It appears and within three seconds, it's gone as well.
Let me start making some changes. It appears and within three seconds, it's gone as well. We are almost done. There's only one small issue. That is if I'm constantly typing and if I stop typing, notice how the saved indicator disappears randomly. It should disappear three seconds after I stop typing. But that's not happening because we are not clearing the timeout. It gets confused. So let's add another timeout state here.
It gets confused. So let's add another timeout state here. Let it be null. We will assign this to timeout. Timeout equals set timeout. And then here, let's clear it. Clear timeout, timeout and then saved equals true. This should work. Let me start typing new task and we see that even if we are continuously typing or we stop, no matter what the saved indicator will disappear only three seconds after I fully stop.
Let me start typing new task and we see that even if we are continuously typing or we stop, no matter what the saved indicator will disappear only three seconds after I fully stop. That's it. We have a simple inline editing component. I know it's really simple, but it has a lot of those little details like showing proper feedback and making sure that the text area expands and contracts accordingly. Also making the saving indicator hide after a few seconds so that everything looks very seamless. On to the next episode.
On to the next episode.
