Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sortable Relations #5298

Closed

Conversation

tobias-kuendig
Copy link
Member

It is Hacktoberfest once again, so what better time to propose some new features for my favourite CMS! 🤗

The wish for sortable relations has been around for some years (#3010). The current solutions are rather hacks than solutions and not in line with October's UI. This PR proposes a possible solution to this problem by adding a ReorderRelationController:

sortable

With this PR, the following steps are required to implement a sortable relation:

  1. Use the new SortableRelation trait in your parent model
  2. Define what relations are sortable in the $sortableRelations property
  3. Implement the new ReorderRelationController in your Controller
  4. Add the new reorder toolbar button to your relation widget config

That's it!

I have added an example implementation to the test plugin (Posts -> Edit Post -> Galleries):
OFFLINE-GmbH/test-plugin@f8eb223

You can optionally define a public $reorderRelationConfig = 'config_reorder_relation.yaml'; property on your controller to define where the name attributes for the treelist control should be taken from:

# ===================================
#  Relation Relation Behavior Config
# ===================================

galleries:
    nameFrom: title

other_relation:
    nameFrom: name

If nothing is configured, the control defaults to the name then title attributes.

Let me know what you think!

Resolves #3010

@LukeTowers
Copy link
Contributor

@tobias-kuendig while this is certainly an improvement, I would much rather see support for reordering added directly to the Lists widget itself with the ability to drag and drop the rows within the widget rather than kicking the user to a separate screen or popup to handle the ordering. Is that something that you're interested in?

@tobias-kuendig
Copy link
Member Author

That would definitely be the nicer solution. It is what the suggestions in #3010 were trying to achieve. Where this always fell apart though, was with larger lists with multiple pages. How would you move a record from page 2 up to page 1? This was my number 1 problem in every sortable list situation. If we find a good solution to this problem I can give the sortable list widget a go.

@bennothommo
Copy link
Contributor

@tobias-kuendig One of the options I considered when I had thought about this in March (although this felt like a lifetime ago, thanks COVID) - was to have "hotspots" on the left and right side of the list that would trigger a "previous" or "next" page when you moved your mouse over it while dragging/reordering a list item. I've seen this sort of interaction before - as long as there's some sort of UI hint and the page change is staggered (ie. only does a page change every second), it should cover this case.

@tobias-kuendig
Copy link
Member Author

tobias-kuendig commented Oct 7, 2020

Good thinking, @bennothommo! Two things that come to mind:

  1. How would you place the hotspots? Having them inside the table might trigger an accidential page change if you grab the drag handle that will most likely be on the far left or far right of each row. Having the hotspot outside the table might be problematic if the list is close to the viewport's edge.

  2. With staggered page changes, given I want to move my newly created record No 500 from the last page 25 to pole position, this would take me multiple seconds! I'm aware that many lists newer get this large and the proposed popup solution also becomes cumbersome to use with a list this large, but I feel like your suggested solution, @bennothommo, will feel even more clunky in that case.

I'm just dropping my initial thoughts here. Does anyone know how other CMS are tackling this problem?

Edit: Another thing I just noticed, what if I have sorted the table by any other column than the sort order? Is sorting disabled? Do we need a "reorder enabled" mode, where the table is changed to a forced sort order? What If you enable this on page 25 where the record you want to move around is only on while the list is sorted by name? Wouldn't that be infuriating if the row simply disappeared to another page once you toggle the "reorder enabled" mode?

@bennothommo
Copy link
Contributor

@tobias-kuendig All very good points, and I dare say this is likely the reason why it hasn't been attempted (and possibly why the reordering occurs on another page with ReorderingController where ordering is forced to the sort order.)

I think if sorted is enabled, then re-ordering by other columns must be disabled, otherwise it makes "inline" sorting pointless and/or confusing to the actual order of items. The "hover" type page switch can always be shorter too, I was merely saying a second off the top of my head - it just needs to be somewhat staggered so that someone doesn't go from page 1 to page 50 in quick succession. As to your other points, I'll have to think about those.

@Eoler
Copy link
Contributor

Eoler commented Oct 7, 2020

My dua lipa/2¢: reordering is isolated (on another controller action) from listing because of sort/search/filter list complications, so it is best to do it as initially proposed in this issue (popup).

@SebastiaanKloos
Copy link
Contributor

I think the sort in table is the best solution. Maybe you can check if the results are paginated. If so, show a button to sort like seen in the popup. If there are less than the per page, then use the sort order in table.

Most of the related lists I'm using only have like 10 records or so

@LukeTowers
Copy link
Contributor

This is a pretty good article on the subject of manually sorting large data sets: https://www.bennadel.com/blog/2532-the-user-experience-ux-of-manually-sorting-data.htm

@Eoler
Copy link
Contributor

Eoler commented Oct 8, 2020

Out of curiosity, what sort of data is being managed that simultaneously needs to be manually sorted and also encompasses a large number of records? I feel like at that point manual sorting falls apart no matter what UX you have.

E-commerce product listings inside a category? I know at least one shopkeeper that loves to re-sort her stuff regularly...

@tobias-kuendig
Copy link
Member Author

tobias-kuendig commented Oct 9, 2020

Out of curiosity, what sort of data is being managed that simultaneously needs to be manually sorted and also encompasses a large number of records?

This is a valid point. Are you suggesting that we might disable sortable table headers if a relation manager is marked as sortable?

I feel like at that point manual sorting falls apart no matter what UX you have.

Absolutely, the popup solution is cumbersome to use in that scenario as well, but it is still better than any table solutions (especially when you factor in different sorting options for the table and search filters)

I know at least one shopkeeper that loves to re-sort her stuff regularly...

In my experience, people really love to sort things. All the things! But it's true, most relation widgets don't have more than a few entries. The shop category example is the one that comes to mind. We have solved this problem in our Mall plugin by re-using the ReorderController. This form would benefit from the popup solution.
https://mall.offline.swiss/backend/offline/mall/categories/update/2#primarytab-offlinemalllangcommonproducts (Reorder entries)

Edit: Users usually don't want to sort all products in a category. What they want is to have their top selling products on the first page, or their favourite products displayed in the first row. So most of the time, a pin product feature would be a better solution, then maybe allow them to sort only the pinned products, which is a much smaller dataset.

Maybe the best solution is a hybrid one as suggested by @SebastiaanKloos. Sortable rows in the table as long as there is no active pagination. Then offer the popup option if pagination is enabled. I can invest some more time in this PR and try to implement a solution, if it is a possible way to go.

@Eoler
Copy link
Contributor

Eoler commented Oct 9, 2020

how many are you talking about though?

Thirty-something usually.

@tobias-kuendig
Copy link
Member Author

tobias-kuendig commented Oct 16, 2020

This took some more effort than expected but I think I've got a nice solution now:

The Lists widget is now sortable by adding a sortable: true property to the config_list.yaml. The list model needs to implement the Sortable trait.

sortable

The view section of the relation manager now also supports a sortable: true property that enables the same in-place sorting for related entries. In this case, the form model (parent model of the relation) needs to implement the new SortableRelation trait.

sortable-relation

A couple of notes:

  • If a list widget is marked as sortable, all custom sorting options are removed and disabled. The showTree option is set to false as well. The sort order from the database will be forced.
  • I have modified the Sortable JS plugin to also work with tables. The placeholder item, however, is still a li. By switching the CSS rule for the "zebra formatting" of the List to a :nth-of-type selector it is possible to retain the colors of each row during sort. If we would insert a tr placeholder, the :nth-child(even/odd) rules lead to ever changing row colors while the placeholder is moved around.
  • Pagination is not disabled. While not always practical, it is technically possible to sort entries on a single list page. It is not possible to move an entry to the previous or next page in the list without changing the number of displayed records first.
  • The reorder popup variant is available for use-cases where lots of related records need to be sortable.

I have updated my fork of the test plugin with a working implementation:

octobercms/test-plugin@master...OFFLINE-GmbH:sortable-relations

  • Playground -> Trees contains a sortable Lists widget
  • Playground -> Posts -> Any Post contains a sortable Gallery relation widget

If this PR gets accepted I will provide documentation for the new features.

@tobias-kuendig
Copy link
Member Author

tobias-kuendig commented Oct 20, 2020

In theory yes, we would only need to send additional sourceNode and targetNode parameters to make it work. The bigger problem is the handling of the drag operation inside the table. The existing sortable widget uses nested ol lists. These make it easy to move a node either to the end of a nested set or any number of level up or down the tree.

nesting

Replicating this using the existing Lists widget which is a table sounds pretty complicated to me. Nesting in tables is not possible which means we would always have to look at the row before and after the placeholder and then try to predict the action a user intended to do (reorder, move deeper down the tree, etc). And all of this while using only tr tags!

@tobias-kuendig
Copy link
Member Author

It depends. The included sortable plugin is very lean and has a limited feature set. Also, the data-handle option was never properly implemented which I just noticed does not work correctly in this PR as well (you can sort the table by clicking anywhere on the row, not just the handle).

SortableJS would certainly optimize the standard sort actions as it covers lots of edge-cases and has support for animations. I don't think, however, it makes sorting a nested tree inside a <table> any easier, as we still cannot use an actual nested HTML structure.

@Samuell1
Copy link
Member

One thing from UX i think the icon should be changed to icon-arrows obrázok, to show that this is handle with user can move rows.

@daftspunk
Copy link
Member

Nice work @tobias-kuendig 🎉 . Lots to review here! Will get to this one soon

As a side note, switching to SortableJS was something we looked at but it didn't offer an on par feature set. So is likely a better path to just improve the existing sortable library as we've done so in the past. It's not just a case of "newer" = "better"

@daftspunk daftspunk self-requested a review March 14, 2021 03:03
@daftspunk daftspunk self-assigned this Mar 14, 2021
@robinbonnes
Copy link
Contributor

robinbonnes commented Sep 16, 2021

Hey guys,

It's almost Hacktoberfest again :)

Any updates on this? Maybe we can extend this in a plugin?

@Samuell1
Copy link
Member

@robinbonnes yep this will be good addition and if allows sorting rows in table that will be best feature!

@daftspunk
Copy link
Member

@robinbonnes This feature is being implemented in stages. The initial support has been added to lists in v2.1. That is the hardest part completed. We hope to have sortable relations included in v2.2 for belongsToMany and hasMany relationship types.

@daftspunk
Copy link
Member

daftspunk commented Nov 26, 2021

This has been reviewed today and we can confirm sortable relations will be included as part of the v2.2 release.

Thank you for your work on this @tobias-kuendig! 🙏

Documentation can be found here: https://docs.octobercms.com/3.x/extend/lists/structures.html#sorting-related-records

@daftspunk daftspunk closed this Nov 26, 2021
@tobias-kuendig tobias-kuendig deleted the sortable-relations branch November 26, 2021 10:49
@octobercms octobercms deleted a comment from LukeTowers Apr 19, 2023
@octobercms octobercms deleted a comment from LukeTowers Apr 19, 2023
@octobercms octobercms deleted a comment from LukeTowers Apr 19, 2023
@octobercms octobercms deleted a comment from LukeTowers Apr 19, 2023
@octobercms octobercms deleted a comment from LukeTowers Apr 19, 2023
@octobercms octobercms deleted a comment from LukeTowers Apr 19, 2023
@octobercms octobercms deleted a comment from github-actions bot Apr 19, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

Reorder of records in relation
8 participants