6.1 Ordering and Nesting Pages
Most CMSs give you the ability to order and nest pages. We’ll add that functionality by incorporating the Nested Set Pattern. In this lesson I’ll show you how to use the laravel-nestedset package to make using this pattern much easier.
1.Introduction1 lesson, 01:23
2.Getting Started4 lessons, 45:12
3.Managing Pages6 lessons, 1:12:31
4.User Management2 lessons, 27:40
5.Managing the Blog3 lessons, 42:25
6.Adding Extras2 lessons, 25:48
7.Implementing the Front-End3 lessons, 32:10
8.Homework Review1 lesson, 07:11
9.Conclusion1 lesson, 01:24
6.1 Ordering and Nesting Pages
Many CMSs have the ability to order and to nest pages. And so, naturally we want to implement that as well. So that if we want to make the Contact page a child of the About page, then we should be able to do that. And so when it comes to implementing something like this, there are many patterns that we can choose. But the one that we are going to use called the nested set pattern. The reason why this is a good pattern to use in this case is because it is very fast when it comes to building the tree. The down side is it's a little confusing at first, I have to admit it took me a few minutes to fully wrap my head around it because there is this idea of left and right. And the values of left and right determine not only the order of the items but also the nesting of the items as well, so it can be a little confusing. And the reason why I like this particular package is first of all, it works with Laravel 5.5 which many of them out there do not, but the API is also very logical. Because there are some packages that force that whole left and right thing on you and it forces you to understand and know how the nested set works. And in this case it doesn't, the API is very straight forward, it's very logical, from a tree perspective. So that's why I like this package, and that's why we are going to use it. So we want to install this with our project, so let's look at the installation instructions. We want to call composer require kalnoy/nestedset. So let's go to the command line and let's do just that. So this is our first step, and then once this is done, we want to modify our seeder for our pages table because we want to then create pages and build this relationship between the pages. We also need to create a migration so that we can modify our pages table with the columns that the nested set package needs. Once that's done, we want to create our migration. We want to edit our model class and then also we want to update our seeder. So let's start with our migration. So we will make a migration, we will call this add_nestedset_to_pages_table, and lets also set the table=Pages. That way the table will automatically be populated so that we don't have to write some extra code. So let's go to database, migrations, let's refresh, there it is, add_nestedset_to_pages table. And then inside of here, we first of all want to add a use statement for Kalnoy\Nestedset. Now notice the capitalization or the lack of rather, lower case s, because here it's NestedSet, with a capital S. So inside of our up method, we are going to say NestedSet and it has a method called columns. So it make sit very easy to add these columns to a table, and then for the down method we are going to use NestedSet again, but it has another method called the drop columns to drop those columns and that's our migration. So we are done there, and now lets go to our model class. So app, and then page. Now we want to add a use statement here for Kalnoy\Nestedset. Once again, lowercase s, and then there is a trait called NodeTrait. We want to use this trait inside of our model, so we will say use NodeTrait, and we're done. This is going to add all of the methods that we need in order to easily manipulate our tree. So the final thing that we need to do to set this up is to go our seeder, so let's go to our Page's seeder. And we are essentially going to do the same thing, we're just going to break it up into separate objects. So let me first of all do that, and I am just going to paste this in. And it's the same thing, we are creating the About page, the Contact page, but the third page in this case is going to be the FAQ Sheets. And this is going to be the child of About. That's I'm creating this objects, I'm passing them to the saveMany method, so that after we do that, we can say about, and then we can use a method from NestedSet that says appendNode and then we pass in FAQ. So there's going to be this relationship between the two. So now we just want to run our migration, so we will say artisan migrate. We shouldn't have any problems there. Then we want to run our seeder, so we will say db:seed, and we will specify the class-PagesTableSeeder. That should run without any issues. So now that we have everything set up, we can start implementing what we need to add this nested set to our application. So let's start with our views because really that's the first logical place. We need to modify our forms so that we have the fields necessary for setting the order and the child or sibling relationship. So views>admin>pages>partials>fields. Let's add some white space in between these div elements so that they are broken up a little bit. And between the URL and the content, we're gonna add another div that will have a class of form-group, and it will also be a row because we are going to use some of the grid in this case. We're going to start with a div element that has a class of col-md-12, and inside of here we will have our labels. So let's go ahead and have our label. The text is going to be simply order, we don't need anything for the for attributes, so let's just get rid of that. And then we are going to have a select box for determining the relationship for the page. Is it going to be before or after a page? Or is it going to be a child of a page? So this is going to be inside of a div element with a class of md-2, and we're going to have our select. Let's give the name="order", the id will be the same, and let's also give this a class="form-control. And then we will have our options. Now it's possible that the user doesn't want to set anything as far as the order. So our first option is going to be just blank. So the value is going to be nothing and the text is going to be nothing as well. However, from here we need three other options, one for before, after, and child. So let's just copy and paste the blank ones so that we can easily add in the others before and the text will be before, the value will be after, and the text will be after, and then finally we'll say child. And we'll say child of, so that is going to be that select box. Now we need another div element because we need a select box for all of the pages, so that we will list the pages that is going to determine the relationship for the page that we are currently creating or editing. So in this case, we'll have a class of md-5, we'll have another select box and we'll call this one orderPage. Now let's give it a class of form-control, ID orderPage, or the name orderPage, the ID orderPage. And in this case we want to retrieve all of our pages and we want to list them as options. However, we should have another blank option in this case. So we'll set the value equal to nothing and the text will be nothing as well. But then we want to Foreach over our pages. So we'll say orderPages, we'll pass this in to the view and we'll say Foreach page, let's add the endforeach, and I need to change that to an @ sign. And then we will have an option element inside of here, the id, not the id, the value will be the pages id, and then we will have simply the title of the page, so page-title. And that's it for our view, at least for right now, so let's close that. And let's go to our pages controller because we're going to spend some time there. And the first thing we need to do is provide those order pages to both the edit and create views. So here we are at the edit method, let's just do that. We will put our model on a different page or on a different line here, and then we will say orderPages. Now these are a little special, we can't just say Page:all() because that isn't really what we want. We want the actual order that the pages are going to be in. So thankfully, we can easily do that with a method called defaultOrder and then we call get and then we have those pages in the correct order. So whenever these pages are displayed within the drop down box, they are going to be in the order that they should be. So the first page is at the top, is children are going to be underneath, and then any siblings of the first page are going to be after that, and so on and so forth. It just does what it's supposed to do. So we need to also supply this for the create view. So let's do the same thing, let's put our model on a new line and then we will just pass in the order pages. So at least as far as those two views are concerned, we will have them. And let's see if running, we are, so let's at least just check to see if this works. So we will go to the About page, we have the order there, and then we have our pages listed. This is the correct order. The FAQ Sheet is a child of About, so it makes sense that FAQ is after About. Now, notice though that as far as how we see this, it's just plain ordinary text. We would like to have some indication as to what page is a child of its parent, and we will do that in the next lesson. For now we are just going to leave this as is. Now something else too, we should probably do the same thing for our list as well. So let's go back to our controller, and instead of just paginating, we will do the same thing. We will call that default order and then paginate, and then our order will change. But now we need to actually do something, whenever we store a page or update a page, we need to take this new information into account. So since we are here at Store, what we can do is call a method that is going to update the page order. So we can say this UpdatePageOrder, we can pass in the page, and the request. And then that would update the page order and we can go on from there. So let's go ahead and write that. And I'm gonna make this at the bottom, this would be protected function updatePageOrder. We want our page as the first argument, then we want the Request as the second argument. And then the first thing that we need to do is check to see if we have both an order and a order page. Because if we are lacking any of those pieces of information, we can't do what we need. So if the request has order and has orderPage, then we want to continue on. And the next thing that we should check is to see if the page that we are editing is the same page that we are trying to create a relationship with. So if they are the same, well, we can't do that, we can't make a page a child of itself or a sibling of itself. So we will add another if check here if the page id is equal to the request orderPage, then we need to tell the user, not gonna do it. So we will redirect back to the edit page, and we will do that with route, that is pages.edit, and then we will pass in the route to data. So our page is going to be the page id and we also want to do that with input. And let's put that on another line. So withInput, withErrors, because we want to display an error that says that you cannot order a page against itself. So that can probably be worded a little bit better, but it's going to work here. So, [LAUGH] Cannot order page against itself, so there we go. So if the page is the same, we say nope, we return, and then we can update our order. But to do that, why don't we create a method on our model class that's going to do that. Because that's really where the actual updating should occur. So in this case we are going to pass in the order first and then we will pass in the request orderPage. Now we will need to go to our model class and write that method. But before we do that let's go to the update method. And before we fill our page with the provided data, we need to check to see if we can update that page. So because our update page automethod returns a response, we could check to see if we have a response. So if this updatePageOrder, we pass in the page and the request. Then we want to simply return a response, because we have a response we will return it. Otherwise we will then fill in and so on and so forth, and we also added the update page to store, so we're done with our controller. Let's go to our Page model and we're going to add that method. So we'll say public function updateOrder, we will have our order. And then let's call this a relative, or better yet, we will call it orderPage because that's what we called it in the other file, but then inside of here we'll call it relative because that's what it is. And we will findOrFail this page because we need the actual object in order to perform tree manipulation. So we want to check to see if the order is equal to before. And if it is, then we will say this, we have a method called the beforeNode, and we will pass in our relative and then after that we will save. Now else if order is equal to after, then we have a similar method and guess what it's called? AfterNode. So we will say afterNode, pass in relative and then we save. Now if we are calling this method, remember we have a value for order. So there's only three possible values, before, after, or child. So we're just gonna have an else, if that is the case then we are going to say relative and we are going to call the appendNode method. We're going to pass in this, making this a child of the relative. And finally, there's this method called fixTree, and frankly I don't know if we need to call it but we're gonna call it. And that's it, so let's test it. We will go to, let's go to the Contact page and let's edit it so that it's going to be Before, About. Since we don't have anything visual to tell us if something is a child of anything, then the next best thing is to just change up the order. So now we see Contact is Before, About, and that's what we expected. So let's move it back to After, About, and once again we should see contact at the bottom, we did. So in the next lesson, we're going to add what's called a presenter, something that's going to allow us to use logic within our views without really being within our views. If that doesn't make sense, you will see it in the next lesson.