-
-
Notifications
You must be signed in to change notification settings - Fork 131
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
[FEATURE] ScrollTo and ScrollToRow #228
Conversation
a33dbe4
to
e9275c5
Compare
I pushed a build to my Are you happy with the way the demo and actual code are implemented or would you like me to make changes? |
This PR deliberately ignores the issues raised in #224. I'll tackle them in one go when this is merged. |
Something worth noting: In order for the Currently the Maybe we can even make I feel like this is the cleanest way. But I'm up for discussion. An alternative would be getting the If everything's in the clear, I'll add the necessary tests in another commit and remove the WIP tag. 😄 |
787541f
to
7e0b67c
Compare
Oh, and the "keep fetching rows until the For this I'd check if the Or can you suggest an observer-free alternative? |
7e0b67c
to
61e4a18
Compare
@buschtoens thank you for taking the time in putting this together. I do have a few concerns.
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 ...}} |
Thanks for replying!
I agree with that. Using the GUID is a neat idea.
This is true when using the
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 |
Ah right. I forgot about that use case.
I think this is okay 😄 |
@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. |
e9847ae
to
bf76e7d
Compare
@offirgolan Ready for review. :) |
There was a problem hiding this 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() { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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');
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
id=row.id
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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
1ed7083
to
1b11b62
Compare
3be68db
to
38b97e8
Compare
let newScrollTo = get(newAttrs, 'scrollTo.value'); | ||
let oldScrollTo = get(oldAttrs, 'scrollTo.value'); | ||
let newScrollToRow = get(newAttrs, 'scrollToRow.value'); | ||
let oldScrollToRow = get(oldAttrs, 'scrollToRow.value'); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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')) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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);
}
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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)
?
There was a problem hiding this comment.
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
170ce20
to
4b84f1a
Compare
Okay, so I reverted back to using Then there's only alphasights/ember-scrollable#26 left :) |
* master: [FEATURE] ScrollTo and ScrollToRow (#228)
Sorry for the silence from my side. I was a bit under the weather over the We're also eagerly waiting for this feature :D On Mon, Oct 24, 2016, 05:17 Offir Golan notifications@github.com wrote:
|
* 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) ...
Closes #148.