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 occlusion]: Preliminary Vertical collection integration #483

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ A lightweight contextual component based table addon that follows Ember's action
- Easy table manipulation
- Easy override to table header, body, and footer
- Contextual component for header, body, and footer, as well as loading, no data, and expanded row
- **EXPERIMENTAL** Occlusion rendering leveraging [vertical-collection](https://github.com/html-next/vertical-collection). See [Demo](http://offirgolan.github.io/ember-light-table/#/cookbook/occlusion-rendering).

## Installation

Expand Down
27 changes: 25 additions & 2 deletions addon/components/light-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function intersections(array1, array2) {

const LightTable = Component.extend({
layout,
classNameBindings: [':ember-light-table'],
classNameBindings: [':ember-light-table', 'occlusion'],
attributeBindings: ['style'],

media: service(),
Expand Down Expand Up @@ -159,6 +159,27 @@ const LightTable = Component.extend({
*/
breakpoints: null,

/**
* Toggles occlusion rendering functionality. Currently experimental.
* If set to true, you must set {{#crossLink 't.body/estimatedRowHeight:property'}}{{/crossLink}} to
* something other than the default value.
*
* @property occlusion
* @type Boolean
* @default False
*/
occlusion: false,

/**
* Estimated size of a row. Used in `vertical-collection` to determine roughly the number
* of rows exist out of the viewport.
*
* @property estimatedRowHeight
* @type Number
* @default false
*/
estimatedRowHeight: 0,

/**
* Table component shared options
*
Expand All @@ -170,7 +191,9 @@ const LightTable = Component.extend({
return {
height: this.get('height'),
fixedHeader: false,
fixedFooter: false
fixedFooter: false,
occlusion: this.get('occlusion'),
estimatedRowHeight: this.get('estimatedRowHeight')
};
}).readOnly(),

Expand Down
27 changes: 24 additions & 3 deletions addon/components/lt-body.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,12 +316,35 @@ export default Component.extend({
this.setupScrollOffset();
},

didInsertElement() {
this._super(...arguments);
if (this.get('sharedOptions.occlusion')) {
this._setupScrollAreaDimensions();
}
},

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

/**
* Calculates the available height remaining in the body of the table by taking the table height defined
* on the light table component and subtracting the rendered height of the header.
* May need to extend this to include the footer.
*
* @method _setupScrollAreaDimensions
* @private
*/
_setupScrollAreaDimensions() {
const lightTableContainer = this.element.parentElement;
const { height: totalHeight } = lightTableContainer.getBoundingClientRect();
const headerElem = lightTableContainer.querySelector('.lt-head-wrap');
const { height: headerHeight } = headerElem.getBoundingClientRect();
this.set('height', totalHeight - headerHeight);
},

_setupVirtualScrollbar() {
let { fixedHeader, fixedFooter } = this.get('sharedOptions');
this.set('useVirtualScrollbar', fixedHeader || fixedFooter);
Expand Down Expand Up @@ -423,9 +446,7 @@ export default Component.extend({

if (canSelect) {
if (e.shiftKey && multiSelect) {
rows
.slice(Math.min(currIndex, prevIndex), Math.max(currIndex, prevIndex) + 1)
.forEach((r) => r.set('selected', !isSelected));
rows.slice(Math.min(currIndex, prevIndex), Math.max(currIndex, prevIndex) + 1).forEach((r) => r.set('selected', !isSelected));
} else if ((!multiSelectRequiresKeyboard || (e.ctrlKey || e.metaKey)) && multiSelect) {
row.toggleProperty('selected');
} else {
Expand Down
22 changes: 22 additions & 0 deletions addon/styles/addon.css
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,28 @@
flex: 1 0 auto;
}

.ember-light-table .lt-scrollable.vertical-collection {
overflow-y: auto;
}

/* This is for when useVirtualScrollbar is disabled */
.ember-light-table.occlusion .lt-head-wrap {
padding-right: 14px;
}

.ember-light-table.occlusion .lt-head-wrap table {
display: inline
}

.ember-light-table vertical-collection {
display: table;
table-layout: fixed;
}

.ember-light-table vertical-collection occluded-content:first-of-type {
display: table-caption;
}

.ember-light-table .align-left {
text-align: left;
}
Expand Down
139 changes: 100 additions & 39 deletions addon/templates/components/lt-body.hbs
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
{{#with (hash
row=(or rowComponent (component 'lt-row'))
spanned-row=(or spannedRowComponent (component 'lt-spanned-row'))
infinity=(or infinityComponent (component 'lt-infinity'))
) as |lt|
row=(or rowComponent (component 'lt-row'))
spanned-row=(or spannedRowComponent (component 'lt-spanned-row'))
infinity=(or infinityComponent (component 'lt-infinity'))
) as |lt|
}}
{{#lt-scrollable
tagName=''
virtualScrollbar=useVirtualScrollbar
autoHide=autoHideScrollbar
scrollTo=targetScrollOffset
onScrollY=(action 'onScroll')
}}
<div id={{concat tableId '_inline_head'}} class="lt-inline lt-head"></div>

<table class={{tableClassNames}}>
<tbody class="lt-body">
{{#unless sharedOptions.occlusion}}
{{#lt-scrollable
tagName=''
virtualScrollbar=useVirtualScrollbar
autoHide=autoHideScrollbar
scrollTo=targetScrollOffset
onScrollY=(action 'onScroll')
}}
<div id={{concat tableId '_inline_head'}} class="lt-inline lt-head"></div>

<table class={{tableClassNames}}>
<tbody class="lt-body">
{{#if enableScaffolding}}
<tr class="lt-scaffolding-row">
{{#each columns as |column|}}
Expand All @@ -28,36 +29,96 @@
{{else}}
{{#each rows as |row|}}
{{lt.row row columns
data-row-id=row.rowId
table=table
tableActions=tableActions
extra=extra
enableScaffolding=enableScaffolding
canExpand=canExpand
canSelect=canSelect
click=(action 'onRowClick' row)
doubleClick=(action 'onRowDoubleClick' row)}}
data-row-id=row.rowId
table=table
tableActions=tableActions
extra=extra
enableScaffolding=enableScaffolding
canExpand=canExpand
canSelect=canSelect
click=(action 'onRowClick' row)
doubleClick=(action 'onRowDoubleClick' row)}}

{{yield (hash
expanded-row=(component lt.spanned-row classes='lt-expanded-row' colspan=colspan yield=row visible=row.expanded)
loader=(component lt.spanned-row visible=false)
no-data=(component lt.spanned-row visible=false)
) rows}}
expanded-row=(component lt.spanned-row classes='lt-expanded-row' colspan=colspan yield=row visible=row.expanded)
loader=(component lt.spanned-row visible=false)
no-data=(component lt.spanned-row visible=false)
) rows}}
{{/each}}

{{yield (hash
loader=(component lt.spanned-row classes='lt-is-loading' colspan=colspan)
no-data=(component lt.spanned-row classes='lt-no-data' colspan=colspan)
expanded-row=(component lt.spanned-row visible=false)
) rows}}
loader=(component lt.spanned-row classes='lt-is-loading' colspan=colspan)
no-data=(component lt.spanned-row classes='lt-no-data' colspan=colspan)
expanded-row=(component lt.spanned-row visible=false)
) rows}}
{{/if}}
</tbody>
</table>

{{#if onScrolledToBottom}}
{{lt.infinity rows=rows onScrolledToBottom=onScrolledToBottom scrollBuffer=scrollBuffer}}
{{/if}}

<div id={{concat tableId '_inline_foot'}} class="lt-inline lt-foot"></div>
{{/lt-scrollable}}
{{else}}
<div class="lt-scrollable tse-scrollable vertical-collection" style="{{html-safe (concat 'height:' height 'px')}}">
<div id="{{concat tableId '_inline_head'}}" class="lt-inline lt-head"></div>

<table class={{tableClassNames}}>
<tbody class="lt-body">
{{#if enableScaffolding}}
<tr class="lt-scaffolding-row">
{{#each columns as |column|}}
<td
style={{html-safe (if column.width (concat 'width: ' column.width))}} class="lt-scaffolding"></td>
{{/each}}
</tr>
{{/if}}

{{#if overwrite}}
{{yield columns rows}}
{{else}}
{{#vertical-collection
rows
tagName='vertical-collection'
estimateHeight=sharedOptions.estimatedRowHeight
containerSelector='.lt-scrollable'
as |row index|
}}
{{lt.row row columns
data-row-id=row.rowId
table=table
tableActions=tableActions
extra=extra
enableScaffolding=enableScaffolding
canExpand=canExpand
canSelect=canSelect
click=(action 'onRowClick' row)
doubleClick=(action 'onRowDoubleClick' row)}}

{{yield (hash
expanded-row=(component lt.spanned-row classes='lt-expanded-row' colspan=colspan yield=row visible=row.expanded)
loader=(component lt.spanned-row visible=false)
no-data=(component lt.spanned-row visible=false)
) rows}}

{{/vertical-collection}}
{{yield (hash
loader=(component lt.spanned-row classes='lt-is-loading' colspan=colspan)
no-data=(component lt.spanned-row classes='lt-no-data' colspan=colspan)
expanded-row=(component lt.spanned-row visible=false)
) rows}}
{{/if}}
</tbody>
</table>
</tbody>
</table>

{{#if onScrolledToBottom}}
{{lt.infinity rows=rows onScrolledToBottom=onScrolledToBottom scrollBuffer=scrollBuffer}}
{{/if}}
{{!--#if onScrolledToBottom}} TODO figure out how to use the scrollbuffer property for infinite loading
lastReached=onScrolledToBottom
{{lt.infinity rows=rows onScrolledToBottom=onScrolledToBottom scrollBuffer=scrollBuffer}}
{{/if --}}

<div id={{concat tableId '_inline_foot'}} class="lt-inline lt-foot"></div>
{{/lt-scrollable}}
<div id="{{concat tableId '_inline_foot'}}" class="lt-inline lt-foot"></div>
</div>
{{/unless}}
{{/with}}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"test": "ember try:each"
},
"dependencies": {
"@html-next/vertical-collection": "^1.0.0-beta.9",
"ember-cli-babel": "^6.6.0",
"ember-cli-htmlbars": "^2.0.2",
"ember-cli-string-helpers": "^1.5.0",
Expand Down
44 changes: 44 additions & 0 deletions tests/dummy/app/components/cookbook/occluded-table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// BEGIN-SNIPPET occluded-table
import Component from '@ember/component';
import TableCommon from '../../mixins/table-common';
import { computed } from '@ember/object';

export default Component.extend(TableCommon, {
limit: 100,
columns: computed(function() {
return [{
label: 'Avatar',
valuePath: 'avatar',
width: '60px',
sortable: false,
cellComponent: 'user-avatar'
}, {
label: 'First Name',
valuePath: 'firstName',
width: '150px'
}, {
label: 'Last Name',
valuePath: 'lastName',
width: '150px'
}, {
label: 'Address',
valuePath: 'address',
width: '150px'
}, {
label: 'State',
valuePath: 'state',
width: '100px'
}, {
label: 'Country',
valuePath: 'country',
width: '100px'
}];
}),

init() {
this._super(...arguments);
this.set('page', 1);
this.get('fetchRecords').perform();
}
});
// END-SNIPPET
5 changes: 3 additions & 2 deletions tests/dummy/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ Router.map(function() {

this.route('cookbook', function() {
this.route('client-side');
this.route('pagination');
this.route('custom-row');
this.route('table-actions');
this.route('horizontal-scrolling');
this.route('occlusion-rendering');
this.route('pagination');
this.route('table-actions');
this.route('custom-sort-icon');
});
});
Expand Down
1 change: 1 addition & 0 deletions tests/dummy/app/routes/cookbook/occlusion-rendering.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '../table-route';
7 changes: 7 additions & 0 deletions tests/dummy/app/styles/table.less
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,10 @@ tfoot {
}
}
}


.ember-light-table.occlusion {
.lt-row td {
vertical-align: middle;
}
}
7 changes: 5 additions & 2 deletions tests/dummy/app/templates/application.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,15 @@
{{#link-to 'cookbook.horizontal-scrolling' tagName="li"}}
{{link-to 'Horizontal Scrolling' 'cookbook.horizontal-scrolling'}}
{{/link-to}}
{{#link-to 'cookbook.table-actions' tagName="li"}}
{{link-to 'Table Actions' 'cookbook.table-actions'}}
{{#link-to 'cookbook.occlusion-rendering' tagName="li"}}
{{link-to 'Occlusion Rendering' 'cookbook.occlusion-rendering'}}
{{/link-to}}
{{#link-to 'cookbook.pagination' tagName="li"}}
{{link-to 'Pagination' 'cookbook.pagination'}}
{{/link-to}}
{{#link-to 'cookbook.table-actions' tagName="li"}}
{{link-to 'Table Actions' 'cookbook.table-actions'}}
{{/link-to}}
{{#link-to 'cookbook.custom-sort-icon' tagName="li"}}
{{link-to 'Custom Sort Icon' 'cookbook.custom-sort-icon'}}
{{/link-to}}
Expand Down
Loading