diff --git a/src/components/radio/Radio.js b/src/components/radio/Radio.js
index 1299021087..35195262bc 100644
--- a/src/components/radio/Radio.js
+++ b/src/components/radio/Radio.js
@@ -2,6 +2,7 @@ import _ from 'lodash';
import ListComponent from '../_classes/list/ListComponent';
import { Formio } from '../../Formio';
import { boolValue, componentValueTypes, getComponentSavedTypes } from '../../utils/utils';
+import { v4 as uuidv4 } from 'uuid';
export default class RadioComponent extends ListComponent {
static schema(...extend) {
@@ -164,6 +165,7 @@ export default class RadioComponent extends ListComponent {
});
this.optionsLoaded = !this.component.dataSrc || this.component.dataSrc === 'values';
this.loadedOptions = [];
+ this.valuesMap = new Map();
if (!this.visible) {
this.itemsLoadedResolve();
@@ -211,9 +213,12 @@ export default class RadioComponent extends ListComponent {
dataValue = _.toString(this.dataValue);
}
- if (this.isSelectURL && _.isObject(this.loadedOptions[index].value)) {
- const optionValue = this.component.dataType === 'string' ? JSON.stringify(this.loadedOptions[index].value) : this.loadedOptions[index].value;
- input.checked = _.isEqual(optionValue, this.dataValue);
+ if (this.isSelectURL) {
+ const valueKey = this.loadedOptions[index].value;
+ const optionValue = this.valuesMap.has(valueKey)
+ ? this.valuesMap.get(valueKey)
+ : valueKey;
+ input.checked = _.isEqual(this.normalizeValue(optionValue), this.dataValue);
}
else {
input.checked = (dataValue === input.value && (input.value || this.component.dataSrc !== 'url'));
@@ -253,9 +258,15 @@ export default class RadioComponent extends ListComponent {
let value = this.component.inputType === 'checkbox' ? '' : this.dataValue;
this.refs.input.forEach((input, index) => {
if (input.checked) {
- value = (this.isSelectURL && _.isObject(this.loadedOptions[index].value)) ?
- this.loadedOptions[index].value :
- input.value;
+ if (!this.isSelectURL) {
+ value = input.value;
+ return;
+ }
+
+ const optionValue = this.loadedOptions[index].value;
+ value = this.valuesMap.has(optionValue)
+ ? this.valuesMap.get(optionValue)
+ : optionValue;
}
});
return value;
@@ -310,8 +321,8 @@ export default class RadioComponent extends ListComponent {
setValueAt(index, value) {
if (this.refs.input && this.refs.input[index] && value !== null && value !== undefined) {
- const inputValue = this.refs.input[index].value;
- this.refs.input[index].checked = (inputValue === value.toString());
+ const inputValue = this.getValueByInput(this.refs.input[index]);
+ this.refs.input[index].checked = _.isEqual(inputValue, value);
}
}
@@ -324,6 +335,27 @@ export default class RadioComponent extends ListComponent {
return super.shouldLoad;
}
+ prepareValue(item, options = {}) {
+ const value = this.component.valueProperty && !options.skipValueProperty
+ ? _.get(item, this.component.valueProperty)
+ : item;
+
+ if (this.component.type === 'radio' && typeof value !== 'string') {
+ const uuid = uuidv4();
+ this.valuesMap.set(uuid, value);
+ return uuid;
+ }
+
+ return value;
+ }
+
+ getValueByInput(input) {
+ const inputValue = input.value;
+ return this.valuesMap.has(inputValue)
+ ? this.valuesMap.get(inputValue)
+ : inputValue;
+ }
+
loadItems(url, search, headers, options, method, body) {
if (this.optionsLoaded) {
this.itemsLoadedResolve();
@@ -381,7 +413,7 @@ export default class RadioComponent extends ListComponent {
label: this.itemTemplate(item)
};
if (_.isEqual(item, this.selectData || _.pick(this.dataValue, _.keys(item)))) {
- this.loadedOptions[i].value = this.dataValue;
+ this.loadedOptions[i].value = this.prepareValue(this.dataValue, { skipValueProperty: true });
}
});
this.optionsLoaded = true;
@@ -392,13 +424,16 @@ export default class RadioComponent extends ListComponent {
const listData = [];
items?.forEach((item, i) => {
const valueAtProperty = _.get(item, this.component.valueProperty);
- this.loadedOptions[i] = {
- value: this.component.valueProperty ? valueAtProperty : item,
- label: this.component.valueProperty ? this.itemTemplate(item, valueAtProperty) : this.itemTemplate(item, item, i)
- };
- listData.push(this.templateData[this.component.valueProperty ? valueAtProperty : i]);
+ const value = this.prepareValue(item);
+ const label = this.component.valueProperty
+ ? this.itemTemplate(item, valueAtProperty, i)
+ : this.itemTemplate(item, item, i);
+ this.loadedOptions[i] = { label, value };
+ listData.push(this.templateData[i]);
+ if (this.valuesMap.has(value)) {
+ this.templateData[value] = this.templateData[i];
+ }
- const value = this.loadedOptions[i].value;
if (!this.isRadio && (
_.isObject(value) || _.isBoolean(value) || _.isUndefined(value)
)) {
@@ -426,7 +461,9 @@ export default class RadioComponent extends ListComponent {
const value = this.dataValue;
this.refs.wrapper.forEach((wrapper, index) => {
const input = this.refs.input[index];
- const checked = (input.type === 'checkbox') ? value[input.value] || input.checked : (input.value.toString() === value.toString());
+ const checked = (input.type === 'checkbox')
+ ? value[input.value] || input.checked
+ : _.isEqual(this.normalizeValue(this.getValueByInput(input)), value);
if (checked) {
//add class to container when selected
this.addClass(wrapper, this.optionSelectedClass);
@@ -441,10 +478,30 @@ export default class RadioComponent extends ListComponent {
}
}
+ setMetadata(value) {
+ let key = value;
+ if (typeof value !== 'string') {
+ const checkedInput = Array.prototype.find.call(
+ this.refs.input,
+ (input => input.type === 'radio' && input.getAttribute('checked'))
+ );
+ key = checkedInput?.value || key;
+ }
+ if (this.isSelectURL && this.templateData && this.templateData[key]) {
+ const submission = this.root.submission;
+ if (!submission.metadata.selectData) {
+ submission.metadata.selectData = {};
+ }
+
+ _.set(submission.metadata.selectData, this.path, this.templateData[key]);
+ }
+ }
+
updateValue(value, flags) {
const changed = super.updateValue(value, flags);
if (changed) {
this.setSelectedClasses();
+ this.setMetadata(this.dataValue);
}
if (!flags || !flags.modified || !this.isRadio) {
@@ -507,15 +564,10 @@ export default class RadioComponent extends ListComponent {
break;
}
- if (this.isSelectURL && this.templateData && this.templateData[value]) {
- const submission = this.root.submission;
- if (!submission.metadata.selectData) {
- submission.metadata.selectData = {};
- }
-
- _.set(submission.metadata.selectData, this.path, this.templateData[value]);
- }
-
return super.normalizeValue(value);
}
+
+ isSingleInputValue() {
+ return true;
+ }
}
diff --git a/test/unit/Radio.unit.js b/test/unit/Radio.unit.js
index a4fd032280..66578c0d98 100644
--- a/test/unit/Radio.unit.js
+++ b/test/unit/Radio.unit.js
@@ -16,7 +16,8 @@ import {
comp9,
comp10,
comp11,
- comp13
+ comp13,
+ comp14,
} from './fixtures/radio';
import { fastCloneDeep } from '@formio/core';
@@ -278,7 +279,8 @@ describe('Radio Component', () => {
setTimeout(()=>{
values.forEach((value, i) => {
- assert.equal(_.isEqual(value, radio.loadedOptions[i].value), true);
+ assert.equal(radio.loadedOptions[i].label, `${value.name}`);
+ assert.equal(typeof radio.loadedOptions[i].value, 'string');
});
radio.setValue(values[1]);
@@ -343,9 +345,7 @@ describe('Radio Component', () => {
}, 350);
}).catch(done);
});
-});
-describe('Radio Component', () => {
it('should have red asterisk left hand side to the options labels if component is required and label is hidden', () => {
return Harness.testCreate(RadioComponent, comp7).then(component => {
const options = component.element.querySelectorAll('.form-check-label');
@@ -609,4 +609,269 @@ describe('Radio Component', () => {
})
.catch(done);
});
+
+ describe('Value property refers to different type of values', () => {
+ const originalMakeRequest = Formio.makeRequest;
+ const values = [
+ {
+ label: 'String',
+ value: 'str',
+ },
+ {
+ label: 'Object',
+ value: {
+ a: 2,
+ b: 'c',
+ }
+ },
+ {
+ label: 'Boolean',
+ value: true,
+ },
+ {
+ label: 'Array',
+ value: [
+ 10,
+ 1330,
+ '132410',
+ ]
+ },
+ {
+ label: 'Number',
+ value: 5,
+ }
+ ];
+
+ const listDataMetadata = {
+ radio: [
+ {
+ label: 'String'
+ },
+ {
+ label: 'Object'
+ },
+ {
+ label: 'Boolean'
+ },
+ {
+ label: 'Array'
+ },
+ {
+ label: 'Number'
+ }
+ ]
+ };
+
+ before(() => {
+ Formio.makeRequest = function() {
+ return new Promise(resolve => {
+ resolve(values);
+ });
+ };
+ });
+
+ after (() => {
+ Formio.makeRequest = originalMakeRequest;
+ });
+
+ it('Should create correct options', (done) => {
+ const form = _.cloneDeep(comp14);
+ const element = document.createElement('div');
+ Formio.createForm(element, form).then(form => {
+ const radio = form.getComponent('radio');
+
+ setTimeout(() => {
+ assert.equal(radio.loadedOptions.length, values.length);
+ values.forEach((value, i) => {
+ assert.equal(radio.loadedOptions[i].label, `${value.label}`);
+ assert.equal(typeof radio.loadedOptions[i].value, 'string');
+ assert.deepEqual(form.submission.metadata.listData, listDataMetadata);
+ });
+ done();
+ }, 200);
+ }).catch(done);
+ });
+
+ it('Should set value by setValue', (done) => {
+ const form = _.cloneDeep(comp14);
+ const element = document.createElement('div');
+ Formio.createForm(element, form).then(form => {
+ const radio = form.getComponent('radio');
+ setTimeout(() => {
+ values.forEach(({label, value}, i) => {
+ radio.setValue(value);
+ assert.equal(radio.refs.input[i].checked, true);
+ assert.deepEqual(radio.dataValue, value);
+ assert.deepEqual(form.submission.metadata.selectData.radio, {
+ label,
+ });
+ });
+
+ done();
+ }, 200);
+ }).catch(done);
+ });
+
+ it('Should work with String value type', (done) => {
+ const form = _.cloneDeep(comp14);
+ const element = document.createElement('div');
+ Formio.createForm(element, form).then(form => {
+ const radio = form.getComponent('radio');
+
+ setTimeout(() => {
+ assert.equal(radio.refs.input.length, values.length);
+ const input = radio.refs.input[0];
+ input.click();
+
+ setTimeout(() => {
+ assert.equal(radio.dataValue, values[0].value);
+ assert.deepEqual(form.submission.metadata.selectData.radio, {
+ label: values[0].label,
+ });
+ done();
+ }, 200);
+ }, 200);
+ }).catch(done);
+ });
+
+ it('Should work with Object value type', (done) => {
+ const form = _.cloneDeep(comp14);
+ const element = document.createElement('div');
+ Formio.createForm(element, form).then(form => {
+ const radio = form.getComponent('radio');
+
+ setTimeout(() => {
+ assert.equal(radio.refs.input.length, values.length);
+ const input = radio.refs.input[1];
+ input.click();
+
+ setTimeout(() => {
+ assert.deepEqual(radio.dataValue, values[1].value);
+ assert.deepEqual(form.submission.metadata.selectData.radio, {
+ label: values[1].label,
+ });
+ done();
+ }, 200);
+ }, 200);
+ }).catch(done);
+ });
+
+ it('Should work with Boolean value type', (done) => {
+ const form = _.cloneDeep(comp14);
+ const element = document.createElement('div');
+ Formio.createForm(element, form).then(form => {
+ const radio = form.getComponent('radio');
+
+ setTimeout(() => {
+ assert.equal(radio.refs.input.length, values.length);
+ const input = radio.refs.input[2];
+ input.click();
+
+ setTimeout(() => {
+ assert.equal(radio.dataValue, values[2].value);
+ assert.deepEqual(form.submission.metadata.selectData.radio, {
+ label: values[2].label,
+ });
+ done();
+ }, 200);
+ }, 200);
+ }).catch(done);
+ });
+
+ it('Should work with Array value type', (done) => {
+ const form = _.cloneDeep(comp14);
+ const element = document.createElement('div');
+ Formio.createForm(element, form).then(form => {
+ const radio = form.getComponent('radio');
+
+ setTimeout(() => {
+ assert.equal(radio.refs.input.length, values.length);
+ const input = radio.refs.input[3];
+ input.click();
+
+ setTimeout(() => {
+ assert.deepEqual(radio.dataValue, values[3].value);
+ assert.deepEqual(form.submission.metadata.selectData.radio, {
+ label: values[3].label,
+ });
+ done();
+ }, 200);
+ }, 200);
+ }).catch(done);
+ });
+
+ it('Should work with Number value type', (done) => {
+ const form = _.cloneDeep(comp14);
+ const element = document.createElement('div');
+ Formio.createForm(element, form).then(form => {
+ const radio = form.getComponent('radio');
+
+ setTimeout(() => {
+ assert.equal(radio.refs.input.length, values.length);
+ const input = radio.refs.input[4];
+ input.click();
+
+ setTimeout(() => {
+ assert.equal(radio.dataValue, values[4].value);
+ assert.deepEqual(form.submission.metadata.selectData.radio, {
+ label: values[4].label,
+ });
+ done();
+ }, 200);
+ }, 200);
+ }).catch(done);
+ });
+
+ it('Should check input with Object value type using submission data', (done) => {
+ const form = _.cloneDeep(comp14);
+ const element = document.createElement('div');
+ Formio.createForm(element, form).then(form => {
+ form.setSubmission({
+ data: {
+ radio: values[1].value,
+ },
+ metadata: {
+ listData: listDataMetadata,
+ selectData: {
+ radio: {
+ label: values[1].label,
+ }
+ }
+ }
+ }).then(() => {
+ const radio = form.getComponent('radio');
+ setTimeout(() => {
+ assert.equal(radio.refs.input[1].checked, true);
+ done();
+ }, 200);
+ })
+ }).catch(done);
+ });
+
+ it('Should check input with Array value type using submission data', (done) => {
+ const form = _.cloneDeep(comp14);
+ const element = document.createElement('div');
+ Formio.createForm(element, form).then(form => {
+ form.setSubmission({
+ data: {
+ radio: values[3].value,
+ },
+ metadata: {
+ listData: listDataMetadata,
+ selectData: {
+ radio: {
+ label: values[3].label,
+ }
+ }
+ }
+ }).then(() => {
+ const radio = form.getComponent('radio');
+ setTimeout(() => {
+ assert.equal(radio.refs.input[3].checked, true);
+ done();
+ }, 200);
+ })
+ }).catch(done);
+ });
+ });
});
diff --git a/test/unit/fixtures/radio/comp14.js b/test/unit/fixtures/radio/comp14.js
new file mode 100644
index 0000000000..27996817b4
--- /dev/null
+++ b/test/unit/fixtures/radio/comp14.js
@@ -0,0 +1,30 @@
+export default {
+ type: 'form',
+ components: [
+ {
+ type: 'radio',
+ label: 'Radio',
+ key: 'radio',
+ dataSrc: 'url',
+ data: {
+ url: 'https://cdn.rawgit.com/mshafrir/2646763/raw/states_titlecase.json'
+ },
+ valueProperty: 'value',
+ template: '{{ item.label }}',
+ input: true
+ },
+ {
+ label: 'Submit',
+ showValidations: false,
+ alwaysEnabled: false,
+ tableView: false,
+ key: 'submit',
+ type: 'button',
+ input: true
+ }
+ ],
+ title: 'FIO-7225',
+ display: 'form',
+ name: 'fio7225',
+ path: 'fio7225',
+};
diff --git a/test/unit/fixtures/radio/index.js b/test/unit/fixtures/radio/index.js
index fcbdfcbbda..78b8f8a76e 100644
--- a/test/unit/fixtures/radio/index.js
+++ b/test/unit/fixtures/radio/index.js
@@ -11,4 +11,5 @@ import comp10 from './comp10';
import comp11 from './comp11';
import comp12 from './comp12';
import comp13 from './comp13';
-export { comp1, comp2, comp3, comp4, comp5, comp6, comp7, comp8, comp9, comp10, comp11, comp12, comp13 };
+import comp14 from './comp14';
+export { comp1, comp2, comp3, comp4, comp5, comp6, comp7, comp8, comp9, comp10, comp11, comp12, comp13, comp14 };