How to create an searchable add-reference-button/action in admin-on-rest?
Asked Answered
P

1

7

Consider a case where I have a functional JSON backend, and the admin-on-rest resources company, user and company_user.

The data is as follows

company

  • id
  • name
  • address ...etc.

user

  • id
  • name
  • phone
  • address ...etc.

company_user

  • id
  • company_id (references company)
  • user_id (references user)
  • position ...etc.

It is very easy to have a separate top-level resource view for the company_user resource type; but it should really be editable per user and per company, on preferably on a tab of the user, or the company resource views.

Thus comes the problem: how to create a tab for user resource, where I can list the current company-references (company_user rows), but in the same tab I need a sane way to do the following functionality:

  1. search for all the companies that are not currently referred to by this user (of course there is an API call for that);
  2. add a row to to the company_user collection and;
  3. refresh the tab
Porush answered 28/11, 2017 at 11:53 Comment(1)
I was going to ask the very same question. As far as I understand, it is possible only with writing some custom React and it is not provided by the aor framework.Benzol
W
0

The simplest way to handle this kind of relations is to provide an extra field on your models that will serve as a representation of the relations.

For example, from a user we need to see his companies: expose a new field user.company_ids. This field don't have to be in the database, your API or a proxy can list the related companies ID in this field, and for write requests use it to create, change or delete relations.

Once this field is implemented, admin-on-rest is very easy to configure. I made a fully working snippet: https://codesandbox.io/s/r04y8rn96p

First, write your top-level resources:

<Admin
    authClient={authClient}
    restClient={restClient}
    title="Example Admin"
    locale="en"
    messages={messages}
  >
    <Resource
      name="companies"
      list={CompanyList}
      create={CompanyCreate}
      edit={CompanyEdit}
      show={CompanyShow}
      remove={Delete}
      icon={CompanyIcon}
    />
    <Resource
      name="users"
      list={UserList}
      create={UserCreate}
      edit={UserEdit}
      remove={Delete}
      icon={UserIcon}
      show={UserShow}
    />
    <Resource
      name="company_user"
      icon={CompanyUserIcon}
      list={CompanyUserList}
      create={CompanyUserCreate}
      show={CompanyUserShow}
      remove={Delete}
    />
</Admin>

Then, you can use ReferenceArrayInput for the user model on the extra field just like a one-to-many relation.

export const UserEdit = ({ ...props }) => (
  <Edit title={<UserTitle />} {...props}>
    <TabbedForm>
      <FormTab label="User Infos">
        <TextInput source="name" />
        <DateInput source="published_at" defaultValue={() => new Date()} />
        <TextInput source="company_ids" />
      </FormTab>
      <FormTab label="Companies">
        <ReferenceArrayInput source="company_ids" reference="companies">
          <SelectArrayInput optionText="title" />
        </ReferenceArrayInput>
      </FormTab>
    </TabbedForm>
  </Edit>
);

Since the idea is to mock the relations into an extra field, you can handle this field from your backend (the best choice) or the frontend via the restClient. Here is an dummy example for read-only relations:

// It's better to implement these rules on the backend
const improvedRestClient = (type, resource, params) => {
  return restClient(type, resource, params).then((response) => {
    // Inject company_ids into user
    if (type === 'GET_ONE' && resource === 'users') {
      return restClient('GET_LIST', 'company_user', defaultOptions).then(({ data: companyUsers }) => {
        const user = response.data;

        return {
          data: {
            ...user,
            company_ids: companyUsers
              .filter(item => item.user_id === user.id)
              .map(item => item.company_id),
          },
        }
      });
    }

    // Inject user_ids into company
    if (type === 'GET_ONE' && resource === 'companies') {
      return restClient('GET_LIST', 'company_user', defaultOptions).then(({ data: companyUsers }) => {
        const company = response.data;

        return {
          data: {
            ...company,
            user_ids: companyUsers
              .filter(item => item.company_id === company.id)
              .map(item => item.user_id),
          },
        }
      });
    }

    return response;
  });
};
Whiffen answered 5/1, 2018 at 10:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.