diff --git a/CHANGELOG.md b/CHANGELOG.md index 85ea6b134c..49b141ff27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * Update the pseudo underline mixin and its usage on the super navigation header ([PR #2439](https://github.com/alphagov/govuk_publishing_components/pull/2439)) * Fix layout glitch seen when scrollbar is permanently visible ([PR #2444](https://github.com/alphagov/govuk_publishing_components/pull/2444 )) * Add draggable=false to links disguised as buttons ([PR #2448](https://github.com/alphagov/govuk_publishing_components/pull/2448)) +* Get single page notification button from personalisation API on load ([PR #2443](https://github.com/alphagov/govuk_publishing_components/pull/2443)) ## 27.12.0 diff --git a/app/assets/javascripts/govuk_publishing_components/components/single-page-notification-button.js b/app/assets/javascripts/govuk_publishing_components/components/single-page-notification-button.js new file mode 100644 index 0000000000..7d55acee5b --- /dev/null +++ b/app/assets/javascripts/govuk_publishing_components/components/single-page-notification-button.js @@ -0,0 +1,44 @@ +/* global XMLHttpRequest */ +window.GOVUK = window.GOVUK || {} +window.GOVUK.Modules = window.GOVUK.Modules || {}; + +(function (Modules) { + function SinglePageNotificationButton ($module) { + this.$module = $module + this.basePath = this.$module.querySelector('input[name="base_path"]').value + this.personalisationEndpoint = '/api/personalisation/check-email-subscription?base_path=' + this.basePath + } + + SinglePageNotificationButton.prototype.init = function () { + var xhr = new XMLHttpRequest() + xhr.open('GET', this.personalisationEndpoint, true) + + xhr.onreadystatechange = function () { + if (xhr.readyState === 4 && xhr.status === 200) { + var responseText = xhr.responseText + // if response text exists and is JSON parse-able, parse the response and get the button html + if (responseText && this.responseIsJSON(responseText)) { + var newButton = JSON.parse(responseText).button_html + var html = document.createElement('div') + html.innerHTML = newButton + // test that the html returned contains the button component; if yes, swap the button for the updated version + var responseHasButton = html.querySelector('form.gem-c-single-page-notification-button') + if (responseHasButton) { + this.$module.outerHTML = newButton + } + } + } + }.bind(this) + xhr.send() + } + + SinglePageNotificationButton.prototype.responseIsJSON = function (string) { + try { + JSON.parse(string) + } catch (e) { + return false + } + return true + } + Modules.SinglePageNotificationButton = SinglePageNotificationButton +})(window.GOVUK.Modules) diff --git a/app/views/govuk_publishing_components/components/_single_page_notification_button.html.erb b/app/views/govuk_publishing_components/components/_single_page_notification_button.html.erb index e143b289b1..027da23ec3 100644 --- a/app/views/govuk_publishing_components/components/_single_page_notification_button.html.erb +++ b/app/views/govuk_publishing_components/components/_single_page_notification_button.html.erb @@ -4,12 +4,20 @@ base_path ||= nil local_assigns[:margin_bottom] ||= 3 already_subscribed ||= false + js_enhancement ||= false text ||= already_subscribed ? t('components.single_page_notification_button.unsubscribe_text') : t('components.single_page_notification_button.subscribe_text') shared_helper = GovukPublishingComponents::Presenters::SharedHelper.new(local_assigns) wrapper_classes = %w(gem-c-single-page-notification-button govuk-!-display-none-print) wrapper_classes << shared_helper.get_margin_bottom classes = "govuk-body-s gem-c-single-page-notification-button__submit" + module_names = %w(gem-track-click) + module_names << "single-page-notification-button" if js_enhancement + + data_attributes[:module] = module_names.join(" ") + data_attributes[:category] = "Single-page-notification-button" + data_attributes[:action] = already_subscribed ? "Unsubscribe button" : "Subscribe button" + data_attributes[:label] = base_path if base_path %> <% button_text = capture do %> <%= text %> diff --git a/app/views/govuk_publishing_components/components/docs/single_page_notification_button.yml b/app/views/govuk_publishing_components/components/docs/single_page_notification_button.yml index 14d223536c..f5eca9cf57 100644 --- a/app/views/govuk_publishing_components/components/docs/single_page_notification_button.yml +++ b/app/views/govuk_publishing_components/components/docs/single_page_notification_button.yml @@ -1,9 +1,10 @@ name: Single page notification button description: A button that subscribes the user to email notifications to a page body: | - By default, the component displays with the "Get emails about this page" state. The component does not render without the `base_path` parameter. + By default, the component displays with the "Get emails about this page" state. + If the `js-enhancement` flag is present, the component uses JavaScript to check if the user has already subscribed to email notifications on the current page. If yes, the state of the component updates accordingly. - The `base_path` is necessary for [checking if an email subscription is active on page load](https://github.com/alphagov/account-api/blob/main/docs/api.md#get-apipersonalisationcheck-email-subscriptiontopic_slug) and the creation/deletion of an email notification subscription. + The component does not render without the `base_path` parameter. The `base_path` is necessary for [checking if an email subscription is active on page load](https://github.com/alphagov/account-api/blob/main/docs/api.md#get-apipersonalisationcheck-email-subscriptiontopic_slug) and the creation/deletion of an email notification subscription. When the button is clicked, the `base_path` is submitted to an endpoint which proceeds to check the user's authentication status and whether they are already subscribed to the page or not. Depending on these factors, they will be routed accordingly. accessibility_criteria: | @@ -30,3 +31,9 @@ examples: data: base_path: '/current-page-path' margin_bottom: 5 + with_js_enhancement: + description: | + If the `js-enhancement` flag is present, the component uses JavaScript to check if the user has already subscribed to email notifications on the current page. If yes, the state of the component updates accordingly. + data: + base_path: '/current-page-path' + js_enhancement: true diff --git a/spec/components/single_page_notification_button_spec.rb b/spec/components/single_page_notification_button_spec.rb index 121fdcee7a..a6c1525117 100644 --- a/spec/components/single_page_notification_button_spec.rb +++ b/spec/components/single_page_notification_button_spec.rb @@ -27,8 +27,8 @@ def component_name end it "has data attributes if data_attributes is specified" do - render_component({ base_path: "/the-current-page", data_attributes: { action: "kaboom!" } }) - assert_select ".gem-c-single-page-notification-button[data-action='kaboom!']" + render_component({ base_path: "/the-current-page", data_attributes: { custom_attribute: "kaboom!" } }) + assert_select ".gem-c-single-page-notification-button[data-custom-attribute='kaboom!']" end it "sets a default bottom margin" do @@ -40,4 +40,23 @@ def component_name render_component({ base_path: "/the-current-page", margin_bottom: 9 }) assert_select '.gem-c-single-page-notification-button.govuk-\!-margin-bottom-9' end + + it "has a data-module attribute for JavaScript, if the js-enhancement flag is present" do + render_component({ base_path: "/the-current-page", js_enhancement: true }) + dom = Nokogiri::HTML(rendered) + form_data_module = dom.xpath("//form")[0].attr("data-module") + + expect(form_data_module).to include("single-page-notification-button") + end + + it "has correct attributes for tracking by default" do + render_component({ base_path: "/the-current-page" }) + assert_select ".gem-c-single-page-notification-button[data-category='Single-page-notification-button'][data-action='Subscribe button'][data-label='/the-current-page']" + end + + it "has correct attributes for tracking when already_subscribed is true" do + render_component({ base_path: "/the-current-page", already_subscribed: true }) + + assert_select ".gem-c-single-page-notification-button[data-category='Single-page-notification-button'][data-action='Unsubscribe button'][data-label='/the-current-page']" + end end diff --git a/spec/dummy/config/routes.rb b/spec/dummy/config/routes.rb index eaaa4d3c6a..0b95d844ab 100644 --- a/spec/dummy/config/routes.rb +++ b/spec/dummy/config/routes.rb @@ -19,4 +19,6 @@ get "error-summary", to: "welcome#errorsummary" get "tabsexample", to: "welcome#tabsexample" get "table", to: "welcome#table" + # we fake this URL to prevent the Single Page notification button from causing an error in the component guide + get "/api/personalisation/check-email-subscription", to: proc { [404, {}, [""]] } end diff --git a/spec/javascripts/components/single-page-notification-button-spec.js b/spec/javascripts/components/single-page-notification-button-spec.js new file mode 100644 index 0000000000..bf6da7f246 --- /dev/null +++ b/spec/javascripts/components/single-page-notification-button-spec.js @@ -0,0 +1,88 @@ +/* eslint-env jasmine */ +/* global GOVUK */ + +describe('Single page notification component', function () { + var FIXTURE + + beforeEach(function () { + FIXTURE = + '
' + + '' + + '' + + '
' + window.setFixtures(FIXTURE) + jasmine.Ajax.install() + }) + + afterEach(function () { + jasmine.Ajax.uninstall() + }) + + it('calls the personalisation API on load', function () { + initButton() + var request = jasmine.Ajax.requests.mostRecent() + expect(request.url).toBe('/api/personalisation/check-email-subscription?base_path=/current-page-path') + expect(request.method).toBe('GET') + }) + + it('replaces the button when the API returns button html', function () { + initButton() + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'application/json', + responseText: '{\n "base_path": "/current-page-path",\n "active": true,\n "button_html": "
\\n \\n \\n
"\n}' + }) + + var form = document.querySelector('form.gem-c-single-page-notification-button.new-button-for-test') + expect(form).toHaveText('Stop getting emails about this page') + }) + + it('should remain unchanged if the response is not JSON', function () { + var responseText = 'I am not JSON, actually' + initButton() + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'application/json', + responseText: responseText + }) + + var form = document.querySelector('form.gem-c-single-page-notification-button.old-button-for-test') + expect(form).toHaveText('Get emails about this page') + expect(GOVUK.Modules.SinglePageNotificationButton.prototype.responseIsJSON(responseText)).toBe(false) + }) + + it('should remain unchanged if response text is empty', function () { + var responseText = '' + initButton() + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'application/json', + responseText: responseText + }) + + var form = document.querySelector('form.gem-c-single-page-notification-button.old-button-for-test') + expect(form).toHaveText('Get emails about this page') + expect(GOVUK.Modules.SinglePageNotificationButton.prototype.responseIsJSON(responseText)).toBe(false) + }) + + it('should remain unchanged if the endpoint fails', function () { + initButton() + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 500, + contentType: 'text/plain', + responseText: '' + }) + + var form = document.querySelector('form.gem-c-single-page-notification-button.old-button-for-test') + expect(form).toHaveText('Get emails about this page') + }) + + function initButton () { + var element = document.querySelector('[data-module=single-page-notification-button]') + new GOVUK.Modules.SinglePageNotificationButton(element).init() + } +})