mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-07-23 09:29:14 -04:00
Removed all govuk css (#2814)
* Removed all govuk css * Updated reference files * Removing govuk js * Fixed casing for modules, removed unused page * Got more reference images * Updated template page * Removed govuk padding util * Updated hint to uswds hint * More govuk cleanup * Commiting backstopjs ref files * Fixed all unit tests that broke due to brittleness around govuk styling * Added new ref images * Final removal of govuk * Officially removed all govuk references * Updated reference file * Updated link to button * UI modernization * Cleanup * removed govuk escaping tests since they are no longer needed * Fix CodeQL security issue in escapeElementName function - Escape backslashes first before other special characters - Prevents potential double-escaping vulnerability - Addresses CodeQL alert about improper string escaping * Found more govuk removal. Fixed unit tests * Add missing pipeline check to pre-commit * updated test * Updated e2e test * More update to e2e test * Fixed another e2e test * Simple PR comments addressed * More updates * Updated backstop ref files * Refactored folder selection for non-admins * Updated redundant line * Updated tests to include correct mocks * Added more ref files * Addressing carlos comments * Addressing Carlo comments, cleanup of window initing * More cleanup and addressing carlo comments * Fixing a11 scan * Fixed a few issues with javascript * Fixed for pr * Fixing e2e tests * Tweaking e2e test * Added more ref files and cleaned up urls.js * Fixed bug with creating new template * Removed brittle test - addressed code ql comment * e2e race condition fix * More e2e test fixes * Updated e2e tests to not wait for text sent * Updated test to not wait for button click response * Made tear down more resilent if staging is down * reverted e2e test to what was working before main merge * Updated backstopRef images * Updated gulp to include job-polling differently
This commit is contained in:
@@ -399,19 +399,19 @@
|
||||
dropdown.addEventListener('change', handleDropdownChange);
|
||||
});
|
||||
|
||||
// Resize chart on window resize
|
||||
window.addEventListener('resize', function() {
|
||||
if (labels.length > 0 && deliveredData.length > 0 && failedData.length > 0 && pendingData.length > 0) {
|
||||
createChart('#weeklyChart', labels, deliveredData, failedData, pendingData);
|
||||
createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData, pendingData);
|
||||
}
|
||||
});
|
||||
// Resize chart on window resize
|
||||
window.addEventListener('resize', function() {
|
||||
if (labels.length > 0 && deliveredData.length > 0 && failedData.length > 0 && pendingData.length > 0) {
|
||||
createChart('#weeklyChart', labels, deliveredData, failedData, pendingData);
|
||||
createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData, pendingData);
|
||||
}
|
||||
});
|
||||
|
||||
// Export functions for testing
|
||||
window.createChart = createChart;
|
||||
window.createTable = createTable;
|
||||
window.handleDropdownChange = handleDropdownChange;
|
||||
window.fetchData = fetchData;
|
||||
}
|
||||
// Export functions for testing
|
||||
window.createChart = createChart;
|
||||
window.createTable = createTable;
|
||||
window.handleDropdownChange = handleDropdownChange;
|
||||
window.fetchData = fetchData;
|
||||
}
|
||||
|
||||
})(window);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(function (global) {
|
||||
(function (window) {
|
||||
"use strict";
|
||||
|
||||
const GOVUK = global.GOVUK;
|
||||
const USWDS = window.USWDS || {};
|
||||
|
||||
function Summary (module) {
|
||||
this.module = module;
|
||||
@@ -222,6 +222,8 @@
|
||||
this.summary.bindEvents(this);
|
||||
};
|
||||
|
||||
GOVUK.Modules.CollapsibleCheckboxes = CollapsibleCheckboxes;
|
||||
NotifyModules['collapsible-checkboxes'] = function() {
|
||||
return new CollapsibleCheckboxes();
|
||||
};
|
||||
|
||||
}(window));
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
(function(Modules) {
|
||||
(function(window) {
|
||||
"use strict";
|
||||
|
||||
if (!document.queryCommandSupported('copy')) return;
|
||||
|
||||
Modules.CopyToClipboard = function() {
|
||||
window.NotifyModules['copy-to-clipboard'] = function() {
|
||||
|
||||
const states = {
|
||||
'valueVisible': (options) => `
|
||||
@@ -90,11 +90,11 @@
|
||||
.find('.usa-button').focus()
|
||||
);
|
||||
|
||||
if ('stickAtBottomWhenScrolling' in GOVUK) {
|
||||
GOVUK.stickAtBottomWhenScrolling.recalculate();
|
||||
if ('stickAtBottomWhenScrolling' in window.NotifyModules) {
|
||||
window.NotifyModules.stickAtBottomWhenScrolling.recalculate();
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
})(window.GOVUK.Modules);
|
||||
})(window);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(function(Modules) {
|
||||
(function(window) {
|
||||
"use strict";
|
||||
|
||||
if (
|
||||
@@ -7,10 +7,11 @@
|
||||
|
||||
const tagPattern = /\(\(([^\)\((\?)]+)(\?\?)?([^\)\(]*)\)\)/g;
|
||||
|
||||
Modules.EnhancedTextbox = function() {
|
||||
window.NotifyModules['enhanced-textbox'] = function() {
|
||||
|
||||
this.start = function(textarea) {
|
||||
this.start = function(element) {
|
||||
|
||||
let textarea = $(element);
|
||||
let visibleTextbox;
|
||||
|
||||
this.highlightPlaceholders = (
|
||||
@@ -18,7 +19,7 @@
|
||||
!!textarea.data('highlightPlaceholders')
|
||||
);
|
||||
|
||||
this.$textbox = $(textarea)
|
||||
this.$textbox = textarea
|
||||
.wrap(`
|
||||
<div class='textbox-highlight-wrapper' />
|
||||
`)
|
||||
@@ -58,8 +59,8 @@
|
||||
)
|
||||
);
|
||||
|
||||
if ('stickAtBottomWhenScrolling' in GOVUK) {
|
||||
GOVUK.stickAtBottomWhenScrolling.recalculate();
|
||||
if ('stickAtBottomWhenScrolling' in window.NotifyModules) {
|
||||
window.NotifyModules.stickAtBottomWhenScrolling.recalculate();
|
||||
}
|
||||
|
||||
};
|
||||
@@ -84,4 +85,4 @@
|
||||
|
||||
};
|
||||
|
||||
})(window.GOVUK.Modules);
|
||||
})(window);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
This may behave in unexpected ways if you have more than one element with the `banner-dangerous` class on your page.
|
||||
*/
|
||||
window.GOVUK.ErrorBanner = {
|
||||
window.NotifyModules.ErrorBanner = {
|
||||
hideBanner: () => $('.banner-dangerous').addClass('display-none'),
|
||||
showBanner: () => $('.banner-dangerous')
|
||||
.removeClass('display-none')
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
(function(window) {
|
||||
"use strict";
|
||||
|
||||
window.GOVUK.Modules.TrackError = function() {
|
||||
window.NotifyModules['track-error'] = function() {
|
||||
|
||||
this.start = function(component) {
|
||||
this.start = function(element) {
|
||||
var component = $(element);
|
||||
|
||||
if (!('analytics' in window.GOVUK)) return;
|
||||
|
||||
window.GOVUK.analytics.trackEvent(
|
||||
'Error',
|
||||
$(component).data('error-type'),
|
||||
{
|
||||
'label': $(component).data('error-label')
|
||||
}
|
||||
);
|
||||
// Track error to analytics if available
|
||||
if (window.NotifyModules && window.NotifyModules.analytics && window.NotifyModules.analytics.trackEvent) {
|
||||
window.NotifyModules.analytics.trackEvent(
|
||||
'Error',
|
||||
component.data('error-type'),
|
||||
{
|
||||
'label': component.data('error-label')
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -24,16 +24,16 @@ function initUploadStatusAnnouncer() {
|
||||
});
|
||||
}
|
||||
|
||||
(function(Modules) {
|
||||
(function(window) {
|
||||
"use strict";
|
||||
|
||||
Modules.FileUpload = function() {
|
||||
window.NotifyModules['file-upload'] = function() {
|
||||
this.submit = () => this.$form.trigger('submit');
|
||||
|
||||
this.showCancelButton = () => {
|
||||
$('.file-upload-button', this.$form).replaceWith(`
|
||||
<button class='usa-button uploading-button' aria-disabled="true" tabindex="0">
|
||||
Uploading<span class="dot-anim" aria-hidden="true"></span>
|
||||
Uploading<span class="loading-spinner" role="status" aria-label="Uploading"></span>
|
||||
</button>
|
||||
`);
|
||||
};
|
||||
@@ -55,7 +55,7 @@ function initUploadStatusAnnouncer() {
|
||||
});
|
||||
};
|
||||
};
|
||||
})(window.GOVUK.Modules);
|
||||
})(window);
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(function(Modules) {
|
||||
(function(window) {
|
||||
"use strict";
|
||||
|
||||
Modules.FullscreenTable = function() {
|
||||
window.NotifyModules['fullscreen-table'] = function() {
|
||||
|
||||
this.start = function(component) {
|
||||
|
||||
@@ -24,10 +24,10 @@
|
||||
.on('scroll', this.maintainHeight)
|
||||
|
||||
if (
|
||||
window.GOVUK.stickAtBottomWhenScrolling &&
|
||||
window.GOVUK.stickAtBottomWhenScrolling.recalculate
|
||||
window.NotifyModules.stickAtBottomWhenScrolling &&
|
||||
window.NotifyModules.stickAtBottomWhenScrolling.recalculate
|
||||
) {
|
||||
window.GOVUK.stickAtBottomWhenScrolling.recalculate();
|
||||
window.NotifyModules.stickAtBottomWhenScrolling.recalculate();
|
||||
}
|
||||
|
||||
this.maintainWidth();
|
||||
@@ -120,4 +120,4 @@
|
||||
|
||||
};
|
||||
|
||||
})(window.GOVUK.Modules);
|
||||
})(window);
|
||||
|
||||
7
app/assets/javascripts/jquery-expose.js
vendored
Normal file
7
app/assets/javascripts/jquery-expose.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// Expose jQuery and other libraries to window for legacy code
|
||||
if (typeof jQuery !== 'undefined') {
|
||||
window.jQuery = window.$ = jQuery;
|
||||
}
|
||||
if (typeof getCaretCoordinates !== 'undefined') {
|
||||
window.getCaretCoordinates = getCaretCoordinates;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
(function (Modules) {
|
||||
(function (window) {
|
||||
'use strict';
|
||||
|
||||
var Modules = window.NotifyModules;
|
||||
var lists = [],
|
||||
listEntry,
|
||||
ListEntry;
|
||||
@@ -216,10 +217,10 @@
|
||||
}
|
||||
};
|
||||
|
||||
Modules.ListEntry = function () {
|
||||
Modules['list-entry'] = function () {
|
||||
|
||||
this.start = component => lists.push(new ListEntry($(component)));
|
||||
|
||||
};
|
||||
|
||||
})(window.GOVUK.Modules);
|
||||
})(window);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
(function(Modules) {
|
||||
(function(window) {
|
||||
"use strict";
|
||||
|
||||
var Modules = window.NotifyModules;
|
||||
let state;
|
||||
let normalize = (string) => string.toLowerCase().replace(/ /g,'');
|
||||
let resultsSummary = (num) => {
|
||||
@@ -61,14 +62,14 @@
|
||||
|
||||
// make sticky JS recalculate its cache of the element's position
|
||||
// because live search can change the height document
|
||||
if ('stickAtBottomWhenScrolling' in GOVUK) {
|
||||
GOVUK.stickAtBottomWhenScrolling.recalculate();
|
||||
if (window.NotifyModules && 'stickAtBottomWhenScrolling' in window.NotifyModules) {
|
||||
window.NotifyModules.stickAtBottomWhenScrolling.recalculate();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
Modules.LiveSearch = function() {
|
||||
Modules['live-search'] = function() {
|
||||
|
||||
this.start = function(component) {
|
||||
|
||||
@@ -95,4 +96,4 @@
|
||||
|
||||
};
|
||||
|
||||
})(window.GOVUK.Modules);
|
||||
})(window);
|
||||
|
||||
@@ -1,22 +1,48 @@
|
||||
window.GOVUK.Frontend.initAll();
|
||||
// Initialize USWDS components
|
||||
if (window.uswds) {
|
||||
window.uswds.init();
|
||||
}
|
||||
|
||||
var showHideContent = new GOVUK.ShowHideContent();
|
||||
showHideContent.init();
|
||||
// Initialize custom modules
|
||||
window.NotifyModules.start = function() {
|
||||
var modules = document.querySelectorAll('[data-module]');
|
||||
modules.forEach(function(element) {
|
||||
var moduleName = element.getAttribute('data-module');
|
||||
var moduleStarted = element.getAttribute('data-module-started');
|
||||
|
||||
$(() => GOVUK.modules.start());
|
||||
|
||||
$(() => $('.error-message, .usa-error-message').eq(0).parent('label').next('input').trigger('focus'));
|
||||
if (!moduleStarted && window.NotifyModules[moduleName]) {
|
||||
var module = new window.NotifyModules[moduleName]();
|
||||
if (module.start) {
|
||||
module.start(element);
|
||||
}
|
||||
element.setAttribute('data-module-started', 'true');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
window.NotifyModules.start();
|
||||
|
||||
const errorElement = document.querySelector('.error-message, .usa-error-message');
|
||||
if (errorElement) {
|
||||
const label = errorElement.closest('label');
|
||||
if (label && label.nextElementSibling && label.nextElementSibling.tagName === 'INPUT') {
|
||||
label.nextElementSibling.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
// Applies our expanded focus style to the siblings of links when that link is wrapped in a heading.
|
||||
//
|
||||
// This will be possible in CSS in the future, using the :has pseudo-class. When :has is available
|
||||
// in the browsers we support, this code can be replaced with a CSS-only solution.
|
||||
$('.js-mark-focus-on-parent').on('focus blur', '*', e => {
|
||||
$target = $(e.target);
|
||||
if (e.type === 'focusin') {
|
||||
$target.parent().addClass('js-child-has-focus');
|
||||
} else {
|
||||
$target.parent().removeClass('js-child-has-focus');
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const markFocusElements = document.querySelectorAll('.js-mark-focus-on-parent');
|
||||
markFocusElements.forEach(element => {
|
||||
element.addEventListener('focusin', function(e) {
|
||||
e.target.parentElement.classList.add('js-child-has-focus');
|
||||
});
|
||||
element.addEventListener('focusout', function(e) {
|
||||
e.target.parentElement.classList.remove('js-child-has-focus');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
// JS Module used to combine all the JS modules used in the application into a single entry point,
|
||||
// a bit like `app/__init__` in the Flask app.
|
||||
//
|
||||
// When processed by a bundler, this is turned into a Immediately Invoked Function Expression (IIFE)
|
||||
// The IIFE format allows it to run in browsers that don't support JS Modules.
|
||||
//
|
||||
// Exported items will be added to the window.GOVUK namespace.
|
||||
// For example, `export { Frontend }` will assign `Frontend` to `window.Frontend`
|
||||
|
||||
// GOVUK Frontend modules
|
||||
import Header from 'govuk-frontend/components/header/header';
|
||||
import Details from 'govuk-frontend/components/details/details';
|
||||
import Button from 'govuk-frontend/components/button/button';
|
||||
import Radios from 'govuk-frontend/components/radios/radios';
|
||||
|
||||
// Modules from 3rd party vendors
|
||||
|
||||
/**
|
||||
* TODO: Ideally this would be a NodeList.prototype.forEach polyfill
|
||||
* This seems to fail in IE8, requires more investigation.
|
||||
* See: https://github.com/imagitama/nodelist-foreach-polyfill
|
||||
*/
|
||||
function nodeListForEach(nodes, callback) {
|
||||
if (window.NodeList.prototype.forEach) {
|
||||
return nodes.forEach(callback)
|
||||
}
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
callback.call(window, nodes[i], i, nodes);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy of the initAll function from https://github.com/alphagov/govuk-frontend/blob/v2.13.0/src/all.js
|
||||
// except it only includes, and initialises, the components used by this application.
|
||||
function initAll(options) {
|
||||
// Set the options to an empty object by default if no options are passed.
|
||||
options = typeof options !== 'undefined' ? options : {}
|
||||
|
||||
// Allow the user to initialise US Frontend in only certain sections of the page
|
||||
// Defaults to the entire document if nothing is set.
|
||||
var scope = typeof options.scope !== 'undefined' ? options.scope : document
|
||||
|
||||
// Find all buttons with [role=button] on the scope to enhance.
|
||||
new Button(scope).init()
|
||||
|
||||
// Find all global details elements to enhance.
|
||||
var $details = scope.querySelectorAll('details')
|
||||
nodeListForEach($details, function ($detail) {
|
||||
new Details($detail).init()
|
||||
})
|
||||
|
||||
// Find first header module to enhance.
|
||||
var $toggleButton = scope.querySelector('[data-module="header"]')
|
||||
new Header($toggleButton).init()
|
||||
|
||||
var $radios = scope.querySelectorAll('[data-module="radios"]')
|
||||
nodeListForEach($radios, function ($radio) {
|
||||
new Radios($radio).init()
|
||||
})
|
||||
}
|
||||
|
||||
// Create separate namespace for GOVUK Frontend.
|
||||
var Frontend = {
|
||||
"Header": Header,
|
||||
"Details": Details,
|
||||
"Button": Button,
|
||||
"initAll": initAll
|
||||
}
|
||||
|
||||
// The exported object will be assigned to window.GOVUK in our production code
|
||||
// (bundled into an IIFE by RollupJS)
|
||||
export {
|
||||
Frontend
|
||||
}
|
||||
4
app/assets/javascripts/modules/init.js
Normal file
4
app/assets/javascripts/modules/init.js
Normal file
@@ -0,0 +1,4 @@
|
||||
// Initialize the NotifyModules namespace
|
||||
// This file MUST be loaded first in the build to ensure window.NotifyModules exists
|
||||
// before any other modules try to use it
|
||||
window.NotifyModules = window.NotifyModules || {};
|
||||
154
app/assets/javascripts/modules/show-hide-content.js
Normal file
154
app/assets/javascripts/modules/show-hide-content.js
Normal file
@@ -0,0 +1,154 @@
|
||||
(function (window) {
|
||||
'use strict';
|
||||
|
||||
var $ = window.jQuery;
|
||||
|
||||
function ShowHideContent () {
|
||||
var self = this;
|
||||
|
||||
var selectors = {
|
||||
namespace: 'ShowHideContent',
|
||||
radio: '[data-target] > input[type="radio"]',
|
||||
checkbox: '[data-target] > input[type="checkbox"]'
|
||||
};
|
||||
|
||||
function initToggledContent () {
|
||||
var $control = $(this);
|
||||
var $content = getToggledContent($control);
|
||||
|
||||
if ($content.length) {
|
||||
$control.attr('aria-controls', $content.attr('id'));
|
||||
$control.attr('aria-expanded', 'false');
|
||||
$content.attr('aria-hidden', 'true');
|
||||
}
|
||||
}
|
||||
|
||||
function getToggledContent ($control) {
|
||||
try {
|
||||
var id = $control.attr('aria-controls');
|
||||
|
||||
if (!id) {
|
||||
id = $control.closest('[data-target]').data('target');
|
||||
}
|
||||
|
||||
if (!id || !/^[\w-]+$/.test(id)) {
|
||||
console.warn('Invalid element ID:', id);
|
||||
return $();
|
||||
}
|
||||
|
||||
return $('#' + id);
|
||||
} catch (error) {
|
||||
console.error('Error getting toggled content:', error);
|
||||
return $();
|
||||
}
|
||||
}
|
||||
|
||||
function showToggledContent ($control, $content) {
|
||||
if ($content.hasClass('display-none')) {
|
||||
$content.removeClass('display-none');
|
||||
$content.attr('aria-hidden', 'false');
|
||||
|
||||
if ($control.attr('aria-controls')) {
|
||||
$control.attr('aria-expanded', 'true');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hideToggledContent ($control, $content) {
|
||||
$content.addClass('display-none');
|
||||
$content.attr('aria-hidden', 'true');
|
||||
|
||||
if ($control.attr('aria-controls')) {
|
||||
$control.attr('aria-expanded', 'false');
|
||||
}
|
||||
}
|
||||
|
||||
function handleRadioContent ($control, $content) {
|
||||
var selector = selectors.radio + '[name=' + escapeElementName($control.attr('name')) + ']';
|
||||
var $radios = $(selector);
|
||||
|
||||
$radios.each(function () {
|
||||
hideToggledContent($(this), getToggledContent($(this)));
|
||||
});
|
||||
|
||||
showToggledContent($control, $content);
|
||||
}
|
||||
|
||||
function handleCheckboxContent ($control, $content) {
|
||||
if ($control.is(':checked')) {
|
||||
showToggledContent($control, $content);
|
||||
} else {
|
||||
hideToggledContent($control, $content);
|
||||
}
|
||||
}
|
||||
|
||||
function escapeElementName (str) {
|
||||
// First escape backslashes, then escape other special characters
|
||||
// This prevents double-escaping issues identified by CodeQL
|
||||
return str
|
||||
? str.replace(/\\/g, '\\\\').replace(/([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g, '\\$1')
|
||||
: str;
|
||||
}
|
||||
|
||||
function setupHandlers () {
|
||||
var $controls = $(selectors.radio + ', ' + selectors.checkbox);
|
||||
|
||||
$(selectors.radio).on('click.' + selectors.namespace, function () {
|
||||
handleRadioContent($(this), getToggledContent($(this)));
|
||||
});
|
||||
|
||||
$(selectors.checkbox).on('click.' + selectors.namespace, function () {
|
||||
handleCheckboxContent($(this), getToggledContent($(this)));
|
||||
});
|
||||
if ($controls.filter(':checked').length) {
|
||||
$controls.filter(':checked').each(function () {
|
||||
var $control = $(this);
|
||||
var $content = getToggledContent($control);
|
||||
|
||||
if ($control.is('[type=radio]')) {
|
||||
handleRadioContent($control, $content);
|
||||
} else {
|
||||
handleCheckboxContent($control, $content);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.destroy = function () {
|
||||
var $controls = $(selectors.radio + ', ' + selectors.checkbox);
|
||||
|
||||
$controls.each(function () {
|
||||
var $control = $(this);
|
||||
var $content = getToggledContent($control);
|
||||
|
||||
$control.removeAttr('aria-controls aria-expanded');
|
||||
$content.removeAttr('aria-hidden');
|
||||
});
|
||||
|
||||
$(selectors.radio).off('.' + selectors.namespace);
|
||||
$(selectors.checkbox).off('.' + selectors.namespace);
|
||||
};
|
||||
|
||||
self.init = function () {
|
||||
try {
|
||||
$(selectors.radio + ', ' + selectors.checkbox).each(initToggledContent);
|
||||
setupHandlers();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error initializing show-hide content:', error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ShowHideContent.prototype.start = function (element) {
|
||||
try {
|
||||
var instance = new ShowHideContent();
|
||||
instance.init(element);
|
||||
} catch (error) {
|
||||
console.error('Failed to start show-hide content module:', error);
|
||||
}
|
||||
};
|
||||
|
||||
window.NotifyModules.ShowHideContent = ShowHideContent;
|
||||
|
||||
})(window);
|
||||
60
app/assets/javascripts/modules/uswds-modules.js
Normal file
60
app/assets/javascripts/modules/uswds-modules.js
Normal file
@@ -0,0 +1,60 @@
|
||||
(function (window) {
|
||||
'use strict';
|
||||
|
||||
var $ = window.jQuery;
|
||||
|
||||
window.NotifyModules.moduleSystem = {
|
||||
find: function (container) {
|
||||
container = container || $('body');
|
||||
|
||||
var modules;
|
||||
var moduleSelector = '[data-module]';
|
||||
|
||||
modules = container.find(moduleSelector);
|
||||
|
||||
if (container.is(moduleSelector)) {
|
||||
modules = modules.add(container);
|
||||
}
|
||||
|
||||
return modules;
|
||||
},
|
||||
|
||||
start: function (container) {
|
||||
var modules = this.find(container);
|
||||
|
||||
for (var i = 0, l = modules.length; i < l; i++) {
|
||||
try {
|
||||
var module;
|
||||
var element = $(modules[i]);
|
||||
var type = this.camelCaseAndCapitalise(element.data('module'));
|
||||
var started = element.data('module-started');
|
||||
|
||||
if (typeof window.NotifyModules[type] === 'function' && !started) {
|
||||
module = new window.NotifyModules[type]();
|
||||
if (module.start) {
|
||||
module.start(element);
|
||||
}
|
||||
element.data('module-started', true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize module:', type || 'unknown', error);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
camelCaseAndCapitalise: function (string) {
|
||||
return this.capitaliseFirstLetter(this.camelCase(string));
|
||||
},
|
||||
|
||||
camelCase: function (string) {
|
||||
return string.replace(/-([a-z])/g, function (g) {
|
||||
return g.charAt(1).toUpperCase();
|
||||
});
|
||||
},
|
||||
|
||||
capitaliseFirstLetter: function (string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
};
|
||||
|
||||
})(window);
|
||||
@@ -11,34 +11,39 @@
|
||||
|
||||
$submitButton.data('clicked', 'true');
|
||||
|
||||
// Add dot animation for Send/Schedule/Cancel buttons
|
||||
// Add loading spinner for Send/Schedule/Cancel buttons
|
||||
const buttonName = $submitButton.attr('name')?.toLowerCase();
|
||||
if (["send", "schedule", "cancel"].includes(buttonName)) {
|
||||
$submitButton.prop('disabled', true);
|
||||
// Use setTimeout with minimal delay to allow form submission to proceed first
|
||||
setTimeout(() => {
|
||||
$submitButton.prop('disabled', true);
|
||||
|
||||
// Inject dot animation span if not already present
|
||||
if ($submitButton.find('.dot-anim').length === 0) {
|
||||
$submitButton.append('<span class="dot-anim" aria-hidden="true"></span>');
|
||||
}
|
||||
// Add loading spinner and aria-busy attribute for accessibility
|
||||
if ($submitButton.find('.loading-spinner').length === 0) {
|
||||
$submitButton.attr('aria-busy', 'true');
|
||||
$submitButton.append('<span class="loading-spinner" role="status" aria-label="Sending"></span>');
|
||||
}
|
||||
|
||||
// Disable Cancel button too
|
||||
const $cancelButton = $('button[name]').filter(function () {
|
||||
return $(this).attr('name')?.toLowerCase() === 'cancel';
|
||||
});
|
||||
$cancelButton.prop('disabled', true);
|
||||
// Disable Cancel button too
|
||||
const $cancelButton = $('button[name]').filter(function () {
|
||||
return $(this).attr('name')?.toLowerCase() === 'cancel';
|
||||
});
|
||||
$cancelButton.prop('disabled', true);
|
||||
}, 50); // Small delay to ensure form submits first
|
||||
|
||||
setTimeout(() => {
|
||||
renableSubmitButton($submitButton);
|
||||
renableSubmitButton($submitButton)();
|
||||
}, 10000); // fallback safety
|
||||
} else {
|
||||
setTimeout(renableSubmitButton($submitButton), 1500);
|
||||
setTimeout(() => renableSubmitButton($submitButton)(), 1500);
|
||||
}
|
||||
};
|
||||
|
||||
const renableSubmitButton = ($submitButton) => () => {
|
||||
$submitButton.data('clicked', '');
|
||||
$submitButton.prop('disabled', false);
|
||||
$submitButton.find('.dot-anim').remove(); // clean up if needed
|
||||
$submitButton.attr('aria-busy', 'false');
|
||||
$submitButton.find('.loading-spinner').remove(); // clean up spinner
|
||||
};
|
||||
|
||||
$('form').on('submit', disableSubmitButtons);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
(function(global) {
|
||||
(function(window) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var Modules = global.GOVUK.Modules;
|
||||
var Modules = window.NotifyModules;
|
||||
|
||||
// Template functions for rendering component states
|
||||
let renderStates = {
|
||||
@@ -79,7 +79,7 @@
|
||||
}
|
||||
};
|
||||
|
||||
Modules.RadioSelect = function() {
|
||||
Modules['radio-select'] = function() {
|
||||
|
||||
this.start = function(component) {
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
(function(global) {
|
||||
(function(window) {
|
||||
|
||||
"use strict";
|
||||
|
||||
global.GOVUK.Modules.RadioSlider = function() {
|
||||
window.NotifyModules['radio-slider'] = function() {
|
||||
|
||||
this.start = function(component) {
|
||||
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
document.querySelectorAll('.usa-button-group a, .usa-pagination a').forEach(function(button) {
|
||||
button.addEventListener('click', function() {
|
||||
sessionStorage.setItem('scrollPosition', window.pageYOffset);
|
||||
button.addEventListener('click', function(e) {
|
||||
// Add scroll position to URL as fragment
|
||||
var scrollPos = window.pageYOffset;
|
||||
if (scrollPos > 0) {
|
||||
var url = new URL(this.href, window.location.href);
|
||||
url.hash = 'scroll=' + scrollPos;
|
||||
this.href = url.toString();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var scrollPosition = sessionStorage.getItem('scrollPosition');
|
||||
if (scrollPosition !== null) {
|
||||
window.scrollTo(0, parseInt(scrollPosition));
|
||||
sessionStorage.removeItem('scrollPosition');
|
||||
var hash = window.location.hash;
|
||||
if (hash && hash.startsWith('#scroll=')) {
|
||||
var scrollPosition = parseInt(hash.substring(8), 10);
|
||||
if (!isNaN(scrollPosition) && scrollPosition > 0) {
|
||||
window.scrollTo(0, scrollPosition);
|
||||
// Clean up the URL
|
||||
if (window.history && window.history.replaceState) {
|
||||
var cleanUrl = window.location.pathname + window.location.search;
|
||||
window.history.replaceState(null, '', cleanUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
;(function (global) {
|
||||
;(function (window) {
|
||||
'use strict';
|
||||
|
||||
var $ = global.jQuery;
|
||||
var GOVUK = global.GOVUK || {};
|
||||
var $ = window.jQuery;
|
||||
var NotifyModules = window.NotifyModules || {};
|
||||
var _mode = 'default';
|
||||
|
||||
// Constructor to make objects representing the area sticky elements can scroll in
|
||||
@@ -630,7 +630,7 @@
|
||||
};
|
||||
|
||||
if ((!el.hasLoaded()) && ($img.length > 0)) {
|
||||
var image = new global.Image();
|
||||
var image = new window.Image();
|
||||
image.onload = function () {
|
||||
onload();
|
||||
};
|
||||
@@ -677,7 +677,7 @@
|
||||
}
|
||||
|
||||
self.setElementDimensions(elObj, onDimensionsSet);
|
||||
};
|
||||
};
|
||||
Sticky.prototype.remove = function (el) {
|
||||
if ($.inArray(el, this._els) !== -1) {
|
||||
|
||||
@@ -734,25 +734,25 @@
|
||||
// window position
|
||||
if (this._scrollTimeout === false) {
|
||||
$(global).scroll(this._scrollEvent);
|
||||
this._scrollTimeout = global.setInterval(this.checkScroll.bind(this), 50);
|
||||
this._scrollTimeout = window.setInterval(this.checkScroll.bind(this), 50);
|
||||
}
|
||||
|
||||
// Recalculate all dimensions when the window resizes
|
||||
if (this._resizeTimeout === false) {
|
||||
$(global).resize(this._resizeEvent);
|
||||
this._resizeTimeout = global.setInterval(this.checkResize.bind(this), 50);
|
||||
this._resizeTimeout = window.setInterval(this.checkResize.bind(this), 50);
|
||||
}
|
||||
};
|
||||
Sticky.prototype.clearEvents = function () {
|
||||
if (this._scrollTimeout !== false) {
|
||||
$(global).off('scroll', this._scrollEvent);
|
||||
global.clearInterval(this._scrollTimeout);
|
||||
window.clearInterval(this._scrollTimeout);
|
||||
this._scrollTimeout = false;
|
||||
}
|
||||
|
||||
if (this._resizeTimeout !== false) {
|
||||
$(global).off('resize', this._resizeEvent);
|
||||
global.clearInterval(this._resizeTimeout);
|
||||
window.clearInterval(this._resizeTimeout);
|
||||
this._resizeTimeout = false;
|
||||
}
|
||||
|
||||
@@ -989,7 +989,7 @@
|
||||
el.unstop();
|
||||
};
|
||||
|
||||
GOVUK.stickAtTopWhenScrolling = stickAtTop;
|
||||
GOVUK.stickAtBottomWhenScrolling = stickAtBottom;
|
||||
global.GOVUK = GOVUK;
|
||||
NotifyModules.stickAtTopWhenScrolling = stickAtTop;
|
||||
NotifyModules.stickAtBottomWhenScrolling = stickAtBottom;
|
||||
window.NotifyModules = NotifyModules;
|
||||
})(window);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(function(Modules) {
|
||||
(function(window) {
|
||||
"use strict";
|
||||
|
||||
Modules.TemplateFolderForm = function() {
|
||||
window.NotifyModules['template-folder-form'] = function() {
|
||||
|
||||
this.start = function(templateFolderForm) {
|
||||
this.$form = $(templateFolderForm);
|
||||
@@ -82,14 +82,15 @@
|
||||
this.render();
|
||||
}
|
||||
|
||||
this.$form.on('click', 'button.usa-button--event', (event) => this.actionButtonClicked(event));
|
||||
this.$form.on('click', 'button.usa-button', (event) => this.actionButtonClicked(event));
|
||||
this.$form.on('change', 'input[type=checkbox]', () => this.templateFolderCheckboxChanged());
|
||||
this.$form.on('change', 'input[name="add_template_by_template_type"]', () => this.templateTypeChanged());
|
||||
};
|
||||
|
||||
this.addDescriptionsToStates = function () {
|
||||
let id, description;
|
||||
|
||||
$.each(this.states.filter(state => 'description' in state), (idx, state) => {
|
||||
$.each(this.states.filter(state => 'description' in state), (_, state) => {
|
||||
id = `${state.key}__description`;
|
||||
description = `<p class="usa-sr-only" id="${id}">${state.description}</p>`;
|
||||
state.$el
|
||||
@@ -191,17 +192,30 @@
|
||||
this.$singleChannelService = (document.querySelector('div[id=add_new_template_form]')).getAttribute("data-service");
|
||||
|
||||
this.actionButtonClicked = function(event) {
|
||||
event.preventDefault();
|
||||
this.currentState = $(event.currentTarget).val();
|
||||
|
||||
if (event.currentTarget.value === 'add-new-template' && this.$singleNotificationChannel) {
|
||||
event.preventDefault();
|
||||
window.location = "/services/" + this.$singleChannelService + "/templates/add-" + this.$singleNotificationChannel;
|
||||
} else {
|
||||
if (this.currentState === 'add-new-template') {
|
||||
} else if (this.currentState === 'add-new-template') {
|
||||
// Check if a template type is selected
|
||||
const selectedTemplateType = this.$form.find('input[name="add_template_by_template_type"]:checked').val();
|
||||
|
||||
if (selectedTemplateType) {
|
||||
// Template type is selected, let the form submit normally
|
||||
return true;
|
||||
} else {
|
||||
// No template type selected, show the selection UI
|
||||
event.preventDefault();
|
||||
this.$form.find('input[type=checkbox]').prop('checked', false);
|
||||
this.selectionStatus.update({ total: 0, templates: 0, folders: 0 });
|
||||
}
|
||||
|
||||
if (this.stateChanged()) {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
event.preventDefault();
|
||||
if (this.stateChanged()) {
|
||||
this.render();
|
||||
}
|
||||
@@ -260,14 +274,29 @@
|
||||
|
||||
};
|
||||
|
||||
this.templateTypeChanged = function() {
|
||||
this.updateContinueButtonState();
|
||||
};
|
||||
|
||||
this.updateContinueButtonState = function() {
|
||||
const selectedTemplateType = this.$form.find('input[name="add_template_by_template_type"]:checked').val();
|
||||
const continueButton = this.$form.find('#add_new_template_form button[value="add-new-template"]');
|
||||
|
||||
if (selectedTemplateType) {
|
||||
continueButton.prop('disabled', false);
|
||||
} else {
|
||||
continueButton.prop('disabled', true);
|
||||
}
|
||||
};
|
||||
|
||||
this.hasCheckboxes = function() {
|
||||
return !!this.$form.find('input:checkbox').length;
|
||||
};
|
||||
|
||||
this.countSelectedCheckboxes = function() {
|
||||
const allSelected = this.$form.find('input:checkbox:checked');
|
||||
const templates = allSelected.filter((idx, el) => $(el).siblings('.template-list-template').length > 0).length;
|
||||
const folders = allSelected.filter((idx, el) => $(el).siblings('.template-list-folder').length > 0).length;
|
||||
const templates = allSelected.filter((_, el) => $(el).siblings('.template-list-template').length > 0).length;
|
||||
const folders = allSelected.filter((_, el) => $(el).siblings('.template-list-folder').length > 0).length;
|
||||
const results = {
|
||||
'templates': templates,
|
||||
'folders': folders,
|
||||
@@ -277,7 +306,6 @@
|
||||
};
|
||||
|
||||
this.render = function() {
|
||||
let mode = 'default';
|
||||
let currentStateObj = this.states.filter(state => { return (state.key === this.currentState); })[0];
|
||||
let scrollTop;
|
||||
|
||||
@@ -286,11 +314,6 @@
|
||||
state => (state.key === this.currentState ? this.$liveRegionCounter.before(state.$el) : state.$el.detach())
|
||||
);
|
||||
|
||||
// use dialog mode for states which contain more than one form control
|
||||
if (['move-to-existing-folder', 'add-new-template'].indexOf(this.currentState) !== -1) {
|
||||
mode = 'dialog';
|
||||
}
|
||||
|
||||
if (this.currentState === 'add-new-template') {
|
||||
this.$form.find('.template-list-item').addClass('js-hidden');
|
||||
$('.live-search').addClass('js-hidden');
|
||||
@@ -302,6 +325,9 @@
|
||||
$('#page-title').text('New Template');
|
||||
$('#page-description').text('Every message starts with a template. Choose to start with a blank template or copy an existing template.');
|
||||
document.title = 'New Templates';
|
||||
|
||||
// Disable Continue button initially and update based on selection
|
||||
this.updateContinueButtonState();
|
||||
} else {
|
||||
this.$form.find('.template-list-item').removeClass('js-hidden');
|
||||
$('.live-search').removeClass('js-hidden');
|
||||
@@ -324,10 +350,12 @@
|
||||
<div id="nothing_selected">
|
||||
<div class="js-stick-at-bottom-when-scrolling">
|
||||
<div class="usa-button-group">
|
||||
<button class="usa-button usa-button--event" value="add-new-template" aria-expanded="false">
|
||||
<button class="usa-button" value="add-new-template" aria-expanded="false" role="button">
|
||||
New template
|
||||
</button>
|
||||
<button class="usa-button usa-button--event" value="add-new-folder" aria-expanded="false">New folder</button>
|
||||
<button class="usa-button usa-button--outline" value="add-new-folder" aria-expanded="false" role="button">
|
||||
New folder
|
||||
</button>
|
||||
</div>
|
||||
<div class="template-list-selected-counter">
|
||||
<span class="template-list-selected-counter__count" aria-hidden="true">
|
||||
@@ -342,13 +370,15 @@
|
||||
<div id="items_selected">
|
||||
<div class="js-stick-at-bottom-when-scrolling">
|
||||
<div class="usa-button-group">
|
||||
<button class="usa-button usa-button--event" value="move-to-existing-folder" aria-expanded="false">
|
||||
<button class="usa-button" value="move-to-existing-folder" aria-expanded="false" role="button">
|
||||
Move<span class="usa-sr-only"> selection to folder</span>
|
||||
</button>
|
||||
<button class="usa-button usa-button--event" value="move-to-new-folder" aria-expanded="false">Add to new folder</button>
|
||||
<button class="usa-button usa-button--outline" value="move-to-new-folder" aria-expanded="false" role="button">
|
||||
Add to new folder
|
||||
</button>
|
||||
</div>
|
||||
<div class="template-list-selected-counter" aria-hidden="true">
|
||||
<span class="template-list-selected-counter__count" aria-hidden="true">
|
||||
<span class="template-list-selected-counter__count text-base" aria-hidden="true">
|
||||
${this.selectionStatus.selected(1)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -357,4 +387,4 @@
|
||||
`).get(0);
|
||||
};
|
||||
|
||||
})(window.GOVUK.Modules);
|
||||
})(window);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
window.GOVUK = window.GOVUK || {};
|
||||
window.GOVUK.Modules = window.GOVUK.Modules || {};
|
||||
window.GOVUK.Modules.TimeoutPopup = window.GOVUK.Modules.TimeoutPopup || {};
|
||||
window.NotifyModules.TimeoutPopup = window.NotifyModules.TimeoutPopup || {};
|
||||
|
||||
(function(global) {
|
||||
(function(window) {
|
||||
"use strict";
|
||||
|
||||
const sessionTimer = document.getElementById("sessionTimer");
|
||||
@@ -56,10 +54,10 @@ window.GOVUK.Modules.TimeoutPopup = window.GOVUK.Modules.TimeoutPopup || {};
|
||||
setTimeout(setSessionTimer, 25 * 60 * 1000);
|
||||
}
|
||||
|
||||
global.GOVUK.Modules.TimeoutPopup.checkTimer = checkTimer;
|
||||
global.GOVUK.Modules.TimeoutPopup.expireUserSession = expireUserSession;
|
||||
global.GOVUK.Modules.TimeoutPopup.signoutUser = signoutUser;
|
||||
global.GOVUK.Modules.TimeoutPopup.extendSession = extendSession;
|
||||
global.GOVUK.Modules.TimeoutPopup.showTimer = showTimer;
|
||||
global.GOVUK.Modules.TimeoutPopup.closeTimer = closeTimer;
|
||||
window.NotifyModules.TimeoutPopup.checkTimer = checkTimer;
|
||||
window.NotifyModules.TimeoutPopup.expireUserSession = expireUserSession;
|
||||
window.NotifyModules.TimeoutPopup.signoutUser = signoutUser;
|
||||
window.NotifyModules.TimeoutPopup.extendSession = extendSession;
|
||||
window.NotifyModules.TimeoutPopup.showTimer = showTimer;
|
||||
window.NotifyModules.TimeoutPopup.closeTimer = closeTimer;
|
||||
})(window);
|
||||
|
||||
@@ -1,150 +1,144 @@
|
||||
(function (window) {
|
||||
function createTotalMessagesChart() {
|
||||
var chartContainer = document.getElementById('totalMessageChartContainer');
|
||||
if (!chartContainer) return;
|
||||
function createTotalMessagesChart() {
|
||||
var chartContainer = document.getElementById('totalMessageChartContainer');
|
||||
if (!chartContainer) return;
|
||||
|
||||
var chartTitle = document.getElementById('chartTitle').textContent;
|
||||
var chartTitle = document.getElementById('chartTitle').textContent;
|
||||
|
||||
// Access data attributes from the HTML
|
||||
var messagesSent = parseInt(chartContainer.getAttribute('data-messages-sent'));
|
||||
var messagesRemaining = parseInt(chartContainer.getAttribute('data-messages-remaining'));
|
||||
var totalMessages = messagesSent + messagesRemaining;
|
||||
// Access data attributes from the HTML
|
||||
var messagesSent = parseInt(chartContainer.getAttribute('data-messages-sent'));
|
||||
var messagesRemaining = parseInt(chartContainer.getAttribute('data-messages-remaining'));
|
||||
var totalMessages = messagesSent + messagesRemaining;
|
||||
|
||||
// Update the message below the chart
|
||||
document.getElementById('message').innerText = `${messagesSent.toLocaleString()} sent / ${messagesRemaining.toLocaleString()} remaining`;
|
||||
// Update the message below the chart
|
||||
document.getElementById('message').innerText = `${messagesSent.toLocaleString()} sent / ${messagesRemaining.toLocaleString()} remaining`;
|
||||
|
||||
// Calculate minimum width for "Messages Sent" as 1% of the total chart width
|
||||
var minSentPercentage = (messagesSent === 0) ? 0 : 0.02;
|
||||
var minSentValue = totalMessages * minSentPercentage;
|
||||
var displaySent = Math.max(messagesSent, minSentValue);
|
||||
var displayRemaining = totalMessages - displaySent;
|
||||
// Calculate minimum width for "Messages Sent" as 1% of the total chart width
|
||||
var minSentPercentage = (messagesSent === 0) ? 0 : 0.02;
|
||||
var minSentValue = totalMessages * minSentPercentage;
|
||||
var displaySent = Math.max(messagesSent, minSentValue);
|
||||
var displayRemaining = totalMessages - displaySent;
|
||||
|
||||
var svg = d3.select("#totalMessageChart");
|
||||
var width = chartContainer.clientWidth;
|
||||
var height = 48;
|
||||
var svg = d3.select("#totalMessageChart");
|
||||
var width = chartContainer.clientWidth;
|
||||
var height = 48;
|
||||
|
||||
// Ensure the width is set correctly
|
||||
if (width === 0) {
|
||||
console.error('Chart container width is 0, cannot set SVG width.');
|
||||
return;
|
||||
}
|
||||
|
||||
svg.attr("width", width).attr("height", height);
|
||||
|
||||
var x = d3.scaleLinear()
|
||||
.domain([0, totalMessages])
|
||||
.range([0, width]);
|
||||
|
||||
// Create tooltip dynamically
|
||||
var tooltip = d3.select("body").append("div")
|
||||
.attr("id", "tooltip");
|
||||
|
||||
// Create the initial bars
|
||||
var sentBar = svg.append("rect")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("height", height)
|
||||
.attr("fill", '#0076d6')
|
||||
.attr("width", 0) // Start with width 0 for animation
|
||||
.on('mouseover', function(event) {
|
||||
tooltip.style('display', 'block')
|
||||
.html(`Messages Sent: ${messagesSent.toLocaleString()}`);
|
||||
})
|
||||
.on('mousemove', function(event) {
|
||||
tooltip.style('left', `${event.pageX + 10}px`)
|
||||
.style('top', `${event.pageY - 20}px`);
|
||||
})
|
||||
.on('mouseout', function() {
|
||||
tooltip.style('display', 'none');
|
||||
});
|
||||
|
||||
var remainingBar = svg.append("rect")
|
||||
.attr("x", 0) // Initially set to 0, will be updated during animation
|
||||
.attr("y", 0)
|
||||
.attr("height", height)
|
||||
.attr("fill", '#C7CACE')
|
||||
.attr("width", 0) // Start with width 0 for animation
|
||||
.on('mouseover', function(event) {
|
||||
tooltip.style('display', 'block')
|
||||
.html(`Remaining: ${messagesRemaining.toLocaleString()}`);
|
||||
})
|
||||
.on('mousemove', function(event) {
|
||||
tooltip.style('left', `${event.pageX + 10}px`)
|
||||
.style('top', `${event.pageY - 20}px`);
|
||||
})
|
||||
.on('mouseout', function() {
|
||||
tooltip.style('display', 'none');
|
||||
});
|
||||
|
||||
// Animate the bars together as a single cohesive line
|
||||
svg.transition()
|
||||
.duration(1000) // Total animation duration
|
||||
.attr("width", width)
|
||||
.tween("resize", function() {
|
||||
var interpolator = d3.interpolate(0, width);
|
||||
return function(t) {
|
||||
var newWidth = interpolator(t);
|
||||
var sentWidth = x(displaySent) / width * newWidth;
|
||||
var remainingWidth = x(displayRemaining) / width * newWidth;
|
||||
sentBar.attr("width", sentWidth);
|
||||
remainingBar.attr("x", sentWidth).attr("width", remainingWidth);
|
||||
};
|
||||
});
|
||||
|
||||
// Create and populate the accessible table
|
||||
var tableContainer = document.getElementById('totalMessageTable');
|
||||
var table = document.createElement('table');
|
||||
table.className = 'usa-sr-only usa-table';
|
||||
|
||||
var caption = document.createElement('caption');
|
||||
caption.textContent = chartTitle;
|
||||
table.appendChild(caption);
|
||||
|
||||
var thead = document.createElement('thead'); // Ensure thead is created
|
||||
var theadRow = document.createElement('tr');
|
||||
var thMessagesSent = document.createElement('th');
|
||||
thMessagesSent.textContent = 'Messages Sent'; // First column header
|
||||
var thRemaining = document.createElement('th');
|
||||
thRemaining.textContent = 'Remaining'; // Second column header
|
||||
theadRow.appendChild(thMessagesSent);
|
||||
theadRow.appendChild(thRemaining);
|
||||
thead.appendChild(theadRow); // Append theadRow to the thead
|
||||
table.appendChild(thead);
|
||||
|
||||
var tbody = document.createElement('tbody');
|
||||
var tbodyRow = document.createElement('tr');
|
||||
|
||||
var tdMessagesSent = document.createElement('td');
|
||||
tdMessagesSent.textContent = messagesSent.toLocaleString(); // Value for Messages Sent
|
||||
var tdRemaining = document.createElement('td');
|
||||
tdRemaining.textContent = messagesRemaining.toLocaleString(); // Value for Remaining
|
||||
|
||||
tbodyRow.appendChild(tdMessagesSent);
|
||||
tbodyRow.appendChild(tdRemaining);
|
||||
tbody.appendChild(tbodyRow);
|
||||
|
||||
table.appendChild(tbody);
|
||||
tableContainer.appendChild(table);
|
||||
|
||||
table.appendChild(tbody);
|
||||
tableContainer.appendChild(table);
|
||||
|
||||
// Ensure the chart resizes correctly on window resize
|
||||
window.addEventListener('resize', function () {
|
||||
width = chartContainer.clientWidth;
|
||||
x.range([0, width]);
|
||||
svg.attr("width", width);
|
||||
sentBar.attr("width", x(displaySent));
|
||||
remainingBar.attr("x", x(displaySent)).attr("width", x(displayRemaining));
|
||||
});
|
||||
// Ensure the width is set correctly
|
||||
if (width === 0) {
|
||||
console.error('Chart container width is 0, cannot set SVG width.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize total messages chart if the container exists
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
createTotalMessagesChart();
|
||||
svg.attr("width", width).attr("height", height);
|
||||
|
||||
var x = d3.scaleLinear()
|
||||
.domain([0, totalMessages])
|
||||
.range([0, width]);
|
||||
|
||||
// Create tooltip dynamically
|
||||
var tooltip = d3.select("body").append("div")
|
||||
.attr("id", "tooltip");
|
||||
|
||||
// Create the initial bars
|
||||
var sentBar = svg.append("rect")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("height", height)
|
||||
.attr("fill", '#0076d6')
|
||||
.attr("width", 0) // Start with width 0 for animation
|
||||
.on('mouseover', function(event) {
|
||||
tooltip.style('display', 'block')
|
||||
.html(`Messages Sent: ${messagesSent.toLocaleString()}`);
|
||||
})
|
||||
.on('mousemove', function(event) {
|
||||
tooltip.style('left', `${event.pageX + 10}px`)
|
||||
.style('top', `${event.pageY - 20}px`);
|
||||
})
|
||||
.on('mouseout', function() {
|
||||
tooltip.style('display', 'none');
|
||||
});
|
||||
|
||||
var remainingBar = svg.append("rect")
|
||||
.attr("x", 0) // Initially set to 0, will be updated during animation
|
||||
.attr("y", 0)
|
||||
.attr("height", height)
|
||||
.attr("fill", '#C7CACE')
|
||||
.attr("width", 0) // Start with width 0 for animation
|
||||
.on('mouseover', function(event) {
|
||||
tooltip.style('display', 'block')
|
||||
.html(`Remaining: ${messagesRemaining.toLocaleString()}`);
|
||||
})
|
||||
.on('mousemove', function(event) {
|
||||
tooltip.style('left', `${event.pageX + 10}px`)
|
||||
.style('top', `${event.pageY - 20}px`);
|
||||
})
|
||||
.on('mouseout', function() {
|
||||
tooltip.style('display', 'none');
|
||||
});
|
||||
|
||||
// Animate the bars together as a single cohesive line
|
||||
svg.transition()
|
||||
.duration(1000) // Total animation duration
|
||||
.attr("width", width)
|
||||
.tween("resize", function() {
|
||||
var interpolator = d3.interpolate(0, width);
|
||||
return function(t) {
|
||||
var newWidth = interpolator(t);
|
||||
var sentWidth = x(displaySent) / width * newWidth;
|
||||
var remainingWidth = x(displayRemaining) / width * newWidth;
|
||||
sentBar.attr("width", sentWidth);
|
||||
remainingBar.attr("x", sentWidth).attr("width", remainingWidth);
|
||||
};
|
||||
});
|
||||
|
||||
// Create and populate the accessible table
|
||||
var tableContainer = document.getElementById('totalMessageTable');
|
||||
var table = document.createElement('table');
|
||||
table.className = 'usa-sr-only usa-table';
|
||||
|
||||
var caption = document.createElement('caption');
|
||||
caption.textContent = chartTitle;
|
||||
table.appendChild(caption);
|
||||
|
||||
var thead = document.createElement('thead'); // Ensure thead is created
|
||||
var theadRow = document.createElement('tr');
|
||||
var thMessagesSent = document.createElement('th');
|
||||
thMessagesSent.textContent = 'Messages Sent'; // First column header
|
||||
var thRemaining = document.createElement('th');
|
||||
thRemaining.textContent = 'Remaining'; // Second column header
|
||||
theadRow.appendChild(thMessagesSent);
|
||||
theadRow.appendChild(thRemaining);
|
||||
thead.appendChild(theadRow); // Append theadRow to the thead
|
||||
table.appendChild(thead);
|
||||
|
||||
var tbody = document.createElement('tbody');
|
||||
var tbodyRow = document.createElement('tr');
|
||||
|
||||
var tdMessagesSent = document.createElement('td');
|
||||
tdMessagesSent.textContent = messagesSent.toLocaleString(); // Value for Messages Sent
|
||||
var tdRemaining = document.createElement('td');
|
||||
tdRemaining.textContent = messagesRemaining.toLocaleString(); // Value for Remaining
|
||||
|
||||
tbodyRow.appendChild(tdMessagesSent);
|
||||
tbodyRow.appendChild(tdRemaining);
|
||||
tbody.appendChild(tbodyRow);
|
||||
|
||||
table.appendChild(tbody);
|
||||
tableContainer.appendChild(table);
|
||||
|
||||
// Ensure the chart resizes correctly on window resize
|
||||
window.addEventListener('resize', function () {
|
||||
width = chartContainer.clientWidth;
|
||||
x.range([0, width]);
|
||||
svg.attr("width", width);
|
||||
sentBar.attr("width", x(displaySent));
|
||||
remainingBar.attr("x", x(displaySent)).attr("width", x(displayRemaining));
|
||||
});
|
||||
}
|
||||
|
||||
// Export function for testing
|
||||
window.createTotalMessagesChart = createTotalMessagesChart;
|
||||
// Initialize total messages chart if the container exists
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
createTotalMessagesChart();
|
||||
});
|
||||
|
||||
})(window);
|
||||
// Export function for testing
|
||||
window.createTotalMessagesChart = createTotalMessagesChart;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(function(window) {
|
||||
"use strict";
|
||||
|
||||
window.GOVUK.Modules.UpdateStatus = function() {
|
||||
window.NotifyModules['update-status'] = function() {
|
||||
|
||||
const getRenderer = $component => response => $component.html(
|
||||
response.html
|
||||
|
||||
Reference in New Issue
Block a user