Created a new table data view from library management and library details that is more generic.

Added support for finish event from H5Ps.
JS now also log to results when the H5P was opened and finished.
d6
Frode Petterson 2014-10-15 11:38:29 +02:00
parent 1479138abb
commit 86c18476cd
4 changed files with 406 additions and 18 deletions

126
js/h5p-data-view.js Normal file
View File

@ -0,0 +1,126 @@
var H5PDataView = (function ($) {
/**
* Initialize a new H5P data view.
*
* @param {Object} container
* @param {String} source URL for data
* @param {Array} headers for data
* @param {Object} l10n translations
*/
function H5PDataView(container, source, headers, l10n, classes) {
var self = this;
self.$container = $(container).addClass('h5p-data-view').html('');
H5PUtils.throbber(l10n.loading).appendTo(self.$container);
self.source = source;
self.headers = headers;
self.l10n = l10n;
self.classes = (classes === undefined ? {} : classes);
self.limit = 20;
self.loadData();
}
/**
* Load data for view.
*
* @param {Number} offset data collection offset
*/
H5PDataView.prototype.loadData = function (offset) {
var self = this;
// Create URL
var url = self.source;
if (offset !== undefined) {
url += (url.indexOf('?') === -1 ? '?' : '&') + 'offset=' + offset + '&limit=' + self.limit;
}
// Fire ajax request
$.ajax({
dataType: 'json',
cache: true,
url: url
}).fail(function () {
// Error handling
self.setMessage(self.l10n.ajaxFailed);
}).done(function (data) {
if (!data.rows.length) {
self.setMessage(self.l10n.noData);
}
else {
// Update table data
self.updateTable(data.rows);
}
// Update pagination widget
self.updatePagination(data.num);
});
};
/**
* Display the given message to the user.
*
* @param {String} message
*/
H5PDataView.prototype.setMessage = function (message) {
var self = this;
var $message = $('<p/>', {
text: message
});
if (self.table === undefined) {
self.$container.children().replaceWith($message);
}
else {
self.table.setBody($('<p/>', {text: message}));
}
};
/**
* Update table data.
*
* @param {Array} rows
*/
H5PDataView.prototype.updateTable = function (rows) {
var self = this;
if (self.table === undefined) {
// Create new table
self.table = new H5PUtils.Table(self.classes, self.headers);
self.table.appendTo(self.$container.html(''));
}
// Add/update rows
self.table.setRows(rows);
};
/**
* Update pagination widget.
*
* @param {Number} num size of data collection
*/
H5PDataView.prototype.updatePagination = function (num) {
var self = this;
if (self.pagination === undefined) {
// Create new widget
var $pagerContainer = $('<div/>', {'class': 'h5p-pagination'});
self.pagination = new H5PUtils.Pagination(num, self.limit, function (offset) {
// Handle page changes in pagination widget
self.table.setBody(H5PUtils.throbber(self.l10n.loading));
self.loadData(offset);
}, self.l10n);
self.pagination.appendTo($pagerContainer);
self.table.setFoot($pagerContainer);
}
else {
// Update existing widget
self.pagination.update(num, self.limit);
}
};
return H5PDataView;
})(H5P.jQuery);

View File

@ -64,7 +64,7 @@ var H5PUtils = H5PUtils || {};
return $field;
};
/**
* Replaces placeholder fields in translation strings
*
@ -130,4 +130,224 @@ var H5PUtils = H5PUtils || {};
return $container;
};
/**
* Generic table class with useful helpers.
*
* @param {Object} classes to use for styling
* @param {Array} cols headers
*/
H5PUtils.Table = function (classes, cols) {
// Create basic table
var tableOptions = {};
if (classes.table !== undefined) {
tableOptions['class'] = classes.table;
}
var $table = $('<table/>', tableOptions);
var $thead = $('<thead/>').appendTo($table);
var $tfoot = $('<tfoot/>').appendTo($table);
var $tbody = $('<tbody/>').appendTo($table);
// Set cols - create header
var $tr = $('<tr/>').appendTo($thead);
for (var i = 0; i < cols.length; i++) {
$('<th>', {
html: cols[i]
}).appendTo($tr);
}
/**
* Public.
*
* @param {Array} rows with cols
*/
this.setRows = function (rows) {
var $newTbody = $('<tbody/>');
for (var i = 0; i < rows.length; i++) {
var $tr = $('<tr/>').appendTo($newTbody);
for (var j = 0; j < rows[i].length; j++) {
$('<td>', {
html: rows[i][j]
}).appendTo($tr);
}
}
$tbody.replaceWith($newTbody);
$tbody = $newTbody;
};
/**
* Public.
*
* @param {jQuery} $content custom
*/
this.setBody = function ($content) {
var $newTbody = $('<tbody/>');
var $tr = $('<tr/>').appendTo($newTbody);
$('<td>', {
colspan: cols.length
}).append($content).appendTo($tr);
$tbody.replaceWith($newTbody);
$tbody = $newTbody;
};
/**
* Public.
*
* @param {jQuery} $content custom
*/
this.setFoot = function ($content) {
var $newTfoot = $('<tfoot/>');
var $tr = $('<tr/>').appendTo($newTfoot);
$('<td>', {
colspan: cols.length
}).append($content).appendTo($tr);
$tfoot.replaceWith($newTfoot);
};
/**
* Public.
*
* @param {jQuery} $container
*/
this.appendTo = function ($container) {
$table.appendTo($container);
};
};
/**
* Generic pagination class.
*
* @param {Number} num total items
* @param {Number} limit items per page
* @param {Function} goneTo page callback
*/
H5PUtils.Pagination = function (num, limit, goneTo, l10n) {
var current = 0;
var pages = Math.ceil(num / limit);
// Create components
// Previous button
var $left = $('<button/>', {
html: '&lt;',
'class': 'button',
title: l10n.previousPage
}).click(function () {
goTo(current - 1);
});
// Current page text
var $text = $('<span/>').click(function () {
$input.width($text.width()).show().val(current + 1).focus();
$text.hide();
});
// Jump to page input
var $input = $('<input/>', {
type: 'number',
min : 1,
max: pages,
on: {
'blur': function () {
gotInput();
},
'keyup': function (event) {
if (event.keyCode === 13) {
gotInput();
}
}
}
}).hide();
// Next button
var $right = $('<button/>', {
html: '&gt;',
'class': 'button',
title: l10n.nextPage
}).click(function () {
goTo(current + 1);
});
/**
* Private. Input box value may have changed.
*/
var gotInput = function () {
var page = parseInt($input.hide().val());
if (!isNaN(page)) {
goTo(page - 1);
}
$text.show();
};
/**
* Private. Update UI elements.
*/
var updateUI = function () {
var next = current + 1;
// Disable or enable buttons
$left.attr('disabled', current === 0);
$right.attr('disabled', next === pages);
// Update counter
$text.html(l10n.currentPage.replace('$current', next).replace('$total', pages));
};
/**
* Private. Try to go to the requested page.
*
* @param {Number} page
*/
var goTo = function (page) {
if (page === current || page < 0 || page >= pages) {
return; // Invalid page number
}
current = page;
updateUI();
// Fire callback
goneTo(page * limit);
};
/**
* Public. Update number of items and limit.
*
* @param {Number} newNum
* @param {Number} newLimit
*/
this.update = function (newNum, newLimit) {
if (newNum !== num || newLimit !== limit) {
// Update num and limit
num = newNum;
limit = newLimit;
pages = Math.ceil(num / limit);
$input.attr('max', pages);
if (current >= pages) {
// Content is gone, move to last page.
goTo(pages - 1);
return;
}
updateUI();
}
};
/**
* Public. Append the pagination widget to the given item.
*
* @param {jQuery} $container
*/
this.appendTo = function ($container) {
$left.add($text).add($input).add($right).appendTo($container);
};
// Update UI
updateUI();
};
})(H5P.jQuery);

View File

@ -30,6 +30,9 @@ else if (document.documentElement.msRequestFullscreen) {
H5P.fullScreenBrowserPrefix = 'ms';
}
// Keep track of when the H5Ps where started
H5P.opened = {};
/**
* Initialize H5P content.
* Scans for ".h5p-content" in the document and initializes H5P instances where found.
@ -89,6 +92,16 @@ H5P.init = function () {
}
$actions.insertAfter($container);
// Keep track of when we started
H5P.opened[contentId] = new Date();
// Handle events when the user finishes the content. Useful for logging exercise results.
instance.$.on('finish', function (event) {
if (event.data !== undefined) {
H5P.setFinished(contentId, event.data.score, event.data.maxScore, event.data.time);
}
});
if (H5P.isFramed) {
// Make it possible to resize the iframe when the content changes size. This way we get no scrollbars.
var iframe = window.parent.document.getElementById('h5p-iframe-' + contentId);
@ -965,16 +978,38 @@ H5P.shuffleArray = function (array) {
};
/**
* DEPRECATED! Do not use this function directly, trigger the finish event
* instead.
*
* Post finished results for user.
* TODO: Should we use events instead? That way the parent can handle the results of the child.
*
* @param {Number} contentId
* @param {Number} points
* @param {Number} maxPoints
* @param {Number} score achieved
* @param {Number} maxScore that can be achieved
* @param {Number} time optional reported time usage
*/
H5P.setFinished = function (contentId, points, maxPoints) {
H5P.setFinished = function (contentId, score, maxScore, time) {
if (H5P.postUserStatistics === true) {
H5P.jQuery.post(H5P.ajaxPath + 'setFinished', {contentId: contentId, points: points, maxPoints: maxPoints});
/**
* Return unix timestamp for the given JS Date.
*
* @param {Date} date
* @returns {Number}
*/
var toUnix = function (date) {
return Math.round(date.getTime() / 1000);
};
// Post the results
// TODO: Should we use a variable with the complete path?
H5P.jQuery.post(H5P.ajaxPath + 'setFinished', {
contentId: contentId,
score: score,
maxScore: maxScore,
opened: toUnix(H5P.opened[contentId]),
finished: toUnix(new Date()),
time: time
});
}
};

View File

@ -11,7 +11,8 @@
border: none;
}
.h5p-admin-table tr:nth-child(odd) {
.h5p-admin-table tr:nth-child(odd),
.h5p-data-view tr:nth-child(odd) {
background-color: #F9F9F9;
}
.h5p-admin-table tbody tr:hover {
@ -30,7 +31,7 @@
}
.h5p-admin-buttons-wrapper {
white-space: nowrap;
white-space: nowrap;
}
.h5p-admin-table.libraries button {
@ -69,13 +70,13 @@
}
.h5p-admin-upgrade-library {
color: #339900;
color: #339900;
}
.h5p-admin-view-library {
color: #0066cc;
color: #0066cc;
}
.h5p-admin-delete-library {
color: #990000;
color: #990000;
}
.h5p-admin-delete-library:disabled,
.h5p-admin-upgrade-library:disabled {
@ -86,9 +87,9 @@
.h5p-library-info {
padding: 1em 1em;
margin: 1em 0;
width: 350px;
border: 1px solid #DDD;
border-radius: 3px;
}
@ -118,14 +119,14 @@
.h5p-content-search {
display: inline-block;
position: relative;
width: 100%;
padding: 5px 0;
margin-top: 10px;
border: 1px solid #CCC;
border-radius: 3px;
box-shadow: 2px 2px 5px #888888;
box-shadow: 2px 2px 5px #888888;
}
.h5p-content-search:before {
font-family: 'H5P';
@ -219,7 +220,7 @@ button.h5p-admin.disabled:hover {
.h5p-content-pager > .pager-info:hover {
background-color: #555;
color: #FFF;
}
}
.h5p-content-pager > .pager-info,
.h5p-content-pager > .h5p-pager-goto {
margin: 0 10px;
@ -244,4 +245,10 @@ button.h5p-admin.disabled:hover {
padding: 0 0.5em;
font-size: 1.5em;
font-weight: bold;
}
}
.h5p-pagination {
text-align: center;
}
.h5p-pagination > span, .h5p-pagination > input {
margin: 0 1em;
}