diff --git a/src/components/datagrid/DataGrid.js b/src/components/datagrid/DataGrid.js index e79e175131..dba1344ad5 100644 --- a/src/components/datagrid/DataGrid.js +++ b/src/components/datagrid/DataGrid.js @@ -2,6 +2,7 @@ import _ from 'lodash'; import NestedArrayComponent from '../_classes/nestedarray/NestedArrayComponent'; import { fastCloneDeep, getFocusableElements } from '../../utils/utils'; import { Components } from '../Components'; +import dragula from 'dragula'; export default class DataGridComponent extends NestedArrayComponent { static schema(...extend) { @@ -332,43 +333,41 @@ export default class DataGridComponent extends NestedArrayComponent { row.dragInfo = { index }; }); - if (this.root.dragulaLib) { - this.dragula = this.root.dragulaLib([this.refs[`${this.datagridKey}-tbody`]], { - moves: (_draggedElement, _oldParent, clickedElement) => { - const clickedElementKey = clickedElement.getAttribute('data-key'); - const oldParentKey = _oldParent.getAttribute('data-key'); + this.dragula = dragula([this.refs[`${this.datagridKey}-tbody`]], { + moves: (_draggedElement, _oldParent, clickedElement) => { + const clickedElementKey = clickedElement.getAttribute('data-key'); + const oldParentKey = _oldParent.getAttribute('data-key'); - //Check if the clicked button belongs to that container, if false, it belongs to the nested container - if (oldParentKey === clickedElementKey) { - return clickedElement.classList.contains('formio-drag-button'); - } + //Check if the clicked button belongs to that container, if false, it belongs to the nested container + if (oldParentKey === clickedElementKey) { + return clickedElement.classList.contains('formio-drag-button'); } - }).on('drop', this.onReorder.bind(this)); + } + }).on('drop', this.onReorder.bind(this)); - this.dragula.on('cloned', (el, original) => { - if (el && el.children && original && original.children) { - _.each(original.children, (child, index) => { - const styles = getComputedStyle(child, null); + this.dragula.on('cloned', (el, original) => { + if (el && el.children && original && original.children) { + _.each(original.children, (child, index) => { + const styles = getComputedStyle(child, null); - if (styles.cssText !== '') { - el.children[index].style.cssText = styles.cssText; - } - else { - const cssText = Object.values(styles).reduce( - (css, propertyName) => { - return `${css}${propertyName}:${styles.getPropertyValue( - propertyName - )};`; - }, - '' - ); - - el.children[index].style.cssText = cssText; - } - }); - } - }); - } + if (styles.cssText !== '') { + el.children[index].style.cssText = styles.cssText; + } + else { + const cssText = Object.values(styles).reduce( + (css, propertyName) => { + return `${css}${propertyName}:${styles.getPropertyValue( + propertyName + )};`; + }, + '' + ); + + el.children[index].style.cssText = cssText; + } + }); + } + }); } this.refs[`${this.datagridKey}-addRow`].forEach((addButton) => { @@ -406,6 +405,26 @@ export default class DataGridComponent extends NestedArrayComponent { return this.component.components; } + /** + * Reorder values in array based on the old and new position + * @param {any} valuesArr - An array of values. + * @param {number} oldPosition - The index of the value in array before reordering. + * @param {number} newPosition - The index of the value in array after reordering. + * @param {boolean|any} movedBelow - Whether or not the value is moved below. + * @returns {void} + */ + reorderValues(valuesArr, oldPosition, newPosition, movedBelow) { + if (!_.isArray(valuesArr) || _.isEmpty(valuesArr)) { + return; + } + + const draggedRowData = valuesArr[oldPosition]; + //insert element at new position + valuesArr.splice(newPosition, 0, draggedRowData); + //remove element from old position (if was moved above, after insertion it's at +1 index) + valuesArr.splice(movedBelow ? oldPosition : oldPosition + 1, 1); + } + onReorder(element, _target, _source, sibling) { if (!element.dragInfo || (sibling && !sibling.dragInfo)) { console.warn('There is no Drag Info available for either dragged or sibling element'); @@ -417,12 +436,9 @@ export default class DataGridComponent extends NestedArrayComponent { const newPosition = sibling ? sibling.dragInfo.index : this.dataValue.length; const movedBelow = newPosition > oldPosition; const dataValue = fastCloneDeep(this.dataValue); - const draggedRowData = dataValue[oldPosition]; - - //insert element at new position - dataValue.splice(newPosition, 0, draggedRowData); - //remove element from old position (if was moved above, after insertion it's at +1 index) - dataValue.splice(movedBelow ? oldPosition : oldPosition + 1, 1); + this.reorderValues(dataValue, oldPosition, newPosition, movedBelow); + //reorder select data + this.reorderValues(_.get(this.root, `submission.metadata.selectData.${this.path}`, []), oldPosition, newPosition, movedBelow); // When components are reordered we need to set the dataGrid and form pristine properties to false this.root.pristine = false; diff --git a/src/components/datagrid/DataGrid.unit.js b/src/components/datagrid/DataGrid.unit.js index 6952b3ccfa..65fb269522 100644 --- a/src/components/datagrid/DataGrid.unit.js +++ b/src/components/datagrid/DataGrid.unit.js @@ -24,6 +24,7 @@ import { withCollapsibleRowGroups, withAllowCalculateOverride, twoWithAllowCalculatedOverride, withCheckboxes, + withReorder } from './fixtures'; describe('DataGrid Component', () => { @@ -747,3 +748,53 @@ describe('DataGrid calculated values', () => { .catch(done); }); }); + +describe('DataGrid Reorder', () => { + it('Should display select components labels correctly on rows reorder', (done) => { + Formio.createForm(document.createElement('div'), withReorder.form) + .then((form) => { + form.setSubmission(withReorder.submission) + .then(() => { + const values = [ + { value: '11', label: 1 }, + { value: '22', label: 2 }, + { value: '33', label: 3 }, + { value: '44', label: 4 }, + { value: '55', label: 5 }, + ]; + + const dataGrid = form.getComponent('dataGrid'); + _.each(dataGrid.components, (selectComp, ind) => { + const expectedValue = values[ind]; + assert.equal(ind, selectComp.rowIndex); + assert.equal(selectComp.dataValue, expectedValue.value); + assert.equal(selectComp.templateData[expectedValue.value].data.number, expectedValue.label); + }); + + dataGrid.onReorder({ dragInfo: { index: 4 } }, null, null, { dragInfo: { index: 0 } }); + dataGrid.onReorder({ dragInfo: { index: 4 } }, null, null, { dragInfo: { index: 1 } }); + dataGrid.onReorder({ dragInfo: { index: 2 } }, null, null, { dragInfo: { index: 4 } }); + + setTimeout(() => { + const values = [ + { value: '55', label: 5 }, + { value: '44', label: 4 }, + { value: '22', label: 2 }, + { value: '11', label: 1 }, + { value: '33', label: 3 }, + ]; + + _.each(dataGrid.components, (selectComp, ind) => { + const expectedValue = values[ind]; + assert.equal(ind, selectComp.rowIndex, 'Component index after reorder'); + assert.equal(selectComp.dataValue, expectedValue.value, 'Component value after reorder'); + assert.equal(selectComp.templateData[expectedValue.value].data.number, expectedValue.label, 'Component label value after reorder'); + }); + + done(); + }, 600); + }); + }) + .catch(done); + }); +}); diff --git a/src/components/datagrid/fixtures/comp-with-reorder.js b/src/components/datagrid/fixtures/comp-with-reorder.js new file mode 100644 index 0000000000..fc008ce9cd --- /dev/null +++ b/src/components/datagrid/fixtures/comp-with-reorder.js @@ -0,0 +1,139 @@ +const form = { + _id: '66742f4146717b98a9fa280f', + title: 'test reorder', + name: 'testReorder', + path: 'testreorder', + type: 'form', + display: 'form', + components: [ + { + label: 'Data Grid', + reorder: true, + addAnotherPosition: 'bottom', + layoutFixed: false, + enableRowGroups: false, + initEmpty: false, + tableView: false, + defaultValue: [ + { + select: '', + }, + ], + key: 'dataGrid', + type: 'datagrid', + input: true, + components: [ + { + label: 'Select', + widget: 'choicesjs', + tableView: true, + dataSrc: 'resource', + data: { + resource: '66742ee946717b98a9fa1b0b', + }, + dataType: 'string', + valueProperty: 'data.textField', + template: '{{ item.data.number }}', + validate: { + select: false, + }, + key: 'select', + type: 'select', + searchField: 'data.textField__regex', + limit: 10, + noRefreshOnScroll: false, + addResource: false, + reference: false, + input: true, + }, + ], + }, + { + type: 'button', + label: 'Submit', + key: 'submit', + disableOnInvalid: true, + input: true, + tableView: false, + }, + ], + created: '2024-06-20T13:31:45.177Z', + modified: '2024-06-25T10:32:46.577Z', + machineName: 'tifwklexhyrgxbr:testReorder', +}; + +const submission = { + form: '66742f4146717b98a9fa280f', + metadata: { + selectData: { + dataGrid: [ + { + select: { + data: { + number: 1, + }, + }, + }, + { + select: { + data: { + number: 2, + }, + }, + }, + { + select: { + data: { + number: 3, + }, + }, + }, + { + select: { + data: { + number: 4, + }, + }, + }, + { + select: { + data: { + number: 5, + }, + }, + }, + ], + }, + }, + data: { + dataGrid: [ + { + select: '11', + }, + { + select: '22', + }, + { + select: '33', + }, + { + select: '44', + }, + { + select: '55', + }, + ], + dataTable: [], + submit: true, + }, + _id: '667ab5ee6a69739703d30def', + project: '65df46bc93bcfaa231f3db1c', + state: 'submitted', + created: '2024-06-25T12:19:58.626Z', + modified: '2024-06-25T12:19:58.627Z', +}; + +export default { + form, + submission, +}; diff --git a/src/components/datagrid/fixtures/index.js b/src/components/datagrid/fixtures/index.js index 6c3bb34703..de65361cc7 100644 --- a/src/components/datagrid/fixtures/index.js +++ b/src/components/datagrid/fixtures/index.js @@ -16,4 +16,5 @@ import withCollapsibleRowGroups from './comp-with-collapsible-groups'; import withAllowCalculateOverride from './comp-with-allow-calculate-override'; import twoWithAllowCalculatedOverride from './two-comp-with-allow-calculate-override'; import withCheckboxes from './comp-with-checkboxes'; -export { comp1, comp2, comp3, comp4, comp5, comp6, comp7, comp8, comp9, withCollapsibleRowGroups, withConditionalFieldsAndValidations, withDefValue, withLogic, withRowGroupsAndDefValue, modalWithRequiredFields, withAllowCalculateOverride, twoWithAllowCalculatedOverride, withCheckboxes }; +import withReorder from './comp-with-reorder'; +export { comp1, comp2, comp3, comp4, comp5, comp6, comp7, comp8, comp9, withCollapsibleRowGroups, withConditionalFieldsAndValidations, withDefValue, withLogic, withRowGroupsAndDefValue, modalWithRequiredFields, withAllowCalculateOverride, twoWithAllowCalculatedOverride, withCheckboxes, withReorder };