mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-02-06 03:13:42 -05:00
In previous iterations of the classPersister, we found issues with the implementation meant classes it should have added back to elements were also added to other elements. This adds tests for this scenario to ensure it doesn't happen again. Also includes changes to fix a linting error with the JS which complained about a function being defined in a loop while referencing variables in the outer scope.
147 lines
4.3 KiB
JavaScript
147 lines
4.3 KiB
JavaScript
(function(global) {
|
|
"use strict";
|
|
|
|
var queues = {};
|
|
var morphdom = global.GOVUK.vendor.morphdom;
|
|
var defaultInterval = 2000;
|
|
var interval = 0;
|
|
|
|
var calculateBackoff = responseTime => parseInt(Math.max(
|
|
(250 * Math.sqrt(responseTime)) - 1000,
|
|
1000
|
|
));
|
|
|
|
// Methods to ensure the DOM fragment is clean of classes added by JS before diffing
|
|
// and that they are replaced afterwards.
|
|
//
|
|
// Added to allow the use of JS, in main.js, to apply styles which in future could be
|
|
// achieved with the :has pseudo-class. If :has is available in our supported browsers,
|
|
// this can be removed in favour of a CSS-only solution.
|
|
var ClassesPersister = function ($contents) {
|
|
this._$contents = $contents;
|
|
this._classNames = [];
|
|
this._classesTo$ElsMap = {};
|
|
};
|
|
ClassesPersister.prototype.addClassName = function (className) {
|
|
if (this._classNames.indexOf(className) === -1) {
|
|
this._classNames.push(className);
|
|
}
|
|
};
|
|
ClassesPersister.prototype.remove = function () {
|
|
// Store references to any elements with class names to persist
|
|
this._classNames.forEach(className => {
|
|
var $elsWithClassName = $('.' + className, this._$contents).removeClass(className);
|
|
|
|
if ($elsWithClassName.length > 0) {
|
|
this._classesTo$ElsMap[className] = $elsWithClassName;
|
|
}
|
|
});
|
|
};
|
|
ClassesPersister.prototype.replace = function () {
|
|
var replaceClasses = (idx, el) => {
|
|
|
|
// Avoid updating elements that are no longer present.
|
|
// elements removed will still exist in memory but won't be attached to the DOM any more
|
|
if (global.document.body.contains(el)) {
|
|
$(el).addClass(className);
|
|
}
|
|
|
|
};
|
|
var className;
|
|
|
|
for (className in this._classesTo$ElsMap) {
|
|
this._classesTo$ElsMap[className].each(replaceClasses);
|
|
}
|
|
|
|
// remove references to elements
|
|
this._classesTo$ElsMap = {};
|
|
};
|
|
|
|
var getRenderer = ($contents, key, classesPersister) => response => {
|
|
classesPersister.remove();
|
|
morphdom(
|
|
$contents.get(0),
|
|
$(response[key]).get(0)
|
|
);
|
|
classesPersister.replace();
|
|
};
|
|
|
|
var getQueue = resource => (
|
|
queues[resource] = queues[resource] || []
|
|
);
|
|
|
|
var flushQueue = function(queue, response) {
|
|
while(queue.length) queue.shift()(response);
|
|
};
|
|
|
|
var clearQueue = queue => (queue.length = 0);
|
|
|
|
var poll = function(renderer, resource, queue, form) {
|
|
|
|
let startTime = Date.now();
|
|
|
|
if (document.visibilityState !== "hidden" && queue.push(renderer) === 1) $.ajax(
|
|
resource,
|
|
{
|
|
'method': form ? 'post' : 'get',
|
|
'data': form ? $('#' + form).serialize() : {}
|
|
}
|
|
).done(
|
|
response => {
|
|
flushQueue(queue, response);
|
|
if (response.stop === 1) {
|
|
poll = function(){};
|
|
}
|
|
interval = calculateBackoff(Date.now() - startTime);
|
|
}
|
|
).fail(
|
|
() => poll = function(){}
|
|
);
|
|
|
|
setTimeout(
|
|
() => poll.apply(window, arguments), interval
|
|
);
|
|
};
|
|
|
|
global.GOVUK.Modules.UpdateContent = function() {
|
|
|
|
this.start = component => {
|
|
var $component = $(component);
|
|
var $contents = $component.children().eq(0);
|
|
var key = $component.data('key');
|
|
var resource = $component.data('resource');
|
|
var form = $component.data('form');
|
|
var classesPersister = new ClassesPersister($contents);
|
|
|
|
// Replace component with contents.
|
|
// The renderer does this anyway when diffing against the first response
|
|
$component.replaceWith($contents);
|
|
|
|
// Store any classes that should persist through updates
|
|
//
|
|
// Added to allow the use of JS, in main.js, to apply styles which in future could be
|
|
// achieved with the :has pseudo-class. If :has is available in our supported browsers,
|
|
// this can be removed in favour of a CSS-only solution.
|
|
if ($contents.data('classesToPersist') !== undefined) {
|
|
$contents.data('classesToPersist')
|
|
.split(' ')
|
|
.forEach(className => classesPersister.addClassName(className));
|
|
}
|
|
|
|
setTimeout(
|
|
() => poll(
|
|
getRenderer($contents, key, classesPersister),
|
|
resource,
|
|
getQueue(resource),
|
|
form
|
|
),
|
|
defaultInterval
|
|
);
|
|
};
|
|
|
|
};
|
|
|
|
global.GOVUK.Modules.UpdateContent.calculateBackoff = calculateBackoff;
|
|
|
|
})(window);
|