Skip to content

Commit 4d7d9c3

Browse files
authored
refactor(ui5-radiobutton): improve group handling (#348)
* Added new property "name", which replaces the property "group" (removed with this change) * When selection changes within the group by user interaction - single "select" event is being fired and the newly selected radio button is the event target. * if more than one radio buttons within a group is set as selected, the last one is effectively selected. * if a ui5-radiobutton is added to a new group, the single selection rule would be applied. * if the selected state is programatically changed, the single selection rule is applied and no event is fired. BREAKING CHANGE: the property "group" is replaced by the "name" property.
1 parent f4dee1c commit 4d7d9c3

File tree

8 files changed

+236
-144
lines changed

8 files changed

+236
-144
lines changed

packages/main/src/RadioButton.hbs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<circle class="sapMRbSvgOuter" cx="{{circle.x}}" cy="{{circle.y}}" r="{{circle.rOuter}}" stroke-width="2" fill="none" />
1212
<circle class="sapMRbSvgInner" cx="{{circle.x}}" cy="{{circle.y}}" r="{{circle.rInner}}" stroke-width="10" />
1313
</svg>
14-
<input type='radio' ?checked="{{ctr.selected}}" ?readonly="{{ctr.readOnly}}" ?disabled="{{ctr.readOnly}}" name="{{ctr.group}}" data-sap-no-tab-ref/>
14+
<input type='radio' ?checked="{{ctr.selected}}" ?readonly="{{ctr.readOnly}}" ?disabled="{{ctr.readOnly}}" name="{{ctr.name}}" data-sap-no-tab-ref/>
1515
</div>
1616

1717
{{#if ctr._label.text}}

packages/main/src/RadioButton.js

+29-24
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,17 @@ const metadata = {
9292
},
9393

9494
/**
95-
* Defines the group to which the <code>ui5-radiobutton</code> belongs.
95+
* Defines the name of the <code>ui5-radiobutton</code>.
96+
* Radio buttons with the same <code>name</code> will form a radio button group.
97+
* <br/><b>Note:</b>
98+
* The selection can be changed with <code>ARROW_UP/DOWN</code> and <code>ARROW_LEFT/RIGHT</code> keys between radios in same group.
99+
* <br/><b>Note:</b>
100+
* Only one radio button can be selected per group.
96101
*
97102
* @type {string}
98103
* @public
99104
*/
100-
group: {
105+
name: {
101106
defaultValue: "",
102107
type: String,
103108
},
@@ -127,8 +132,8 @@ const metadata = {
127132
* When a <code>ui5-radiobutton</code> is selected by the user, the
128133
* <code>select</code> event is fired.
129134
* When a <code>ui5-radiobutton</code> that is within a group is selected, the one
130-
* that was previously selected gets
131-
* automatically deselected.
135+
* that was previously selected gets automatically deselected. You can group radio buttons by using the <code>name</code> property.
136+
*
132137
*
133138
* <h3>ES6 Module Import</h3>
134139
*
@@ -170,32 +175,32 @@ class RadioButton extends UI5Element {
170175
}
171176

172177
syncGroup() {
173-
const oldGroup = this._group;
174-
const currentGroup = this.group;
175-
176-
if (currentGroup === oldGroup) {
177-
return;
178-
}
179-
180-
if (oldGroup) {
181-
// remove the control from the previous group
182-
RadioButtonGroup.removeFromGroup(this, oldGroup);
183-
}
184-
185-
if (currentGroup) {
186-
// add the control to the existing group
187-
RadioButtonGroup.addToGroup(this, currentGroup);
178+
const oldGroup = this._name;
179+
const currentGroup = this.name;
180+
181+
if (currentGroup !== oldGroup) {
182+
if (oldGroup) {
183+
// remove the control from the previous group
184+
RadioButtonGroup.removeFromGroup(this, oldGroup);
185+
}
186+
187+
if (currentGroup) {
188+
// add the control to the existing group
189+
RadioButtonGroup.addToGroup(this, currentGroup);
190+
}
191+
} else if (currentGroup) {
192+
RadioButtonGroup.enforceSingleSelection(this, currentGroup);
188193
}
189194

190-
this._group = this.group;
195+
this._name = this.name;
191196
}
192197

193198
onclick() {
194199
return this.toggle();
195200
}
196201

197202
_handleDown(event) {
198-
const currentGroup = this.group;
203+
const currentGroup = this.name;
199204

200205
if (!currentGroup) {
201206
return;
@@ -206,7 +211,7 @@ class RadioButton extends UI5Element {
206211
}
207212

208213
_handleUp(event) {
209-
const currentGroup = this.group;
214+
const currentGroup = this.name;
210215

211216
if (!currentGroup) {
212217
return;
@@ -245,13 +250,13 @@ class RadioButton extends UI5Element {
245250
return this;
246251
}
247252

248-
if (!this.group) {
253+
if (!this.name) {
249254
this.selected = !this.selected;
250255
this.fireEvent("select");
251256
return this;
252257
}
253258

254-
RadioButtonGroup.selectItem(this, this.group);
259+
RadioButtonGroup.selectItem(this, this.name);
255260
return this;
256261
}
257262

packages/main/src/RadioButtonGroup.js

+81-55
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,55 @@ class RadioButtonGroup {
77
return this.groups.get(groupName);
88
}
99

10+
static getSelectedRadioFromGroup(groupName) {
11+
return this.selectedRadios.get(groupName);
12+
}
13+
1014
static removeGroup(groupName) {
15+
this.selectedRadios.delete(groupName);
1116
return this.groups.delete(groupName);
1217
}
1318

14-
static addToGroup(control, groupName) {
19+
static addToGroup(radioBtn, groupName) {
1520
if (this.hasGroup(groupName)) {
16-
this.getGroup(groupName).push(control);
21+
this.enforceSingleSelection(radioBtn, groupName);
22+
this.getGroup(groupName).push(radioBtn);
1723
} else {
18-
this.createGroup(control, groupName);
24+
this.createGroup(radioBtn, groupName);
1925
}
2026
}
2127

22-
static removeFromGroup(control, groupName) {
28+
static removeFromGroup(radioBtn, groupName) {
2329
if (!this.hasGroup(groupName)) {
2430
return;
2531
}
2632

2733
const group = this.getGroup(groupName);
34+
const selectedRadio = this.getSelectedRadioFromGroup(groupName);
2835

29-
// Remove the control from the given group
30-
group.forEach((_control, idx, arr) => {
31-
if (control._id === _control._id) {
36+
// Remove the radio button from the given group
37+
group.forEach((_radioBtn, idx, arr) => {
38+
if (radioBtn._id === _radioBtn._id) {
3239
return arr.splice(idx, 1);
3340
}
3441
});
3542

43+
if (selectedRadio === radioBtn) {
44+
this.selectedRadios.set(groupName, null);
45+
}
46+
3647
// Remove the group if it is empty
3748
if (!group.length) {
3849
this.removeGroup(groupName);
3950
}
4051
}
4152

42-
static createGroup(control, groupName) {
43-
if (!this.hasGroup(groupName)) {
44-
this.groups.set(groupName, [control]);
53+
static createGroup(radioBtn, groupName) {
54+
if (radioBtn.selected) {
55+
this.selectedRadios.set(groupName, radioBtn);
4556
}
57+
58+
this.groups.set(groupName, [radioBtn]);
4659
}
4760

4861
static selectNextItem(item, groupName) {
@@ -56,16 +69,7 @@ class RadioButtonGroup {
5669

5770
const nextItemToSelect = this._nextSelectable(currentItemPosition, group);
5871

59-
// de-select all the rest
60-
group.forEach(radio => {
61-
if (radio._id !== nextItemToSelect._id) {
62-
radio.selected = false;
63-
radio.fireEvent("select", { selected: radio.selected });
64-
}
65-
});
66-
67-
// select the next item
68-
this._selectItem(nextItemToSelect);
72+
this.updateSelectionInGroup(nextItemToSelect, groupName);
6973
}
7074

7175
static selectPreviousItem(item, groupName) {
@@ -79,81 +83,103 @@ class RadioButtonGroup {
7983

8084
const previousItemToSelect = this._previousSelectable(currentItemPosition, group);
8185

82-
// de-select all the rest
83-
group.forEach(radio => {
84-
if (radio._id !== previousItemToSelect._id) {
85-
radio.selected = false;
86-
radio.fireEvent("select", { selected: radio.selected });
87-
}
88-
});
89-
90-
// select the next item
91-
this._selectItem(previousItemToSelect);
86+
this.updateSelectionInGroup(previousItemToSelect, groupName);
9287
}
9388

9489
static selectItem(item, groupName) {
95-
const group = this.getGroup(groupName);
90+
this.updateSelectionInGroup(item, groupName);
91+
}
9692

97-
// de-select all the rest
98-
group.forEach(radio => {
99-
if (radio._id !== item._id) {
100-
radio.selected = false;
101-
radio.fireEvent("select", { selected: radio.selected });
102-
}
103-
});
93+
static updateSelectionInGroup(radioBtnToSelect, groupName) {
94+
const selectedRadio = this.getSelectedRadioFromGroup(groupName);
10495

105-
this._selectItem(item);
96+
this._deselectRadio(selectedRadio);
97+
this._selectRadio(radioBtnToSelect);
98+
this.selectedRadios.set(groupName, radioBtnToSelect);
10699
}
107100

108-
static get groups() {
109-
if (!this._groups) {
110-
this._groups = new Map();
101+
static _deselectRadio(radioBtn) {
102+
if (radioBtn) {
103+
radioBtn.selected = false;
111104
}
112-
return this._groups;
113105
}
114106

115-
static _selectItem(item) {
116-
item.focus();
117-
item.selected = true;
118-
item.fireEvent("select", { selected: item.selected });
107+
static _selectRadio(radioBtn) {
108+
if (radioBtn) {
109+
radioBtn.focus();
110+
radioBtn.selected = true;
111+
radioBtn._selected = true;
112+
radioBtn.fireEvent("select");
113+
}
119114
}
120115

121116
static _nextSelectable(pos, group) {
122117
const groupLength = group.length;
123-
let nextItemToSelect = null;
118+
let nextRadioToSelect = null;
124119

125120
if (pos === groupLength - 1) {
126121
if (!group[0].disabled) {
127-
nextItemToSelect = group[0];
122+
nextRadioToSelect = group[0];
128123
} else {
129124
return this._nextSelectable(0, group);
130125
}
131126
} else if (!group[++pos].disabled) {
132-
nextItemToSelect = group[pos];
127+
nextRadioToSelect = group[pos];
133128
} else {
134129
return this._nextSelectable(pos, group);
135130
}
136131

137-
return nextItemToSelect;
132+
return nextRadioToSelect;
138133
}
139134

140135
static _previousSelectable(pos, group) {
141136
const groupLength = group.length;
142-
let previousSelectable = null;
137+
let previousRadioToSelect = null;
143138

144139
if (pos === 0) {
145140
if (!group[groupLength - 1].disabled) {
146-
previousSelectable = group[groupLength - 1];
141+
previousRadioToSelect = group[groupLength - 1];
147142
} else {
148143
return this._previousSelectable(groupLength - 1, group);
149144
}
150145
} else if (!group[--pos].disabled) {
151-
previousSelectable = group[pos];
146+
previousRadioToSelect = group[pos];
152147
} else {
153148
return this._previousSelectable(pos, group);
154149
}
155150

156-
return previousSelectable;
151+
return previousRadioToSelect;
152+
}
153+
154+
static enforceSingleSelection(radioBtn, groupName) {
155+
const selectedRadio = this.getSelectedRadioFromGroup(groupName);
156+
157+
if (!selectedRadio) {
158+
return;
159+
}
160+
161+
if (radioBtn.selected) {
162+
if (radioBtn !== selectedRadio) {
163+
this._deselectRadio(selectedRadio);
164+
this.selectedRadios.set(groupName, radioBtn);
165+
}
166+
} else if (radioBtn === selectedRadio) {
167+
this.selectedRadios.set(groupName, null);
168+
}
169+
}
170+
171+
static get groups() {
172+
if (!this._groups) {
173+
this._groups = new Map();
174+
}
175+
return this._groups;
176+
}
177+
178+
static get selectedRadios() {
179+
if (!this._selectedRadios) {
180+
this._selectedRadios = new Map();
181+
}
182+
return this._selectedRadios;
157183
}
158184
}
159185

packages/main/src/RadioButtonTemplateContext.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class RadioButtonTemplateContext {
2525
context = {
2626
ctr: state,
2727
readOnly: state.disabled || state.readOnly,
28-
tabIndex: state.disabled || (!state.selected && state.group) ? "-1" : "0",
28+
tabIndex: state.disabled || (!state.selected && state.name) ? "-1" : "0",
2929
circle: compact ? SVGConfig.compact : SVGConfig.default,
3030
classes: { main: mainClasses, inner: innerClasses },
3131
styles: {

0 commit comments

Comments
 (0)