From 9cc2aab09cefb835fbc6a3ba719d0fbcc3fe75b6 Mon Sep 17 00:00:00 2001 From: Mat Moore Date: Fri, 26 Jan 2024 13:19:20 +0000 Subject: [PATCH] Update MOJ frontend and make sure JS is initialised --- package-lock.json | 8 +- package.json | 2 +- static/assets/js/moj-frontend.js | 1233 ++++++++++++++++++++++++++++++ templates/base/base.html | 4 +- 4 files changed, 1240 insertions(+), 7 deletions(-) create mode 100644 static/assets/js/moj-frontend.js diff --git a/package-lock.json b/package-lock.json index b68939f9..8be152aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,15 +5,15 @@ "packages": { "": { "dependencies": { - "@ministryofjustice/frontend": "^2.0.1", + "@ministryofjustice/frontend": "^2.1.0", "govuk-frontend": "^5.0.0", "sass": "^1.70.0" } }, "node_modules/@ministryofjustice/frontend": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@ministryofjustice/frontend/-/frontend-2.0.1.tgz", - "integrity": "sha512-LqB+3B90VzSyTh7QM81hXpg2g3EzGDWNbWdUdEKu82DqyHf1n+h9rhSC+Vfdah3g1wgZyjpWVl9RCqyKzgK1Lg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@ministryofjustice/frontend/-/frontend-2.1.0.tgz", + "integrity": "sha512-LekTR097OsFku0+sREn7gR3G7UvH7jATw40PvZH4mKtnE8hyyw0gDrCSvFYsRS4kPLDsoFqZ5l0Y3CZE9f364g==", "dependencies": { "govuk-frontend": "^5.0.0", "moment": "^2.27.0" diff --git a/package.json b/package.json index 6c33b53c..ece087f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "@ministryofjustice/frontend": "^2.0.1", + "@ministryofjustice/frontend": "^2.1.0", "govuk-frontend": "^5.0.0", "sass": "^1.70.0" }, diff --git a/static/assets/js/moj-frontend.js b/static/assets/js/moj-frontend.js new file mode 100644 index 00000000..ae7be92c --- /dev/null +++ b/static/assets/js/moj-frontend.js @@ -0,0 +1,1233 @@ +;(function(root, factory) { + if (typeof define === 'function' && define.amd) { + define([], factory); + } else if (typeof exports === 'object') { + module.exports = factory(); + } else { + root.MOJFrontend = factory(); + } +}(this, function() { +var MOJFrontend = {}; +MOJFrontend.removeAttributeValue = function(el, attr, value) { + var re, m; + if (el.getAttribute(attr)) { + if (el.getAttribute(attr) == value) { + el.removeAttribute(attr); + } else { + re = new RegExp('(^|\\s)' + value + '(\\s|$)'); + m = el.getAttribute(attr).match(re); + if (m && m.length == 3) { + el.setAttribute(attr, el.getAttribute(attr).replace(re, (m[1] && m[2])?' ':'')) + } + } + } +} + +MOJFrontend.addAttributeValue = function(el, attr, value) { + var re; + if (!el.getAttribute(attr)) { + el.setAttribute(attr, value); + } + else { + re = new RegExp('(^|\\s)' + value + '(\\s|$)'); + if (!re.test(el.getAttribute(attr))) { + el.setAttribute(attr, el.getAttribute(attr) + ' ' + value); + } + } +}; + +MOJFrontend.dragAndDropSupported = function() { + var div = document.createElement('div'); + return typeof div.ondrop != 'undefined'; +}; + +MOJFrontend.formDataSupported = function() { + return typeof FormData == 'function'; +}; + +MOJFrontend.fileApiSupported = function() { + var input = document.createElement('input'); + input.type = 'file'; + return typeof input.files != 'undefined'; +}; + +MOJFrontend.nodeListForEach = function(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) + } +}; + +MOJFrontend.initAll = function (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 MOJ 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; + + var $addAnothers = scope.querySelectorAll('[data-module="moj-add-another"]'); + MOJFrontend.nodeListForEach($addAnothers, function ($addAnother) { + new MOJFrontend.AddAnother($addAnother); + }); + + var $multiSelects = scope.querySelectorAll('[data-module="moj-multi-select"]'); + MOJFrontend.nodeListForEach($multiSelects, function ($multiSelect) { + new MOJFrontend.MultiSelect({ + container: $multiSelect.querySelector($multiSelect.getAttribute('data-multi-select-checkbox')), + checkboxes: $multiSelect.querySelectorAll('tbody .govuk-checkboxes__input') + }); + }); + + var $passwordReveals = scope.querySelectorAll('[data-module="moj-password-reveal"]'); + MOJFrontend.nodeListForEach($passwordReveals, function ($passwordReveal) { + new MOJFrontend.PasswordReveal($passwordReveal); + }); + + var $richTextEditors = scope.querySelectorAll('[data-module="moj-rich-text-editor"]'); + MOJFrontend.nodeListForEach($richTextEditors, function ($richTextEditor) { + var options = { + textarea: $($richTextEditor) + }; + + var toolbarAttr = $richTextEditor.getAttribute('data-moj-rich-text-editor-toolbar'); + if (toolbarAttr) { + var toolbar = toolbarAttr.split(','); + options.toolbar = {}; + for (var item in toolbar) options.toolbar[toolbar[item]] = true; + } + + new MOJFrontend.RichTextEditor(options); + }); + + var $searchToggles = scope.querySelectorAll('[data-module="moj-search-toggle"]'); + MOJFrontend.nodeListForEach($searchToggles, function ($searchToggle) { + new MOJFrontend.SearchToggle({ + toggleButton: { + container: $($searchToggle.querySelector('.moj-search-toggle__toggle')), + text: $searchToggle.getAttribute('data-moj-search-toggle-text') + }, + search: { + container: $($searchToggle.querySelector('.moj-search')) + } + }); + }); + + var $sortableTables = scope.querySelectorAll('[data-module="moj-sortable-table"]'); + MOJFrontend.nodeListForEach($sortableTables, function ($table) { + new MOJFrontend.SortableTable({ + table: $table + }); + }); + + var $sortableTables = scope.querySelectorAll('[data-module="moj-sortable-table"]'); + MOJFrontend.nodeListForEach($sortableTables, function ($table) { + new MOJFrontend.SortableTable({ + table: $table + }); + }); +} + +MOJFrontend.AddAnother = function(container) { + this.container = $(container); + + if (this.container.data('moj-add-another-initialised')) { + return + } + + this.container.data('moj-add-another-initialised', true); + + this.container.on('click', '.moj-add-another__remove-button', $.proxy(this, 'onRemoveButtonClick')); + this.container.on('click', '.moj-add-another__add-button', $.proxy(this, 'onAddButtonClick')); + this.container.find('.moj-add-another__add-button, moj-add-another__remove-button').prop('type', 'button'); +}; + +MOJFrontend.AddAnother.prototype.onAddButtonClick = function(e) { + var item = this.getNewItem(); + this.updateAttributes(this.getItems().length, item); + this.resetItem(item); + var firstItem = this.getItems().first(); + if(!this.hasRemoveButton(firstItem)) { + this.createRemoveButton(firstItem); + } + this.getItems().last().after(item); + item.find('input, textarea, select').first().focus(); +}; + +MOJFrontend.AddAnother.prototype.hasRemoveButton = function(item) { + return item.find('.moj-add-another__remove-button').length; +}; + +MOJFrontend.AddAnother.prototype.getItems = function() { + return this.container.find('.moj-add-another__item'); +}; + +MOJFrontend.AddAnother.prototype.getNewItem = function() { + var item = this.getItems().first().clone(); + if(!this.hasRemoveButton(item)) { + this.createRemoveButton(item); + } + return item; +}; + +MOJFrontend.AddAnother.prototype.updateAttributes = function(index, item) { + item.find('[data-name]').each(function(i, el) { + var originalId = el.id + + el.name = $(el).attr('data-name').replace(/%index%/, index); + el.id = $(el).attr('data-id').replace(/%index%/, index); + + var label = $(el).siblings('label')[0] || $(el).parents('label')[0] || item.find('[for="' + originalId + '"]')[0]; + label.htmlFor = el.id; + }); +}; + +MOJFrontend.AddAnother.prototype.createRemoveButton = function(item) { + item.append(''); +}; + +MOJFrontend.AddAnother.prototype.resetItem = function(item) { + item.find('[data-name], [data-id]').each(function(index, el) { + if(el.type == 'checkbox' || el.type == 'radio') { + el.checked = false; + } else { + el.value = ''; + } + }); +}; + +MOJFrontend.AddAnother.prototype.onRemoveButtonClick = function(e) { + $(e.currentTarget).parents('.moj-add-another__item').remove(); + var items = this.getItems(); + if(items.length === 1) { + items.find('.moj-add-another__remove-button').remove(); + } + items.each($.proxy(function(index, el) { + this.updateAttributes(index, $(el)); + }, this)); + this.focusHeading(); +}; + +MOJFrontend.AddAnother.prototype.focusHeading = function() { + this.container.find('.moj-add-another__heading').focus(); +}; + +MOJFrontend.ButtonMenu = function(params) { + this.container = $(params.container); + this.menu = this.container.find('.moj-button-menu__wrapper'); + if(params.menuClasses) { + this.menu.addClass(params.menuClasses); + } + this.menu.attr('role', 'menu'); + this.mq = params.mq; + this.buttonText = params.buttonText; + this.buttonClasses = params.buttonClasses || ''; + this.keys = { esc: 27, up: 38, down: 40, tab: 9 }; + this.menu.on('keydown', '[role=menuitem]', $.proxy(this, 'onButtonKeydown')); + this.createToggleButton(); + this.setupResponsiveChecks(); + $(document).on('click', $.proxy(this, 'onDocumentClick')); +}; + +MOJFrontend.ButtonMenu.prototype.onDocumentClick = function(e) { + if(!$.contains(this.container[0], e.target)) { + this.hideMenu(); + } +}; + +MOJFrontend.ButtonMenu.prototype.createToggleButton = function() { + this.menuButton = $(''); + this.menuButton.on('click', $.proxy(this, 'onMenuButtonClick')); + this.menuButton.on('keydown', $.proxy(this, 'onMenuKeyDown')); +}; + +MOJFrontend.ButtonMenu.prototype.setupResponsiveChecks = function() { + this.mql = window.matchMedia(this.mq); + this.mql.addListener($.proxy(this, 'checkMode')); + this.checkMode(this.mql); +}; + +MOJFrontend.ButtonMenu.prototype.checkMode = function(mql) { + if(mql.matches) { + this.enableBigMode(); + } else { + this.enableSmallMode(); + } +}; + +MOJFrontend.ButtonMenu.prototype.enableSmallMode = function() { + this.container.prepend(this.menuButton); + this.hideMenu(); + this.removeButtonClasses(); + this.menu.attr('role', 'menu'); + this.container.find('.moj-button-menu__item').attr('role', 'menuitem'); +}; + +MOJFrontend.ButtonMenu.prototype.enableBigMode = function() { + this.menuButton.detach(); + this.showMenu(); + this.addButtonClasses(); + this.menu.removeAttr('role'); + this.container.find('.moj-button-menu__item').removeAttr('role'); +}; + +MOJFrontend.ButtonMenu.prototype.removeButtonClasses = function() { + this.menu.find('.moj-button-menu__item').each(function(index, el) { + if($(el).hasClass('govuk-button--secondary')) { + $(el).attr('data-secondary', 'true'); + $(el).removeClass('govuk-button--secondary'); + } + if($(el).hasClass('govuk-button--warning')) { + $(el).attr('data-warning', 'true'); + $(el).removeClass('govuk-button--warning'); + } + $(el).removeClass('govuk-button'); + }); +}; + +MOJFrontend.ButtonMenu.prototype.addButtonClasses = function() { + this.menu.find('.moj-button-menu__item').each(function(index, el) { + if($(el).attr('data-secondary') == 'true') { + $(el).addClass('govuk-button--secondary'); + } + if($(el).attr('data-warning') == 'true') { + $(el).addClass('govuk-button--warning'); + } + $(el).addClass('govuk-button'); + }); +}; + +MOJFrontend.ButtonMenu.prototype.hideMenu = function() { + this.menuButton.attr('aria-expanded', 'false'); +}; + +MOJFrontend.ButtonMenu.prototype.showMenu = function() { + this.menuButton.attr('aria-expanded', 'true'); +}; + +MOJFrontend.ButtonMenu.prototype.onMenuButtonClick = function() { + this.toggle(); +}; + +MOJFrontend.ButtonMenu.prototype.toggle = function() { + if(this.menuButton.attr('aria-expanded') == 'false') { + this.showMenu(); + this.menu.find('[role=menuitem]').first().focus(); + } else { + this.hideMenu(); + this.menuButton.focus(); + } +}; + +MOJFrontend.ButtonMenu.prototype.onMenuKeyDown = function(e) { + switch (e.keyCode) { + case this.keys.down: + this.toggle(); + break; + } +}; + +MOJFrontend.ButtonMenu.prototype.onButtonKeydown = function(e) { + switch (e.keyCode) { + case this.keys.up: + e.preventDefault(); + this.focusPrevious(e.currentTarget); + break; + case this.keys.down: + e.preventDefault(); + this.focusNext(e.currentTarget); + break; + case this.keys.esc: + if(!this.mql.matches) { + this.menuButton.focus(); + this.hideMenu(); + } + break; + case this.keys.tab: + if(!this.mql.matches) { + this.hideMenu(); + } + } +}; + +MOJFrontend.ButtonMenu.prototype.focusNext = function(currentButton) { + var next = $(currentButton).next(); + if(next[0]) { + next.focus(); + } else { + this.container.find('[role=menuitem]').first().focus(); + } +}; + +MOJFrontend.ButtonMenu.prototype.focusPrevious = function(currentButton) { + var prev = $(currentButton).prev(); + if(prev[0]) { + prev.focus(); + } else { + this.container.find('[role=menuitem]').last().focus(); + } +}; + +MOJFrontend.FilterToggleButton = function(options) { + this.options = options; + this.container = $(this.options.toggleButton.container); + this.filterContainer = $(this.options.filter.container); + + this.createToggleButton(); + this.setupResponsiveChecks(); + this.filterContainer.attr('tabindex', '-1'); + if(this.options.startHidden) { + this.hideMenu(); + } +}; + +MOJFrontend.FilterToggleButton.prototype.setupResponsiveChecks = function() { + this.mq = window.matchMedia(this.options.bigModeMediaQuery); + this.mq.addListener($.proxy(this, 'checkMode')); + this.checkMode(this.mq); +}; + +MOJFrontend.FilterToggleButton.prototype.createToggleButton = function() { + this.menuButton = $(''); + this.menuButton.on('click', $.proxy(this, 'onMenuButtonClick')); + this.options.toggleButton.container.append(this.menuButton); +}; + +MOJFrontend.FilterToggleButton.prototype.checkMode = function(mq) { + if(mq.matches) { + this.enableBigMode(); + } else { + this.enableSmallMode(); + } +}; + +MOJFrontend.FilterToggleButton.prototype.enableBigMode = function() { + this.showMenu(); + this.removeCloseButton(); +}; + +MOJFrontend.FilterToggleButton.prototype.enableSmallMode = function() { + this.hideMenu(); + this.addCloseButton(); +}; + +MOJFrontend.FilterToggleButton.prototype.addCloseButton = function() { + if(this.options.closeButton) { + this.closeButton = $(''); + this.closeButton.on('click', $.proxy(this, 'onCloseClick')); + $(this.options.closeButton.container).append(this.closeButton); + } +}; + +MOJFrontend.FilterToggleButton.prototype.onCloseClick = function() { + this.hideMenu(); + this.menuButton.focus(); +}; + +MOJFrontend.FilterToggleButton.prototype.removeCloseButton = function() { + if(this.closeButton) { + this.closeButton.remove(); + this.closeButton = null; + } +}; + +MOJFrontend.FilterToggleButton.prototype.hideMenu = function() { + this.menuButton.attr('aria-expanded', 'false'); + this.filterContainer.addClass('moj-js-hidden'); + this.menuButton.text(this.options.toggleButton.showText); +}; + +MOJFrontend.FilterToggleButton.prototype.showMenu = function() { + this.menuButton.attr('aria-expanded', 'true'); + this.filterContainer.removeClass('moj-js-hidden'); + this.menuButton.text(this.options.toggleButton.hideText); +}; + +MOJFrontend.FilterToggleButton.prototype.onMenuButtonClick = function() { + this.toggle(); +}; + +MOJFrontend.FilterToggleButton.prototype.toggle = function() { + if(this.menuButton.attr('aria-expanded') == 'false') { + this.showMenu(); + this.filterContainer.focus(); + } else { + this.hideMenu(); + } +}; + +MOJFrontend.FormValidator = function(form, options) { + this.form = form; + this.errors = []; + this.validators = []; + $(this.form).on('submit', $.proxy(this, 'onSubmit')); + this.summary = (options && options.summary) ? $(options.summary) : $('.govuk-error-summary'); + this.originalTitle = document.title; +}; + +MOJFrontend.FormValidator.entityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/', + '`': '`', + '=': '=' +}; + +MOJFrontend.FormValidator.prototype.escapeHtml = function(string) { + return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) { + return MOJFrontend.FormValidator.entityMap[s]; + }); +}; + +MOJFrontend.FormValidator.prototype.resetTitle = function() { + document.title = this.originalTitle; +}; + +MOJFrontend.FormValidator.prototype.updateTitle = function() { + document.title = "" + this.errors.length + " errors - " + document.title; +}; + +MOJFrontend.FormValidator.prototype.showSummary = function () { + this.summary.html(this.getSummaryHtml()); + this.summary.removeClass('moj-hidden'); + this.summary.attr('aria-labelledby', 'errorSummary-heading'); + this.summary.focus(); +}; + +MOJFrontend.FormValidator.prototype.getSummaryHtml = function() { + var html = '

There is a problem

'; + html += '
'; + html += ''; + html += '
'; + return html; +}; + +MOJFrontend.FormValidator.prototype.hideSummary = function() { + this.summary.addClass('moj-hidden'); + this.summary.removeAttr('aria-labelledby'); +}; + +MOJFrontend.FormValidator.prototype.onSubmit = function (e) { + this.removeInlineErrors(); + this.hideSummary(); + this.resetTitle(); + if(!this.validate()) { + e.preventDefault(); + this.updateTitle(); + this.showSummary(); + this.showInlineErrors(); + } +}; + +MOJFrontend.FormValidator.prototype.showInlineErrors = function() { + for (var i = 0, j = this.errors.length; i < j; i++) { + this.showInlineError(this.errors[i]); + } +}; + +MOJFrontend.FormValidator.prototype.showInlineError = function (error) { + var errorSpanId = error.fieldName + '-error'; + var errorSpan = ''+this.escapeHtml(error.message)+''; + var control = $("#" + error.fieldName); + var fieldContainer = control.parents(".govuk-form-group"); + var label = fieldContainer.find('label'); + var legend = fieldContainer.find("legend"); + var fieldset = fieldContainer.find("fieldset"); + fieldContainer.addClass('govuk-form-group--error'); + if(legend.length) { + legend.after(errorSpan); + fieldContainer.attr('aria-invalid', 'true'); + MOJFrontend.addAttributeValue(fieldset[0], 'aria-describedby', errorSpanId); + } else { + label.after(errorSpan); + control.attr('aria-invalid', 'true'); + MOJFrontend.addAttributeValue(control[0], 'aria-describedby', errorSpanId); + } +}; + +MOJFrontend.FormValidator.prototype.removeInlineErrors = function() { + var error; + var i; + for (var i = 0; i < this.errors.length; i++) { + this.removeInlineError(this.errors[i]); + } +}; + +MOJFrontend.FormValidator.prototype.removeInlineError = function(error) { + var control = $("#" + error.fieldName); + var fieldContainer = control.parents(".govuk-form-group"); + fieldContainer.find('.govuk-error-message').remove(); + fieldContainer.removeClass('govuk-form-group--error'); + fieldContainer.find("[aria-invalid]").attr('aria-invalid', 'false'); + var errorSpanId = error.fieldName + '-error'; + MOJFrontend.removeAttributeValue(fieldContainer.find('[aria-describedby]')[0], 'aria-describedby', errorSpanId); +}; + +MOJFrontend.FormValidator.prototype.addValidator = function(fieldName, rules) { + this.validators.push({ + fieldName: fieldName, + rules: rules, + field: this.form.elements[fieldName] + }); +}; + +MOJFrontend.FormValidator.prototype.validate = function() { + this.errors = []; + var validator = null, + validatorReturnValue = true, + i, + j; + for (i = 0; i < this.validators.length; i++) { + validator = this.validators[i]; + for (j = 0; j < validator.rules.length; j++) { + validatorReturnValue = validator.rules[j].method(validator.field, + validator.rules[j].params); + + if (typeof validatorReturnValue === 'boolean' && !validatorReturnValue) { + this.errors.push({ + fieldName: validator.fieldName, + message: validator.rules[j].message + }); + break; + } else if(typeof validatorReturnValue === 'string') { + this.errors.push({ + fieldName: validatorReturnValue, + message: validator.rules[j].message + }); + break; + } + } + } + return this.errors.length === 0; +}; +if(MOJFrontend.dragAndDropSupported() && MOJFrontend.formDataSupported() && MOJFrontend.fileApiSupported()) { + MOJFrontend.MultiFileUpload = function(params) { + this.defaultParams = { + uploadFileEntryHook: $.noop, + uploadFileExitHook: $.noop, + uploadFileErrorHook: $.noop, + fileDeleteHook: $.noop, + uploadStatusText: 'Uploading files, please wait', + dropzoneHintText: 'Drag and drop files here or', + dropzoneButtonText: 'Choose files' + }; + + this.params = $.extend({}, this.defaultParams, params); + this.container = $(this.params.container); + + this.container.addClass('moj-multi-file-upload--enhanced'); + + this.feedbackContainer = this.container.find('.moj-multi-file__uploaded-files'); + this.setupFileInput(); + this.setupDropzone(); + this.setupLabel(); + this.setupStatusBox(); + this.container.on('click', '.moj-multi-file-upload__delete', $.proxy(this, 'onFileDeleteClick')); + }; + + MOJFrontend.MultiFileUpload.prototype.setupDropzone = function() { + this.fileInput.wrap('
'); + this.dropzone = this.container.find('.moj-multi-file-upload__dropzone'); + this.dropzone.on('dragover', $.proxy(this, 'onDragOver')); + this.dropzone.on('dragleave', $.proxy(this, 'onDragLeave')); + this.dropzone.on('drop', $.proxy(this, 'onDrop')); + }; + + MOJFrontend.MultiFileUpload.prototype.setupLabel = function() { + this.label = $(''); + this.dropzone.append('

' + this.params.dropzoneHintText + '

'); + this.dropzone.append(this.label); + }; + + MOJFrontend.MultiFileUpload.prototype.setupFileInput = function() { + this.fileInput = this.container.find('.moj-multi-file-upload__input'); + this.fileInput.on('change', $.proxy(this, 'onFileChange')); + this.fileInput.on('focus', $.proxy(this, 'onFileFocus')); + this.fileInput.on('blur', $.proxy(this, 'onFileBlur')); + }; + + MOJFrontend.MultiFileUpload.prototype.setupStatusBox = function() { + this.status = $('
'); + this.dropzone.append(this.status); + }; + + MOJFrontend.MultiFileUpload.prototype.onDragOver = function(e) { + e.preventDefault(); + this.dropzone.addClass('moj-multi-file-upload--dragover'); + }; + + MOJFrontend.MultiFileUpload.prototype.onDragLeave = function() { + this.dropzone.removeClass('moj-multi-file-upload--dragover'); + }; + + MOJFrontend.MultiFileUpload.prototype.onDrop = function(e) { + e.preventDefault(); + this.dropzone.removeClass('moj-multi-file-upload--dragover'); + this.feedbackContainer.removeClass('moj-hidden'); + this.status.html(this.params.uploadStatusText); + this.uploadFiles(e.originalEvent.dataTransfer.files); + }; + + MOJFrontend.MultiFileUpload.prototype.uploadFiles = function(files) { + for(var i = 0; i < files.length; i++) { + this.uploadFile(files[i]); + } + }; + + MOJFrontend.MultiFileUpload.prototype.onFileChange = function(e) { + this.feedbackContainer.removeClass('moj-hidden'); + this.status.html(this.params.uploadStatusText); + this.uploadFiles(e.currentTarget.files); + this.fileInput.replaceWith($(e.currentTarget).val('').clone(true)); + this.setupFileInput(); + this.fileInput.focus(); + }; + + MOJFrontend.MultiFileUpload.prototype.onFileFocus = function(e) { + this.label.addClass('moj-multi-file-upload--focused'); + }; + + MOJFrontend.MultiFileUpload.prototype.onFileBlur = function(e) { + this.label.removeClass('moj-multi-file-upload--focused'); + }; + + MOJFrontend.MultiFileUpload.prototype.getSuccessHtml = function(success) { + return ' ' + success.messageHtml + ''; + }; + + MOJFrontend.MultiFileUpload.prototype.getErrorHtml = function(error) { + return ' '+ error.message +''; + }; + + MOJFrontend.MultiFileUpload.prototype.getFileRowHtml = function(file) { + var html = ''; + html += '
'; + html += '
'; + html += ''+file.name+''; + html += '0%'; + html += '
'; + html += '
'; + html += '
'; + return html; + }; + + MOJFrontend.MultiFileUpload.prototype.getDeleteButtonHtml = function(file) { + var html = ''; + return html; + }; + + MOJFrontend.MultiFileUpload.prototype.uploadFile = function(file) { + this.params.uploadFileEntryHook(this, file); + var formData = new FormData(); + formData.append('documents', file); + var item = $(this.getFileRowHtml(file)); + this.feedbackContainer.find('.moj-multi-file-upload__list').append(item); + + $.ajax({ + url: this.params.uploadUrl, + type: 'post', + data: formData, + processData: false, + contentType: false, + success: $.proxy(function(response){ + if(response.error) { + item.find('.moj-multi-file-upload__message').html(this.getErrorHtml(response.error)); + this.status.html(response.error.message); + } else { + item.find('.moj-multi-file-upload__message').html(this.getSuccessHtml(response.success)); + this.status.html(response.success.messageText); + } + item.find('.moj-multi-file-upload__actions').append(this.getDeleteButtonHtml(response.file)); + this.params.uploadFileExitHook(this, file, response); + }, this), + error: $.proxy(function(jqXHR, textStatus, errorThrown) { + this.params.uploadFileErrorHook(this, file, jqXHR, textStatus, errorThrown); + }, this), + xhr: function() { + var xhr = new XMLHttpRequest(); + xhr.upload.addEventListener('progress', function(e) { + if (e.lengthComputable) { + var percentComplete = e.loaded / e.total; + percentComplete = parseInt(percentComplete * 100, 10); + item.find('.moj-multi-file-upload__progress').text(' ' + percentComplete + '%'); + } + }, false); + return xhr; + } + }); + }; + + MOJFrontend.MultiFileUpload.prototype.onFileDeleteClick = function(e) { + e.preventDefault(); // if user refreshes page and then deletes + var button = $(e.currentTarget); + var data = {}; + data[button[0].name] = button[0].value; + $.ajax({ + url: this.params.deleteUrl, + type: 'post', + dataType: 'json', + data: data, + success: $.proxy(function(response){ + if(response.error) { + // handle error + } else { + button.parents('.moj-multi-file-upload__row').remove(); + if(this.feedbackContainer.find('.moj-multi-file-upload__row').length === 0) { + this.feedbackContainer.addClass('moj-hidden'); + } + } + this.params.fileDeleteHook(this, response); + }, this) + }); + }; +} + +MOJFrontend.MultiSelect = function(options) { + this.container = $(options.container); + + if (this.container.data('moj-multi-select-initialised')) { + return + } + + this.container.data('moj-multi-select-initialised', true); + + this.toggle = $(this.getToggleHtml()); + this.toggleButton = this.toggle.find('input'); + this.toggleButton.on('click', $.proxy(this, 'onButtonClick')); + this.container.append(this.toggle); + this.checkboxes = $(options.checkboxes); + this.checkboxes.on('click', $.proxy(this, 'onCheckboxClick')); + this.checked = options.checked || false; +}; + +MOJFrontend.MultiSelect.prototype.getToggleHtml = function() { + var html = ''; + html += '
'; + html += ' '; + html += ' '; + html += '
'; + return html; +}; + +MOJFrontend.MultiSelect.prototype.onButtonClick = function(e) { + if(this.checked) { + this.uncheckAll(); + this.toggleButton[0].checked = false; + } else { + this.checkAll(); + this.toggleButton[0].checked = true; + } +}; + +MOJFrontend.MultiSelect.prototype.checkAll = function() { + this.checkboxes.each($.proxy(function(index, el) { + el.checked = true; + }, this)); + this.checked = true; +}; + +MOJFrontend.MultiSelect.prototype.uncheckAll = function() { + this.checkboxes.each($.proxy(function(index, el) { + el.checked = false; + }, this)); + this.checked = false; +}; + +MOJFrontend.MultiSelect.prototype.onCheckboxClick = function(e) { + if(!e.target.checked) { + this.toggleButton[0].checked = false; + this.checked = false; + } else { + if(this.checkboxes.filter(':checked').length === this.checkboxes.length) { + this.toggleButton[0].checked = true; + this.checked = true; + } + } +}; + +MOJFrontend.PasswordReveal = function(element) { + this.el = element; + var $el = $(this.el) + + if ($el.data('moj-password-reveal-initialised')) { + return + } + + $el.data('moj-password-reveal-initialised', true); + $el.attr('spellcheck', 'false'); + + $el.wrap('
'); + this.container = $(this.el).parent(); + this.createButton(); +}; + +MOJFrontend.PasswordReveal.prototype.createButton = function() { + this.button = $(''); + this.container.append(this.button); + this.button.on('click', $.proxy(this, 'onButtonClick')); +}; + +MOJFrontend.PasswordReveal.prototype.onButtonClick = function() { + if (this.el.type === 'password') { + this.el.type = 'text'; + this.button.html('Hide password'); + } else { + this.el.type = 'password'; + this.button.html('Show password'); + } +}; + +if('contentEditable' in document.documentElement) { + MOJFrontend.RichTextEditor = function(options) { + this.options = options; + this.options.toolbar = this.options.toolbar || { + bold: false, + italic: false, + underline: false, + bullets: true, + numbers: true + }; + this.textarea = this.options.textarea; + this.container = $(this.textarea).parent(); + + if (this.container.data('moj-rich-text-editor-initialised')) { + return + } + + this.container.data('moj-rich-text-editor-initialised', true); + + this.createToolbar(); + this.hideDefault(); + this.configureToolbar(); + this.keys = { + left: 37, + right: 39, + up: 38, + down: 40 + }; + this.container.on('click', '.moj-rich-text-editor__toolbar-button', $.proxy(this, 'onButtonClick')); + this.container.find('.moj-rich-text-editor__content').on('input', $.proxy(this, 'onEditorInput')); + this.container.find('label').on('click', $.proxy(this, 'onLabelClick')); + this.toolbar.on('keydown', $.proxy(this, 'onToolbarKeydown')); + }; + + MOJFrontend.RichTextEditor.prototype.onToolbarKeydown = function(e) { + var focusableButton; + switch(e.keyCode) { + case this.keys.right: + case this.keys.down: + focusableButton = this.toolbar.find('button[tabindex=0]'); + var nextButton = focusableButton.next('button'); + if(nextButton[0]) { + nextButton.focus(); + focusableButton.attr('tabindex', '-1'); + nextButton.attr('tabindex', '0'); + } + break; + case this.keys.left: + case this.keys.up: + focusableButton = this.toolbar.find('button[tabindex=0]'); + var previousButton = focusableButton.prev('button'); + if(previousButton[0]) { + previousButton.focus(); + focusableButton.attr('tabindex', '-1'); + previousButton.attr('tabindex', '0'); + } + break; + } + }; + + MOJFrontend.RichTextEditor.prototype.getToolbarHtml = function() { + var html = ''; + + html += ''; + return html; + }; + + MOJFrontend.RichTextEditor.prototype.getEnhancedHtml = function(val) { + return this.getToolbarHtml() + '
'; + }; + + MOJFrontend.RichTextEditor.prototype.hideDefault = function() { + this.textarea = this.container.find('textarea'); + this.textarea.addClass('govuk-visually-hidden'); + this.textarea.attr('aria-hidden', true); + this.textarea.attr('tabindex', '-1'); + }; + + MOJFrontend.RichTextEditor.prototype.createToolbar = function() { + this.toolbar = document.createElement('div'); + this.toolbar.className = 'moj-rich-text-editor'; + this.toolbar.innerHTML = this.getEnhancedHtml(); + this.container.append(this.toolbar); + this.toolbar = this.container.find('.moj-rich-text-editor__toolbar'); + this.container.find('.moj-rich-text-editor__content').html(this.textarea.val()); + }; + + MOJFrontend.RichTextEditor.prototype.configureToolbar = function() { + this.buttons = this.container.find('.moj-rich-text-editor__toolbar-button'); + this.buttons.prop('tabindex', '-1'); + var firstTab = this.buttons.first(); + firstTab.prop('tabindex', '0'); + }; + + MOJFrontend.RichTextEditor.prototype.onButtonClick = function(e) { + document.execCommand($(e.currentTarget).data('command'), false, null); + }; + + MOJFrontend.RichTextEditor.prototype.getContent = function() { + return this.container.find('.moj-rich-text-editor__content').html(); + }; + + MOJFrontend.RichTextEditor.prototype.onEditorInput = function(e) { + this.updateTextarea(); + }; + + MOJFrontend.RichTextEditor.prototype.updateTextarea = function() { + document.execCommand('defaultParagraphSeparator', false, 'p'); + this.textarea.val(this.getContent()); + }; + + MOJFrontend.RichTextEditor.prototype.onLabelClick = function(e) { + e.preventDefault(); + this.container.find('.moj-rich-text-editor__content').focus(); + }; + +} + +MOJFrontend.SearchToggle = function (options) { + this.options = options; + this.container = $(this.options.search.container); + this.toggleButtonContainer = $(this.options.toggleButton.container); + + if (this.container.data("moj-search-toggle-initialised")) { + return; + } + + this.container.data("moj-search-toggle-initialised", true); + + const svg = + ''; + + this.toggleButton = $( + '" + ); + this.toggleButton.on("click", $.proxy(this, "onToggleButtonClick")); + this.toggleButtonContainer.append(this.toggleButton); + $(document).on("click", this.onDocumentClick.bind(this)); + $(document).on("focusin", this.onDocumentClick.bind(this)); +}; + +MOJFrontend.SearchToggle.prototype.showMenu = function () { + this.toggleButton.attr("aria-expanded", "true"); + this.container.removeClass("moj-js-hidden"); + this.container.find("input").first().focus(); +}; + +MOJFrontend.SearchToggle.prototype.hideMenu = function () { + this.container.addClass("moj-js-hidden"); + this.toggleButton.attr("aria-expanded", "false"); +}; + +MOJFrontend.SearchToggle.prototype.onToggleButtonClick = function () { + if (this.toggleButton.attr("aria-expanded") == "false") { + this.showMenu(); + } else { + this.hideMenu(); + } +}; + +MOJFrontend.SearchToggle.prototype.onDocumentClick = function (e) { + if ( + !$.contains(this.toggleButtonContainer[0], e.target) && + !$.contains(this.container[0], e.target) + ) { + this.hideMenu(); + } +}; + +MOJFrontend.SortableTable = function(params) { + this.table = $(params.table); + + if (this.table.data('moj-search-toggle-initialised')) { + return + } + + this.table.data('moj-search-toggle-initialised', true); + + this.setupOptions(params); + this.body = this.table.find('tbody'); + this.createHeadingButtons(); + this.createStatusBox(); + this.initialiseSortedColumn(); + this.table.on('click', 'th button', $.proxy(this, 'onSortButtonClick')); +}; + +MOJFrontend.SortableTable.prototype.setupOptions = function(params) { + params = params || {}; + this.statusMessage = params.statusMessage || 'Sort by %heading% (%direction%)'; + this.ascendingText = params.ascendingText || 'ascending'; + this.descendingText = params.descendingText || 'descending'; +}; + +MOJFrontend.SortableTable.prototype.createHeadingButtons = function() { + var headings = this.table.find('thead th'); + var heading; + for(var i = 0; i < headings.length; i++) { + heading = $(headings[i]); + if(heading.attr('aria-sort')) { + this.createHeadingButton(heading, i); + } + } +}; + +MOJFrontend.SortableTable.prototype.createHeadingButton = function(heading, i) { + var text = heading.text(); + var button = $(''); + heading.text(''); + heading.append(button); +}; + +MOJFrontend.SortableTable.prototype.createStatusBox = function() { + this.status = $('
'); + this.table.parent().append(this.status); +}; + +MOJFrontend.SortableTable.prototype.initialiseSortedColumn = function () { + var rows = this.getTableRowsArray(); + + this.table.find("th") + .filter('[aria-sort="ascending"], [aria-sort="descending"]') + .first() + .each((index, el) => { + var sortDirection = $(el).attr('aria-sort'); + var columnNumber = $(el).find('button').attr('data-index'); + var sortedRows = this.sort(rows, columnNumber, sortDirection); + this.addRows(sortedRows); + }) +}; + +MOJFrontend.SortableTable.prototype.onSortButtonClick = function(e) { + var columnNumber = e.currentTarget.getAttribute('data-index'); + var sortDirection = $(e.currentTarget).parent().attr('aria-sort'); + var newSortDirection; + if(sortDirection === 'none' || sortDirection === 'descending') { + newSortDirection = 'ascending'; + } else { + newSortDirection = 'descending'; + } + var rows = this.getTableRowsArray(); + var sortedRows = this.sort(rows, columnNumber, newSortDirection); + this.addRows(sortedRows); + this.removeButtonStates(); + this.updateButtonState($(e.currentTarget), newSortDirection); +}; + +MOJFrontend.SortableTable.prototype.updateButtonState = function(button, direction) { + button.parent().attr('aria-sort', direction); + var message = this.statusMessage; + message = message.replace(/%heading%/, button.text()); + message = message.replace(/%direction%/, this[direction+'Text']); + this.status.text(message); +}; + +MOJFrontend.SortableTable.prototype.removeButtonStates = function() { + this.table.find('thead th').attr('aria-sort', 'none'); +}; + +MOJFrontend.SortableTable.prototype.addRows = function(rows) { + for(var i = 0; i < rows.length; i++) { + this.body.append(rows[i]); + } +}; + +MOJFrontend.SortableTable.prototype.getTableRowsArray = function() { + var rows = []; + var trs = this.body.find('tr'); + for (var i = 0; i < trs.length; i++) { + rows.push(trs[i]); + } + return rows; +}; + +MOJFrontend.SortableTable.prototype.sort = function(rows, columnNumber, sortDirection) { + var newRows = rows.sort($.proxy(function(rowA, rowB) { + var tdA = $(rowA).find('td,th').eq(columnNumber); + var tdB = $(rowB).find('td,th').eq(columnNumber); + var valueA = this.getCellValue(tdA); + var valueB = this.getCellValue(tdB); + if(sortDirection === 'ascending') { + if(valueA < valueB) { + return -1; + } + if(valueA > valueB) { + return 1; + } + return 0; + } else { + if(valueB < valueA) { + return -1; + } + if(valueB > valueA) { + return 1; + } + return 0; + } + }, this)); + return newRows; +}; + +MOJFrontend.SortableTable.prototype.getCellValue = function(cell) { + var val = cell.attr('data-sort-value'); + val = val || cell.html(); + if($.isNumeric(val)) { + val = parseInt(val, 10); + } + return val; +}; + +return MOJFrontend; +})); diff --git a/templates/base/base.html b/templates/base/base.html index 179f6e63..9edf8e7e 100644 --- a/templates/base/base.html +++ b/templates/base/base.html @@ -29,7 +29,7 @@
{% include "base/footer.html" %} - +