Commit 7975af2f authored by Kushal Pandya's avatar Kushal Pandya

Update template, add mixin, cleanup methods for new template

parent 7339cf59
<script>
import $ from 'jquery';
import eventHub from '../event_hub';
import { SCROLL_BAR_SIZE } from '../constants';
import SectionMixin from '../mixins/section_mixin';
import epicItem from './epic_item.vue';
......@@ -10,6 +9,9 @@
components: {
epicItem,
},
mixins: [
SectionMixin,
],
props: {
epics: {
type: Array,
......@@ -27,6 +29,10 @@
type: Number,
required: true,
},
listScrollable: {
type: Boolean,
required: true,
},
},
data() {
return {
......@@ -38,26 +44,20 @@
};
},
computed: {
/**
* Return width after reducing scrollbar size
* such that Epic item cells do not consider
* scrollbar
*/
calcShellWidth() {
return this.shellWidth - SCROLL_BAR_SIZE;
},
/**
* Adjust tbody styles while pushing scrollbar further away
* from the view
*/
tbodyStyles() {
return `width: ${this.shellWidth + SCROLL_BAR_SIZE}px; height: ${this.shellHeight}px;`;
emptyRowContainerStyles() {
return {
height: `${this.emptyRowHeight}px`,
};
},
emptyRowCellStyles() {
return `height: ${this.emptyRowHeight}px;`;
return {
width: `${this.sectionItemWidth}px`,
};
},
shadowCellStyles() {
return `left: ${this.offsetLeft}px;`;
return {
left: `${this.offsetLeft}px`,
};
},
},
watch: {
......@@ -69,14 +69,18 @@
},
},
mounted() {
eventHub.$on('epicsListScrolled', this.handleEpicsListScroll);
this.$nextTick(() => {
this.initMounted();
});
},
beforeDestroy() {
eventHub.$off('epicsListScrolled', this.handleEpicsListScroll);
},
methods: {
initMounted() {
// Get available shell height based on viewport height
this.shellHeight = window.innerHeight - (this.$el.offsetTop + this.$root.$el.offsetTop);
this.shellHeight = window.innerHeight - this.$el.offsetTop;
// In case there are epics present, initialize empty row
if (this.epics.length) {
......@@ -113,32 +117,12 @@
});
// set height and show empty row reducing horizontal scrollbar size
this.emptyRowHeight = (this.shellHeight - approxChildrenHeight) - 1;
this.emptyRowHeight = (this.shellHeight - approxChildrenHeight);
this.showEmptyRow = true;
} else {
this.showBottomShadow = true;
}
},
/**
* We can easily use `eventHub` and dispatch this event
* to all sibling and child components but it adds an overhead/delay
* resulting to janky element positioning. Hence, we directly
* update raw element properties upon event via jQuery.
*/
handleScroll() {
const { scrollTop, scrollLeft, scrollHeight, clientHeight } = this.$el;
const tableEl = this.$el.parentElement;
if (tableEl) {
const $theadEl = $(tableEl).find('thead');
const $tbodyEl = $(tableEl).find('tbody');
$theadEl.css('left', -scrollLeft);
$theadEl.find('th:nth-child(1)').css('left', scrollLeft);
$tbodyEl.find('td:nth-child(1)').css('left', scrollLeft);
}
this.showBottomShadow = (Math.ceil(scrollTop) + clientHeight) < scrollHeight;
eventHub.$emit('epicsListScrolled', scrollTop, scrollLeft);
},
/**
* `clientWidth` is full width of list section, and we need to
* scroll up to 60% of the view where today indicator is present.
......@@ -149,45 +133,45 @@
const uptoTodayIndicator = Math.ceil((this.$el.clientWidth * 60) / 100);
this.$el.scrollTo(uptoTodayIndicator, 0);
},
handleEpicsListScroll({ scrollTop, clientHeight, scrollHeight }) {
this.showBottomShadow = (Math.ceil(scrollTop) + clientHeight) < scrollHeight;
},
},
};
</script>
<template>
<tbody
<div
class="epics-list-section"
:style="tbodyStyles"
@scroll="handleScroll"
:style="sectionContainerStyles"
>
<tr
v-if="showBottomShadow"
class="bottom-shadow-cell"
:style="shadowCellStyles"
></tr>
<epic-item
v-for="(epic, index) in epics"
:key="index"
:epic="epic"
:timeframe="timeframe"
:current-group-id="currentGroupId"
:shell-width="calcShellWidth"
:shell-width="sectionShellWidth"
:item-width="sectionItemWidth"
/>
<tr
<div
v-if="showEmptyRow"
class="epics-list-item epics-list-item-empty"
class="epics-list-item epics-list-item-empty clearfix"
:style="emptyRowContainerStyles"
>
<td
class="epic-details-cell"
:style="emptyRowCellStyles"
>
</td>
<td
class="epic-timeline-cell"
<span class="epic-details-cell"></span>
<span
v-for="(timeframeItem, index) in timeframe"
:key="index"
class="epic-timeline-cell"
:style="emptyRowCellStyles"
>
</td>
</tr>
</tbody>
</span>
</div>
<div
v-if="showBottomShadow"
class="scroll-bottom-shadow"
:style="shadowCellStyles"
></div>
</div>
</template>
......@@ -15,6 +15,7 @@ const createComponent = ({
timeframe = mockTimeframe,
currentGroupId = mockGroupId,
shellWidth = mockShellWidth,
listScrollable = false,
}) => {
const Component = Vue.extend(epicsListSectionComponent);
......@@ -23,6 +24,7 @@ const createComponent = ({
timeframe,
currentGroupId,
shellWidth,
listScrollable,
});
};
......@@ -39,6 +41,8 @@ describe('EpicsListSectionComponent', () => {
expect(vm.shellHeight).toBe(0);
expect(vm.emptyRowHeight).toBe(0);
expect(vm.showEmptyRow).toBe(false);
expect(vm.offsetLeft).toBe(0);
expect(vm.showBottomShadow).toBe(false);
});
});
......@@ -47,24 +51,21 @@ describe('EpicsListSectionComponent', () => {
vm = createComponent({});
});
describe('calcShellWidth', () => {
it('returns shellWidth after deducting predefined scrollbar size', () => {
// shellWidth is 2000 (as defined above in mockShellWidth)
// SCROLLBAR_SIZE is 15 (as defined in app's constants.js)
// Hence, calcShellWidth = shellWidth - SCROLLBAR_SIZE
expect(vm.calcShellWidth).toBe(1985);
describe('emptyRowContainerStyles', () => {
it('returns computed style object based on emptyRowHeight prop value', () => {
expect(vm.emptyRowContainerStyles.height).toBe('0px');
});
});
describe('tbodyStyles', () => {
it('returns computed style string based on shellWidth and shellHeight', () => {
expect(vm.tbodyStyles).toBe('width: 2015px; height: 0px;');
describe('emptyRowCellStyles', () => {
it('returns computed style object based on sectionItemWidth prop value', () => {
expect(vm.emptyRowCellStyles.width).toBe('280px');
});
});
describe('emptyRowCellStyles', () => {
it('returns computed style string based on emptyRowHeight', () => {
expect(vm.emptyRowCellStyles).toBe('height: 0px;');
describe('shadowCellStyles', () => {
it('returns computed style object based on `offsetLeft` prop value', () => {
expect(vm.shadowCellStyles.left).toBe('0px');
});
});
});
......@@ -104,7 +105,7 @@ describe('EpicsListSectionComponent', () => {
describe('initEmptyRow', () => {
it('sets `emptyRowHeight` and `showEmptyRow` props when shellHeight is greater than approximate height of epics list', (done) => {
vm.$nextTick(() => {
expect(vm.emptyRowHeight).toBe(599); // total size -1px
expect(vm.emptyRowHeight).toBe(600);
expect(vm.showEmptyRow).toBe(true);
done();
});
......@@ -125,14 +126,6 @@ describe('EpicsListSectionComponent', () => {
});
});
describe('handleScroll', () => {
it('emits `epicsListScrolled` event via eventHub', () => {
spyOn(eventHub, '$emit');
vm.handleScroll();
expect(eventHub.$emit).toHaveBeenCalledWith('epicsListScrolled', jasmine.any(Number), jasmine.any(Number));
});
});
describe('scrollToTodayIndicator', () => {
it('scrolls table body to put timeline today indicator in focus', () => {
spyOn(vm.$el, 'scrollTo');
......@@ -154,9 +147,17 @@ describe('EpicsListSectionComponent', () => {
});
});
it('renders component container element with `width` and `left` properties applied via style attribute', (done) => {
it('renders component container element with `width` property applied via style attribute', (done) => {
vm.$nextTick(() => {
expect(vm.$el.getAttribute('style')).toBe(`width: ${mockShellWidth}px;`);
done();
});
});
it('renders bottom shadow element when `showBottomShadow` prop is true', (done) => {
vm.showBottomShadow = true;
vm.$nextTick(() => {
expect(vm.$el.getAttribute('style')).toBe('width: 2015px; height: 0px;');
expect(vm.$el.querySelector('.scroll-bottom-shadow')).not.toBe(null);
done();
});
});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment