Files
notifications-admin/app/assets/javascripts/updateContent.js
Tom Byers 476ed1593c Make updateContent handle all AJAX the same
The current updateContent JS replaces the in-page
HTML with the HTML from the server the first time
an AJAX request is fired, even if the HTML from
the server has no changes. This is because the
code that compares the two operates on two
different things:

The HTML in the page is the component HTML, with
all the data attributes and the partial HTML
(marked with the 'ajax-block-container' class) as
its first child:

```
<div data-module="update-content" data-url="...">
  <div class="ajax-block-container">
    ...
  </div>
</div>
```

The HTML from the server only contains the
partial:

```
<div class="ajax-block-container">
  ...
</div>
```

The diffing code just sees them as different at
the top level so replaces the page HTML with the
partial from the server. This means all subsequent
diffs are between partial HTML and partial HTML so
only update on actual changes.

These replace the component with the partial, as
part of the component initialising. This means all
code that runs on an AJAX response will only
compare like-for-like so will result in actual
changes (or none at all), not just swapping one
element out for another.

Note: this commit also removes the
aria-live="polite" from the ajax_block component.
It has always been overwritten by the first
response so never announces anything to assistive
technologies. Removing it makes this more clear.
2022-02-09 12:25:15 +00:00

119 lines
3.0 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.
var classesToPersist = {
classNames: [],
$els: [],
remove: function () {
this.classNames.forEach(className => {
var $elsWithClassName = $('.' + className).removeClass(className);
// store elements for that className at the same index
this.$els.push($elsWithClassName);
});
},
replace: function () {
this.classNames.forEach((className, index) => {
this.$els[index].addClass(className);
});
// remove references to elements
this.$els = [];
}
};
var getRenderer = ($contents, key) => response => {
classesToPersist.remove();
morphdom(
$contents.get(0),
$(response[key]).get(0)
);
classesToPersist.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');
// 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
if ($contents.data('classesToPersist') !== undefined) {
$contents.data('classesToPersist')
.split(' ')
.forEach(className => classesToPersist.classNames.push(className));
}
setTimeout(
() => poll(
getRenderer($contents, key),
resource,
getQueue(resource),
form
),
defaultInterval
);
};
};
global.GOVUK.Modules.UpdateContent.calculateBackoff = calculateBackoff;
})(window);