@benwatsonuk/govuk-pages-plugin 1.3.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +5 -1
  3. package/dist/assets/javascripts/all.js +2 -2
  4. package/dist/assets/javascripts/modules/pageOptions.js +160 -0
  5. package/dist/assets/styles/scss/_page.scss +30 -0
  6. package/dist/assets/styles/scss/_pages-plugin.scss +1 -0
  7. package/dist/functions/pages/getPages.js +10 -1
  8. package/dist/index.js +15 -1
  9. package/dist/views/components/pageItem.njk +1 -1
  10. package/dist/views/components/step.njk +1 -1
  11. package/dist/views/includes/flows.html +2 -71
  12. package/dist/views/includes/page-overview.html +15 -0
  13. package/dist/views/includes/stages.html +2 -1
  14. package/dist/views/omni-page.html +12 -5
  15. package/dist/views/page-overview.html +36 -0
  16. package/package.json +1 -1
  17. package/src/assets/javascripts/all.js +2 -2
  18. package/src/assets/javascripts/modules/pageOptions.js +160 -0
  19. package/src/assets/styles/scss/_page.scss +30 -0
  20. package/src/assets/styles/scss/_pages-plugin.scss +1 -0
  21. package/src/functions/pages/getPages.ts +11 -0
  22. package/src/index.ts +15 -1
  23. package/src/views/components/pageItem.njk +1 -1
  24. package/src/views/components/step.njk +1 -1
  25. package/src/views/includes/flows.html +2 -71
  26. package/src/views/includes/page-overview.html +15 -0
  27. package/src/views/includes/stages.html +2 -1
  28. package/src/views/omni-page.html +12 -5
  29. package/src/views/page-overview.html +36 -0
  30. package/test/data/outputs.ts +13 -1
  31. package/test/getPages.test.js +18 -0
  32. package/dist/assets/javascripts/modules/pageFlowTypeToggle.js +0 -12
  33. package/src/assets/javascripts/modules/pageFlowTypeToggle.js +0 -12
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.4.1
4
+
5
+ ### Fixes
6
+ - Incompatible with updated GOVUK FRONTEND - issue resolved with this fix.
7
+
8
+
9
+ ## v1.4.0
10
+
11
+ ### Features
12
+ - 'Overview Page' added to offer a step between the index/flow and the page itself. This allows users to zoom in to the page and its detail without being dropped directly to the page. It is a 'slightly elevated' view of the page.
13
+
3
14
  ## v1.3.0
4
15
 
5
16
  ### Features
package/README.md CHANGED
@@ -22,7 +22,7 @@ npm install https://github.com/benwatsonuk/govuk-pages-plugin
22
22
  #### Basic Setup
23
23
 
24
24
  ```js
25
- const { govukPagesPlugin } = require('@benwatsonuk/govuk-pages-plugin')
25
+ const { govukPagesPlugin, pageOverview } = require('@benwatsonuk/govuk-pages-plugin')
26
26
  ```
27
27
 
28
28
  If you use multiple routes files, include the above code on the pages that you wish to use the features of the plugin on.
@@ -31,6 +31,10 @@ Then for the most basic implementation declare the route that you wish to use an
31
31
 
32
32
  ```js
33
33
  router.use(`/path-of-your-choice/pages`, govukPagesPlugin(pages, stages, flows))
34
+
35
+ router.get(`/path-of-your-choice/page/:pageId`, (req, res) => {
36
+ pageOverview(Number(req.params.pageId), pages, req, res)
37
+ })
34
38
  ```
35
39
 
36
40
  For all functions, you MUST provide a valid `pages` property (see below), `stages` and `flows` are always optional but provide some useful functionality. See below about how to use these optionally.
@@ -1,8 +1,8 @@
1
1
  import { defineCustomElements } from "./vendors/ukic/loader.js";
2
- import { InitPageFlowTypeToggles } from "./modules/pageFlowTypeToggle.js"
2
+ import { InitIcSwitches } from "./modules/pageOptions.js"
3
3
 
4
4
  document.addEventListener("DOMContentLoaded", () => {
5
5
  defineCustomElements();
6
- InitPageFlowTypeToggles();
6
+ InitIcSwitches();
7
7
  });
8
8
 
@@ -0,0 +1,160 @@
1
+ /* ============================================================
2
+ Actions
3
+ ============================================================ */
4
+
5
+ const ACTIONS = {
6
+ toggleElement: (checked, cfg) => {
7
+ const el = document.getElementById(cfg.actionTarget);
8
+ if (!el) return;
9
+ el.classList.toggle("govuk-visually-hidden", !checked);
10
+ }
11
+ };
12
+
13
+ /* ============================================================
14
+ Switch configuration
15
+ ============================================================ */
16
+
17
+ const SWITCH_HANDLERS = {
18
+ showPageOptions: {
19
+ targets: ".govuk-pages-plugin__page-options",
20
+ className: "govuk-visually-hidden",
21
+ inverse: true,
22
+ onToggleOn: exampleFunc,
23
+ onToggleOff: exampleFunc
24
+ },
25
+
26
+ showList: {
27
+ targets: ".govuk-pages-plugin__flows__flow",
28
+ className: "govuk-pages-plugin__flows__flow--list",
29
+ hideControls: ["showFullWidth", "showInline"],
30
+ hideOnChecked: true
31
+ },
32
+
33
+ showInline: {
34
+ targets: ".govuk-pages-plugin__flows__flow",
35
+ className: "govuk-pages-plugin__flows__flow--inline",
36
+ hideControls: ["showFullWidth"],
37
+ hideOnChecked: false
38
+ },
39
+
40
+ showFullWidth: {
41
+ targets: ".govuk-pages-plugin__flows__flow",
42
+ className: "govuk-pages-plugin__flows__flow--full-width"
43
+ }
44
+ };
45
+
46
+ /* ============================================================
47
+ Helper functions
48
+ ============================================================ */
49
+
50
+ const toggleClassOnTargets = (selector, className, add) => {
51
+ document.querySelectorAll(selector).forEach(el => {
52
+ el.classList.toggle(className, add);
53
+ });
54
+ };
55
+
56
+ const setControlVisibility = (ids, hide) => {
57
+ ids.forEach(id => {
58
+ const el = document.getElementById(id);
59
+ if (el) el.classList.toggle("govuk-visually-hidden", hide);
60
+ });
61
+ };
62
+
63
+ /* ============================================================
64
+ Main icChange handler
65
+ ============================================================ */
66
+
67
+ const handleIcChange = ev => {
68
+ const src = ev.target;
69
+ if (!src || !src.id) return;
70
+
71
+ const cfg = SWITCH_HANDLERS[src.id];
72
+ if (!cfg) return;
73
+
74
+ const checked = !!ev.detail?.checked;
75
+
76
+ // Toggle classes
77
+ if (cfg.targets && cfg.className) {
78
+ const add = cfg.inverse ? !checked : checked;
79
+ toggleClassOnTargets(cfg.targets, cfg.className, add);
80
+ }
81
+
82
+ // Hide/show related controls
83
+ if (cfg.hideControls) {
84
+ const hide = cfg.hideOnChecked ? checked : !checked;
85
+ setControlVisibility(cfg.hideControls, hide);
86
+ }
87
+
88
+ // Run actions
89
+ const runAction = action => {
90
+ if (typeof action === "string") action = ACTIONS[action];
91
+ if (typeof action === "function") {
92
+ try {
93
+ action(checked, cfg, ev);
94
+ } catch (e) {
95
+ console.error("Switch action error", e);
96
+ }
97
+ }
98
+ };
99
+
100
+ if (checked && cfg.onToggleOn) runAction(cfg.onToggleOn);
101
+ if (!checked && cfg.onToggleOff) runAction(cfg.onToggleOff);
102
+ };
103
+
104
+ /* ============================================================
105
+ Init ic-switch handling
106
+ ============================================================ */
107
+
108
+ export const InitIcSwitches = () => {
109
+ document.addEventListener("icChange", handleIcChange);
110
+ watchForPageOverviewToggle();
111
+ };
112
+
113
+ /* ============================================================
114
+ Page overview toggle logic
115
+ ============================================================ */
116
+
117
+ export const updateFlowLinks = useOverview => {
118
+ document.querySelectorAll(".govuk-pages-plugin__pages__link").forEach(link => {
119
+ const { overviewPageLink, directLink } = link.dataset;
120
+ if (!overviewPageLink || !directLink) return;
121
+ link.href = useOverview ? overviewPageLink : directLink;
122
+ });
123
+ };
124
+
125
+ const watchForPageOverviewToggle = () => {
126
+ const init = toggle => {
127
+ if (toggle.dataset.bound) return;
128
+ toggle.dataset.bound = "true";
129
+
130
+ updateFlowLinks(toggle.attributes.checked);
131
+
132
+ toggle.addEventListener("icChange", e => {
133
+ updateFlowLinks(e.detail.checked);
134
+ });
135
+ };
136
+
137
+ const existing = document.querySelector("#usePageOverview");
138
+ if (existing) return init(existing);
139
+
140
+ const observer = new MutationObserver(() => {
141
+ const toggle = document.querySelector("#usePageOverview");
142
+ if (!toggle) return;
143
+
144
+ observer.disconnect();
145
+ init(toggle);
146
+ });
147
+
148
+ observer.observe(document.body, {
149
+ childList: true,
150
+ subtree: true
151
+ });
152
+ };
153
+
154
+ /* ============================================================
155
+ Example custom action
156
+ ============================================================ */
157
+
158
+ function exampleFunc(checked, cfg){
159
+ console.log("Switch toggled:", checked, cfg);
160
+ };
@@ -0,0 +1,30 @@
1
+ .govuk-pages-plugin__page {
2
+
3
+ &__preview {
4
+ width: 100%;
5
+ height: 400px;
6
+ overflow: hidden;
7
+ border: 1px solid $govuk-border-colour;
8
+ border-radius: 4px;
9
+ &__screen {
10
+ width: 200%;
11
+ height: 800px;
12
+ transform: scale(0.5);
13
+ transform-origin: 0 0;
14
+ background: none;
15
+ border: 0;
16
+ }
17
+ }
18
+
19
+ &__header {
20
+ display: flex;
21
+ align-items: center;
22
+ justify-content: space-between;
23
+
24
+ .govuk-button {
25
+ @include govuk-responsive-margin(2, "top");
26
+ @include govuk-responsive-margin(2, "bottom");
27
+ }
28
+ }
29
+
30
+ }
@@ -1,4 +1,5 @@
1
1
  @import "pages";
2
+ @import "page";
2
3
  @import "stages";
3
4
  @import "flows";
4
5
  @import "overview-page";
@@ -1,9 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getPages = void 0;
3
+ exports.getPage = exports.getPages = void 0;
4
4
  const validate_1 = require("../../validate");
5
5
  const getPages = (pages) => {
6
6
  const validatedPages = (0, validate_1.validatePagesArray)(pages);
7
7
  return validatedPages;
8
8
  };
9
9
  exports.getPages = getPages;
10
+ const getPage = (pageId, pages) => {
11
+ const validatedPages = (0, validate_1.validatePagesArray)(pages);
12
+ const page = validatedPages.find(p => p.id === pageId);
13
+ if (page) {
14
+ return page;
15
+ }
16
+ throw new Error(`Page ID ${pageId} does not exist`);
17
+ };
18
+ exports.getPage = getPage;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.govukPagesPlugin = exports.flowIndex = exports.flowIndexData = exports.stageIndex = exports.stageIndexData = exports.pageIndex = exports.pageIndexData = exports.omniPage = void 0;
3
+ exports.govukPagesPlugin = exports.flowIndex = exports.flowIndexData = exports.stageIndex = exports.stageIndexData = exports.pageIndex = exports.pageIndexData = exports.pageOverview = exports.pageOverviewData = exports.omniPage = void 0;
4
4
  const express_1 = require("express");
5
5
  const getPages_1 = require("./functions/pages/getPages");
6
6
  const getStages_1 = require("./functions/stages/getStages");
@@ -30,6 +30,20 @@ const omniPage = (pages, stages, flows) => {
30
30
  }
31
31
  };
32
32
  exports.omniPage = omniPage;
33
+ // Single Page Overview
34
+ const pageOverviewData = (pageId, pages) => {
35
+ const pagesData = (0, getPages_1.getPages)(pages);
36
+ return pagesData.find(page => pageId === page.id);
37
+ };
38
+ exports.pageOverviewData = pageOverviewData;
39
+ const pageOverview = (pageId, pages, req, res) => {
40
+ const page = pages.find(p => p.id === pageId);
41
+ if (!page) {
42
+ return res.status(404).render("page-overview", { noPage: true });
43
+ }
44
+ res.render("page-overview", { page });
45
+ };
46
+ exports.pageOverview = pageOverview;
33
47
  // Pages
34
48
  const pageIndexData = (pages) => {
35
49
  return (0, getPages_1.getPages)(pages);
@@ -1,7 +1,7 @@
1
1
  {% macro pageItem(params) %}
2
2
 
3
3
  <div class="govuk-pages-plugin__page__item">
4
- <a href="{{ params.page.route }}">
4
+ <a class="govuk-pages-plugin__pages__link" href="{{ params.page.route }}" data-overview-page-link="./page/{{params.page.id}}" data-direct-link="{{ params.page.route }}">
5
5
  {{ params.page.title }}
6
6
  </a>
7
7
  </div>
@@ -1,6 +1,6 @@
1
1
  {% macro govukPagesPluginStep(step) %}
2
2
  <div class="govuk-pages-plugin__flows__step">
3
- <a href="{{ step.route }}" class="govuk-pages-plugin__flows__step">
3
+ <a href="{{ step.route }}" class="govuk-pages-plugin__flows__step govuk-pages-plugin__pages__link" data-overview-page-link="./page/{{step.id}}" data-direct-link="{{ step.route }}">
4
4
  <div class="govuk-pages-plugin__screen-icon {% if step.type %}govuk-pages-plugin__screen-icon--{{ step.type }}{% endif%}"></div>
5
5
  {{ step.title }}
6
6
  </a>
@@ -6,11 +6,10 @@
6
6
  }) }}
7
7
 
8
8
  <div class="govuk-pages-plugin__page-options__trigger">
9
- <ic-switch id="showPageOptions" label="Show page options" checked="false" size=""></ic-switch>
9
+ <ic-switch id="showPageOptions" label="Show Flow options" checked="false" size=""></ic-switch>
10
10
  </div>
11
11
 
12
12
  <div class="govuk-pages-plugin__page-options govuk-visually-hidden">
13
- <h2 class="govuk-heading-s">Page options</h2>
14
13
  <div class="govuk-pages-plugin__page-options__container">
15
14
  <ic-switch id="showList" label="Show Flows as list" checked="false" size="small" helper-text="Toggle between vertical list and horizontal flow"></ic-switch>
16
15
  {% if not hasStages %}
@@ -19,75 +18,7 @@
19
18
  <ic-switch id="showFullWidth" label="Show full width" checked="false" size="small" helper-text="Allow large flows to extend beyond edge of screen"></ic-switch>
20
19
 
21
20
  <script>
22
- const switchEl = document.querySelectorAll('ic-switch');
23
- switchEl.forEach( el => { addEventListener('icChange', (ev) => {
24
- if (ev.srcElement.id === 'showList') {
25
- const target = document.querySelectorAll(".govuk-pages-plugin__flows__flow");
26
- const classToUse = 'govuk-pages-plugin__flows__flow--list'
27
- const showFullWidth = document.getElementById("showFullWidth");
28
- const showInline = document.getElementById("showInline");
29
- if (ev.detail.checked === true) {
30
- target.forEach(a => {
31
- a.classList.add(classToUse)
32
- if (showFullWidth) {
33
- showFullWidth.classList.add("govuk-visually-hidden");
34
- }
35
- if (showInline) {
36
- showInline.classList.add("govuk-visually-hidden");
37
- }
38
- })
39
- } else {
40
- target.forEach(a => {
41
- a.classList.remove(classToUse)
42
- if (showFullWidth) {
43
- showFullWidth.classList.remove("govuk-visually-hidden");
44
- }
45
- if (showInline) {
46
- showInline.classList.remove("govuk-visually-hidden");
47
- }
48
- })
49
- }
50
- } else if (ev.srcElement.id === 'showInline') {
51
- const target = document.querySelectorAll(".govuk-pages-plugin__flows__flow");
52
- const classToUse = 'govuk-pages-plugin__flows__flow--inline'
53
- if (ev.detail.checked === true) {
54
- target.forEach(a => {
55
- a.classList.add(classToUse)
56
- document.getElementById('showFullWidth').classList.remove('govuk-visually-hidden')
57
- })
58
- } else {
59
- target.forEach(a => {
60
- a.classList.remove(classToUse)
61
- document.getElementById('showFullWidth').classList.add('govuk-visually-hidden')
62
- })
63
- }
64
- } else if (ev.srcElement.id === 'showPageOptions') {
65
- const target = document.querySelectorAll(".govuk-pages-plugin__page-options");
66
- const classToUse = 'govuk-visually-hidden'
67
- if (ev.detail.checked === true) {
68
- target.forEach(a => {
69
- a.classList.remove(classToUse)
70
- })
71
- } else {
72
- target.forEach(a => {
73
- a.classList.add(classToUse)
74
- })
75
- }
76
- } else if (ev.srcElement.id === 'showFullWidth') {
77
- const target = document.querySelectorAll(".govuk-pages-plugin__flows__flow");
78
- const classToUse = 'govuk-pages-plugin__flows__flow--full-width'
79
- if (ev.detail.checked === true) {
80
- target.forEach(a => {
81
- a.classList.add(classToUse)
82
- })
83
- } else {
84
- target.forEach(a => {
85
- a.classList.remove(classToUse)
86
- })
87
- }
88
- }
89
- })
90
- })
21
+
91
22
  </script>
92
23
  </div>
93
24
  </div>
@@ -0,0 +1,15 @@
1
+ {% if page.description %}
2
+ <p>{{ page.description }}</p>
3
+ {% endif %}
4
+
5
+ <div class="govuk-grid-row">
6
+ <div class="govuk-grid-column-two-thirds">
7
+ <div class="govuk-pages-plugin__page__preview">
8
+ <iframe class="govuk-pages-plugin__page__preview__screen" src="../{{page.route}}"></iframe>
9
+ </div>
10
+ </div>
11
+ <div class="govuk-grid-column-one-third">
12
+
13
+ <!-- <a href="../{{ page.route }}">Go to page</a> -->
14
+ </div>
15
+ </div>
@@ -9,7 +9,8 @@
9
9
  {% endif %}
10
10
  <ol>
11
11
  {% for page in stage.pages %}
12
- <li><a href="{{ page.route }}">{{ page.title }}</a>{% if page.description %} - <span class="govuk-body-s">{{ page.description }}</span> {% endif %}</li>
12
+ <li><a class="govuk-pages-plugin__pages__link" href="{{ page.route }}" data-overview-page-link="./page/{{page.id}}" data-direct-link="{{ page.route }}">
13
+ {{ page.title }}</a>{% if page.description %} - <span class="govuk-body-s">{{ page.description }}</span> {% endif %}</li>
13
14
  {% endfor %}
14
15
  </ol>
15
16
  <hr/>
@@ -9,12 +9,19 @@
9
9
  <div class="govuk-pages-plugin">
10
10
  <ic-theme theme="light">
11
11
  <h1 class="govuk-heading-l">Pages</h1>
12
- {% from "govuk/components/details/macro.njk" import govukDetails %}
13
12
 
14
- <!-- {{ govukDetails({
15
- summaryText: "What is the 'Stage Index'?",
16
- html: "<p>This page contains links to tracked pages in this prototype, these pages are grouped by the 'stages' that they are assigned to. It is useful for navigating the prototype without having to go through flows.</p><p>'Stages' are defined by the plugin user but can be throught about as logical groups of pages that work to achieve a common goal, such as 'authenticating' or 'registering'.</p><p class='govuk-body-s'> Note, the prototype might contain more pages than are listed here.</p>"
17
- }) }} -->
13
+ {% set pageOptions %}
14
+ <div class="govuk-pages-plugin__page-options__container">
15
+ <ic-switch id="usePageOverview" label="Page links go to an overview page" checked="true" size="small" helper-text="If checked, all links to pages will go to an overview page rather than directly to referenced page"></ic-switch>
16
+ </div>
17
+ {% endset %}
18
+
19
+ {{ govukDetails({
20
+ summaryText: "Page options",
21
+ html: pageOptions
22
+ })}}
23
+
24
+ {% from "govuk/components/details/macro.njk" import govukDetails %}
18
25
 
19
26
  {% set tabItems = [] %}
20
27
 
@@ -0,0 +1,36 @@
1
+ {% extends "./layouts/govuk-pages-plugin.html" %}
2
+
3
+ {% block pageTitle %}
4
+ Page Overview
5
+ {% endblock %}
6
+
7
+ {% block beforeContent %}
8
+ <div class="govuk-pages-plugin__page__header">
9
+ {{ govukBackLink({
10
+ href: "javascript:history.back(-1)"
11
+ })}}
12
+
13
+ {{ govukButton({
14
+ text: "Go to page",
15
+ href: "../" + page.route,
16
+ isStartButton: true
17
+ })}}
18
+ </div>
19
+ {% endblock %}
20
+
21
+ {% block content %}
22
+
23
+ <div class="govuk-pages-plugin">
24
+ {% if noPage %}
25
+ <h1 class="govuk-heading-l">Oops, no page exists with that ID!</h1>
26
+ {% else %}
27
+ <span class="govuk-caption-m">Page Overview</span>
28
+ <h1 class="govuk-heading-l">{{ page.title }}</h1>
29
+
30
+ {% include './includes/page-overview.html' %}
31
+
32
+ {% endif %}
33
+
34
+ </div>
35
+
36
+ {% endblock %}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@benwatsonuk/govuk-pages-plugin",
3
3
  "author": "BenWatsonUK",
4
4
  "description": "A plugin to visualise selected pages of a GOV.UK prototype with a linear user journey or page index.",
5
- "version": "1.3.0",
5
+ "version": "1.4.1",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "homepage": "https://github.com/benwatsonuk/govuk-pages-plugin",
@@ -1,8 +1,8 @@
1
1
  import { defineCustomElements } from "./vendors/ukic/loader.js";
2
- import { InitPageFlowTypeToggles } from "./modules/pageFlowTypeToggle.js"
2
+ import { InitIcSwitches } from "./modules/pageOptions.js"
3
3
 
4
4
  document.addEventListener("DOMContentLoaded", () => {
5
5
  defineCustomElements();
6
- InitPageFlowTypeToggles();
6
+ InitIcSwitches();
7
7
  });
8
8
 
@@ -0,0 +1,160 @@
1
+ /* ============================================================
2
+ Actions
3
+ ============================================================ */
4
+
5
+ const ACTIONS = {
6
+ toggleElement: (checked, cfg) => {
7
+ const el = document.getElementById(cfg.actionTarget);
8
+ if (!el) return;
9
+ el.classList.toggle("govuk-visually-hidden", !checked);
10
+ }
11
+ };
12
+
13
+ /* ============================================================
14
+ Switch configuration
15
+ ============================================================ */
16
+
17
+ const SWITCH_HANDLERS = {
18
+ showPageOptions: {
19
+ targets: ".govuk-pages-plugin__page-options",
20
+ className: "govuk-visually-hidden",
21
+ inverse: true,
22
+ onToggleOn: exampleFunc,
23
+ onToggleOff: exampleFunc
24
+ },
25
+
26
+ showList: {
27
+ targets: ".govuk-pages-plugin__flows__flow",
28
+ className: "govuk-pages-plugin__flows__flow--list",
29
+ hideControls: ["showFullWidth", "showInline"],
30
+ hideOnChecked: true
31
+ },
32
+
33
+ showInline: {
34
+ targets: ".govuk-pages-plugin__flows__flow",
35
+ className: "govuk-pages-plugin__flows__flow--inline",
36
+ hideControls: ["showFullWidth"],
37
+ hideOnChecked: false
38
+ },
39
+
40
+ showFullWidth: {
41
+ targets: ".govuk-pages-plugin__flows__flow",
42
+ className: "govuk-pages-plugin__flows__flow--full-width"
43
+ }
44
+ };
45
+
46
+ /* ============================================================
47
+ Helper functions
48
+ ============================================================ */
49
+
50
+ const toggleClassOnTargets = (selector, className, add) => {
51
+ document.querySelectorAll(selector).forEach(el => {
52
+ el.classList.toggle(className, add);
53
+ });
54
+ };
55
+
56
+ const setControlVisibility = (ids, hide) => {
57
+ ids.forEach(id => {
58
+ const el = document.getElementById(id);
59
+ if (el) el.classList.toggle("govuk-visually-hidden", hide);
60
+ });
61
+ };
62
+
63
+ /* ============================================================
64
+ Main icChange handler
65
+ ============================================================ */
66
+
67
+ const handleIcChange = ev => {
68
+ const src = ev.target;
69
+ if (!src || !src.id) return;
70
+
71
+ const cfg = SWITCH_HANDLERS[src.id];
72
+ if (!cfg) return;
73
+
74
+ const checked = !!ev.detail?.checked;
75
+
76
+ // Toggle classes
77
+ if (cfg.targets && cfg.className) {
78
+ const add = cfg.inverse ? !checked : checked;
79
+ toggleClassOnTargets(cfg.targets, cfg.className, add);
80
+ }
81
+
82
+ // Hide/show related controls
83
+ if (cfg.hideControls) {
84
+ const hide = cfg.hideOnChecked ? checked : !checked;
85
+ setControlVisibility(cfg.hideControls, hide);
86
+ }
87
+
88
+ // Run actions
89
+ const runAction = action => {
90
+ if (typeof action === "string") action = ACTIONS[action];
91
+ if (typeof action === "function") {
92
+ try {
93
+ action(checked, cfg, ev);
94
+ } catch (e) {
95
+ console.error("Switch action error", e);
96
+ }
97
+ }
98
+ };
99
+
100
+ if (checked && cfg.onToggleOn) runAction(cfg.onToggleOn);
101
+ if (!checked && cfg.onToggleOff) runAction(cfg.onToggleOff);
102
+ };
103
+
104
+ /* ============================================================
105
+ Init ic-switch handling
106
+ ============================================================ */
107
+
108
+ export const InitIcSwitches = () => {
109
+ document.addEventListener("icChange", handleIcChange);
110
+ watchForPageOverviewToggle();
111
+ };
112
+
113
+ /* ============================================================
114
+ Page overview toggle logic
115
+ ============================================================ */
116
+
117
+ export const updateFlowLinks = useOverview => {
118
+ document.querySelectorAll(".govuk-pages-plugin__pages__link").forEach(link => {
119
+ const { overviewPageLink, directLink } = link.dataset;
120
+ if (!overviewPageLink || !directLink) return;
121
+ link.href = useOverview ? overviewPageLink : directLink;
122
+ });
123
+ };
124
+
125
+ const watchForPageOverviewToggle = () => {
126
+ const init = toggle => {
127
+ if (toggle.dataset.bound) return;
128
+ toggle.dataset.bound = "true";
129
+
130
+ updateFlowLinks(toggle.attributes.checked);
131
+
132
+ toggle.addEventListener("icChange", e => {
133
+ updateFlowLinks(e.detail.checked);
134
+ });
135
+ };
136
+
137
+ const existing = document.querySelector("#usePageOverview");
138
+ if (existing) return init(existing);
139
+
140
+ const observer = new MutationObserver(() => {
141
+ const toggle = document.querySelector("#usePageOverview");
142
+ if (!toggle) return;
143
+
144
+ observer.disconnect();
145
+ init(toggle);
146
+ });
147
+
148
+ observer.observe(document.body, {
149
+ childList: true,
150
+ subtree: true
151
+ });
152
+ };
153
+
154
+ /* ============================================================
155
+ Example custom action
156
+ ============================================================ */
157
+
158
+ function exampleFunc(checked, cfg){
159
+ console.log("Switch toggled:", checked, cfg);
160
+ };
@@ -0,0 +1,30 @@
1
+ .govuk-pages-plugin__page {
2
+
3
+ &__preview {
4
+ width: 100%;
5
+ height: 400px;
6
+ overflow: hidden;
7
+ border: 1px solid $govuk-border-colour;
8
+ border-radius: 4px;
9
+ &__screen {
10
+ width: 200%;
11
+ height: 800px;
12
+ transform: scale(0.5);
13
+ transform-origin: 0 0;
14
+ background: none;
15
+ border: 0;
16
+ }
17
+ }
18
+
19
+ &__header {
20
+ display: flex;
21
+ align-items: center;
22
+ justify-content: space-between;
23
+
24
+ .govuk-button {
25
+ @include govuk-responsive-margin(2, "top");
26
+ @include govuk-responsive-margin(2, "bottom");
27
+ }
28
+ }
29
+
30
+ }
@@ -1,4 +1,5 @@
1
1
  @import "pages";
2
+ @import "page";
2
3
  @import "stages";
3
4
  @import "flows";
4
5
  @import "overview-page";
@@ -4,4 +4,15 @@ import { validatePagesArray } from "../../validate"
4
4
  export const getPages = (pages: PagesArray) => {
5
5
  const validatedPages = validatePagesArray(pages)
6
6
  return validatedPages
7
+ }
8
+
9
+ export const getPage = (pageId: number, pages: PagesArray) => {
10
+ const validatedPages = validatePagesArray(pages)
11
+ const page = validatedPages.find(p => p.id === pageId)
12
+ if (page) {
13
+ return page
14
+ }
15
+ throw new Error(
16
+ `Page ID ${pageId} does not exist`
17
+ )
7
18
  }
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@ import {Router} from "express"
2
2
  import { getPages } from "./functions/pages/getPages"
3
3
  import { getStagesWithPages } from "./functions/stages/getStages"
4
4
  import { getFlows } from "./functions/flows/getFlows"
5
- import { PagesArray, StagesArray, PageFlowArray } from "./types"
5
+ import { PagesArray, StagesArray, PageFlowArray, PageOutput } from "./types"
6
6
 
7
7
  /*--- UTILITIES (used by supplied routes AND made available to plugin users) ---*/
8
8
 
@@ -27,6 +27,20 @@ export const omniPage = (pages: PagesArray, stages?: StagesArray, flows?: PageFl
27
27
  }
28
28
  }
29
29
 
30
+ // Single Page Overview
31
+ export const pageOverviewData = (pageId: number, pages: PagesArray): PageOutput | undefined => {
32
+ const pagesData = getPages(pages)
33
+ return pagesData.find(page => pageId === page.id)
34
+ }
35
+
36
+ export const pageOverview = (pageId: number, pages: PagesArray, req: any, res: any) => {
37
+ const page = pages.find(p => p.id === pageId);
38
+ if (!page) {
39
+ return res.status(404).render("page-overview", {noPage: true});
40
+ }
41
+ res.render("page-overview", { page });
42
+ }
43
+
30
44
  // Pages
31
45
  export const pageIndexData = (pages: PagesArray) => {
32
46
  return getPages(pages)
@@ -1,7 +1,7 @@
1
1
  {% macro pageItem(params) %}
2
2
 
3
3
  <div class="govuk-pages-plugin__page__item">
4
- <a href="{{ params.page.route }}">
4
+ <a class="govuk-pages-plugin__pages__link" href="{{ params.page.route }}" data-overview-page-link="./page/{{params.page.id}}" data-direct-link="{{ params.page.route }}">
5
5
  {{ params.page.title }}
6
6
  </a>
7
7
  </div>
@@ -1,6 +1,6 @@
1
1
  {% macro govukPagesPluginStep(step) %}
2
2
  <div class="govuk-pages-plugin__flows__step">
3
- <a href="{{ step.route }}" class="govuk-pages-plugin__flows__step">
3
+ <a href="{{ step.route }}" class="govuk-pages-plugin__flows__step govuk-pages-plugin__pages__link" data-overview-page-link="./page/{{step.id}}" data-direct-link="{{ step.route }}">
4
4
  <div class="govuk-pages-plugin__screen-icon {% if step.type %}govuk-pages-plugin__screen-icon--{{ step.type }}{% endif%}"></div>
5
5
  {{ step.title }}
6
6
  </a>
@@ -6,11 +6,10 @@
6
6
  }) }}
7
7
 
8
8
  <div class="govuk-pages-plugin__page-options__trigger">
9
- <ic-switch id="showPageOptions" label="Show page options" checked="false" size=""></ic-switch>
9
+ <ic-switch id="showPageOptions" label="Show Flow options" checked="false" size=""></ic-switch>
10
10
  </div>
11
11
 
12
12
  <div class="govuk-pages-plugin__page-options govuk-visually-hidden">
13
- <h2 class="govuk-heading-s">Page options</h2>
14
13
  <div class="govuk-pages-plugin__page-options__container">
15
14
  <ic-switch id="showList" label="Show Flows as list" checked="false" size="small" helper-text="Toggle between vertical list and horizontal flow"></ic-switch>
16
15
  {% if not hasStages %}
@@ -19,75 +18,7 @@
19
18
  <ic-switch id="showFullWidth" label="Show full width" checked="false" size="small" helper-text="Allow large flows to extend beyond edge of screen"></ic-switch>
20
19
 
21
20
  <script>
22
- const switchEl = document.querySelectorAll('ic-switch');
23
- switchEl.forEach( el => { addEventListener('icChange', (ev) => {
24
- if (ev.srcElement.id === 'showList') {
25
- const target = document.querySelectorAll(".govuk-pages-plugin__flows__flow");
26
- const classToUse = 'govuk-pages-plugin__flows__flow--list'
27
- const showFullWidth = document.getElementById("showFullWidth");
28
- const showInline = document.getElementById("showInline");
29
- if (ev.detail.checked === true) {
30
- target.forEach(a => {
31
- a.classList.add(classToUse)
32
- if (showFullWidth) {
33
- showFullWidth.classList.add("govuk-visually-hidden");
34
- }
35
- if (showInline) {
36
- showInline.classList.add("govuk-visually-hidden");
37
- }
38
- })
39
- } else {
40
- target.forEach(a => {
41
- a.classList.remove(classToUse)
42
- if (showFullWidth) {
43
- showFullWidth.classList.remove("govuk-visually-hidden");
44
- }
45
- if (showInline) {
46
- showInline.classList.remove("govuk-visually-hidden");
47
- }
48
- })
49
- }
50
- } else if (ev.srcElement.id === 'showInline') {
51
- const target = document.querySelectorAll(".govuk-pages-plugin__flows__flow");
52
- const classToUse = 'govuk-pages-plugin__flows__flow--inline'
53
- if (ev.detail.checked === true) {
54
- target.forEach(a => {
55
- a.classList.add(classToUse)
56
- document.getElementById('showFullWidth').classList.remove('govuk-visually-hidden')
57
- })
58
- } else {
59
- target.forEach(a => {
60
- a.classList.remove(classToUse)
61
- document.getElementById('showFullWidth').classList.add('govuk-visually-hidden')
62
- })
63
- }
64
- } else if (ev.srcElement.id === 'showPageOptions') {
65
- const target = document.querySelectorAll(".govuk-pages-plugin__page-options");
66
- const classToUse = 'govuk-visually-hidden'
67
- if (ev.detail.checked === true) {
68
- target.forEach(a => {
69
- a.classList.remove(classToUse)
70
- })
71
- } else {
72
- target.forEach(a => {
73
- a.classList.add(classToUse)
74
- })
75
- }
76
- } else if (ev.srcElement.id === 'showFullWidth') {
77
- const target = document.querySelectorAll(".govuk-pages-plugin__flows__flow");
78
- const classToUse = 'govuk-pages-plugin__flows__flow--full-width'
79
- if (ev.detail.checked === true) {
80
- target.forEach(a => {
81
- a.classList.add(classToUse)
82
- })
83
- } else {
84
- target.forEach(a => {
85
- a.classList.remove(classToUse)
86
- })
87
- }
88
- }
89
- })
90
- })
21
+
91
22
  </script>
92
23
  </div>
93
24
  </div>
@@ -0,0 +1,15 @@
1
+ {% if page.description %}
2
+ <p>{{ page.description }}</p>
3
+ {% endif %}
4
+
5
+ <div class="govuk-grid-row">
6
+ <div class="govuk-grid-column-two-thirds">
7
+ <div class="govuk-pages-plugin__page__preview">
8
+ <iframe class="govuk-pages-plugin__page__preview__screen" src="../{{page.route}}"></iframe>
9
+ </div>
10
+ </div>
11
+ <div class="govuk-grid-column-one-third">
12
+
13
+ <!-- <a href="../{{ page.route }}">Go to page</a> -->
14
+ </div>
15
+ </div>
@@ -9,7 +9,8 @@
9
9
  {% endif %}
10
10
  <ol>
11
11
  {% for page in stage.pages %}
12
- <li><a href="{{ page.route }}">{{ page.title }}</a>{% if page.description %} - <span class="govuk-body-s">{{ page.description }}</span> {% endif %}</li>
12
+ <li><a class="govuk-pages-plugin__pages__link" href="{{ page.route }}" data-overview-page-link="./page/{{page.id}}" data-direct-link="{{ page.route }}">
13
+ {{ page.title }}</a>{% if page.description %} - <span class="govuk-body-s">{{ page.description }}</span> {% endif %}</li>
13
14
  {% endfor %}
14
15
  </ol>
15
16
  <hr/>
@@ -9,12 +9,19 @@
9
9
  <div class="govuk-pages-plugin">
10
10
  <ic-theme theme="light">
11
11
  <h1 class="govuk-heading-l">Pages</h1>
12
- {% from "govuk/components/details/macro.njk" import govukDetails %}
13
12
 
14
- <!-- {{ govukDetails({
15
- summaryText: "What is the 'Stage Index'?",
16
- html: "<p>This page contains links to tracked pages in this prototype, these pages are grouped by the 'stages' that they are assigned to. It is useful for navigating the prototype without having to go through flows.</p><p>'Stages' are defined by the plugin user but can be throught about as logical groups of pages that work to achieve a common goal, such as 'authenticating' or 'registering'.</p><p class='govuk-body-s'> Note, the prototype might contain more pages than are listed here.</p>"
17
- }) }} -->
13
+ {% set pageOptions %}
14
+ <div class="govuk-pages-plugin__page-options__container">
15
+ <ic-switch id="usePageOverview" label="Page links go to an overview page" checked="true" size="small" helper-text="If checked, all links to pages will go to an overview page rather than directly to referenced page"></ic-switch>
16
+ </div>
17
+ {% endset %}
18
+
19
+ {{ govukDetails({
20
+ summaryText: "Page options",
21
+ html: pageOptions
22
+ })}}
23
+
24
+ {% from "govuk/components/details/macro.njk" import govukDetails %}
18
25
 
19
26
  {% set tabItems = [] %}
20
27
 
@@ -0,0 +1,36 @@
1
+ {% extends "./layouts/govuk-pages-plugin.html" %}
2
+
3
+ {% block pageTitle %}
4
+ Page Overview
5
+ {% endblock %}
6
+
7
+ {% block beforeContent %}
8
+ <div class="govuk-pages-plugin__page__header">
9
+ {{ govukBackLink({
10
+ href: "javascript:history.back(-1)"
11
+ })}}
12
+
13
+ {{ govukButton({
14
+ text: "Go to page",
15
+ href: "../" + page.route,
16
+ isStartButton: true
17
+ })}}
18
+ </div>
19
+ {% endblock %}
20
+
21
+ {% block content %}
22
+
23
+ <div class="govuk-pages-plugin">
24
+ {% if noPage %}
25
+ <h1 class="govuk-heading-l">Oops, no page exists with that ID!</h1>
26
+ {% else %}
27
+ <span class="govuk-caption-m">Page Overview</span>
28
+ <h1 class="govuk-heading-l">{{ page.title }}</h1>
29
+
30
+ {% include './includes/page-overview.html' %}
31
+
32
+ {% endif %}
33
+
34
+ </div>
35
+
36
+ {% endblock %}
@@ -1,4 +1,4 @@
1
- import { PageFlowOutput } from "../../src/types"
1
+ import { PageFlowOutput, PageOutput } from "../../src/types"
2
2
 
3
3
  export const getPagesTest = {
4
4
  output: [
@@ -427,4 +427,16 @@ export const getPageFlowTestB : { output: { hasStages: boolean, flows: PageFlowO
427
427
  }
428
428
  ]
429
429
  }
430
+ }
431
+
432
+ export const getPageTestA: PageOutput = {
433
+ id: 1,
434
+ title: "Title 1",
435
+ type: "email",
436
+ description: "A description about item 1",
437
+ route: "/a/a/1",
438
+ stage: {
439
+ main: "stage1",
440
+ subStage: 1,
441
+ }
430
442
  }
@@ -17,3 +17,21 @@ describe('Basic getPages functions', () => {
17
17
  })
18
18
  });
19
19
  });
20
+
21
+ describe('Basic getPage functions', () => {
22
+ describe('getPage', () => {
23
+ it('should return an object with information about the given page', () => {
24
+ const result = getPages.getPage(1, validPages)
25
+ const output = outputs.getPageTestA
26
+ expect(result).to.eql(output)
27
+ })
28
+
29
+ it('should return a useful error if an INVALID page ID is provided', () => {
30
+ expect(() => getPages.getPage(100, validPages)).to.throw("Page ID 100 does not exist")
31
+ })
32
+
33
+ it('should return a useful error when INVALID pages JSON is provided', () => {
34
+ expect(() => getPages.getPage(1, invalidPages)).to.throw("Invalid array of PAGES passed to govuk-pages-plugin - please check the documentation to ensure the JSON schema you are passing matches what is expected")
35
+ })
36
+ });
37
+ });
@@ -1,12 +0,0 @@
1
- export const PageFlowTypeToggle = () => {
2
- console.log('The type toggle is being called')
3
- }
4
-
5
- export const InitPageFlowTypeToggles = () => {
6
- PageFlowTypeToggle();
7
- document.querySelectorAll("[data-toggle]").forEach(el => {
8
- el.addEventListener("click", () => {
9
- el.classList.toggle("active");
10
- });
11
- });
12
- }
@@ -1,12 +0,0 @@
1
- export const PageFlowTypeToggle = () => {
2
- console.log('The type toggle is being called')
3
- }
4
-
5
- export const InitPageFlowTypeToggles = () => {
6
- PageFlowTypeToggle();
7
- document.querySelectorAll("[data-toggle]").forEach(el => {
8
- el.addEventListener("click", () => {
9
- el.classList.toggle("active");
10
- });
11
- });
12
- }