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

Gradebook UIUX #2123

Merged
merged 13 commits into from
Apr 28, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -25,39 +25,44 @@
$menu.empty();
columnCheckboxes = [];

var $li, $input;
var $li, $input, $span;
$("<span />").text("Toggle on/off").appendTo($menu)
for (var i = 0; i < columns.length; i++) {

$li = $("<li />").appendTo($menu);

$input = $("<input type='checkbox' />").data("column-id", columns[i].id);
$input = $("<input type='checkbox'/>").data("column-id", columns[i].id).attr("data-id", i);
columnCheckboxes.push($input);
if (grid.getColumnIndex(columns[i].id) != null) {
$input.attr("checked", "checked");
}

$span = $("<span />").text(columns[i].name)

$("<label />")
.text(columns[i].name)
.prepend($input)
.append($span)
.appendTo($li);
}

$("<hr/>").appendTo($menu);
$li = $("<li />").appendTo($menu);
$input = $("<input type='checkbox' />").data("option", "autoresize");
$span = $("<span />").text("Force fit columns")
$("<label />")
.text("Force fit columns")
.prepend($input)
.append($span)
.appendTo($li);
if (grid.getOptions().forceFitColumns) {
$input.attr("checked", "checked");
}

$li = $("<li />").appendTo($menu);
$input = $("<input type='checkbox' />").data("option", "syncresize");
$span = $("<span />").text("Synchronous resize")
$("<label />")
.text("Synchronous resize")
.prepend($input)
.append($span)
.appendTo($li);
if (grid.getOptions().syncColumnCellResize) {
$input.attr("checked", "checked");
Expand All @@ -70,6 +75,17 @@
}

function updateColumn(e) {

var assessment_versions = {}
for (var i = 0; i < columns.length; i++) {
var underscoreIndex = columns[i].name.lastIndexOf('_');
var version = columns[i].name.substring(underscoreIndex + 1);
if (version === "Version") {
assessment_versions[i] = [i, i-1];
assessment_versions[i-1] = [i, i-1];
}
}

if ($(e.target).data("option") == "autoresize") {
if (e.target.checked) {
grid.setOptions({forceFitColumns:true});
Expand All @@ -89,6 +105,16 @@
return;
}

$("input").click(function(event) {
var column_index = event.target.dataset.id
if (column_index in assessment_versions) {
var checked = event.target.checked
columnCheckboxes[assessment_versions[column_index][0]].prop("checked", checked)
columnCheckboxes[assessment_versions[column_index][1]].prop("checked", checked)
}
})


if ($(e.target).is(":checkbox")) {
var visibleColumns = [];
$.each(columnCheckboxes, function (i, e) {
Expand Down
160 changes: 160 additions & 0 deletions app/assets/javascripts/gradebook.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

var slickgrid_options = {
enableCellNavigation: true,
enableColumnReorder: false,
rowHeight: ROW_HEIGHT,
defaultColumnWidth: 65,
syncColumnCellResize: true,
Expand Down Expand Up @@ -82,6 +83,163 @@
return "<a href=" + url + ">" + title + "</a>";
}

(function($) {

$.extend(true, window, {
"Slick": {
"AutoColumnSize": AutoColumnSize
}
});

function AutoColumnSize(maxWidth) {

var grid, $container, context,
keyCodes = {
'A': 65
};

function init(_grid) {
grid = _grid;
maxWidth = maxWidth || 300;

$container = $(grid.getContainerNode());
$container.on("dblclick.autosize", ".slick-resizable-handle", reSizeColumn);
$container.keydown(handleControlKeys);

context = document.createElement("canvas").getContext("2d");
}

function destroy() {
$container.off();
}

function handleControlKeys(event) {
if (event.ctrlKey && event.shiftKey && event.keyCode === keyCodes.A) {
resizeAllColumns();
}
}

function resizeAllColumns() {
var elHeaders = $container.find(".slick-header-column");
var allColumns = grid.getColumns();
elHeaders.each(function(index, el) {
var columnDef = $(el).data('column');
var headerWidth = getElementWidth(el);
var colIndex = grid.getColumnIndex(columnDef.id);
var column = allColumns[colIndex];
var autoSizeWidth = Math.max(headerWidth, getMaxColumnTextWidth(columnDef, colIndex)) + 1;
autoSizeWidth = Math.min(maxWidth, autoSizeWidth);
column.width = autoSizeWidth;
});
grid.setColumns(allColumns);
grid.onColumnsResized.notify();
}

function reSizeColumn(e) {
var headerEl = $(e.currentTarget).closest('.slick-header-column');
var columnDef = headerEl.data('column');

if (!columnDef || !columnDef.resizable) {
return;
}

e.preventDefault();
e.stopPropagation();

var headerWidth = getElementWidth(headerEl[0]);
var colIndex = grid.getColumnIndex(columnDef.id);
var allColumns = grid.getColumns();
var column = allColumns[colIndex];

var autoSizeWidth = Math.max(headerWidth, getMaxColumnTextWidth(columnDef, colIndex)) + 1;

if (autoSizeWidth !== column.width) {
column.width = autoSizeWidth;
grid.setColumns(allColumns);
grid.onColumnsResized.notify();
}
}

function getMaxColumnTextWidth(columnDef, colIndex) {
var texts = [];
var rowEl = createRow(columnDef);
var data = grid.getData();
if (Slick.Data && data instanceof Slick.Data.DataView) {
data = data.getItems();
}
for (var i = 0; i < data.length; i++) {
texts.push(data[i][columnDef.field]);
}
var template = getMaxTextTemplate(texts, columnDef, colIndex, data, rowEl);
var width = getTemplateWidth(rowEl, template);
deleteRow(rowEl);
return width;
}

function getTemplateWidth(rowEl, template) {
var cell = $(rowEl.find(".slick-cell"));
cell.append(template);
Fixed Show fixed Hide fixed
$(cell).find("*").css("position", "relative");
return cell.outerWidth() + 1;
}

function getMaxTextTemplate(texts, columnDef, colIndex, data, rowEl) {
var max = 0,
maxTemplate = null;
var formatFun = columnDef.formatter;
$(texts).each(function(index, text) {
var template;
if (formatFun) {
template = $("<span>" + formatFun(index, colIndex, text, columnDef, data[index]) + "</span>");
text = template.text() || text;
}
var length = text ? getElementWidthUsingCanvas(rowEl, text) : 0;
if (length > max) {
max = length;
maxTemplate = template || text;
}
});
return maxTemplate;
}

function createRow(columnDef) {
var rowEl = $('<div class="slick-row"><div class="slick-cell"></div></div>');
rowEl.find(".slick-cell").css({
"visibility": "hidden",
"text-overflow": "initial",
"white-space": "nowrap"
});
var gridCanvas = $container.find(".grid-canvas");
$(gridCanvas).append(rowEl);
return rowEl;
}

function deleteRow(rowEl) {
$(rowEl).remove();
}

function getElementWidth(element) {
var width, clone = element.cloneNode(true);
clone.style.cssText = 'position: absolute; visibility: hidden;right: auto;text-overflow: initial;white-space: nowrap;';
element.parentNode.insertBefore(clone, element);
width = clone.offsetWidth;
clone.parentNode.removeChild(clone);
return width;
}

function getElementWidthUsingCanvas(element, text) {
context.font = element.css("font-size") + " " + element.css("font-family");
var metrics = context.measureText(text);
return metrics.width;
}

return {
init: init,
destroy: destroy
};
}
}(jQuery));

$(function () {
// enumerator
columns[0].formatter = function(row, cell, val, colDef, data) {
Expand All @@ -108,6 +266,8 @@

var dataView = new Slick.Data.DataView();
grid = new Slick.Grid("#gradebook", dataView, columns, slickgrid_options);
grid.registerPlugin( new Slick.AutoColumnSize());

new Slick.Controls.ColumnPicker(columns, grid, options);

grid.onSort.subscribe(function (e, args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
.slick-columnpicker {
border: 1px solid #718BB7;
background: #f0f0f0;
padding: 6px;
background: white;
-moz-box-shadow: 2px 2px 2px silver;
-webkit-box-shadow: 2px 2px 2px silver;
min-width: 100px;
cursor: default;
max-height: 60vh;
padding-top: 10px;
overflow-y: scroll;
}

.slick-columnpicker li {
Expand All @@ -15,16 +16,39 @@
background: none;
}

.slick-columnpicker input {
.slick-columnpicker li label {
padding: 6px;
margin: 4px;
cursor: pointer;
}

.slick-columnpicker li a {
display: block;
padding: 4px;
font-weight: bold;
.slick-columnpicker li:hover {
background: #f0f0f0;
}

.slick-columnpicker li {
cursor: pointer;
margin: 4px;
}

.slick-columnpicker li label {
padding: 1px;
width: 100%;
display: inline-flex;
cursor: pointer;
}

.slick-columnpicker li label input[type=checkbox] {
display: none; /* to hide the checkbox itself */
}

.slick-columnpicker span {
margin-left: 10px;
margin-bottom: 4px;
margin-right: 10px;
}

.slick-columnpicker li label input[type=checkbox]:checked + span:before{
opacity: 1;
}

.slick-columnpicker li a:hover {
background: white;
}
6 changes: 0 additions & 6 deletions app/assets/stylesheets/instructor_gradebook.scss
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,6 @@ div#footer {
font: inherit;
}

#gradebook .slick-cell.email,
#gradebook .slick-cell.first_name,
#gradebook .slick-cell.last_name {
text-align: left;
}

#gradebook .slick-header-column.andrew.right,
#gradebook .slick-cell.andrew.right {
text-align: center;
Expand Down
20 changes: 10 additions & 10 deletions app/helpers/gradebook_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ def gradebook_columns(matrix, course)
sortable: true, width: 100, cssClass: "last_name",
headerCssClass: "last_name" },
{ id: "course_number", name: "Course &#8470;", field: "course_number",
sortable: true, width: 100 },
sortable: true, width: 120 },
{ id: "lecture", name: "Lecture", field: "lecture",
sortable: true, width: 100 },
{ id: "section", name: "Section", field: "section",
sortable: true, width: 100 },
{ id: "grace_days", name: "Grace Days", field: "grace_days",
sortable: true, width: 100 },
sortable: true, width: 150 },
{ id: "late_days", name: "Penalty Late Days", field: "late_days",
sortable: true, width: 100 }
sortable: true, width: 150 }
]

course.assessment_categories.each do |cat|
Expand Down Expand Up @@ -150,12 +150,12 @@ def csv_header(matrix, course)

def formatted_status(status)
case status
when Float
round status
when String
status
else
throw "FATAL: AUD status must be Float or String; was #{status.class}"
when Float
round status
when String
status
else
throw "FATAL: AUD status must be Float or String; was #{status.class}"
KesterTan marked this conversation as resolved.
Show resolved Hide resolved
end
end

Expand Down Expand Up @@ -200,4 +200,4 @@ def gradebook_csv(matrix, course)
end
end
end
end
end