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

Replace/move item? #171

Open
taujoey opened this issue Apr 23, 2020 · 4 comments
Open

Replace/move item? #171

taujoey opened this issue Apr 23, 2020 · 4 comments
Labels
enhancement permanent ignore stale bot

Comments

@taujoey
Copy link

taujoey commented Apr 23, 2020

Hi,

I switched my app to this ui-scroll implementation, it works great. Also started using the implemented 'insert' method, which i asked for, really thanks for that, works very well.

About replace.. :)
So, when i not only try to insert a single item, but before the insertion, I also remove another, the list flickers for a little. I think it is understandable, because the list immediately updates after item removal, and a few msec-s later updates again with the inserted item. The problem with it, i cant perform an "item replace" process smoothly.
(Just to make things little more complicated, my use case is 'sorting the items', more specifically dragging an item from one position to another, so in the list's perspective, it is a 'move' process.)

Is there any way to do this replace/move process 'atomically' (like a transaction in a database) ? Calling the remove and insert methods after another just don't do the job perfectly.

Here's a very simple/stupid test code in a button click handler (the methods here are simple wrappers around the ui-scroll's remove and insert methods), which removes a random item, and places it back into the same position:

onclick() {

        let data = this.itemCache[5];

        this.removeFromListById(data.id);
        this.insertIntoListBeforeId(data, this.itemCache[6].id);

        return;
}

Do You have any suggestion, how i can achieve, what i described to you?
Thanks!

@dhilt
Copy link
Owner

dhilt commented Apr 23, 2020

The case does make sense, absolutely. According to the UIScroll Workflow, each Adapter call raises at least 1 inner loop cycle which is asynchronous and takes at least 1 browser reflow. So, having 2 separate Adapter calls (remove and insert), we fall into 2+ reflows.

I think, there should be another Adapter method, allowing to perform both operations within a single Workflow inner loop cycle. And this might be quite interesting to think about the implementation, I mean, the solution might be applicable to all or almost all of the Adapter methods, something like pipe approach : Adapter.pipe(insert(...), remove(...), clip(...)). I need to think anyway.

@dhilt dhilt added enhancement permanent ignore stale bot labels Apr 23, 2020
@taujoey
Copy link
Author

taujoey commented Apr 23, 2020

Thanks for the fast reply. :) I'm happy i didn't miss something obvious :)
At first i thought about requesting the move method, but actually making multiple sequential api calls 'atomic' or transaction based could makes this much more powerful, and cover many use-cases.
Anyway, it's up to You :)

Thanks, I'll be checking back later :)

@StriderRanger
Copy link

StriderRanger commented Jul 1, 2020

Hey!
I just want to share some thoughts about the replace/move method since it's still in the development phase.

I. Concerning the flicker problem

I think that this behavior will mainly be noticeable when we first call the Adapter.remove() method and then the Adapter.insert() method.

What about, instead of removing the item to move and then inserting it in the new position, just update the items that are between the old position and the new position by the items that are above or below (depending on the move direction)?

For example, giving that we have the following list, and that 'X' will move above 'Y':

id: 1, item: "A"
id: 2, item: "X"
id: 3, item: "B"
id: 4, item: "C"
id: 5, item: "Y"

Then without removing any item, just update the items between the id: 2 and id: 4 to have the following result

id: 1, item: "A"
id: 2, item: "B"
id: 3, item: "C"
id: 4, item: "X"
id: 5, item: "Y"

II. Concerning items that will move outside the actual list

If an element moves outside the actual loaded list, would it be more efficient to apply an update (like in the example above) or just apply a simple remove since the element is no longer visible in the list?

For example, giving that we have the following list, and that 'Z will move outside the list:

id: 1, item: "A"
id: 2, item: "Z"
id: 3, item: "B"
id: 4, item: "C"
id: 5, item: "Y"

Then the result would be:

id: 1, item: "A"
id: 2, item: "B"
id: 3, item: "C"
id: 4, item: "Y"
id: 5, item: "An item after Y" //--------> Should we necessarily load this item to keep the same number of loaded items?

The same result could be achieved by a simple remove, or by updating the elements between id:2 and id: 5.

This was referenced Dec 1, 2020
@dhilt
Copy link
Owner

dhilt commented Dec 18, 2020

I'm ready to suggest an approach for in-place items reordering by using Adapter fix-updater method. Assumptions:

  • we have first and last indexes which correspond to moving the "first" item to the "last" position and shifting all in-between items for 1 position in the upward direction, while items before first and after last remain pristine with their indexes;
  • first < last, another case can be simply implemented based on the considered one;
  • we are going to guarantee the data consistency and accessibility in the Datasource during/after the change, which means the change made via adapter-fix-updater propagates to the Datasource by object reference (Object.assign does the trick).
resort(first, last) {
  const items = {};
  // find in-place items lying between first and last indexes and save their clones
  this.datasource.adapter.fix({
    updater: ({ $index, data }) => {
      if ($index >= first && $index <= last) {
        items[$index] = { ...data };
      }
    }
  });
  if (items[first] && items[last]) {
    // reorder found items keeping indexes unchanged
    this.datasource.adapter.fix({
      updater: ({ $index, data }) => {
        if ($index >= first && $index < last) {
          Object.assign(data, items[$index + 1]);
        }
        if ($index === last) {
          Object.assign(data, items[first]);
        }
      }
    });
  }
}

If the real Datasource has no object-by-reference bindings with the data provided via Adapter-fix-updater, then some code must be added to ensure this reordering at the Datasource level.

I applied this approach in the drag-and-drop demo available on the stackblitz:

The demo deals with "angular/cdk/drag-drop" dnd solution and has no first < last limitation. The core thing not related to re-ordering is that the DND infrastructure is being re-initialized in the runtime per each scroll event debounced by some fixed delay (250ms).

Unfortunately the Adapter.replace method I worked on for the last month does not satisfy this issue 100%... Of course, it should be possible to make a replacement for the [first..last] in-place items with similar data shifting, but it will take 1 UIScroll Workflow cycle over the Angular bindings and might result in visible flickering. The "replace" flickering will be less than "insert" + "remove" flickering (which takes 2 Workflow cycles), but the "updater-fix" approach is faster as it deals with only Angular bindings and does not involve the UIScroll at all.

At last, both approaches don't take into account virtual reordering meant by @StriderRanger. It is applicable to in-place drag-and-drop functionality (and similar).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement permanent ignore stale bot
Projects
None yet
Development

No branches or pull requests

3 participants