Resize the Tasks table when necessary

The `JobDetails` component now emits a `reshuffled` event whenever its
contents have changed, so that other components can respond to any changes
in available space. This event now triggers a resize of the tasks table
on the next DOM tick (so that the new sizes of the HTML elements are
available).

The `TasksTable` component also recomputes the table size when the task
status filters change (because that might have triggered a show/hide of
the filter bar). It also computes the available height slightly differently
so that it's all done relative to the tabulator element.

There is just one TODO left, which is a hard-coded offset of 12 that
should be obtained dynamically from somewhere -- no idea where it comes
from or why it's necessary.
This commit is contained in:
Sybren A. Stüvel 2022-05-30 15:08:34 +02:00
parent 09dbcfe512
commit 81aec77059
3 changed files with 63 additions and 12 deletions

View File

@ -59,6 +59,9 @@ export default {
props: [ props: [
"jobData", // Job data to show. "jobData", // Job data to show.
], ],
emits: [
"reshuffled", // Emitted when the size of this component may have changed. Used to resize other components in response.
],
data() { data() {
return { return {
datetime: datetime, // So that the template can access it. datetime: datetime, // So that the template can access it.
@ -105,13 +108,13 @@ export default {
methods: { methods: {
_refreshJobSettings(newJobData) { _refreshJobSettings(newJobData) {
if (objectEmpty(newJobData)) { if (objectEmpty(newJobData)) {
this.simpleSettings = null; this._clearJobSettings();
return; return;
} }
// Only fetch the job type if it's different from what's already loaded. // Only fetch the job type if it's different from what's already loaded.
if (objectEmpty(this.jobType) || this.jobType.name != newJobData.type) { if (objectEmpty(this.jobType) || this.jobType.name != newJobData.type) {
this.simpleSettings = null; // They should only be shown when the type info is known. this._clearJobSettings(); // They should only be shown when the type info is known.
this.jobsApi.getJobType(newJobData.type) this.jobsApi.getJobType(newJobData.type)
.then(this.onJobTypeLoaded) .then(this.onJobTypeLoaded)
@ -136,14 +139,20 @@ export default {
} }
}, },
_clearJobSettings() {
this.simpleSettings = null;
this.$emit('reshuffled');
},
_setJobSettings(newJobSettings) { _setJobSettings(newJobSettings) {
if (objectEmpty(newJobSettings)) { if (objectEmpty(newJobSettings)) {
this.simpleSettings = null; this._clearJobSettings();
return; return;
} }
if (objectEmpty(this.jobTypeSettings)) { if (objectEmpty(this.jobTypeSettings)) {
console.warn("empty job type settings"); console.warn("empty job type settings");
this._clearJobSettings();
return; return;
} }
@ -167,6 +176,7 @@ export default {
} }
this.simpleSettings = filtered; this.simpleSettings = filtered;
this.$emit('reshuffled');
} }
} }
}; };

View File

@ -10,6 +10,7 @@
</div> </div>
</template> </template>
<script lang="js"> <script lang="js">
import { TabulatorFull as Tabulator } from 'tabulator-tables'; import { TabulatorFull as Tabulator } from 'tabulator-tables';
import * as datetime from "@/datetime"; import * as datetime from "@/datetime";
@ -84,10 +85,10 @@ export default {
this.tabulator.on("rowClick", this.onRowClick); this.tabulator.on("rowClick", this.onRowClick);
this.tabulator.on("tableBuilt", this._onTableBuilt); this.tabulator.on("tableBuilt", this._onTableBuilt);
window.addEventListener('resize', this._setTableHeight); window.addEventListener('resize', this.recalcTableHeight);
}, },
unmounted() { unmounted() {
window.removeEventListener('resize', this._setTableHeight); window.removeEventListener('resize', this.recalcTableHeight);
}, },
watch: { watch: {
jobID() { jobID() {
@ -97,6 +98,12 @@ export default {
this._reformatRow(oldID); this._reformatRow(oldID);
this._reformatRow(newID); this._reformatRow(newID);
}, },
availableStatuses() {
// Statuses changed, so the filter bar could have gone from "no statuses"
// to "any statuses" (or one row of filtering stuff to two, I don't know)
// and changed height.
this.$nextTick(this.recalcTableHeight);
},
}, },
methods: { methods: {
onReconnected() { onReconnected() {
@ -131,7 +138,7 @@ export default {
this.tabulator.setData(data.tasks); this.tabulator.setData(data.tasks);
this._refreshAvailableStatuses(); this._refreshAvailableStatuses();
this._setTableHeight(); this.recalcTableHeight();
}, },
processTaskUpdate(taskUpdate) { processTaskUpdate(taskUpdate) {
// updateData() will only overwrite properties that are actually set on // updateData() will only overwrite properties that are actually set on
@ -180,15 +187,30 @@ export default {
if (row.reformat) row.reformat(); if (row.reformat) row.reformat();
else if (row.reinitialize) row.reinitialize(true); else if (row.reinitialize) row.reinitialize(true);
}, },
_setTableHeight() {
const jobDetailsColumn = this.$el.parentElement;
const tableContainer = this.tabulator.element.parentElement;
if (!jobDetailsColumn || !taskListTable) { /**
* Recalculate the appropriate table height to fit in the column without making that scroll.
*/
recalcTableHeight() {
if (!this.tabulator.initialized) {
// Sometimes this function is called too early, before the table was initialised.
// After the table is initialised it gets resized anyway, so this call can be ignored.
return;
}
const table = this.tabulator.element;
const tableContainer = table.parentElement;
const outerContainer = tableContainer.parentElement;
const availableHeight = outerContainer.clientHeight - 12; // TODO: figure out where the -12 comes from.
if (tableContainer.offsetParent != tableContainer.parentElement) {
// `offsetParent` is assumed to be the actual column in the 3-column
// view. To ensure this, it's given `position: relative` in the CSS
// styling.
console.warn("TaskTable.recalcTableHeight() only works when the offset parent is the real parent of the element.");
return; return;
} }
let tableHeight = jobDetailsColumn.clientHeight - taskListTable.offsetTop; const tableHeight = availableHeight - tableContainer.offsetTop;
this.tabulator.setHeight(tableHeight); this.tabulator.setHeight(tableHeight);
}, },
} }

View File

@ -3,7 +3,7 @@
<jobs-table ref="jobsTable" :activeJobID="jobID" @tableRowClicked="onTableJobClicked" /> <jobs-table ref="jobsTable" :activeJobID="jobID" @tableRowClicked="onTableJobClicked" />
</div> </div>
<div class="col col-2" id="col-job-details"> <div class="col col-2" id="col-job-details">
<job-details :jobData="jobs.activeJob" /> <job-details :jobData="jobs.activeJob" @reshuffled="_recalcTasksTableHeight" />
<tasks-table v-if="hasJobData" ref="tasksTable" :jobID="jobID" :taskID="taskID" @tableRowClicked="onTableTaskClicked" /> <tasks-table v-if="hasJobData" ref="tasksTable" :jobID="jobID" :taskID="taskID" @tableRowClicked="onTableTaskClicked" />
</div> </div>
<div class="col col-3"> <div class="col col-3">
@ -226,6 +226,12 @@ export default {
}, },
onSIODisconnected(reason) { onSIODisconnected(reason) {
}, },
_recalcTasksTableHeight() {
if (!this.$refs.tasksTable) return;
// Any recalculation should be done after the DOM has updated.
this.$nextTick(this.$refs.tasksTable.recalcTableHeight);
},
}, },
} }
</script> </script>
@ -234,4 +240,17 @@ export default {
.isFetching { .isFetching {
opacity: 50%; opacity: 50%;
} }
#col-job-details {
/* These two are necessary for the automatic resizing of the tasks table: */
/* Ensures that the table cannot push down the bottom of the column element,
* and thus the column height is a stable reference. */
overflow-y: scroll;
/* Ensures the offsetParent of the table is the column itself; without this,
* offsetParent would be <body>. */
position: relative;
}
</style> </style>