We have a need where 3 different menu items perform CRUD operations to the same resource ("assets"), but with just the category_id being different on all CRUD operations (until you press again a different menu item).
In order to do this, we introduced a query param, called kind, which is the only practical difference among these 3 links:
<MenuItemLink
to={{
pathname: '/assets',
search: stringify({page: 1, perPage: 25}),
}}
primaryText="Assets"
onClick={onMenuTap}
leftIcon={<AssetsIcon />}
/>
<MenuItemLink
to={{
pathname: '/assets',
search: stringify({
page: 1,
perPage: 25,
kind: 'printers'
}),
}}
primaryText="Printers"
onClick={onMenuTap}
leftIcon={<AssetsIcon />}
/>
<MenuItemLink
to={{
pathname: '/assets',
search: stringify({
page: 1,
perPage: 25,
kind: 'vehicles'
}),
}}
primaryText="Vehicles"
onClick={onMenuTap}
leftIcon={<AssetsIcon />}
/>
Through the following code, the List fetches records only for these assets:
// NOTE: 'parsedKind' is parsed grom query params with previous code, it gets one of these values: '' OR 'printers' OR 'vehicles'
<List
title={<TitleList location={props.location} />}
{...props}
filters={<AssetFilter location={props.location} key={parsedKind} />}
perPage={15}
filter={{cat_id: dbIds[parsedKind]}}
sort={{field: 'name', order: 'ASC'}}
actions={<OurCustomListActions parsedKind={parsedKind} {...props} />}
key={parsedKind}
>
In order for this to work we had to customize the "actions" with custom Buttons, so that the parsedKindis following through. For example, the CreateButton now needs a prop ourQuery:
<FlatButton
primary
label={label && translate(label)}
icon={<ContentAdd />}
containerElement={<Link to={`${basePath}/create?${ourQuery}`} />}
style={styles.flat}
/>
I have 3 questions here:
This was a tedious work (all buttons have been customized) and I wonder if there was another solution to cover this need.
Still, the "SaveButton" causes headaches, because it uses a handleSubmitWithRedirect function that comes from props and the parameter is just the name of the view ('list' for example). So how can I add the query param on the final URL there? I used an ugly setTimeout to do this after 2 seconds (window.location.hash = window.location.hash + '?' + this.props.ourQuery;) but obviously this is very wrong. The DeleteButtonis also problematic because it redirects to a subview.
Using the above approach I get NaN-NaN from 19 in the List pagination. Why? Can this also be solved somehow?
Thank you!
So I think your best bet would have been to simply create three resources: Assets, Vehicles and Printers.
Than in your restClient/dataProvider, create logic to route these three resources to the assets endpoint with the correct parameter. I simple middleware would suffice, something in the line off:
// MyAssetResourceMiddleware.js
export default restClient => (type, resource, params) => {
switch(resource){
case 'Assets':
case 'Vehicles':
case 'Printers':
return restClient(type,'assets',{...params,kind:resource});
default:
return restClient(type,resource,params);
}
}
And wrap your provider with it
<Admin dataProvider={MyAssetResourceMiddleware(dataProvider)} .../>
Related
I'm enjoying using react-admin and how promising this frontend seems to be for some dashboard development,
I followed a tutorial react-admin + loopback 4 and trying to filter a long list using a ReferenceInput + Autosuggestion as mentioned here
the list of department show properly in the dropdown list and if I select an item the list gets filtered as it should, however if I type in, the results is an empty dropdown with "No Option" as a result.
Do I have to populate the list somewhere before passing it? or am I missing something? Below is an example of a machine list that I try to filter by department.
Thanks a lot
const machineFilters = [
<ReferenceInput
source='department_id'
reference='departments'
alwaysOn={true}
>
<AutocompleteInput />
</ReferenceInput>,
];
If I manually enter some choices (I tried with one only) the typing in seems to work, although I thought I wouldn't need to provide the choice according to the doc of RA
Tip: If you want to populate the choices attribute with a list of
related records, you should decorate with
, and leave the choices empty
EDIT:
i'm using loopback4, in postman i query this address
http://localhost:3000/categories?filter={"where": {"description": { "like": "marking","options": "i"}}}
in my RA I used
const filterToQuery = (searchText) => ({
where: { description: `${searchText}` },
});
const machineFilters = [
<ReferenceInput
source='category_id'
reference='categories'
alwaysOn={true}
sort={{ field: 'code', order: 'ASC' }}
>
<AutocompleteInput
style={{ width: '300px' }}
source='categories'
filterToQuery={filterToQuery}
/>
</ReferenceInput>,
];
but still no luck so far, I'm continuing investigating the doc of RA and LB4, any help is appreciated, thank you
You probably have to tweak the <AutocompleteInput filterToQuery> prop so that the string entered by users is turned into the filter you want. Something like that:
<AutocompleteInput filterToQuery={string => ({ name_ilike: `%${string}%` })} />
This prop is documented here: https://marmelab.com/react-admin/AutocompleteInput.html#properties
thanks to the link provided by François Zaninotto and some digging here and there, I came up with this, I think there is still some issues I need to fix (the clear button of the AutocomleteInput doesn't give me back the full list of options after typing and deleting the text I entered manually but that's ok for me now.
I still have no idea why I ad to remove the"where" in my query though...
const filterToQuery = (searchText) => ({
description: { like: searchText, options: 'i' },
});
const machineFilters = [
<ReferenceInput
source='category_id'
reference='categories'
alwaysOn={true}
sort={{ field: 'code', order: 'ASC' }}
>
<AutocompleteInput
style={{ width: '300px' }}
source='categories'
filterToQuery={filterToQuery}
emptyValue={''}
/>
</ReferenceInput>,
];
some links that helped me:
Loopback filter like case insensitive
Loopback docs
I'm using on react-admin and try to add a filter options to a list.
My problem is how to add a filter input based on another filter input?
I want to filter the list by organization and by project bu project is associated to an organization so I want to enable select a project only after organization is selected and and only of projects associated tothe selected organization.
That is waht I try to do:
const ProjectInputs = () => {
const { values } = useFormState();
const translate = useTranslate();
const classes = useStyles();
return (
<ReferenceInput
label={translate("resources.projects.name", { smart_count: 1 })}
source="projectId"
reference="projects"
filter={{ organizationId: values.organizationId }}
disabled={!values.organizationId}
alwaysOn
variant="standard"
>
<SelectInput
optionText="name"
resettable
alwaysOn
/>
</ReferenceInput>
);
}
export const Paymentilter: FC<Omit<FilterProps, 'children'>> = props => {
const classes = useFilterStyles();
return (
<Filter {...props}>
<ReferenceInput
source="organizationId"
reference="organizations"
variant="standard"
>
<AutocompleteInput
optionText={(choice?: Organization) =>
choice?.id // the empty choice is { id: '' }
? `${choice.name}`
: ''
}
/>
</ReferenceInput>
<ProjectInputs />
</Filter>
);
};
It works but there are some problems:
I cant define the project input to be always on.
In the add filter button there is no name for this input only blank shrink space.
The position of the project input is unordered input is popping up.
after choosing this filter I cant close it.
Do you have an idea for me?
How to fix my problems?
Sholud I solve my issue in another way?
Thank you!
EDIT:
I did like #doctoragon way,
and that is ok. in this way the project input cant be choosen from the add filter button only when we choose an organization it apears and when we cancel it disaper.
That ok for me but the second input still looks different as you can see on the picture it is upper then al the others inputs, and the cancel button is overirded by the chosen option.
You might be able to solve this using a FormDataConsumer inside your Filter.
<Filter {...props}>
<ReferenceInput source="organizationId" ... />
<FormDataConsumer source="projects" alwaysOn>
{
({ formData, ...restOfTheProps }) => organizationId && <ProjectInputs />
}
</FormDataConsumer>
</Filter>
I'm wondering if it's possible to have multiple password requirements passed to VeeValidate in order to display which requirements a user is missing.
For instance, if we require that a user's password should have at least one uppercase letter and at least one number and be at least 5 characters long, and the user types in "a1bb", we'd like to be able to display two error messages; one associated with the length requirement not being met and one related to the uppercase requirement not being met.
Ultimately, we'd like to be able to do something like this:
Would that be possible with VeeValidate? Or are there any examples of a similar approach?
VeeValidate allows you to display multiple errors fields but you need to disable the fastExist config option first:
VeeValidate only generates one message per field by default as it uses
a fast-exit strategy when running the validation pipeline. [...] To disable this behavior you may want to configure the
fastExit option in VeeValidate's config or use the continues modifier. (source)
For the rules you want to apply to password, you can use the existing min rule for the minimum length.
For the other rules (check upcase, lowercase, special char and digit), it's about regex check. VeeValidate actually provides a regex rule but in your case you need multiple ones.
So you need to define custom rules. I put some examples in the snippet below in the created hook but you also can define them globally.
In my example, I create a method to return a css class error by checking the errors by rule name.(source)
Now the style is yours.
const config = {
fastExit: false
}
Vue.use(VeeValidate, config)
new Vue({
el: "#app",
data() {
return { password: '' }
},
created() {
this.$validator.extend('upCase', {
getMessage: () => "One uppercase character",
validate: value => value.match(/[A-Z]/g) !== null
})
this.$validator.extend('number', {
getMessage: () => "One number",
validate: value => value.match(/[0-9]/g) !== null
})
},
methods: {
errorClass(rule) {
return {
'error': this.errors.firstByRule('password', rule)
}
}
}
})
li {
color: #B1B1B1;
}
li.error {
color: #262626;
}
<script src="https://unpkg.com/vue#2.6.8/dist/vue.min.js"></script>
<script src="https://unpkg.com/vee-validate#2.0.0-rc.19/dist/vee-validate.js"></script>
<div id="app">
<input type="text" v-validate="'min:8|number|upCase'" name="password" >
<ul>
<li :class="errorClass('upCase')">One uppercase character</li>
<li :class="errorClass('number')">One number</li>
<li :class="errorClass('min')">8 characters minimum</li>
</ul>
</div>
check this solution
make a custom rule form VEEVALIDATE
STRONG PASSWORD VALIDATION
http://frankclark.xyz/veevalidate-strong-password-and-confirmation-validation
Similar to admin-on-rest: Access row's column data within a Datagrid component although I think it doesn't apply to my cases:
export const PlantShow = (props) => {
return (<Show {...props}>
<TabbedShowLayout>
<Tab label="Analytics">
{ record.oneId && <MetricsCharts {...props} manufacturer="one" /> }
{ record.otherId && <MetricsCharts {...props} manufacturer="other" /> }
{ record.anotherId && <MetricsCharts {...props} manufacturer="another" /> }
</Tab>
</TabbedShowLayout>
</Show>)
}
There should be a way to access current record's internals so I can introduce conditional logic .. I didn't find anything useful in props.. I even tried to inject {record} in PlantShow function but that didn't work either. Also DependentInput definitely doesn't help here
Thanks!
You'll have to use an intermediate component as a child of Show. It will receive the record prop.
See https://codesandbox.io/s/wyln51r907 (in posts.js, around the PostShow component.
I have a list of 6k entries related to a ressource.
I would like to be able to list, search and paginate over them in a TabbedForm/FormTab. ReferenceManyField shows a limited number of entries.
What is the recommended way to extend ReferenceManyField or use List instead?
You can use the DataGrid component to large number of entries. I think there might be confusion on your part about List. List only fetches records and the actual rendering is done by DataGrid.
ReferenceManyField accepts filter, sort etc. parameters which you can use to control the number of records being fetched from your API.
According to those two issues: https://github.com/marmelab/admin-on-rest/issues/998 and https://github.com/marmelab/admin-on-rest/issues/561 you cannot use List in ReferenceManyField and the suggested way to do it is having a button that redirects you to the related List component with the proper filter.
Example:
class LinkToRelatedReviews extends React.Component {
render() {
return (<FlatButton
primary
label={ translate("Full list of reviews by user") }
icon={<ReviewsIcon />}
containerElement={
<Link to={{
pathname: '/reviews',
search: stringify({ filter: JSON.stringify({ userId: [this.props.params.id] }), page: 1 }),
}}
/>}
/>)
}
}
export default LinkToRelatedReviews;
Something like that can be put in UsersShow Component
<LinkToRelatedReviews params={props.match.params}/>
under DataGrid that doesn't provide pagination but can fetch some of the results for you.
You can also see it in action by navigating to: https://marmelab.com/admin-on-rest-demo/#/segments and clicking Customers. This will redirect you to CustomersList filtered by the specific segment.