در حال بارگذاری ...

Project Overview0:00

So this is what we're going to build today. It's a data table written in Livewire. Basic data table functionality. You can change the amount per page, pagination, real-time searching, all that good stuff. And we're going to do the whole thing with writing zero lines of JavaScript, if you can believe that. So we're going to use Livewire. It's a tool I've been working on for the past year. I'm pretty excited about it. And it allows you to write interactive UIs without all the JavaScript.

Vanilla JS Prototype0:22

I'm pretty excited about it. And it allows you to write interactive UIs without all the JavaScript. So before we jump in and actually build the data table, we're going to build kind of a smaller little version of the data table and not even use Livewire to build it. We're going to do it from first principles and implement this functionality with just raw vanilla JavaScript so you can kind of understand how Livewire works under the hood. And then we're going to move forward and actually build the data table. So this is a statically rendered page in Laravel. This is contacts from a database and then a dummy search box that doesn't actually do anything yet.

This is contacts from a database and then a dummy search box that doesn't actually do anything yet. So we're going to wire this up so that as we type, we get real-time searching functionality. So let's take a look at the app in the backend. We have a basic routes file, returns a root route, welcome view, and passes contacts from the database into this view, which is a pretty simple boilerplate HTML head tag with bootstrap fun awesome. And then here's our little search box and then the list of all the contacts. And this contacts partial is pretty straightforward. It just foreach is through the contacts and puts them in a list item.

And this contacts partial is pretty straightforward. It just for each is through the contacts and puts them in a list item. Great. So normally if you wanted to do something like this, you'd probably reach for Vue.js or some JavaScript like that, and you would bind some data to this input element like search so that as the user types, search is updated. And then every time search is updated, you'd fire an Ajax request to fetch some JSON from the backend and then rerender the template or view would rerender the template. So what we're going to do is called server fetched partials. Instead of fetching JSON and then rendering a template in the front end, we'll fetch the

Server-Fetched Partials1:47

So what we're going to do is called server fetched partials. Instead of fetching JSON and then rendering a template in the front end, we'll fetch the template already rendered on the backend. So we're just going to fetch a partial and then we're going to swap it into the page. So you'll see exactly what I mean. For starters, we're going to need to create another endpoint, a dedicated endpoint for this little partial. So we'll call it contextPartial and we'll return this contextPartial view, which is actually just the Blade include. So we're just returning a partial and then we'll pass in the data, all the contacts.

actually just the Blade include. So we're just returning a partial and then we'll pass in the data, all the contacts. Okay. So let's take a look at this inside of the browser. So if we go to context.blade.php, we see just that partial with all the list item elements shown right there. So let's add some searching functionality so that we can say search equals and then pass in prof and actually search by names containing professor. So instead of all, we'll do search and then we'll get a query parameter called search and get the results.

So instead of all, we'll do search and then we'll get a query parameter called search and get the results. And now we refresh the page. We get them all filtered, can filter by doctor and you get the idea. Awesome. So now we have our dedicated endpoint just for that HTML partial and we can wire this up so that as we type, we'll have a JavaScript listener sending out Ajax requests to that endpoint to get the HTML and swap it into the page. So we'll do this by hand, no dependencies, just vanilla JavaScript. We'll use document.querySelector to get the input element right here.

So we'll do this by hand, no dependencies, just vanilla JavaScript. We'll use document.querySelector to get the input element right here. And then we're going to add an eventListener to that for the input event so that every time a user types into it, this callback fires and inside of here, we're going to fetch from that endpoint, that container, sorry, that contactsPartial. So contactsPartial, and then we'll add that searchParameter, e.target.value. And if you haven't seen that before, the event has a target, which is the element that it was basically generated from. And then we can use the value from that to get whatever text was in there at that point in time.

And then we can use the value from that to get whatever text was in there at that point in time. All right. So we get the HTML back. That's all filtered and we'll use document.querySelector to get the UL right here and then dot innerHTML to swap out its contents with a new HTML that we got from the backend. So check it out. Good luck. We're going to type in and voila. So there you go.

inspector and just watch the AJAX requests fly. So I'm going to hover over transfer issue and check this out already. There's an AJAX request that gets fired with a bunch of HTML. So Livewire documentation docs. And now if I open this, you'll see that's the little bit of HTML. So if I search in here, blah, blah, blah, blah, blah, blah, no repositories match. Check it out. No repositories match. So as this is exactly what we just did, we sent an AJAX request with some query. We rendered a template on the backend, got some HTML and swapped it into the page.

Install Livewire Component4:57

So as this is exactly what we just did, we sent an AJAX request with some query. We rendered a template on the backend, got some HTML and swapped it into the page. So for me, GitHub is using this all over the place. And if they're doing it, it's a pretty, it's pretty viable pattern. So Livewire is basically built on this exact concept. It just provides a framework around it. Now that we've built this by hand, let's swap it out with Livewire. Let's get Livewire installed and extract this functionality into a Livewire component. So first step, install it with composer, composer require livewire/livewire. And after that, the final step is including the JavaScript assets that Livewire needs.

So first step, install it with composer, composer require livewire/livewire. And after that, the final step is including the JavaScript assets that Livewire needs to work its magic. And we just use the livewire assets blade directive that Livewire gives us. So you can put it right before the end of the head tag and we're totally done. Livewire is totally installed. Now let's, let's create that new component. We're going to call it ContextTable. So we'll say php artisan make:livewire ContextTable. Okay.

So we'll say php artisan make:livewire contacts table. Okay. And Livewire created two files for us, the class for the component, and then the Blade view for the component. Okay. So let me give you a quick tour of a Livewire component. You have a class and that's where everything starts, kind of like a view component script tag or a React component. And it has a render method that's in charge of returning a Blade view. And this is just a normal Blade view, and this will get run every time the component

And it has a render method that's in charge of returning a Blade view. And this is just a normal Blade view, and this will get run every time the component updates. So like I said, it's a normal Blade view. So you can follow Livewire ContextTable is just in the normal location, resources/views/livewire/context-table. And then this is just a Blade view and we can replace this with hello world. And we'll swap this into the page and we'll see the basics of a Livewire component. So let's just throw this in the page right here. We'll say livewire context-table.

So let's just throw this in the page right here. We'll say Livewire ContactsTable. I like to think of Livewire components, kind of like Blade includes with a bunch of magic. You include them kind of the same way, right in line in your Blade. We refresh the page and check it out. We get hello world. So one important note here, Livewire is SEO friendly. It's basically a Blade include for the first render, and then it does fancy stuff after that. Right now, so far, it's just a fancy Blade include.

that. Right now, so far, it's just a fancy Blade include. So it's all backend php. Now let's extract this bit of HTML into our component. So we'll go back to the view of our component and swap it in right here. Now to make this work, we're going to need to provide this include with context. Livewire components have their own scope, so they don't inherit any variables from the parent context. In other words, there's no context for this partial to use. We need to provide it with context ourselves.

In other words, there's no context for this partial to use. We need to provide it with context ourselves. So we'll pass it in just kind of like you would inside a controller method. So context, we'll pass in all for now, and then this should work. Now let's reload our page and see what we have. There we go. It totally worked. But we're going to have two of these because we didn't remove all that boilerplate. So let's get rid of all that other stuff that we left behind. So we don't actually need to pass context anymore because the component is going to

So let's get rid of all that other stuff that we left behind. So we don't actually need to pass context anymore because the component is going to get it itself. So we can get rid of all this. In fact, we can get rid of this whole endpoint. And then over here, we can get rid of all that extra stuff that we added, the script tag. Okay. And now we've cleaned everything up. And now the Livewire component is basically in charge of the whole show now.

Livewire Data Binding8:03

And now we've cleaned everything up. And now the Livewire component is basically in charge of the whole show now. Now let's add that searching functionality back. And how we did that before with this little search helper, and before we passed in this request and we got a little search parameter out of the query string, but we're going to do it a little bit differently because we're in a Livewire component. We'll use a piece of local data, a little bit of state that we're going to track. And we're going to call this search. Okay. We'll get it.

Okay. We'll get it. And then we can add this as a public property and now public properties in Livewire. They're similar to data properties inside of a view component. They get tracked as state and you can actually bind them to input just like you would inside Vue. So in Vue, you'd do something like v-model equals search and in Livewire, you do wire:model equals search. So really familiar syntax. And basically Livewire is going to take care of making sure that the value of this input.

So really familiar syntax. And basically Livewire is going to take care of making sure that the value of this input element is synchronized with the value of this data property in the class. So check it out. We search in here and voila, like magic. We have real time searching built with Livewire that handled all the wiring up for us. We didn't have to manually fire AJAX requests or anything, but under the hood, it's the same inner workings as we did before. Every time you type, there's an AJAX request sent out. And what comes back is a bunch of HTML that Livewire intelligently swaps into the page.

Every time you type, there's an AJAX request sent out. And what comes back is a bunch of HTML that Livewire intelligently swaps into the page and it does a bunch of other helpful stuff. But that's the basic gist of it. All right. Now the other piece to the puzzle is not only can you bind data, you can also call methods kind of like you would inside a view component when you bind maybe a click handler. So let's create a little button and we'll add wire:click clear. We'll make this a clear button so you can clear the input. Now in view, you might do something like v-on or @click, but Livewire is wire:click.

We'll make this a clear button so you can clear the input. Now in view, you might do something like @click, but Livewire is wire:click. And just like view, you can listen to any event that an element fires. So we're listening for click and we're going to fire the clear method. So let's create a clear method. And all this is going to do is literally reset search. So the paradigm should feel pretty familiar to you if you use view. Okay, now we have a clear button, we fill search, we hit clear. And there we go. If we add some initial data, it's going to be bound out of the gate.

And there we go. If we add some initial data, it's going to be bound out of the gate. We hit clear and we're back and good to go. One other helpful note, all the public properties are automatically made available to the Blade view kind of like in a mailable. So we can echo out search right here, just normal blade echo. And you'll see that as we type, it's going to update that. And the inputs also debounced. So if you type really fast, it's not going to send an AJAX request for every keystroke we let up, and then it sends the request.

So if you type really fast, it's not going to send an AJAX request for every keystroke we let up, and then it sends the request. Alright, so there's the basics of Livewire. There's data binding, there's firing actions on event listeners, there's passing data to the view, all that good stuff. So now we can apply everything we learned and actually build that data table. Let's clean up this component and swap it out with our little data table project. So we're still going to pass in contacts, but over here, we're going to replace this with a bunch of boilerplate code that I have set up for this data table. So let's take a look.

Build Table Pagination11:24

So for starters, why don't we take the contacts and shove it into the table for each of the contacts and echo out a row for each table. Okay, so for each, and again, this is all just standard Blade. And then insert the name in the first row. Email in the second and birth_date in the third. All right, and load the page, and we have a table filled with all of the contacts, which is a lot. So why don't we start with some pagination? Let's paginate this and only show 10. This is just normal Laravel pagination, call paginate, pass in 10.

Let's paginate this and only show 10. This is just normal Laravel pagination, call paginate, pass in 10. And there we go. All right, so let's bind the pagination to this little select dropdown. And to do that, we're going to need a piece of data called perPage that's going to default to 10. And then we'll reference it inside paginate. And then we can actually bind it to that select dropdown with our simple wire:model that you saw earlier. Okay, and now when we change 10 to 15, check it out, it automatically updates.

saw earlier. Okay, and now when we change 10 to 15, check it out, it automatically updates. Awesome. All right, so now let's add our pagination links. And to do this, again, we can just keep using Laravel's default pagination behavior. So out of the box, Laravel ships with pagination links automatically generated. So if we call contacts and then links, we're going to get Laravel's pagination links. And these are fine, and they actually technically work. But they're not ideal because they do full page reloads. And we want this to be a reactive Livewire component that's not doing full page reloads.

But they're not ideal because they do full page reloads. And we want this to be a reactive Livewire component that's not doing full page reloads. And every time we reload the page, we're going to lose all our Livewire state. So that's not exactly ideal. So Livewire actually provides a nice little utility out of the box. It's a trait called WithPagination that basically hijacks Laravel's internal pagination system and makes it all Livewire-y. So that's really all we need to do. And now when we switch pages, check it out, there's no full page reload. It's just loading.

And now when we switch pages, check it out, there's no full page reload. It's just loading. Perfect. So for a source diving type, it's actually not that complex. It just keeps a little bit of public state called paginator and hijacks the page resolver to use that current page. So not too intimidating, perfect. And now we'll fill in these X's in the show X to X out of X results. And again, this is going to be stock Laravel pagination stuff. So contacts, first item.

And again, this is going to be stock Laravel pagination stuff. So contacts, first item. This is going to be contacts, last item. And then this one's going to be contacts total. Refresh and we get showing one to 10 out of 500. And that's going to keep updating no matter what page we're on. Awesome. So we have full pagination per page functionality. Let's do the column sorting. So for this feature, I think we should drive it out with the data first and then we can

Add Column Sorting14:20

Let's do the column sorting. So for this feature, I think we should drive it out with the data first and then we can worry about the template. So we know that we're going to need some sort of orderBy inside Eloquent. And we're going to need a field to sort on, like name or whatever's currently selected. So of course, we're going to bind some data and use that. So we'll say sortField, and then we'll add that piece of data here, public $sortField. And we'll just default it to null. And then we'll add a method to actually do the sorting. So sortBy, it accepts a field and sets that sortField property to whatever is selected.

And then we'll add a method to actually do the sorting. So sortBy accepts a field and sets that sortField property to whatever is selected. Perfect. And now we can actually go to these links, these column header links and add the wire:click to sortBy. So we would do something like wire:click="sortBy", and we can pass in parameters just kind of like you would in Blade. And for this one, we want to pass in name. And we can just duplicate this and do email and birthDate. So we're using an <a> tag to behave like a button.

And we can just duplicate this and do email and birthDate. So we're using an <a> tag to behave like a button. And this isn't necessarily great for accessibility reasons. If you're using a screen reader to browse the web, it's going to read this out like a link, it's going to actually say the word link. So to make it treat it like a button, we can add role button. And also we are referencing a hash, a root hash, because we're using this link as a button and not actually as a real link. So just like in Vue, you're going to want to add the .prevent to make sure that it actually doesn't visit the link itself.

So just like in view, you're going to want to add the dot prevent to make sure that it actually doesn't visit the link itself. Okay, so now if we click a column, we have successful sorting. Awesome. But if we click it again, we want it to change sorting directions. And right now it doesn't do that. So let's implement the sort direction logic. And again, we'll kind of drive this out just with the data. So here, we're going to need some logic that basically says if activeField, meaning the field you're trying to sort by is the currently sorted field, then reverse the sort direction.

So here, we're going to need some logic that basically says if active field, meaning the field you're trying to sort by is the currently sorted field, then reverse the sort direction. If else, then set the direction to true. If you're sorting another field coming from a different field, we want to start with true and not just the inverse of the field before it. So this little bit of logic is a little bit nasty, but it shouldn't be too bad. So let's start by saying if this sort field is equal to the field that we're trying to sort by, we're going to need to now reverse the sort direction. So how do we do that? We don't really have that concept yet.

So how do we do that? We don't really have that concept yet. So let's drive it out backwards. So inside of orderBy, we're going to pass in the field, but you can also pass in a second parameter that is ascending or descending. So we can conditionally render this with a piece of data we'll keep track of called sortAscending. So we can say if it is sortAscending, pass in ASC, and then if not, pass in descending right there. Nice.

right there. Nice. And this is getting a little bit ugly and long, so I'm going to put this on a new line and call this query. And that just gets the query builder, and then we can tack stuff on there. Great. So we have sortAscending. Let's add that property. So public sortAscending defaults to true. And then now we can reverse sortAscending.

So public sortAscending defaults to true. And then now we can reverse sortAscending. So we'll say sortAscending equals the opposite of sortAscending. Okay. And now our else statement, if we're switching over to a new field, we want to set sortAscending to true, meaning that it's always going to be ascending to start. Okay. So like I said, I might extract this into its own little method that's well named in a real project, but for now I think we're pretty good. So let's see if the functionality worked.

This is great. The one missing piece is we need those icons, those little sorting icons. So to do this, I added a little Blade include that should make this a little bit nicer. So let's clean these rows up just a little bit, put these on their own separate line, and now we'll include that Blade include. We'll say include, I put it in an includes directory, sort-icon, and we pass it the field that we want it to sort on, or the currently sorted field we'll say. And the first one's going to be name, second one's going to be email, and third one's going to be birth_date. Okay.

TDD Search Feature19:14

And there we go. We have the icons, we have the sorting, and we are doing pretty well. So the last piece of functionality in this page is the real-time searching. So you saw how we did this before where you just wired:model up that search public property, and then it'll automatically search if we use it in the query builder. Let's take it one step further and actually use Livewire to TDD this functionality and see how Livewire does with testing. Okay. So I had this little boilerplate test made an example test. Contacts table is searchable, and I'm using a model factory to create two different contacts.

So I had this little boilerplate test made an example test. Contacts table is searchable, and I'm using a model factory to create two different Contact instances in the database with two different names, foo and bar. So let's test drive this feature with Livewire. We'll say php artisan test, and then we pass in the name of the component that we want to test. In this case, ContactsTable. Okay. And now we can make assertions. We'll say assertSee(foo) and assertSee(bar).

And now we can make assertions. We'll say assert C foo and assert C bar. Run this test. And we get passing. And just to show you that I'm not joshing you, if I say something else and rerun it, we get a failure. So it's saying, hey, I can't see that it contains this little bit of text. And it has all of that right here. Okay. Sounds good.

Okay. Sounds good. So let's actually test some functionality. So let's say that if we set a piece of data called search to foo, we want to assert that we see foo because we've searched for foo. But now we want to assert that we don't see bar. Okay. Rerun this test. And we get a failure. Well, the first failure is because I broke the test earlier.

And we get a failure. Well, the first failure is because I broke the test earlier. And this is the better failure. It says property binding exception because search doesn't exist yet. So let's go back to that table. And we'll add search. And we'll default it to an empty field. Rerun the test. And we get a different failure. It says, hey, failed asserting that it does not contain bar because it actually does.

And we get a different failure. It says, hey, failed asserting that it does not contain bar because it actually does C bar, meaning that the search doesn't actually do anything. It exists, but it's not useful. So replace query with search. Pass it in. Rerun our test. And there we go. We have it passing. So we've just driven a feature, a front-end type feature with a back-end test that runs

We have it passing. So we've just driven a feature, a front-end type feature with a back-end test that runs instantly. Cool. So now we can go ahead and add it into the template. So we'll wire:model search. Okay. Refresh our page. And now with any luck, as we search, there we go. We are getting real-time searching functionality.

And now with any luck, as we search, there we go. We are getting real-time searching functionality. Perfect. Okay. So this next leaf is probably going to be pretty underwhelming to you, but I want to format this birthdate column. So right now it's just spitting out a raw Carbon birthdate. So let's go to this column here, birthdate. And we can just format it like this, format month, day, and year. And this is because in our Contact model, I have birthdate cast to a date field.

And we can just format it like this, format month, day, and year. And this is because in our Contact model, I have birthdate cast to a date field. So we can just add that format on the end of it because it's a Carbon instance. Okay. And there we go. Nicely formatted dates. So this may be underwhelming and not impressive at all. But I think it's pretty powerful because it's something that maybe I would have done in JavaScript if I built this whole thing in JavaScript. And it's hard to manage two like date formatting systems and money formatting systems between

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