@ministryofjustice/frontend 3.3.1 → 3.4.0
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.
- package/README.md +4 -10
- package/govuk-prototype-kit.config.json +5 -16
- package/moj/all.jquery.min.js +77 -3
- package/moj/all.js +2021 -1436
- package/moj/all.scss +2 -0
- package/moj/all.spec.js +15 -13
- package/moj/components/_all.scss +1 -0
- package/moj/components/action-bar/_action-bar.scss +4 -6
- package/moj/components/add-another/_add-another.scss +9 -7
- package/moj/components/add-another/add-another.js +90 -69
- package/moj/components/add-another/add-another.spec.js +165 -0
- package/moj/components/alert/README.md +0 -0
- package/moj/components/alert/_alert.scss +142 -0
- package/moj/components/alert/alert.js +247 -0
- package/moj/components/alert/alert.spec.helper.js +67 -0
- package/moj/components/alert/alert.spec.js +229 -0
- package/moj/components/alert/macro.njk +3 -0
- package/moj/components/alert/template.njk +83 -0
- package/moj/components/badge/_badge.scss +3 -4
- package/moj/components/banner/_banner.scss +5 -10
- package/moj/components/button-menu/_button-menu.scss +10 -9
- package/moj/components/button-menu/button-menu.js +139 -136
- package/moj/components/button-menu/button-menu.spec.js +295 -296
- package/moj/components/cookie-banner/_cookie-banner.scss +6 -5
- package/moj/components/currency-input/_currency-input.scss +4 -4
- package/moj/components/date-picker/README.md +14 -17
- package/moj/components/date-picker/_date-picker.scss +122 -106
- package/moj/components/date-picker/date-picker.js +473 -471
- package/moj/components/date-picker/date-picker.spec.js +971 -923
- package/moj/components/filter/README.md +1 -1
- package/moj/components/filter/_filter.scss +53 -75
- package/moj/components/filter-toggle-button/filter-toggle-button.js +71 -67
- package/moj/components/filter-toggle-button/filter-toggle-button.spec.js +203 -205
- package/moj/components/form-validator/form-validator.js +117 -109
- package/moj/components/header/_header.scss +17 -19
- package/moj/components/identity-bar/_identity-bar.scss +5 -5
- package/moj/components/interruption-card/_interruption-card.scss +2 -2
- package/moj/components/messages/_messages.scss +12 -19
- package/moj/components/multi-file-upload/README.md +1 -1
- package/moj/components/multi-file-upload/_multi-file-upload.scss +34 -30
- package/moj/components/multi-file-upload/multi-file-upload.js +188 -152
- package/moj/components/multi-file-upload/multi-file-upload.spec.js +510 -0
- package/moj/components/multi-select/_multi-select.scss +4 -3
- package/moj/components/multi-select/multi-select.js +55 -50
- package/moj/components/multi-select/multi-select.spec.js +72 -79
- package/moj/components/notification-badge/_notification-badge.scss +12 -12
- package/moj/components/organisation-switcher/_organisation-switcher.scss +1 -1
- package/moj/components/page-header-actions/_page-header-actions.scss +3 -2
- package/moj/components/pagination/_pagination.scss +26 -31
- package/moj/components/password-reveal/_password-reveal.scss +1 -2
- package/moj/components/password-reveal/password-reveal.js +22 -21
- package/moj/components/password-reveal/password-reveal.spec.js +39 -37
- package/moj/components/primary-navigation/_primary-navigation.scss +26 -29
- package/moj/components/progress-bar/_progress-bar.scss +21 -26
- package/moj/components/rich-text-editor/_rich-text-editor.scss +17 -16
- package/moj/components/rich-text-editor/rich-text-editor.js +117 -103
- package/moj/components/search/_search.scss +6 -4
- package/moj/components/search-toggle/search-toggle.js +29 -30
- package/moj/components/search-toggle/search-toggle.scss +21 -15
- package/moj/components/search-toggle/search-toggle.spec.js +65 -70
- package/moj/components/side-navigation/_side-navigation.scss +12 -21
- package/moj/components/sortable-table/_sortable-table.scss +25 -23
- package/moj/components/sortable-table/sortable-table.js +139 -117
- package/moj/components/sortable-table/sortable-table.spec.js +362 -0
- package/moj/components/sub-navigation/_sub-navigation.scss +24 -28
- package/moj/components/tag/_tag.scss +8 -9
- package/moj/components/task-list/_task-list.scss +8 -7
- package/moj/components/ticket-panel/_ticket-panel.scss +14 -6
- package/moj/components/timeline/_timeline.scss +18 -20
- package/moj/filters/all.js +28 -30
- package/moj/filters/prototype-kit-13-filters.js +2 -1
- package/moj/helpers/_all.scss +1 -0
- package/moj/helpers/_hidden.scss +1 -1
- package/moj/helpers/_links.scss +20 -0
- package/moj/helpers.js +160 -31
- package/moj/helpers.spec.js +235 -0
- package/moj/init.js +2 -2
- package/moj/moj-frontend.min.css +2 -2
- package/moj/moj-frontend.min.js +77 -3
- package/moj/namespace.js +2 -1
- package/moj/objects/_filter-layout.scss +11 -10
- package/moj/objects/_scrollable-pane.scss +11 -14
- package/moj/settings/_colours.scss +5 -0
- package/moj/settings/_measurements.scss +0 -2
- package/moj/utilities/_hidden.scss +3 -3
- package/moj/utilities/_width-container.scss +1 -1
- package/package.json +1 -1
|
@@ -1,20 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
const { userEvent } = require("@testing-library/user-event");
|
|
3
|
-
const { configureAxe } = require("jest-axe");
|
|
4
|
-
const $ = require("jquery");
|
|
1
|
+
/* eslint-disable no-new */
|
|
5
2
|
|
|
6
|
-
require(
|
|
3
|
+
const { queryByRole } = require('@testing-library/dom')
|
|
4
|
+
const { userEvent } = require('@testing-library/user-event')
|
|
5
|
+
const $ = require('jquery')
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// disable landmark rules when testing isolated components.
|
|
12
|
-
region: { enabled: false },
|
|
13
|
-
},
|
|
14
|
-
});
|
|
7
|
+
require('./search-toggle.js')
|
|
8
|
+
|
|
9
|
+
const user = userEvent.setup()
|
|
15
10
|
|
|
16
11
|
const createComponent = () => {
|
|
17
|
-
html = `
|
|
12
|
+
const html = `
|
|
18
13
|
<div class="moj-search-toggle" data-module="moj-search-toggle" data-moj-search-toggle-text="Find case">
|
|
19
14
|
<div class="moj-search-toggle__toggle"></div>
|
|
20
15
|
<div class="moj-search-toggle__search">
|
|
@@ -45,90 +40,90 @@ const createComponent = () => {
|
|
|
45
40
|
|
|
46
41
|
</div>
|
|
47
42
|
</div>
|
|
48
|
-
<a href="#">link</a
|
|
49
|
-
document.body.insertAdjacentHTML(
|
|
50
|
-
const component = document.querySelector(
|
|
51
|
-
return component
|
|
52
|
-
}
|
|
43
|
+
<a href="#">link</a>`
|
|
44
|
+
document.body.insertAdjacentHTML('afterbegin', html)
|
|
45
|
+
const component = document.querySelector('.moj-search-toggle')
|
|
46
|
+
return component
|
|
47
|
+
}
|
|
53
48
|
|
|
54
|
-
describe(
|
|
55
|
-
let component, buttonContainer, searchContainer
|
|
49
|
+
describe('search toggle', () => {
|
|
50
|
+
let component, buttonContainer, searchContainer
|
|
56
51
|
|
|
57
52
|
beforeEach(() => {
|
|
58
|
-
component = createComponent()
|
|
59
|
-
searchContainer = component.querySelector(
|
|
60
|
-
buttonContainer = component.querySelector(
|
|
53
|
+
component = createComponent()
|
|
54
|
+
searchContainer = component.querySelector('.moj-search')
|
|
55
|
+
buttonContainer = component.querySelector('.moj-search-toggle__toggle')
|
|
61
56
|
|
|
62
57
|
new MOJFrontend.SearchToggle({
|
|
63
58
|
toggleButton: {
|
|
64
59
|
container: $(buttonContainer),
|
|
65
|
-
text: component.getAttribute(
|
|
60
|
+
text: component.getAttribute('data-moj-search-toggle-text')
|
|
66
61
|
},
|
|
67
62
|
search: {
|
|
68
|
-
container: $(searchContainer)
|
|
69
|
-
}
|
|
70
|
-
})
|
|
71
|
-
})
|
|
63
|
+
container: $(searchContainer)
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
})
|
|
72
67
|
|
|
73
68
|
afterEach(() => {
|
|
74
|
-
document.body.innerHTML =
|
|
75
|
-
})
|
|
69
|
+
document.body.innerHTML = ''
|
|
70
|
+
})
|
|
76
71
|
|
|
77
|
-
test(
|
|
78
|
-
const toggleButton = queryByRole(buttonContainer,
|
|
72
|
+
test('initialises component', () => {
|
|
73
|
+
const toggleButton = queryByRole(buttonContainer, 'button')
|
|
79
74
|
|
|
80
|
-
expect(toggleButton).
|
|
81
|
-
expect(toggleButton).toHaveTextContent(
|
|
82
|
-
expect(toggleButton).toHaveAttribute(
|
|
83
|
-
expect(toggleButton).toHaveAttribute(
|
|
75
|
+
expect(toggleButton).toBeInTheDocument()
|
|
76
|
+
expect(toggleButton).toHaveTextContent('Find case')
|
|
77
|
+
expect(toggleButton).toHaveAttribute('aria-haspopup', 'true')
|
|
78
|
+
expect(toggleButton).toHaveAttribute('aria-expanded', 'false')
|
|
84
79
|
|
|
85
|
-
expect(searchContainer).toHaveClass(
|
|
86
|
-
})
|
|
80
|
+
expect(searchContainer).toHaveClass('moj-js-hidden')
|
|
81
|
+
})
|
|
87
82
|
|
|
88
|
-
test(
|
|
89
|
-
const toggleButton = queryByRole(buttonContainer,
|
|
83
|
+
test('clicking button toggles search container', async () => {
|
|
84
|
+
const toggleButton = queryByRole(buttonContainer, 'button')
|
|
90
85
|
|
|
91
|
-
await user.click(toggleButton)
|
|
86
|
+
await user.click(toggleButton)
|
|
92
87
|
|
|
93
|
-
expect(toggleButton).toHaveAttribute(
|
|
94
|
-
expect(searchContainer).not.toHaveClass(
|
|
88
|
+
expect(toggleButton).toHaveAttribute('aria-expanded', 'true')
|
|
89
|
+
expect(searchContainer).not.toHaveClass('moj-js-hidden')
|
|
95
90
|
|
|
96
|
-
const input = queryByRole(searchContainer,
|
|
97
|
-
expect(input).toHaveFocus()
|
|
91
|
+
const input = queryByRole(searchContainer, 'searchbox')
|
|
92
|
+
expect(input).toHaveFocus()
|
|
98
93
|
|
|
99
|
-
await user.click(toggleButton)
|
|
94
|
+
await user.click(toggleButton)
|
|
100
95
|
|
|
101
|
-
expect(toggleButton).toHaveAttribute(
|
|
102
|
-
expect(searchContainer).toHaveClass(
|
|
103
|
-
expect(toggleButton).toHaveFocus()
|
|
104
|
-
})
|
|
96
|
+
expect(toggleButton).toHaveAttribute('aria-expanded', 'false')
|
|
97
|
+
expect(searchContainer).toHaveClass('moj-js-hidden')
|
|
98
|
+
expect(toggleButton).toHaveFocus()
|
|
99
|
+
})
|
|
105
100
|
|
|
106
|
-
test(
|
|
107
|
-
const toggleButton = queryByRole(buttonContainer,
|
|
101
|
+
test('clicking outside closes the search container', async () => {
|
|
102
|
+
const toggleButton = queryByRole(buttonContainer, 'button')
|
|
108
103
|
|
|
109
|
-
await user.click(toggleButton)
|
|
104
|
+
await user.click(toggleButton)
|
|
110
105
|
|
|
111
|
-
expect(toggleButton).toHaveAttribute(
|
|
112
|
-
expect(searchContainer).not.toHaveClass(
|
|
106
|
+
expect(toggleButton).toHaveAttribute('aria-expanded', 'true')
|
|
107
|
+
expect(searchContainer).not.toHaveClass('moj-js-hidden')
|
|
113
108
|
|
|
114
|
-
await user.click(document.body)
|
|
109
|
+
await user.click(document.body)
|
|
115
110
|
|
|
116
|
-
expect(toggleButton).toHaveAttribute(
|
|
117
|
-
expect(searchContainer).toHaveClass(
|
|
118
|
-
})
|
|
111
|
+
expect(toggleButton).toHaveAttribute('aria-expanded', 'false')
|
|
112
|
+
expect(searchContainer).toHaveClass('moj-js-hidden')
|
|
113
|
+
})
|
|
119
114
|
|
|
120
|
-
test(
|
|
121
|
-
const toggleButton = queryByRole(buttonContainer,
|
|
115
|
+
test('tabbing closes the search container', async () => {
|
|
116
|
+
const toggleButton = queryByRole(buttonContainer, 'button')
|
|
122
117
|
|
|
123
|
-
await user.click(toggleButton)
|
|
118
|
+
await user.click(toggleButton)
|
|
124
119
|
|
|
125
|
-
expect(toggleButton).toHaveAttribute(
|
|
126
|
-
expect(searchContainer).not.toHaveClass(
|
|
120
|
+
expect(toggleButton).toHaveAttribute('aria-expanded', 'true')
|
|
121
|
+
expect(searchContainer).not.toHaveClass('moj-js-hidden')
|
|
127
122
|
|
|
128
|
-
await user.tab()
|
|
129
|
-
await user.tab()
|
|
123
|
+
await user.tab()
|
|
124
|
+
await user.tab()
|
|
130
125
|
|
|
131
|
-
expect(toggleButton).toHaveAttribute(
|
|
132
|
-
expect(searchContainer).toHaveClass(
|
|
133
|
-
})
|
|
134
|
-
})
|
|
126
|
+
expect(toggleButton).toHaveAttribute('aria-expanded', 'false')
|
|
127
|
+
expect(searchContainer).toHaveClass('moj-js-hidden')
|
|
128
|
+
})
|
|
129
|
+
})
|
|
@@ -14,27 +14,25 @@
|
|
|
14
14
|
display: block;
|
|
15
15
|
padding: govuk-spacing(4) 0 0;
|
|
16
16
|
}
|
|
17
|
-
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
.moj-side-navigation__title {
|
|
21
20
|
@include govuk-font($size: 19);
|
|
22
|
-
color: govuk-colour("dark-grey");
|
|
23
|
-
font-weight: normal;
|
|
24
21
|
margin: 0;
|
|
25
22
|
padding: govuk-spacing(2);
|
|
26
23
|
padding-left: govuk-spacing(2) + 4px;
|
|
24
|
+
color: govuk-colour("dark-grey");
|
|
25
|
+
font-weight: normal;
|
|
27
26
|
|
|
28
27
|
@include govuk-media-query($until: tablet) {
|
|
29
28
|
display: none;
|
|
30
29
|
}
|
|
31
|
-
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
.moj-side-navigation__list {
|
|
35
|
-
list-style: none;
|
|
36
33
|
margin: 0;
|
|
37
34
|
padding: 0;
|
|
35
|
+
list-style: none;
|
|
38
36
|
|
|
39
37
|
@include govuk-media-query($until: tablet) {
|
|
40
38
|
display: flex;
|
|
@@ -48,7 +46,6 @@
|
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
.moj-side-navigation__item {
|
|
51
|
-
|
|
52
49
|
@include govuk-media-query($until: tablet) {
|
|
53
50
|
display: flex;
|
|
54
51
|
}
|
|
@@ -56,24 +53,22 @@
|
|
|
56
53
|
a,
|
|
57
54
|
a:link,
|
|
58
55
|
a:visited {
|
|
59
|
-
background-color: inherit;
|
|
60
|
-
color: $govuk-link-colour;
|
|
61
56
|
display: block;
|
|
57
|
+
color: $govuk-link-colour;
|
|
58
|
+
background-color: inherit;
|
|
62
59
|
text-decoration: none;
|
|
63
60
|
|
|
64
61
|
@include govuk-media-query($until: tablet) {
|
|
65
|
-
border-bottom: 4px solid transparent;
|
|
66
62
|
padding: govuk-spacing(3);
|
|
67
63
|
padding-bottom: govuk-spacing(3) - 4px; // Compensate for 4px border
|
|
64
|
+
border-bottom: 4px solid transparent;
|
|
68
65
|
}
|
|
69
66
|
|
|
70
67
|
@include govuk-media-query($from: tablet) {
|
|
71
|
-
background-color: inherit;
|
|
72
|
-
border-left: 4px solid transparent;
|
|
73
68
|
padding: govuk-spacing(2);
|
|
69
|
+
border-left: 4px solid transparent;
|
|
70
|
+
background-color: inherit;
|
|
74
71
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
72
|
}
|
|
78
73
|
|
|
79
74
|
a:hover {
|
|
@@ -81,16 +76,14 @@
|
|
|
81
76
|
}
|
|
82
77
|
|
|
83
78
|
a:focus {
|
|
79
|
+
position: relative;
|
|
80
|
+
border-color: $govuk-focus-text-colour;
|
|
84
81
|
color: $govuk-focus-text-colour;
|
|
85
82
|
background-color: $govuk-focus-colour;
|
|
86
|
-
border-color: $govuk-focus-text-colour;
|
|
87
|
-
position: relative;
|
|
88
83
|
}
|
|
89
|
-
|
|
90
84
|
}
|
|
91
85
|
|
|
92
86
|
.moj-side-navigation__item--active {
|
|
93
|
-
|
|
94
87
|
a:link,
|
|
95
88
|
a:visited {
|
|
96
89
|
border-color: $govuk-link-colour;
|
|
@@ -99,14 +92,14 @@
|
|
|
99
92
|
}
|
|
100
93
|
|
|
101
94
|
a:hover {
|
|
102
|
-
color: $govuk-link-hover-colour;
|
|
103
95
|
border-color: $govuk-link-hover-colour;
|
|
96
|
+
color: $govuk-link-hover-colour;
|
|
104
97
|
}
|
|
105
98
|
|
|
106
99
|
a:focus {
|
|
100
|
+
border-color: $govuk-focus-text-colour;
|
|
107
101
|
color: $govuk-focus-text-colour;
|
|
108
102
|
background-color: $govuk-focus-colour;
|
|
109
|
-
border-color: $govuk-focus-text-colour;
|
|
110
103
|
}
|
|
111
104
|
|
|
112
105
|
@include govuk-media-query($from: tablet) {
|
|
@@ -120,6 +113,4 @@
|
|
|
120
113
|
background-color: $govuk-focus-colour;
|
|
121
114
|
}
|
|
122
115
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
116
|
}
|
|
@@ -1,66 +1,68 @@
|
|
|
1
1
|
[aria-sort] button,
|
|
2
2
|
[aria-sort] button:hover {
|
|
3
|
-
|
|
3
|
+
position: relative;
|
|
4
|
+
margin: 0;
|
|
5
|
+
padding: 0 10px 0 0;
|
|
4
6
|
border-width: 0;
|
|
7
|
+
color: #005ea5;
|
|
8
|
+
background-color: transparent;
|
|
5
9
|
-webkit-box-shadow: 0 0 0 0;
|
|
6
10
|
-moz-box-shadow: 0 0 0 0;
|
|
7
11
|
box-shadow: 0 0 0 0;
|
|
8
|
-
color: #005ea5;
|
|
9
|
-
cursor: pointer;
|
|
10
12
|
font-family: inherit;
|
|
11
13
|
font-size: inherit;
|
|
14
|
+
font-size: 1em;
|
|
12
15
|
font-weight: inherit;
|
|
13
|
-
padding: 0 10px 0 0;
|
|
14
|
-
position: relative;
|
|
15
16
|
text-align: inherit;
|
|
16
|
-
|
|
17
|
-
margin: 0;
|
|
17
|
+
cursor: pointer;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
[aria-sort] button:focus {
|
|
21
|
-
background-color: $govuk-focus-colour;
|
|
22
|
-
color: $govuk-focus-text-colour;
|
|
23
|
-
box-shadow: 0 -2px $govuk-focus-colour, 0 4px $govuk-focus-text-colour;
|
|
24
21
|
outline: none;
|
|
22
|
+
color: $govuk-focus-text-colour;
|
|
23
|
+
background-color: $govuk-focus-colour;
|
|
24
|
+
box-shadow:
|
|
25
|
+
0 -2px $govuk-focus-colour,
|
|
26
|
+
0 4px $govuk-focus-text-colour;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
[aria-sort]:first-child button {
|
|
28
30
|
right: auto;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
[aria-sort] button
|
|
33
|
+
[aria-sort] button::before {
|
|
32
34
|
content: " \25bc";
|
|
33
35
|
position: absolute;
|
|
34
|
-
right: -1px;
|
|
35
36
|
top: 9px;
|
|
37
|
+
right: -1px;
|
|
36
38
|
font-size: 0.5em;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
[aria-sort] button
|
|
41
|
+
[aria-sort] button::after {
|
|
40
42
|
content: " \25b2";
|
|
41
43
|
position: absolute;
|
|
42
|
-
right: -1px;
|
|
43
44
|
top: 1px;
|
|
45
|
+
right: -1px;
|
|
44
46
|
font-size: 0.5em;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
[aria-sort="ascending"] button
|
|
48
|
-
[aria-sort="descending"] button
|
|
49
|
+
[aria-sort="ascending"] button::before,
|
|
50
|
+
[aria-sort="descending"] button::before {
|
|
49
51
|
content: none;
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
[aria-sort="ascending"] button
|
|
54
|
+
[aria-sort="ascending"] button::after {
|
|
53
55
|
content: " \25b2";
|
|
54
|
-
font-size: .8em;
|
|
55
56
|
position: absolute;
|
|
56
|
-
right: -5px;
|
|
57
57
|
top: 2px;
|
|
58
|
+
right: -5px;
|
|
59
|
+
font-size: 0.8em;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
[aria-sort="descending"] button
|
|
62
|
+
[aria-sort="descending"] button::after {
|
|
61
63
|
content: " \25bc";
|
|
62
|
-
font-size: .8em;
|
|
63
64
|
position: absolute;
|
|
64
|
-
right: -5px;
|
|
65
65
|
top: 2px;
|
|
66
|
-
|
|
66
|
+
right: -5px;
|
|
67
|
+
font-size: 0.8em;
|
|
68
|
+
}
|
|
@@ -1,124 +1,146 @@
|
|
|
1
|
-
MOJFrontend.SortableTable = function(params) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
this.initialiseSortedColumn()
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
MOJFrontend.SortableTable.prototype.setupOptions = function(params) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
MOJFrontend.SortableTable.prototype.createHeadingButtons = function() {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
MOJFrontend.SortableTable.prototype.createHeadingButton = function(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
1
|
+
MOJFrontend.SortableTable = function (params) {
|
|
2
|
+
this.table = $(params.table)
|
|
3
|
+
|
|
4
|
+
if (this.table.data('moj-search-toggle-initialised')) {
|
|
5
|
+
return
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
this.table.data('moj-search-toggle-initialised', true)
|
|
9
|
+
|
|
10
|
+
this.setupOptions(params)
|
|
11
|
+
this.body = this.table.find('tbody')
|
|
12
|
+
this.createHeadingButtons()
|
|
13
|
+
this.createStatusBox()
|
|
14
|
+
this.initialiseSortedColumn()
|
|
15
|
+
this.table.on('click', 'th button', $.proxy(this, 'onSortButtonClick'))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
MOJFrontend.SortableTable.prototype.setupOptions = function (params) {
|
|
19
|
+
params = params || {}
|
|
20
|
+
this.statusMessage = params.statusMessage || 'Sort by %heading% (%direction%)'
|
|
21
|
+
this.ascendingText = params.ascendingText || 'ascending'
|
|
22
|
+
this.descendingText = params.descendingText || 'descending'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
MOJFrontend.SortableTable.prototype.createHeadingButtons = function () {
|
|
26
|
+
const headings = this.table.find('thead th')
|
|
27
|
+
let heading
|
|
28
|
+
for (let i = 0; i < headings.length; i++) {
|
|
29
|
+
heading = $(headings[i])
|
|
30
|
+
if (heading.attr('aria-sort')) {
|
|
31
|
+
this.createHeadingButton(heading, i)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
MOJFrontend.SortableTable.prototype.createHeadingButton = function (
|
|
37
|
+
heading,
|
|
38
|
+
i
|
|
39
|
+
) {
|
|
40
|
+
const text = heading.text()
|
|
41
|
+
const button = $(`<button type="button" data-index="${i}">${text}</button>`)
|
|
42
|
+
heading.text('')
|
|
43
|
+
heading.append(button)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
MOJFrontend.SortableTable.prototype.createStatusBox = function () {
|
|
47
|
+
this.status = $(
|
|
48
|
+
'<div aria-live="polite" role="status" aria-atomic="true" class="govuk-visually-hidden" />'
|
|
49
|
+
)
|
|
50
|
+
this.table.parent().append(this.status)
|
|
51
|
+
}
|
|
47
52
|
|
|
48
53
|
MOJFrontend.SortableTable.prototype.initialiseSortedColumn = function () {
|
|
49
|
-
|
|
54
|
+
const rows = this.getTableRowsArray()
|
|
50
55
|
|
|
51
|
-
this.table
|
|
56
|
+
this.table
|
|
57
|
+
.find('th')
|
|
52
58
|
.filter('[aria-sort="ascending"], [aria-sort="descending"]')
|
|
53
59
|
.first()
|
|
54
60
|
.each((index, el) => {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
this.addRows(sortedRows)
|
|
61
|
+
const sortDirection = $(el).attr('aria-sort')
|
|
62
|
+
const columnNumber = $(el).find('button').attr('data-index')
|
|
63
|
+
const sortedRows = this.sort(rows, columnNumber, sortDirection)
|
|
64
|
+
this.addRows(sortedRows)
|
|
59
65
|
})
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
MOJFrontend.SortableTable.prototype.onSortButtonClick = function(e) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
MOJFrontend.SortableTable.prototype.updateButtonState = function(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
MOJFrontend.SortableTable.prototype.onSortButtonClick = function (e) {
|
|
69
|
+
const columnNumber = e.currentTarget.getAttribute('data-index')
|
|
70
|
+
const sortDirection = $(e.currentTarget).parent().attr('aria-sort')
|
|
71
|
+
let newSortDirection
|
|
72
|
+
if (sortDirection === 'none' || sortDirection === 'descending') {
|
|
73
|
+
newSortDirection = 'ascending'
|
|
74
|
+
} else {
|
|
75
|
+
newSortDirection = 'descending'
|
|
76
|
+
}
|
|
77
|
+
const rows = this.getTableRowsArray()
|
|
78
|
+
const sortedRows = this.sort(rows, columnNumber, newSortDirection)
|
|
79
|
+
this.addRows(sortedRows)
|
|
80
|
+
this.removeButtonStates()
|
|
81
|
+
this.updateButtonState($(e.currentTarget), newSortDirection)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
MOJFrontend.SortableTable.prototype.updateButtonState = function (
|
|
85
|
+
button,
|
|
86
|
+
direction
|
|
87
|
+
) {
|
|
88
|
+
button.parent().attr('aria-sort', direction)
|
|
89
|
+
let message = this.statusMessage
|
|
90
|
+
message = message.replace(/%heading%/, button.text())
|
|
91
|
+
message = message.replace(/%direction%/, this[`${direction}Text`])
|
|
92
|
+
this.status.text(message)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
MOJFrontend.SortableTable.prototype.removeButtonStates = function () {
|
|
96
|
+
this.table.find('thead th').attr('aria-sort', 'none')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
MOJFrontend.SortableTable.prototype.addRows = function (rows) {
|
|
100
|
+
for (let i = 0; i < rows.length; i++) {
|
|
101
|
+
this.body.append(rows[i])
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
MOJFrontend.SortableTable.prototype.getTableRowsArray = function () {
|
|
106
|
+
const rows = []
|
|
107
|
+
const trs = this.body.find('tr')
|
|
108
|
+
for (let i = 0; i < trs.length; i++) {
|
|
109
|
+
rows.push(trs[i])
|
|
110
|
+
}
|
|
111
|
+
return rows
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
MOJFrontend.SortableTable.prototype.sort = function (
|
|
115
|
+
rows,
|
|
116
|
+
columnNumber,
|
|
117
|
+
sortDirection
|
|
118
|
+
) {
|
|
119
|
+
const newRows = rows.sort(
|
|
120
|
+
function (rowA, rowB) {
|
|
121
|
+
const tdA = $(rowA).find('td,th').eq(columnNumber)
|
|
122
|
+
const tdB = $(rowB).find('td,th').eq(columnNumber)
|
|
123
|
+
|
|
124
|
+
const valueA =
|
|
125
|
+
sortDirection === 'ascending'
|
|
126
|
+
? this.getCellValue(tdA)
|
|
127
|
+
: this.getCellValue(tdB)
|
|
128
|
+
const valueB =
|
|
129
|
+
sortDirection === 'ascending'
|
|
130
|
+
? this.getCellValue(tdB)
|
|
131
|
+
: this.getCellValue(tdA)
|
|
132
|
+
|
|
133
|
+
if (typeof valueA === 'string' || typeof valueB === 'string')
|
|
134
|
+
return valueA.toString().localeCompare(valueB.toString())
|
|
135
|
+
return valueA - valueB
|
|
136
|
+
}.bind(this)
|
|
137
|
+
)
|
|
138
|
+
return newRows
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
MOJFrontend.SortableTable.prototype.getCellValue = function (cell) {
|
|
142
|
+
const val = cell.attr('data-sort-value') || cell.html()
|
|
143
|
+
|
|
144
|
+
const valAsNumber = Number(val)
|
|
145
|
+
return isNaN(valAsNumber) ? val : valAsNumber
|
|
146
|
+
}
|