Skip to content

Commit

Permalink
Allow insertion into array above and below existing items. (#1352)
Browse files Browse the repository at this point in the history
  • Loading branch information
fsteger authored and epicfaace committed Jul 26, 2019
1 parent beee02f commit c8c0249
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 15 deletions.
1 change: 1 addition & 0 deletions docs/advanced-customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ The following props are part of each element in `items`:
- `hasToolbar`: A boolean value stating whether the array item has a toolbar.
- `index`: A number stating the index the array item occurs in `items`.
- `key`: A stable, unique key for the array item.
- `onAddIndexClick: (index) => (event) => void`: Returns a function that adds a new item at `index`.
- `onDropIndexClick: (index) => (event) => void`: Returns a function that removes the item at `index`.
- `onReorderClick: (index, newIndex) => (event) => void`: Returns a function that swaps the items at `index` with `newIndex`.
- `readonly`: A boolean value stating if the array item is read-only.
Expand Down
52 changes: 37 additions & 15 deletions src/components/fields/ArrayField.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,26 +262,25 @@ class ArrayField extends Component {
return addable;
}

onAddClick = event => {
event.preventDefault();
const { schema, registry = getDefaultRegistry(), onChange } = this.props;
_getNewFormDataRow = () => {
const { schema, registry = getDefaultRegistry() } = this.props;
const { definitions } = registry;
let itemSchema = schema.items;
if (isFixedItems(schema) && allowAdditionalItems(schema)) {
itemSchema = schema.additionalItems;
}
const newFormDataRow = getDefaultFormState(
itemSchema,
undefined,
definitions
);
const newKeyedFormData = [
...this.state.keyedFormData,
{
key: generateRowId(),
item: newFormDataRow,
},
];
return getDefaultFormState(itemSchema, undefined, definitions);
};

onAddClick = event => {
event.preventDefault();

const { onChange } = this.props;
const newKeyedFormDataRow = {
key: generateRowId(),
item: this._getNewFormDataRow(),
};
const newKeyedFormData = [...this.state.keyedFormData, newKeyedFormDataRow];

this.setState(
{
Expand All @@ -291,6 +290,28 @@ class ArrayField extends Component {
);
};

onAddIndexClick = index => {
return event => {
if (event) {
event.preventDefault();
}
const { onChange } = this.props;
const newKeyedFormDataRow = {
key: generateRowId(),
item: this._getNewFormDataRow(),
};
let newKeyedFormData = [...this.state.keyedFormData];
newKeyedFormData.splice(index, 0, newKeyedFormDataRow);

this.setState(
{
keyedFormData: newKeyedFormData,
},
() => onChange(keyedToPlainFormData(newKeyedFormData))
);
};
};

onDropIndexClick = index => {
return event => {
if (event) {
Expand Down Expand Up @@ -745,6 +766,7 @@ class ArrayField extends Component {
hasRemove: has.remove,
index,
key,
onAddIndexClick: this.onAddIndexClick,
onDropIndexClick: this.onDropIndexClick,
onReorderClick: this.onReorderClick,
readonly,
Expand Down
78 changes: 78 additions & 0 deletions test/ArrayField_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,84 @@ describe("ArrayField", () => {
expect(endRows[1].hasAttribute(ArrayKeyDataAttr)).to.be.true;
});

it("should allow inserting anywhere in list", () => {
function addItemAboveOrBelow(item) {
const beforeIndex = item.index;
const addBeforeButton = (
<button
key={`array-item-add-before-${item.key}`}
className={
"array-item-move-before array-item-move-before-to-" + beforeIndex
}
onClick={item.onAddIndexClick(beforeIndex)}>
{"Add Item Above"}
</button>
);

const afterIndex = item.index + 1;
const addAfterButton = (
<button
key={`array-item-add-after-${item.key}`}
className={
"array-item-move-after array-item-move-after-to-" + afterIndex
}
onClick={item.onAddIndexClick(afterIndex)}>
{"Add Item Below"}
</button>
);

return (
<div
key={item.key}
data-rjsf-itemkey={item.key}
className={`array-item item-${item.index}`}>
<div>{addBeforeButton}</div>
{item.children}
<div>{addAfterButton}</div>
<hr />
</div>
);
}

function addAboveOrBelowArrayFieldTemplate(props) {
return (
<div className="array">{props.items.map(addItemAboveOrBelow)}</div>
);
}

const { node } = createFormComponent({
schema,
formData: ["foo", "bar", "baz"],
ArrayFieldTemplate: addAboveOrBelowArrayFieldTemplate,
});

const addBeforeButtons = node.querySelectorAll(".array-item-move-before");
const addAfterButtons = node.querySelectorAll(".array-item-move-after");

const startRows = node.querySelectorAll(".array-item");
const startRow1_key = startRows[0].getAttribute(ArrayKeyDataAttr);
const startRow2_key = startRows[1].getAttribute(ArrayKeyDataAttr);
const startRow3_key = startRows[2].getAttribute(ArrayKeyDataAttr);

Simulate.click(addBeforeButtons[0]);
Simulate.click(addAfterButtons[0]);

const endRows = node.querySelectorAll(".array-item");
const endRow2_key = endRows[1].getAttribute(ArrayKeyDataAttr);
const endRow4_key = endRows[3].getAttribute(ArrayKeyDataAttr);
const endRow5_key = endRows[4].getAttribute(ArrayKeyDataAttr);

expect(startRow1_key).to.equal(endRow2_key);
expect(startRow2_key).to.equal(endRow4_key);
expect(startRow3_key).to.equal(endRow5_key);

expect(endRows[0].hasAttribute(ArrayKeyDataAttr)).to.be.true;
expect(endRows[1].hasAttribute(ArrayKeyDataAttr)).to.be.true;
expect(endRows[2].hasAttribute(ArrayKeyDataAttr)).to.be.true;
expect(endRows[3].hasAttribute(ArrayKeyDataAttr)).to.be.true;
expect(endRows[4].hasAttribute(ArrayKeyDataAttr)).to.be.true;
});

it("should not provide an add button if addable is expliclty false regardless maxItems value", () => {
const { node } = createFormComponent({
schema: { maxItems: 2, ...schema },
Expand Down

0 comments on commit c8c0249

Please sign in to comment.