Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NEW Add MultiLinkField #120

Merged

Conversation

GuySartorelli
Copy link
Member

@GuySartorelli GuySartorelli commented Nov 28, 2023

Notes

  • I've refactored some of the original components to avoid code duplication - most of this can be seen in the first commit.
  • Looking at the existing unit test coverage, there's nothing particular about this PR that needs to have new unit tests. I suspect a majority of it will be covered by behat tests later on instead, which is a separate card.

Issue

@GuySartorelli GuySartorelli self-assigned this Nov 28, 2023
This was referenced Nov 28, 2023
Comment on lines -61 to +71
Root.unmount();
if (Root) {
Root.unmount();
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was sometimes getting console errors about unmount not being a function on undefined.
See https://docs.silverstripe.org/en/5/changelogs/5.0.0/#reactdom-render-replaced-with-reactdom-createroot-render where this pattern of checking for the root before unmounting is documented as best practice anyway.

@GuySartorelli GuySartorelli force-pushed the pulls/4/multi-link-field branch from 14c1d4e to f9925f3 Compare November 28, 2023 22:13
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +3 to +6
// Allows null coalescing and optional chaining operators.
parserOptions: {
ecmaVersion: 2020
},
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Null coallescing (x ?? y) and optional chaining (x?.y) are well supported now - the build would have failed if they weren't supported by the browsers we target, due to the browser config in package.json

Copy link
Member Author

@GuySartorelli GuySartorelli Nov 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This component now handles single and multi link fields.
I originally had them as two separate components, but the differences were so minimal it just made more sense to have a isMulti boolean prop and let the one component handle both scenarios.

Note that I've moved the LinkPickerTitle out of the picker, so that it can be used to render the links when linkfield is multi.

The picker is now responsible for knowing what type was picked, and passing it through to the modal. The linkfield shouldn't care about that, it only cares about the link once the link has been created.

I've also moved the responsibility of rendering the modal. If it's a new link, the modal is rendered by the picker (indirectly, through the new LinkModalContainer). The linkfield itself only renders modals for links that are being edited.

Finally, the logic for figuring out which link modal component to render was moved into LinkModalContainer so it didn't get duplicated between the linkfield and the picker components.

/**
* Call back used by LinkModal after the form has been submitted and the response has been received
*/
const onSubmit = async (modalData, action, submitFn) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved a bunch of this logic from linkfield into here - linkfield doesn't care that the modal was submitted, it only cares about success. Similarly, the modal should be responsible for knowing about validation errors - linkfield itself doesn't care about them.

Copy link
Member Author

@GuySartorelli GuySartorelli Nov 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only change to LinkPickerTitle was to give it the classnames necessary for it to look correct when rendered outside of the picker component.

Comment on lines -52 to +53
"@silverstripe/eslint-config": "^1.0.0-alpha6",
"@silverstripe/webpack-config": "^2.0.0-alpha9",
"@silverstripe/eslint-config": "^1.0.0",
"@silverstripe/webpack-config": "^2.0.0",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't be using alpha anymore.

@GuySartorelli GuySartorelli force-pushed the pulls/4/multi-link-field branch from f9925f3 to 8852c98 Compare November 28, 2023 23:34
Comment on lines -151 to +171
value: PropTypes.number.isRequired,
value: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.number]),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't be required, because the value is undefined when no links are in the field and the field is rendered in an elemental block.
The component handles undefined perfectly so that's not a problem.

@GuySartorelli GuySartorelli force-pushed the pulls/4/multi-link-field branch from 8852c98 to e95916b Compare November 29, 2023 00:09
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reduced all of the &__ down to their full .link-picker__ forms so that we can easily do a text search for any given CSS class and find the style here.

<DropdownToggle className="link-picker__menu-toggle font-icon-link" caret>{i18n._t('LinkField.ADD_LINK', 'Add Link')}</DropdownToggle>
<DropdownToggle className="link-picker__menu-toggle font-icon-plus-1" caret>{i18n._t('LinkField.ADD_LINK', 'Add Link')}</DropdownToggle>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we are rendering links below the picker, it makes way more sense to have a + symbol for the picker, and a link symbol for the links. It's a super quick visual distinction.

Comment on lines -22 to -36
### Silverstripe 5

```sh
composer require silverstripe/linkfield
```

### GraphQL v4 - Silverstripe 4

`composer require silverstripe/linkfield:^2`

### GraphQL v3 - Silverstripe 4

```sh
composer require silverstripe/linkfield:^1
```
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch is only valid for CMS 5

Comment on lines -70 to -76
## Migrating from Version `1.0.0` or `dev-master`

Please be aware that in early versions of this module (and in untagged `dev-master`) there were no table names defined
for our `Link` classes. These have now all been defined, which may mean that you need to rename your old tables, or
migrate the data across.

EG: `SilverStripe_LinkField_Models_Link` needs to be migrated to `LinkField_Link`.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are old and don't apply to the jump from 2.x/3.x to 4.x

README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
client/src/components/LinkField/LinkField.js Outdated Show resolved Hide resolved
client/src/components/LinkField/LinkField.js Outdated Show resolved Hide resolved
client/src/components/LinkField/LinkField.js Show resolved Hide resolved
const [data, setData] = useState({});
const [editing, setEditing] = useState(false);
const [editingID, setEditingID] = useState(null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const [editingID, setEditingID] = useState(null);
const [editingID, setEditingID] = useState(-1);

Are we using null because 0 is reserved for editing new unsaved Links?

-1 is better than null because it's easy to make errors by testing the truthiness of editingID when 0 is a valid value. Instead explicitly test for !== -1 similar to indexOf().

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're using null because it's unset, and we can easily just do a truthy check against it. If there's an ID, it'll be truthy. I'll change to 0 if you want but -1 is nonsensical and isn't what we use anywhere to denote "not an ID".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK 0 makes sense

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

client/src/components/LinkField/LinkField.js Outdated Show resolved Hide resolved
src/Controllers/LinkFieldController.php Outdated Show resolved Hide resolved
src/Controllers/LinkFieldController.php Outdated Show resolved Hide resolved
src/Form/MultiLinkField.php Outdated Show resolved Hide resolved
@GuySartorelli GuySartorelli force-pushed the pulls/4/multi-link-field branch 2 times, most recently from bef7985 to 0706a98 Compare November 29, 2023 23:24
Copy link
Member

@emteknetnz emteknetnz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regular singlelink field wasn't working when I tested with the latest changes, though the multilink field was working

const [data, setData] = useState({});
const [editing, setEditing] = useState(false);
const [editingID, setEditingID] = useState(null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK 0 makes sense

for (const linkID of linkIDs) {
query.push(`itemID[]=${linkID}`);
}
const endpoint = `${Config.getSection(section).form.linkForm.dataUrl}/?${query.join('&')}`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const endpoint = `${Config.getSection(section).form.linkForm.dataUrl}/?${query.join('&')}`;
const endpoint = `${Config.getSection(section).form.linkForm.dataUrl}?${query.join('&')}`;

Don't need the trailing slash since there's no params being used on the endpoint

Changes
/admin/linkfield/data/?itemID[]=6&itemID[]=10&itemID[]=11&itemID[]=12
to
/admin/linkfield/data?itemID[]=6&itemID[]=10&itemID[]=11&itemID[]=12

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed to avoid ping pong, though it doesn't affect anything one way or another.

if (!editingID && linkIDs.length > 0) {
const query = [];
for (const linkID of linkIDs) {
query.push(`itemID[]=${linkID}`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
query.push(`itemID[]=${linkID}`);
query.push(`itemIDs[]=${linkID}`);

Copy link
Member Author

@GuySartorelli GuySartorelli Nov 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we don't have behat tests yet small unnecessary changes like this are more likely to break things than to be useful. I don't want to keep making these small changes that don't actually affect anything - it means I have to test every bit of functionality every time it goes through a review.

Done.

private function itemIDsFromRequest(): array
{
$request = $this->getRequest();
$itemIDs = $request->getVar('itemID');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$itemIDs = $request->getVar('itemID');
$itemIDs = $request->getVar('itemIDs');

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@GuySartorelli GuySartorelli force-pushed the pulls/4/multi-link-field branch from 0706a98 to cc21f8a Compare November 30, 2023 21:55
@GuySartorelli
Copy link
Member Author

The regular singlelink field wasn't working when I tested with the latest changes, though the multilink field was working

It's working for me locally with this set of changes - hopefully something I did fixed whatever was broken, but if there's still something about it that isn't working please describe the actual faulty behaviour so I know what specifically needs fixing.

Copy link
Member

@emteknetnz emteknetnz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested locally, worked good

@emteknetnz emteknetnz merged commit 9debde6 into silverstripe:4 Nov 30, 2023
10 checks passed
@emteknetnz emteknetnz deleted the pulls/4/multi-link-field branch November 30, 2023 22:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants