(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);