liberapay.com/js/charts.js
Changaco 317ce837bb unify JavaScript handling of form submissions
- drop the `js-submit` class and merge the two separate `submit` event handlers into one
  - it's now easy to run custom functions both before and after a form submission
    - however, the after functions may not be called if the server responds to the form submission with an error
  - the unified function's source code is commented
  - the unified function sends debugging messages to the console
  - form inputs are no longer disabled during submission, because it was complicating the JavaScript code
    - CSS is used instead to gray out the entire form and disable mouse events, but keyboard events aren't be disabled
  - the submission feedback overlay is back, for forms making slow requests before submitting
- replace most uses of `jQuery.ajax()` with `fetch()`, simplifying the `Liberapay.error()` function and removing the need for the `Liberapay.getCookie()` function
  - all forms are now required to include an anti-CSRF token, whereas previously the forms that required JavaScript didn't need to contain the token as it would be taken from the cookies anyway
  - since we no longer need the `csrf_token` cookie to be accessible from JavaScript, mark it as `httponly`
2024-07-02 11:53:57 +02:00

121 lines
3.7 KiB
JavaScript

Liberapay.charts = {};
Liberapay.charts.init = function() {
$('[data-charts]').each(function () {
var url = $(this).data('charts');
if (this.tagName == 'BUTTON') {
var $container = $($(this).data('charts-container'));
$(this).click(function() {
$(this).attr('disabled', '').prop('disabled');
Liberapay.charts.load(url, $container);
});
} else {
Liberapay.charts.load(url, $(this));
}
});
}
Liberapay.charts.load = function(url, $container) {
fetch(url).then(function(response) {
response.json().then(function(series) {
$(function() {
Liberapay.charts.make(series, $container);
});
}).catch(Liberapay.error);
}).catch(Liberapay.error);
}
Liberapay.charts.make = function(series, $container) {
if (series.length) {
$('.chart-wrapper').show();
} else {
if ($container.attr('data-msg-empty')) {
$container.append($('<span>').text(' '+$container.attr('data-msg-empty')));
}
return;
}
function parsePoint(o) {
return parseFloat(o ? o.amount || o : 0);
}
// Reverse the series.
// ===================
// For historical reasons the API is descending when we want ascending.
series.reverse();
// Gather charts.
// ==============
// Sniff the first element to determine what data points are available, and
// then search the page for chart containers matching each data point
// variable name.
var charts = Object.keys(series[0]).map(function(name) {
return $('[data-chart=' + name + ']');
}).filter(function(c) { return c.length });
var H = $('.chart').height() - 20;
var W = (1 / series.length).toFixed(10) * $('.chart').width();
var skip = 0;
if (W < 5) {
var keep = Math.floor($('.chart').width() / 5);
skip = series.length - keep;
series = series.slice(-keep);
}
W = W > 10 ? '10px' : (W < 5 ? '5px' : Math.floor(W)+'px');
// Compute maxes and scales.
// =========================
var maxes = charts.map(function(chart) {
return series.reduce(function(previous, current) {
return Math.max(previous, parsePoint(current[chart.data('chart')]));
}, 0);
});
var scales = maxes.map(function(max) {
return Math.ceil(max / 100) * 100; // round to nearest hundred
});
// Draw bars.
// ==========
charts.forEach(function(chart, chart_index) {
chart.css('min-width', (series.length * 5) + 'px');
series.forEach(function(point, index) {
var y = parsePoint(point[chart.data('chart')]);
var bar = $('<div>').addClass('bar');
var shaded = $('<div>').addClass('shaded');
shaded.html('<span class="y-label">'+ y.toFixed() +'</span>');
if (index < series.length / 2) {
bar.addClass('left');
}
bar.append(shaded);
var xTick = $('<span>').addClass('x-tick');
xTick.text(point.date);
bar.append(xTick);
// Display a max flag (only once)
if (y === maxes[chart_index] && !chart.data('max-applied')) {
bar.addClass('flagged');
chart.data('max-applied', true);
}
bar.css('width', W);
var h = y / scales[chart_index] * H;
if (y > 0) h = Math.max(h, 1); // make sure only true 0 is 0 height
shaded.css('height', h);
bar.click(function() {
$(this).toggleClass('flagged');
});
chart.append(bar);
});
});
};