Skip to content

Commit

Permalink
refactor(ui5-radiobutton): improve group handling (#348)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
ilhan007 authored Apr 23, 2019
1 parent f4dee1c commit 4d7d9c3
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 144 deletions.
2 changes: 1 addition & 1 deletion packages/main/src/RadioButton.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<circle class="sapMRbSvgOuter" cx="{{circle.x}}" cy="{{circle.y}}" r="{{circle.rOuter}}" stroke-width="2" fill="none" />
<circle class="sapMRbSvgInner" cx="{{circle.x}}" cy="{{circle.y}}" r="{{circle.rInner}}" stroke-width="10" />
</svg>
<input type='radio' ?checked="{{ctr.selected}}" ?readonly="{{ctr.readOnly}}" ?disabled="{{ctr.readOnly}}" name="{{ctr.group}}" data-sap-no-tab-ref/>
<input type='radio' ?checked="{{ctr.selected}}" ?readonly="{{ctr.readOnly}}" ?disabled="{{ctr.readOnly}}" name="{{ctr.name}}" data-sap-no-tab-ref/>
</div>

{{#if ctr._label.text}}
Expand Down
53 changes: 29 additions & 24 deletions packages/main/src/RadioButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,17 @@ const metadata = {
},

/**
* Defines the group to which the <code>ui5-radiobutton</code> belongs.
* Defines the name of the <code>ui5-radiobutton</code>.
* Radio buttons with the same <code>name</code> will form a radio button group.
* <br/><b>Note:</b>
* The selection can be changed with <code>ARROW_UP/DOWN</code> and <code>ARROW_LEFT/RIGHT</code> keys between radios in same group.
* <br/><b>Note:</b>
* Only one radio button can be selected per group.
*
* @type {string}
* @public
*/
group: {
name: {
defaultValue: "",
type: String,
},
Expand Down Expand Up @@ -127,8 +132,8 @@ const metadata = {
* When a <code>ui5-radiobutton</code> is selected by the user, the
* <code>select</code> event is fired.
* When a <code>ui5-radiobutton</code> that is within a group is selected, the one
* that was previously selected gets
* automatically deselected.
* that was previously selected gets automatically deselected. You can group radio buttons by using the <code>name</code> property.
*
*
* <h3>ES6 Module Import</h3>
*
Expand Down Expand Up @@ -170,32 +175,32 @@ class RadioButton extends UI5Element {
}

syncGroup() {
const oldGroup = this._group;
const currentGroup = this.group;

if (currentGroup === oldGroup) {
return;
}

if (oldGroup) {
// remove the control from the previous group
RadioButtonGroup.removeFromGroup(this, oldGroup);
}

if (currentGroup) {
// add the control to the existing group
RadioButtonGroup.addToGroup(this, currentGroup);
const oldGroup = this._name;
const currentGroup = this.name;

if (currentGroup !== oldGroup) {
if (oldGroup) {
// remove the control from the previous group
RadioButtonGroup.removeFromGroup(this, oldGroup);
}

if (currentGroup) {
// add the control to the existing group
RadioButtonGroup.addToGroup(this, currentGroup);
}
} else if (currentGroup) {
RadioButtonGroup.enforceSingleSelection(this, currentGroup);
}

this._group = this.group;
this._name = this.name;
}

onclick() {
return this.toggle();
}

_handleDown(event) {
const currentGroup = this.group;
const currentGroup = this.name;

if (!currentGroup) {
return;
Expand All @@ -206,7 +211,7 @@ class RadioButton extends UI5Element {
}

_handleUp(event) {
const currentGroup = this.group;
const currentGroup = this.name;

if (!currentGroup) {
return;
Expand Down Expand Up @@ -245,13 +250,13 @@ class RadioButton extends UI5Element {
return this;
}

if (!this.group) {
if (!this.name) {
this.selected = !this.selected;
this.fireEvent("select");
return this;
}

RadioButtonGroup.selectItem(this, this.group);
RadioButtonGroup.selectItem(this, this.name);
return this;
}

Expand Down
136 changes: 81 additions & 55 deletions packages/main/src/RadioButtonGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,55 @@ class RadioButtonGroup {
return this.groups.get(groupName);
}

static getSelectedRadioFromGroup(groupName) {
return this.selectedRadios.get(groupName);
}

static removeGroup(groupName) {
this.selectedRadios.delete(groupName);
return this.groups.delete(groupName);
}

static addToGroup(control, groupName) {
static addToGroup(radioBtn, groupName) {
if (this.hasGroup(groupName)) {
this.getGroup(groupName).push(control);
this.enforceSingleSelection(radioBtn, groupName);
this.getGroup(groupName).push(radioBtn);
} else {
this.createGroup(control, groupName);
this.createGroup(radioBtn, groupName);
}
}

static removeFromGroup(control, groupName) {
static removeFromGroup(radioBtn, groupName) {
if (!this.hasGroup(groupName)) {
return;
}

const group = this.getGroup(groupName);
const selectedRadio = this.getSelectedRadioFromGroup(groupName);

// Remove the control from the given group
group.forEach((_control, idx, arr) => {
if (control._id === _control._id) {
// Remove the radio button from the given group
group.forEach((_radioBtn, idx, arr) => {
if (radioBtn._id === _radioBtn._id) {
return arr.splice(idx, 1);
}
});

if (selectedRadio === radioBtn) {
this.selectedRadios.set(groupName, null);
}

// Remove the group if it is empty
if (!group.length) {
this.removeGroup(groupName);
}
}

static createGroup(control, groupName) {
if (!this.hasGroup(groupName)) {
this.groups.set(groupName, [control]);
static createGroup(radioBtn, groupName) {
if (radioBtn.selected) {
this.selectedRadios.set(groupName, radioBtn);
}

this.groups.set(groupName, [radioBtn]);
}

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

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

// de-select all the rest
group.forEach(radio => {
if (radio._id !== nextItemToSelect._id) {
radio.selected = false;
radio.fireEvent("select", { selected: radio.selected });
}
});

// select the next item
this._selectItem(nextItemToSelect);
this.updateSelectionInGroup(nextItemToSelect, groupName);
}

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

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

// de-select all the rest
group.forEach(radio => {
if (radio._id !== previousItemToSelect._id) {
radio.selected = false;
radio.fireEvent("select", { selected: radio.selected });
}
});

// select the next item
this._selectItem(previousItemToSelect);
this.updateSelectionInGroup(previousItemToSelect, groupName);
}

static selectItem(item, groupName) {
const group = this.getGroup(groupName);
this.updateSelectionInGroup(item, groupName);
}

// de-select all the rest
group.forEach(radio => {
if (radio._id !== item._id) {
radio.selected = false;
radio.fireEvent("select", { selected: radio.selected });
}
});
static updateSelectionInGroup(radioBtnToSelect, groupName) {
const selectedRadio = this.getSelectedRadioFromGroup(groupName);

this._selectItem(item);
this._deselectRadio(selectedRadio);
this._selectRadio(radioBtnToSelect);
this.selectedRadios.set(groupName, radioBtnToSelect);
}

static get groups() {
if (!this._groups) {
this._groups = new Map();
static _deselectRadio(radioBtn) {
if (radioBtn) {
radioBtn.selected = false;
}
return this._groups;
}

static _selectItem(item) {
item.focus();
item.selected = true;
item.fireEvent("select", { selected: item.selected });
static _selectRadio(radioBtn) {
if (radioBtn) {
radioBtn.focus();
radioBtn.selected = true;
radioBtn._selected = true;
radioBtn.fireEvent("select");
}
}

static _nextSelectable(pos, group) {
const groupLength = group.length;
let nextItemToSelect = null;
let nextRadioToSelect = null;

if (pos === groupLength - 1) {
if (!group[0].disabled) {
nextItemToSelect = group[0];
nextRadioToSelect = group[0];
} else {
return this._nextSelectable(0, group);
}
} else if (!group[++pos].disabled) {
nextItemToSelect = group[pos];
nextRadioToSelect = group[pos];
} else {
return this._nextSelectable(pos, group);
}

return nextItemToSelect;
return nextRadioToSelect;
}

static _previousSelectable(pos, group) {
const groupLength = group.length;
let previousSelectable = null;
let previousRadioToSelect = null;

if (pos === 0) {
if (!group[groupLength - 1].disabled) {
previousSelectable = group[groupLength - 1];
previousRadioToSelect = group[groupLength - 1];
} else {
return this._previousSelectable(groupLength - 1, group);
}
} else if (!group[--pos].disabled) {
previousSelectable = group[pos];
previousRadioToSelect = group[pos];
} else {
return this._previousSelectable(pos, group);
}

return previousSelectable;
return previousRadioToSelect;
}

static enforceSingleSelection(radioBtn, groupName) {
const selectedRadio = this.getSelectedRadioFromGroup(groupName);

if (!selectedRadio) {
return;
}

if (radioBtn.selected) {
if (radioBtn !== selectedRadio) {
this._deselectRadio(selectedRadio);
this.selectedRadios.set(groupName, radioBtn);
}
} else if (radioBtn === selectedRadio) {
this.selectedRadios.set(groupName, null);
}
}

static get groups() {
if (!this._groups) {
this._groups = new Map();
}
return this._groups;
}

static get selectedRadios() {
if (!this._selectedRadios) {
this._selectedRadios = new Map();
}
return this._selectedRadios;
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/main/src/RadioButtonTemplateContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class RadioButtonTemplateContext {
context = {
ctr: state,
readOnly: state.disabled || state.readOnly,
tabIndex: state.disabled || (!state.selected && state.group) ? "-1" : "0",
tabIndex: state.disabled || (!state.selected && state.name) ? "-1" : "0",
circle: compact ? SVGConfig.compact : SVGConfig.default,
classes: { main: mainClasses, inner: innerClasses },
styles: {
Expand Down
Loading

0 comments on commit 4d7d9c3

Please sign in to comment.