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

[FEATURE] ScrollTo and ScrollToRow #228

Merged
merged 13 commits into from
Oct 25, 2016

Conversation

buschtoens
Copy link
Collaborator

Closes #148.

@buschtoens
Copy link
Collaborator Author

I pushed a build to my gh-pages for your convenience.

Are you happy with the way the demo and actual code are implemented or would you like me to make changes?

@buschtoens
Copy link
Collaborator Author

This PR deliberately ignores the issues raised in #224. I'll tackle them in one go when this is merged.

@buschtoens
Copy link
Collaborator Author

buschtoens commented Oct 18, 2016

Something worth noting:

In order for the showRow feature to work, I needed a way to retrieve the element.offsetTop property of the corresponding {{lt-row}} component for a given Row instance.

Currently the {{lt-row}} component has a reference to the Row instance, but not the other way around. I therefore added the private rowComponent property to the Row class which is populated in the didInsertElement hook of the {{lt-row}} component.

Maybe we can even make rowComponent and scrollOffset public.

I feel like this is the cleanest way. But I'm up for discussion. An alternative would be getting the this.get('table.rows').indexOf(targetRow) and querying the DOM for the n-th row.

If everything's in the clear, I'll add the necessary tests in another commit and remove the WIP tag. 😄

@buschtoens buschtoens force-pushed the 148-scroll-control branch 2 times, most recently from 787541f to 7e0b67c Compare October 18, 2016 00:50
@buschtoens
Copy link
Collaborator Author

buschtoens commented Oct 18, 2016

Oh, and the "keep fetching rows until the targetScrollOffset is reached" feature isn't implemented yet.

For this I'd check if the targetScrollOffset is greater than then scrollOffset of the last Row in table.rows and in that case trigger onScrolledToBottom by attempting to scroll to that position nevertheless and keep on doing so until the previous condition is false. For keeping on doing that an observer on table.rows is required.

Or can you suggest an observer-free alternative?

@offirgolan
Copy link
Collaborator

@buschtoens thank you for taking the time in putting this together. I do have a few concerns.

  1. Offset logic should not be placed in the Row class.
  2. What happens when we are not using ember-scrollable (i.e. simple use cases with no fixed header/footer?
  3. Why do we need to keep fetching rows until the target row is reached? If we require the Row instance to exist, shouldn't that instance already be in the table / rendered?

In regards to my first concern, I believe we can use the row instance's guid as the id of the lt-row component instance'd id. With that, we can get the offset of the row from the lt-body.

id: computed(function() {
  return guidFor(this);
}).readOnly()
{{lt-row id=row.id row=row ...}}

@buschtoens
Copy link
Collaborator Author

Thanks for replying!

  1. Offset logic should not be placed in the Row class.

I agree with that. Using the GUID is a neat idea.

  1. Why do we need to keep fetching rows until the target row is reached? If we require the Row instance to exist, shouldn't that instance already be in the table / rendered?

This is true when using the showRow interface. But there's also a scrollTo interface which just takes a numeric scroll offset in px. Primarily I'm interested in the latter one to easily restore state after changing the route.

  1. What happens when we are not using ember-scrollable (i.e. simple use cases with no fixed header/footer?

So far, nothing. As far as I'm concerned I only need these features when I have a table that is intended to overflow and scroll, in which case I'm already opting for the fixed headers/footers.

I can't think of a legit reason to not use fixed headers/footers, other than possible performance gains, because the UX of disappearing headers/footers is pretty moot.

Options:

a. Explicitly document that showRow, scrollTo and onScroll only work when using fixed headers/footers.
b. Basically reimplement alphasights/ember-scrollable#25, however this only works when the user's CSS targets the {{lt-body}} as the scroll container.

@offirgolan
Copy link
Collaborator

This is true when using the showRow interface. But there's also a scrollTo interface which just takes a numeric scroll offset in px. Primarily I'm interested in the latter one to easily restore state after changing the route.

Ah right. I forgot about that use case.

Explicitly document that showRow, scrollTo and onScroll only work when using fixed headers/footers.

I think this is okay 😄

@offirgolan
Copy link
Collaborator

@buschtoens dont worry too much on the demo. When this is fully functional and merged, I'll go back and make some changes. I also have a client that will require this feature so if you need any help, just let me know or shoot me a message on slack.

@buschtoens
Copy link
Collaborator Author

@offirgolan Ready for review. :)

@buschtoens buschtoens changed the title WIP: scroll control scroll control Oct 24, 2016
Copy link
Collaborator

@offirgolan offirgolan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@buschtoens added some review comments. Let me know if you need more clarification on something 😄

* @type {String}
* @readOnly
*/
elementId: computed(function() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets keep this as id and just return the guid. This should be unique enough. I just dont think any property here should directly correlate to a component.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: The Column class will also have the same implementation once I finish the drag n drop column headers feature.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I named this field elementId, so it does not shadow the id field of the content object which is most likely an Ember Data Record.

Still want me to rename it?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah thats a good call. Maybe change this to rowId and add a small comment as to why we didn't go by id (just so I randomly change this later 😛 )

* @type {Row}
* @default null
*/
showRow: null,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think scrollToRow is a more fitting name here and is consistent with the other scrollTo property.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree.

@@ -239,6 +292,57 @@ export default Component.extend({
this.set('useVirtualScrollbar', fixedHeader || fixedFooter);
},

didReceiveAttrs() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to handle this with didUpdateAttrs since it gives you the before and after values? That could remove the need of having _scrollTo and _scrollToRow. You can handle the initial scroll with didInsertElement

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know that didUpdateAttrs() actually can be used to diff the attrs. Nice one, thanks!

if (scrollTo !== _scrollTo) {
this.set('targetScrollOffset', scrollTo);
this.set('hasReachedTargetScrollOffset', false);
run.scheduleOnce('afterRender', this, this.checkTargetScrollOffset);
Copy link
Collaborator

@offirgolan offirgolan Oct 24, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed this is done in more than one place. Lets move this to another method.

Also, please make sure to capture all timer instances and cancel them in destroy.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, none of this should be happening if scrollTo and scrollToRow are not set.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note: checkTargetScrollOffset() is only applicable for scrollTo. Other than that I agree.

* @default null
* @private
*/
targetScrollOffset: null,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set the default here to 0

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would trigger a scroll to 0 px down the line. However that shouldn't matter as we start at 0 px anyway. So for clarity's sake, you're probably right.

* @default null
* @private
*/
currentScrollOffset: true,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set the default here to 0

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that was a copy pasta typo.

* @default true
* @private
*/
hasReachedTargetScrollOffset: true,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be a CP? Something like:

hasReachedTargetScrollOffset: computed.gte('currentScrollOffset', 'targetScrollOffset');

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cannot be a CP. The check you mentioned is performed inside the checkTargetScrollOffset() method.

This property stores state. checkTargetScrollOffset() is called every time new rows are added. But it's only supposed to keep scrolling until the desired offset has been reached once.

@@ -18,6 +20,7 @@
{{else}}
{{#each rows as |row|}}
{{lt.row row columns
elementId=row.elementId
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

id=row.id

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually has to be named elementId.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah gotcha. Thanks for clarifying. I believe the id field is an alias to the elementId

let newScrollTo = get(newAttrs, 'scrollTo.value');
let oldScrollTo = get(oldAttrs, 'scrollTo.value');
let newScrollToRow = get(newAttrs, 'scrollToRow.value');
let oldScrollToRow = get(oldAttrs, 'scrollToRow.value');
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The oldAttrs and newAttrs hashes are not documented and appear to be foreign Glimmer objects. I'm not sure if this is the correct way to access the values.

Copy link
Collaborator

@offirgolan offirgolan Oct 24, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh... you're totally right. That functionality is no longer documented. Lets revert back to your previous implementation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So instead of using didUpdateAttrs we should go back to didReceiveAttrs and store private copies of the attributes for diffing?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I spoke with locks and confirmed that we should not be using the arguments in didUpdateAttrs. Sorry for making you revert 😅

* @default 0
* @private
*/
targetScrollOffset: 0,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to this property triggering an initial scroll, this PR is blocked until alphasights/ember-scrollable#26 gets published.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @taras

}),

checkTargetScrollOffset() {
if (!this.get('hasReachedTargetScrollOffset') && !this.get('isDestroyed')) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm checking for isDestroyed instead of cancelling the queued task, which effectively achieves the same thing. Are you okay with this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think this is enough. On line 334 you are doing a set on what could be a destroyed object. I think capturing all viable timers and cancelling them on destroy is a much safer bet.

},

rowObserver: observer('rows.[]', function() {
run.scheduleOnce('afterRender', this, this.checkTargetScrollOffset);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@buschtoens I suggest adding a private method:

_scheduleCheckTargetScrollOffset() {
  run.cancel(this._checkTargetOffsetTimer); // Cancel existing checks since only one needs to happen (I'm not sure if this is actually needed since we are using scheduleOnce.... but just to be safe?
  this._checkTargetOffsetTimer = run.scheduleOnce('afterRender', this, this.checkTargetScrollOffset);
}

destroy() {
  this._super(...arguments);
  run.cancel(this._checkTargetOffsetTimer);
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wouldn't account for the timer in line 334. But tbh, I doubt that the timer in 334 could ever cause problems.


run.cancel(...) inside _scheduleCheckTargetScrollOffset() is unnecessary.

Relevant code in Backburner:
https://github.com/ebryn/backburner.js/blob/master/lib/backburner/deferred-action-queues.js#L38-L42
https://github.com/ebryn/backburner.js/blob/master/lib/backburner/queue.js#L33-L37

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I doubt that the timer in 334 could ever cause problems.

I've run into a couple issues like that in the past. Better to be cautious I suppose 😜

run.cancel(...) inside _scheduleCheckTargetScrollOffset() is unnecessary.

Even better.

Copy link
Collaborator Author

@buschtoens buschtoens Oct 24, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So should I add a second reference for that timer as well?

Edit: Did so. 😄

if (newScrollTo > 0) {
hasReachedTargetScrollOffset = false;
}
} else if (oldScrollToRow !== newScrollToRow) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this just be else if (newScrollToRow instanceof Row && oldScrollToRow !== newScrollToRow)?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option is to have an assert in didReceiveAttrs

@buschtoens
Copy link
Collaborator Author

buschtoens commented Oct 24, 2016

Okay, so I reverted back to using didReceiveAttrs and manual diffing. If I'm not mistaken, that should be all. 😆

Then there's only alphasights/ember-scrollable#26 left :)

@offirgolan offirgolan changed the title scroll control [FEATURE] ScrollTo and ScrollToRow Oct 24, 2016
@offirgolan offirgolan merged commit 9afc7a9 into adopted-ember-addons:master Oct 25, 2016
offirgolan pushed a commit that referenced this pull request Oct 25, 2016
* master:
  [FEATURE] ScrollTo and ScrollToRow (#228)
@buschtoens
Copy link
Collaborator Author

Sorry for the silence from my side. I was a bit under the weather over the
course of the last days. I'll finish the PR in a few hours when I'm in the
office. :)

We're also eagerly waiting for this feature :D

On Mon, Oct 24, 2016, 05:17 Offir Golan notifications@github.com wrote:

@buschtoens https://github.com/buschtoens dont worry too much on the
demo. When this is fully functional and merged, I'll go back and make some
changes. I also have a client that will require this feature so if you need
any help, just let me know or shoot me a message on slack.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#228 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAy8TGxRWrSTqoV4a-y5QrXzck-wONeLks5q3CNRgaJpZM4KZPhL
.

offirgolan added a commit that referenced this pull request Nov 7, 2016
* master: (42 commits)
  Resizable column fixes (#252)
  fix: repeated scrollToRow (#254)
  chore(package): update ember-cli-code-coverage to version 0.3.7 (#253)
  Demo Cleanup (#251)
  chore(package): update ember-lodash to version 0.0.11 (#245)
  chore(package): update ember-cli-code-coverage to version 0.3.6 (#249)
  Document `Row#content` (#250)
  Update resizable snippet
  Released v1.5.2
  [FEATURE] minResizeWidth + Event bubbling fix (#244)
  Update readme
  Update readme
  Released v1.5.1
  Add safe checks scroll logic (#241)
  [DOCFIX] Make breakpoints of demo app the same as ember-responsive's default (#240)
  Released v1.5.0
  Demo + Scrolling Cleanup (#238)
  [FEATURE] Responsive Columns (#235)
  [FEATURE] ScrollTo and ScrollToRow (#228)
  chore(package): update broccoli-asset-rev to version 2.5.0 (#227)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants