Choosing Alpine for UI0:00
One of the hallmarks of modern web applications is interactivity. In fact, they are highly interactive, and they provide a lot of extra information to the user. And that's something that this application does not do. So, let's fix that. Now, when it comes to interactivity, we of course need to use JavaScript. But the question is, what do we do? Do we use vanilla JavaScript, Vue, React? I mean, I've been talking about trying to get away from Vue and React. It kind of doesn't make sense to then use Vue and React, but those are options. But really, when it comes to working with HTMX, there's of course vanilla JavaScript, but Alpine works very, very well. Because typically, we use Alpine directly inside of our markup.
but Alpine works very, very well. Because typically, we use Alpine directly inside of our markup. There are cases where we don't, but for the most part, we do. And one of the things that we have been doing throughout this course is doing things specifically in markup. So, that's what we're going to do. We're going to use Alpine. And since I'm using Vite to bundle everything, I'm just going to use the npm package. So, we need to make this globally accessible. So, we will do the same thing that we did with HTMX inside of bootstrap.js. But then we also need to go to app.js so that we can call Alpine.start. And that's going to be a good starting point.
Active Snapshot Indicator1:19
But then we also need to go to app.js so that we can call Alpine.start(). And that's going to be a good starting point. So, now we just need to decide what are we going to do to make this interactive. And the first thing I want to address is our snapshots. I want users to know which set of invoices are loaded. And yes, we have this text over on the left-hand side, but I want more. A good UI has a lot of redundancies. So, we have this blue box in the upper right-hand corners of our snapshots. And I think that would be a good active indicator. So, let's do that.
And I think that would be a good active indicator. So, let's do that. That's inside of our index.blade.php view, not index.php, index.blade.php. And of course, here is one of our snapshots. We have the other snapshot here. So, we need to be able to track which is the currently active snapshot. And I think it makes sense to do that here in our section, especially since our fragment for reloading snapshots is inside of our section. So, this is where we will add our component. And really all we need here is to track the selected ID of the snapshot.
So, this is where we will add our component. And really all we need here is to track the selected ID of the snapshot. Let's initialize it with an arbitrary string value. It doesn't matter what it is, just as long as it doesn't match what we assign to our snapshots, just so that one isn't active immediately. And then we just need to add our IDs here. So, this is the open invoices snapshot. Let's copy that and paste that down here for our approved invoices snapshot. And then we could say, okay, our snapshots themselves are going to be components because our snapshots are what actually contains the blue box.
And then we could say, okay, our snapshots themselves are going to be components because our snapshots are what actually contains the blue box. In fact, that is directly inside of our snapshot Blade component. So, let's open that up and I think it would be wise to just put our x-data and everything else inside of the snapshot directly. That way we don't have to replicate that for all of our snapshots. It's just automatically done here. And looking at this, I also need to include all of the attributes so that those flow down. So, here we will have our x-data and we just need an active property so that for this span element, this is that blue box.
So, here we will have our X data and we just need an active property so that for this span element, this is that blue box. We'll say X show and bind that to the active property. So, then what we can do is initialize this component and we'll set active equal to if selectedID is equal to this element's ID. And that should work. The only other thing that we need to do then is actually set the selectedID. So, that's done here for our A element. We just need to handle the click event and we will say selectedID equals and we want the root.
We just need to handle the click event and we will say selectedId equals and we want the root. Now, $root is a magical value. It points to the closest element that has an x-data attribute, which in our case is the snapshot. So, that means it's going to use this ID value here, which means we need to take this click event, add that down here for the approved invoices, and that should make this work. So, we can see that those indicators are now not there. If we click on these, voila, we have our currently active indicator.
Disable Submit and Total4:37
So, we can see that those indicators are now not there. If we click on these, voila, we have our currently active indicator. So, perfect. That's great. But I want more. I want to first of all make the submit button disabled so that if there are no invoices selected, then there's nothing to click on. It just makes sense. And then as we add invoices to submit, then we will enable the submit button. But I also want to keep a running total of all of the invoices that we have selected.
And then as we add invoices to submit, then we will enable the submit button. But I also want to keep a running total of all of the invoices that we have selected, meaning I want to take their amountDue and add them all together so that we can just see what that running total is. So, let's go to, let's see, what view is that? That is the list view. So, let's open that up. I think we're done in index, so we can close that. And the first thing I want to do is make these tr elements inside of the table's body. I want these to be components.
And the first thing I want to do is make these tr elements inside of the table's body. I want these to be components. They'll be very simple components so that we will have a selected value, which we will initialize as false, and we are going to bind that to these input elements because these are our checkboxes. So, we will use x-model here and set that to selected. But then we are going to initialize a watcher. We're going to watch the selected property. And when it changes, we are, of course, going to do something. But that something needs to be done outside of this component.
And when it changes, we are, of course, going to do something. But that something needs to be done outside of this component. It needs to be a little more global because we have to include the button and we will also have that running total. So, let's go ahead and add that span. We'll give it a class of inline-block. It's going to have some dynamically loaded text, so we'll go ahead and initialize that. But this also needs to look good. So, for this div element here, let's add a class of flex, justify-between, and then items-center.
So, for this div element here, let's add a class of flex, justify-between, and then items-center. So, I think what we could do is add a component here at the section level. And we'll have two properties. We'll have count for zero, then we will have total, and we will initialize that as zero. So, the idea here is our button, we are going to bind the disabled attribute to be true if the count is zero. And then for our running total, that's where we will have our total there. So, if we take a look at this in the browser, let's open up just one of these forms.
And then for our running total, that's where we will have our total there. So, if we take a look at this in the browser, let's open up just one of these forms. Okay, so that's great. We can now see that our button is disabled. We have some text over here. So, whenever we select some of these invoices, we, of course, need to manipulate the count and the total values. And I think one of the best ways that we can do that is with a method. So, here, whenever we are watching selected, we are going to call a change method on that parent component that's on the section.
So, here, whenever we are watching selected, we are going to call a change method on that parent component that's on the section. We will pass in the value, that way we know if we need to add or subtract. And then we need to include the amountDue of the invoice. Now, we can approach this in a couple of different ways. We can include it as an attribute. But that means that anytime that we need to get that value, we have to use the DOM in order to extract that from the attribute. It's not that big of a deal. But I think what we could do is just directly add that amountDue to the output markup.
Extracting InvoiceList Component7:56
It's not that big of a deal. But I think what we could do is just directly add that amount due to the output markup so that it looks like this. We directly output the invoice amount due so that it is there as a literal inside of the markup. That's really the quickest and the most efficient way that we can use that value. So, let's just do that. Now, since we have that change method, I don't necessarily want to define that here. So, let's make this an external component. So, let's just call this InvoiceList. That means that we will need to create a file.
So, let's just call this invoiceList. That means that we will need to create a file. Let's do that inside of JS. We will have an invoices folder and then list.js. That also means we will need to pull this into app.js. So, we will simply import invoiceList. And since everything is being bundled together, that's all that we need to do there. So, that's here. We will call the data method. We have invoiceList as the name.
We will call the data method. We have invoiceList as the name. And then we just need to define that component. So, that we will have the count initialized as zero, total initialized as zero. Then we will have that change method. We'll have the toggle. That's going to be the value coming from the checkbox. Or more specifically, it's going to be the value coming from the selected property since that is bound to our checkbox. And then we will have our amount there.
since that is bound to our checkbox. And then we will have our amount there. So, if toggle is true, that means we need to increment the count. And that means we need to add to our total. If toggle is false, then we need to decrement our count. And we need to subtract from our total. It sounds simple enough. So, let's just make sure it's going to work. If we check this, sure enough, our submit button enables. And we see our running total, but no one can read that.
Formatting Currency Totals9:48
If we check this, sure enough, our submit button enables. And we see our running total, but no one can read that. So, what we need to do is actually format that. So, what we can do is use the Intl.NumberFormat. And since I am in the U.S., I'm going to set that as my locale. The style I'm going to set as currency. And then the currency is going to be USD. So, then I could add another method here. And really, this doesn't even have to be a method. This can just be a property with a getter.
And really, this doesn't even have to be a method. This can just be a property with a getter. But we will format the total so that we will return the formatted total. That ought to work just fine. So, we can go back to our list view. And wherever we output the total, we want to use that formatTotal. Was it formatted or formatTotal? formatTotal. So, there we go. So, now our total is formatted using USD currency.
So, there we go. So, now our total is formatted using USD currency. Whenever we select an invoice, we have that value. When we deselect it, it goes back to zero. It looks okay. Negative zero. Okay, so we are dealing with JavaScript. We are dealing with numbers, especially floating point numbers. So, it's not 100% precise. But I like the feel of this.
Testing End-to-End Behavior11:01
So, it's not 100% precise. But I like the feel of this. So, if we submit this, it should submit that invoice. That is $2.08. It should become approved. And it should no longer be listed here. Sure enough, it's not. Our components reset. And we can start all over again. So, it works individually.
And we can start all over again. So, it works individually. Let's make sure it works here inside of our dashboard. So, we go to open invoices. Let's go to approved invoices. And let's see if we can find that $2.08 invoice. We have a sea of invoices here. So, let's just pick one. Let's pick another. That same functionality is working.
Let's pick another. That same functionality is working. If we submit, our snapshot indicator is the same. Our table reset. And it looks like everything is working as intended. So, even though our server returns strictly content, just markup, we can still create interactive web applications. And we haven't really lost anything, except maybe complexity.
