Skip to content

Commit

Permalink
refactor participants table (#164)
Browse files Browse the repository at this point in the history
- Drops floatthead and additional scrollbar
- Makes header and first column sticky
- Refactors code for readability

Sticky header is only working in Firefox. Chrome and Edge does not support `position: sticky` for `<thead>`. Haven't tested Safari.
  • Loading branch information
jelhan authored Apr 20, 2019
1 parent 6c12214 commit bb160cc
Show file tree
Hide file tree
Showing 29 changed files with 686 additions and 712 deletions.
92 changes: 48 additions & 44 deletions app/components/poll-evaluation-chart.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { inject as service } from '@ember/service';
import Component from '@ember/component';
import { get, computed } from '@ember/object';
import { readOnly } from '@ember/object/computed';
import { isArray } from '@ember/array';
import { isPresent } from '@ember/utils';
import moment from 'moment';
Expand All @@ -25,7 +26,42 @@ const addArrays = function() {

export default Component.extend({
i18n: service(),
type: 'bar',

chartOptions: computed(function () {
return {
legend: {
display: false
},
scales: {
xAxes: [{
stacked: true
}],
yAxes: [{
stacked: true,
ticks: {
callback(value) {
return `${value} %`;
},
max: 100,
min: 0
}
}]
},
tooltips: {
mode: 'label',
callbacks: {
label(tooltipItem, data) {
let { datasets } = data;
let { datasetIndex } = tooltipItem;
let { label } = datasets[datasetIndex];
let value = tooltipItem.yLabel;
return `${label}: ${value} %`;
}
}
}
}
}),

data: computed('users.[]', 'options.{[],each.title}', 'currentLocale', function() {
let labels = this.options.map((option) => {
let value = get(option, 'title');
Expand All @@ -42,12 +78,10 @@ export default Component.extend({
});

let datasets = [];
let participants = this.get('users.length');
let participants = this.users.length;

let yes = this.users.map((user) => {
return user.get('selections').map((selection) => {
return selection.get('type') === 'yes' ? 1 : 0;
});
let yes = this.users.map(({ selections }) => {
return selections.map(({ type }) => type === 'yes' ? 1 : 0);
});
datasets.push({
label: this.i18n.t('answerTypes.yes.label').toString(),
Expand All @@ -59,10 +93,8 @@ export default Component.extend({
});

if (this.answerType === 'YesNoMaybe') {
let maybe = this.users.map((user) => {
return user.get('selections').map((selection) => {
return selection.get('type') === 'maybe' ? 1 : 0;
});
let maybe = this.users.map(({ selections }) => {
return selections.map(({ type }) => type === 'maybe' ? 1 : 0);
});
datasets.push({
label: this.i18n.t('answerTypes.maybe.label').toString(),
Expand All @@ -79,38 +111,10 @@ export default Component.extend({
labels
};
}),
chartOptions: computed(function () {
return {
legend: {
display: false
},
scales: {
xAxes: [{
stacked: true
}],
yAxes: [{
stacked: true,
ticks: {
callback(value) {
return `${value} %`;
},
max: 100,
min: 0
}
}]
},
tooltips: {
mode: 'label',
callbacks: {
label(tooltipItem, data) {
let { datasets } = data;
let { datasetIndex } = tooltipItem;
let { label } = datasets[datasetIndex];
let value = tooltipItem.yLabel;
return `${label}: ${value} %`;
}
}
}
}
}),

answerType: readOnly('poll.answerType'),
currentLocale: readOnly('i18n.locale'),
isFindADate: readOnly('poll.isFindADate'),
options: readOnly('poll.options'),
users: readOnly('poll.users'),
});
176 changes: 10 additions & 166 deletions app/components/poll-evaluation-participants-table.js
Original file line number Diff line number Diff line change
@@ -1,173 +1,17 @@
import { observer } from '@ember/object';
import $ from 'jquery';
import { scheduleOnce, next } from '@ember/runloop';
import Component from '@ember/component';
import moment from 'moment';
import { groupBy } from 'ember-awesome-macros/array';
import { readOnly } from '@ember/object/computed';
import { raw } from 'ember-awesome-macros';
import { groupBy, sort } from 'ember-awesome-macros/array';

export default Component.extend({
didInsertElement() {
this._super();
scheduleOnce('afterRender', this, function() {
/*
* adding floatThead jQuery plugin to poll table
* https://mkoryak.github.io/floatThead/
*
* top:
* Offset from the top of the `window` where the floating header will
* 'stick' when scrolling down
* Since we are adding a browser horizontal scrollbar on top, scrollingTop
* has to be set to height of horizontal scrollbar which depends on
* used browser
*/
$('.user-selections-table').floatThead({
position: 'absolute',
top: this.getScrollbarHeight
});
hasTimes: readOnly('poll.hasTimes'),

/*
* fix width calculation error caused by bootstrap glyphicon on webkit
*/
$('.glyphicon').css('width', '14px');
isFindADate: readOnly('poll.isFindADate'),
isFreeText: readOnly('poll.isFreeText'),

/*
* scrollbar on top of table
*/
const topScrollbarInner = $('<div></div>')
.css('width', $('.user-selections-table').width())
.css('height', '1px');
const topScrollbarOuter = $('<div></div>')
.addClass('top-scrollbar')
.css('width', '100%')
.css('overflow-x', 'scroll')
.css('overflow-y', 'hidden')
.css('position', 'relative')
.css('z-index', '1002');
$('.table-scroll').before(
topScrollbarOuter.append(topScrollbarInner)
);
options: readOnly('poll.options'),
optionsGroupedByDays: groupBy('options', raw('day')),

/*
* scrollbar on top of table for thead
*/
const topScrollbarInnerThead = $('<div></div>')
.css('width', $('.user-selections-table').width())
.css('height', '1px');
const topScrollbarOuterThead = $('<div></div>')
.addClass('top-scrollbar-floatThead')
.css('width', $('.table-scroll').outerWidth())
.css('overflow-x', 'scroll')
.css('overflow-y', 'hidden')
.css('position', 'fixed')
.css('top', '-1px')
.css('z-index', '1002')
.css('margin-left', `${($('.table-scroll').outerWidth() - $('.table-scroll').width()) / 2 * (-1)}px`)
.css('margin-right', `${($('.table-scroll').outerWidth() - $('.table-scroll').width()) / 2 * (-1)}px`);
$('.table-scroll').prepend(
topScrollbarOuterThead.append(topScrollbarInnerThead).hide()
);

// add listener to resize scrollbars if window get resized
$(window).resize(this.resizeScrollbars);

/*
* bind scroll event on all scrollbars
*/
$('.table-scroll').scroll(function() {
$('.top-scrollbar').scrollLeft($('.table-scroll').scrollLeft());
$('.top-scrollbar-floatThead').scrollLeft($('.table-scroll').scrollLeft());
});
$('.top-scrollbar').scroll(function() {
$('.table-scroll').scrollLeft($('.top-scrollbar').scrollLeft());
$('.top-scrollbar-floatThead').scrollLeft($('.top-scrollbar').scrollLeft());
});
$('.top-scrollbar-floatThead').scroll(function() {
$('.table-scroll').scrollLeft($('.top-scrollbar-floatThead').scrollLeft());
$('.top-scrollbar').scrollLeft($('.top-scrollbar-floatThead').scrollLeft());
});

/*
* show inner scrollbar only, if header is fixed
*/
$(window).scroll($.proxy(this.updateScrollbarTopVisibility, this));
});
},

/*
* calculates horizontal scrollbar height depending on current browser
*/
getScrollbarHeight() {
const wideScrollWtml = $('<div>').attr('id', 'wide_scroll_div_one').css({
'width': 50,
'height': 50,
'overflow-y': 'scroll',
'position': 'absolute',
'top': -200,
'left': -200
}).append(
$('<div>').attr('id', 'wide_scroll_div_two').css({
'height': '100%',
'width': 100
})
);
$('body').append(wideScrollWtml); // Append our div and add the hmtl to your document for calculations
const scrollW1 = $('#wide_scroll_div_one').height(); // Getting the width of the surrounding(parent) div - we already know it is 50px since we styled it but just to make sure.
const scrollW2 = $('#wide_scroll_div_two').innerHeight(); // Find the inner width of the inner(child) div.
const scrollBarWidth = scrollW1 - scrollW2; // subtract the difference
$('#wide_scroll_div_one').remove(); // remove the html from your document
return scrollBarWidth;
},

optionsGroupedByDates: groupBy('options', 'optionsGroupedBy', function(groupValue, currentValue) {
// have to parse the date cause due to timezone it may start with another day string but be at same day due to timezone
// e.g. '2015-01-01T23:00:00.000Z' and '2015-01-02T00:00:00.000Z' both are at '2015-01-02' for timezone offset '+01:00'
return moment(groupValue).format('YYYY-MM-DD') === moment(currentValue).format('YYYY-MM-DD');
}),
optionsGroupedBy: 'title',

/*
* resize scrollbars
* used as event callback when window is resized
*/
resizeScrollbars() {
$('.top-scrollbar div').css('width', $('.user-selections-table').width());
$('.top-scrollbar-floatThead').css('width', $('.table-scroll').outerWidth());
$('.top-scrollbar-floatThead div').css('width', $('.user-selections-table').width());
},

/*
* resize scrollbars if document height might be changed
* and therefore scrollbars might be added
*/
triggerResizeScrollbars: observer('controller.isEvaluable', 'controller.model.users.[]', function() {
next(() => {
this.resizeScrollbars();
});
}),

/*
* show / hide top scrollbar depending on window position
* used as event callback when window is scrolled
*/
updateScrollbarTopVisibility() {
const windowTop = $(window).scrollTop();
const tableTop = $('.table-scroll table').offset().top;
if (windowTop >= tableTop - this.getScrollbarHeight()) {
$('.top-scrollbar-floatThead').show();

// update scroll position
$('.top-scrollbar-floatThead').scrollLeft($('.table-scroll').scrollLeft());
} else {
$('.top-scrollbar-floatThead').hide();
}
},

/*
* clean up
* especially remove event listeners
*/
willDestroyElement() {
$(window).off('resize', this.resizeScrollbars);
$(window).off('scroll', this.updateScrollbarTopVisibility);
}
users: readOnly('poll.users'),
usersSorted: sort('users', ['creationDate']),
});
Loading

0 comments on commit bb160cc

Please sign in to comment.