Feature Request - Nested Sets (items with hierarchical parent/child relationships) #752
Labels
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: joomla/Component-Builder#752
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
I'm following up on #751. I think a good way to implement this would be to add a 'Parent' field type that triggers these changes to the view when included in the form.
I'm going to lay out all of the steps I took to get this working to help with automating it, but I'll also write it like a tutorial in case someone needs to use it in the meantime (at the very least I will probably use it). This was the article I used as a starting point by the way: https://docs.joomla.org/Using_nested_sets
I'll summarize gotchas at the bottom; might be helpful for @Llewellynvdm.
EDIT: I fixed a few spots in the code where I copied the view and component name from my own project, and clarified that you have to edit your own in there.
Create the fields
Step one is to create some columns that are required by the JTableNested class (which we'll be setting up soon). Some of them are already created by JCB, but you'll have to create in the rest. You can do that using fields. The required fields are:
Title
Set the type to "Text"
The 'name' option for this field must be set to 'title' so do that. I think that in JCB titles can have any name, but in this case JTableNested assumes that the field is named 'title'.
In the database tab, set the options so they're equivalent to
varchar(255) NOT NULL
.Lft, Rgt, Level, and Path
These fields should all be set to the "Hidden" type. They are used by JTableNested to maintain the tree structure.
Like with the title, it's important to get the name and database details right. I'll list them below for each:
int(11) NOT NULL DEFAULT '0'
int(11) NOT NULL DEFAULT '0'
int(10) unsigned NOT NULL DEFAULT '0'
varchar(255) NOT NULL DEFAULT ''
Parent Id
This field will let items in your view add other items as their parents.
To start, create a field with a "Custom" type. Then fill in the fields with these values:
Note that if you want to reuse this field in the same component but a different view, you'll have to make a copy and change the 'type' field. You'll of course also have to change all of the references to your view, but that type change isn't obvious so that's why I mention it.
If you include 'view' and 'views' it adds an edit button to the dropdown, but we don't want that.
We're going to want to add code in the text field at the very bottom, but before we do that, set your database options so they're equivalent to
int(10) NOT NULL DEFAULT '0'
.Okay, now it's time to add the code that generates the dropdown options. This lets the user select what item they want to make the parent of the item they're editing.
Make sure you replace 'yourview' and 'Yourcomponent' with the item view and component name for your project.
Link the fields to your admin view
If you haven't already, create your view. Give it names, a short description, and save.
Now, go to the "Fields" tab, and click the "Edit" button for "Linked Fields". A new page will open.
Add the fields:
Save and close.
Add admin fields relations to the view
In the list view, we're going to want to display the nesting of the fields. The usual way Joomla does this is by adding characters like '–' before the title link to show how deeply it's nested. That's what we're going to set up now.
In the 'Fields' tab, there's a button that says "Edit admin fields relations for this admin view". Click on that.
Set "List Field" to your title field. Then, for the Join field select your level field.
Set "Join Type" to Custom Code, and in the Set field paste this code, replacing LEVEL and TITLE with the codes that JCB generates for you.
Save and close.
Add custom code to the admin view
We're almost done in the view! Click the PHP tab and add this code to the following sections:
PHP getListQuery Method
PHP getForm Method
That's it. Save and close.
Modify the table
Before we can continue, we need to compile. If you haven't already, add the view to your component, and compile. It will not be functional yet.
Now, open up your IDE and navigate to administrator/components/com_yourcomponent/tables/yourview.php.
Inherit from JTableNested
JTableNested is where all of the magic happens. It does like 95% of the work for us. If you want to dig around in the code, go to libraries/src/Table/Nested.php in your joomla installation. Okay let's get to the code.
Remove the line at the top that looks like:
... and replace it with:
Make sure you're not just straight up copy pasting, as your class name will be different than this!
Modify the __construct() function
After the line:
... insert the following snippet:
This is required to get ordering in the list view working properly. Note that this is a bit of a HACK: normally you wouldn't include an ordering column when using nested sets, but JCB adds it automatically.
Add table functions
Somewhere in the class, add these functions. I dropped them in under the
__construct
function. We'll be using them elsewhere in the code.Make sure you replace the #_yourcomponent_yourview table name with one that fits your project.
Modify the table's store() function
JTableNested does most of the work, but we have to manually tell it to add child nodes to their parents, and also help it rebuild the paths afterward. Just so you can see the context, I'm going to paste the entire function. The custom code you need to add is indicated by the custom code tags.
Edit: I replaced 'last-child' with 'first-child' in the calls to moveByReference and setLocation. Depending on your preference, you may want to use one or the other.
Modify the table's check() function
We only want to replace one line in this function. At the very end, replace the line:
... with:
That's is for the table class!
Get list view ordering working
At this point your view should be working. You can add items, add a parent using the dropdown, and as you do this, JTableNested modified the lft and rgt fields to reflect the new nested order.
But in the list view, things are completely out of order, so we want to fix that. Doing so will involve overwriting a few files:
administrator/components/com_yourcomponent/models/yourview.php
Note that this is the model for the single item view, not for the list view.
Somewhere in the class, insert the following function:
It is taken straight out of Joomla's category component. Normally the model handles the saveorder function, but JTableNested has custom handling for it, so we want to forward along to it.
administrator/components/com_yourcomponent/models/yourviews.php
Note that this is the model for your list view, not your item view. You can tell the difference because the list view is plural.
In the
getListQuery()
function, find the lines:... and replace them with:
administrator/components/com_yourcomponent/views/yourviews/view.html.php
Again, check that the view folder's name is plural. This is the list view we're modifying.
In the
display()
function, find the lines:... and replace them with:
administrator/components/com_yourcomponent/views/yourviews/tmpl/default_body.php
Modify this template using the custom code snippets shown here:
administrator/components/com_yourcomponent/views/yourviews/tmpl/default.php
Finally, insert the custom code snippet used here.
Make sure to replace 'yourview' so that it matches the item view in your project.
EDIT May 17 2022: administrator/components/com_yourcomponent/models/forms/filter_yourviews.xml
I missed this one: you also need to make 'ordering' the default ordering option in the filter XML. Without this you occasionally get bugs where items are unexpectedly ordered by id.
In this file you want to change the line
default="a.id DESC"
todefault="a.ordering ASC"
. You have to replace the entire node to make this change because XML doesn't allow you to add comments inside of nodes. The end result will look something like:That's it!
You did it! Your list view now behaves a lot like the categories view does now, except you can do a lot more interesting things with it.
If you notice any issues let me know and I'll update this!
Gotchas
This looks straightforward really... and brings up another topic, the option to change what class a (controller,model,view,table...) extends. I am working on a change to JCB that will give this functionality so we can change the classes being extended. This is part of getting ready for Joomla 4.
One thing I didn't think about when I wrote this is exporting/importing items. I tried it just to see how things would go, but it came out a mess. I'm not planning to tackle it right now but I thought I'd mention it!
My first thoughts: the parent relationship is based on other items in the same table, so you could preserve the nesting by storing that relationship in the csv file somehow, but there would need to be import code that interprets it and adds items to their parents. The import code would also need to exclude the 'level', 'path', and 'rgt' fields, and while 'lft' should be referenced to get the insertion order right, it should also be excluded when actually storing the item, because it trips up JTableNested to include any of those four fields in the data.
PS: I think I maybe found a bug. When I exported, the titles in my list view were used, (the ones I set up using JCB's admin fields relations), not the actual item's titles. So for example, one item called "Child Item" might instead say "- Child Item" with a dash at the start. That extra dash then becomes part of the title on import.
I missed a few things for getting list view sorting working properly. I added them above but I'll also list the changes here.
The most important one is that JTableNested has its own function for handling the saveorder() task, so we need to add the following function to the item view (not the list view). This is nabbed straight out of com_categories:
The other two changes are more hacks to work around JCB's default handling of ordering. They may not be relevant to you depending on how you decide to implement this.
In the __construct function in the table for the view, I add this line:
I do this because in JTableNested's rebuild function, it will sort by the 'ordering' column if it exists, rather than the 'lft' column, and this causes issues. You can read the code for it at libraries/src/Table/Nested.php
Finally, you need to use 'lft' as the value for the drag handles on list items, not ordering. So in the default_body.php template I swap in this code: