/
expander.js
144 lines (125 loc) · 5.3 KB
/
expander.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
window.GOVUK = window.GOVUK || {}
window.GOVUK.Modules = window.GOVUK.Modules || {}; // if this ; is omitted, none of this JS runs :(
(function (Modules) {
/* This JavaScript provides two functional enhancements to the expander component:
1) A count that shows how many items have been used in the expander container
2) Open/closing of the content
*/
function Expander ($module) {
this.$module = $module
this.$toggle = this.$module.querySelector('.js-toggle')
this.$content = this.$module.querySelector('.js-content')
this.$allInteractiveElements = this.$content.querySelectorAll('select, input[type=text]')
}
Expander.prototype.init = function () {
this.selectedElements = []
var openOnLoad = this.$module.getAttribute('data-open-on-load') === 'true'
this.replaceHeadingSpanWithButton(openOnLoad)
this.$module.toggleContent = this.toggleContent.bind(this)
this.$toggleButton = this.$module.querySelector('.js-button')
this.$toggleButton.addEventListener('click', this.$module.toggleContent)
var selectedString = this.selectedString()
if (selectedString) {
this.attachSelectedCounter(selectedString)
// expand the content
this.$toggleButton.click()
}
// Attach listener function to update selected count
var boundChangeEvents = this.bindChangeEvents.bind(this)
boundChangeEvents()
}
Expander.prototype.bindChangeEvents = function (e) {
for (var i = 0; i < this.$allInteractiveElements.length; i++) {
var $el = this.$allInteractiveElements[i]
if ($el.tagName === 'SELECT') {
$el.addEventListener('change', this.updateSelectedCount.bind(this))
}
// but for inputs we need both change and enter key event
if ($el.tagName === 'INPUT') {
$el.addEventListener('change', this.handleInputEvent.bind(this))
$el.addEventListener('keyup', this.handleInputEvent.bind(this))
}
}
}
Expander.prototype.handleInputEvent = function (e) {
var ENTER_KEY = 13
// we only want to fire when ENTER key is pressed or
// user selected a different element
if (e.keyCode === ENTER_KEY || e.type === 'change') {
this.updateSelectedCount()
}
}
Expander.prototype.replaceHeadingSpanWithButton = function (expanded) {
var toggleHtml = this.$toggle.innerHTML
var $button = document.createElement('button')
$button.classList.add('app-c-expander__button')
$button.classList.add('js-button')
$button.setAttribute('type', 'button')
$button.setAttribute('aria-expanded', expanded)
$button.setAttribute('aria-controls', this.$content.getAttribute('id'))
var buttonAttributes = this.$module.getAttribute('data-button-data-attributes')
if (buttonAttributes) {
try {
buttonAttributes = JSON.parse(buttonAttributes)
for (var rawKey in buttonAttributes) {
var key = rawKey.replace(/_/g, '-').toLowerCase()
var rawValue = buttonAttributes[rawKey]
var value = typeof rawValue === 'object' ? JSON.stringify(rawValue) : rawValue
$button.setAttribute('data-' + key, value)
}
} catch (e) {
console.error('Error with expander button data attributes, invalid JSON passed' + e.message, window.location)
}
}
$button.innerHTML = toggleHtml
this.$toggle.parentNode.replaceChild($button, this.$toggle)
}
Expander.prototype.toggleContent = function (e) {
if (this.$toggleButton.getAttribute('aria-expanded') === 'false') {
this.$toggleButton.setAttribute('aria-expanded', true)
this.$content.classList.add('app-c-expander__content--visible')
} else {
this.$toggleButton.setAttribute('aria-expanded', false)
this.$content.classList.remove('app-c-expander__content--visible')
}
}
Expander.prototype.attachSelectedCounter = function attachSelectedCounter (selectedString) {
var $selectedCounter = document.createElement('span')
$selectedCounter.classList.add('app-c-expander__selected-counter')
$selectedCounter.classList.add('js-selected-counter')
$selectedCounter.innerHTML = selectedString
this.$toggleButton.parentNode.insertBefore($selectedCounter, this.$toggleButton.nextSibling)
}
Expander.prototype.updateSelectedCount = function updateSelectedCount () {
var selectedString = this.selectedString()
var selectedStringElement = this.$module.querySelector('.js-selected-counter')
if (selectedString) {
if (selectedStringElement) {
selectedStringElement.innerHTML = selectedString
} else {
this.attachSelectedCounter(selectedString)
}
} else if (selectedStringElement) {
selectedStringElement.parentNode.removeChild(selectedStringElement)
}
}
Expander.prototype.selectedString = function selectedString () {
this.getAllSelectedElements()
var count = this.selectedElements.length
var selectedString = false
if (count > 0) {
selectedString = count + ' selected'
}
return selectedString
}
Expander.prototype.getAllSelectedElements = function getAllSelectedElements () {
this.selectedElements = []
var that = this
for (var i = 0; i < this.$allInteractiveElements.length; i++) {
if (this.$allInteractiveElements[i].value.length > 0) {
that.selectedElements.push(i)
}
}
}
Modules.Expander = Expander
})(window.GOVUK.Modules)