Count Entries from ArrayField or Datagrid in React-Admin - react-admin

How can i count and output the length of an array field as a textfield or numberfield?
Code of the ArrayField that works and outputs a DataGrid
<ArrayField source="re_benutzers">
<Datagrid>
<TextField source="vorname" />
</Datagrid>
</ArrayField>
Source: (from Strapi)
{
"id": 5,
"name": "sdf",
"re_benutzers": [{
"id": 1,
"vorname": "Something",
},
^this can hold multiple entries and i want to output the count.

I think you can use a custom component for that instead of the DataGrid.
ArrayField will inject all ids retrieved in a list context.
So you can do something like:
const LengthField = (props) => {
const { ids, loaded } = useListContext(props);
return loaded ? ids.length : null;
};
// And then
<ArrayField source="re_benutzers">
<LengthField />
</ArrayField>

You can do this using the FunctionField component:
https://marmelab.com/react-admin/Fields.html#functionfield:
<FunctionField label="Count" render={record => `${record?.re_benutzers?.length ?? ""}`} />

Related

Display computed results in child component in VueJS

I pull data in from an Axios request. I have approx 500 records each with several results. I have simplified it for ease.
I display the data on the main component as a drop-down menu and it displays one entry for 'John' and one for 'Jane'. It splits the JSON data and removes duplicates and this works fine.
What Im stuck with is how to display the same data in the child component. At the moment it just displays 'Jane,Jane,Jane,John,John,John,Jane,'.
Do I have to do another methods in the child component for this? I can pass in a prop with uniquenames but it just displays them all in each result.
Any help would be most appreciated.
JSON
"results": [
"result": {
"name": "Jane,Jane,John,John,John,John,Jane,Jane,"
}
"result": {
"name": "Jane,Jane,Jane,John,John,John,Jane,"
}
"result": {
"name": "John,Jane,"
}
"result": {
"name": "Jane"
}
]
Main component
<Result
v-for="result in Results"
:result="result"
:key="result.docNum"
/>
<ul v-for="name in uniquenames" :key="name">
<li>
<label class="pr-2" for="name">{{ name }}</label>
<input
type="checkbox"
v-model="names"
:value="name"
/>
</li>
</ul>
methods: {
const metanames = this.Results
.filter((result) => results.result && results.result.name)
.map((item) => item.name)
.filter((names, i, arr) => arr.indexOf(names) === i)
let names = []
metanames.forEach((item) => {
const splitArr = item.split(', ')
names = names.concat(splitArr)
})
this.uniquenames = names.filter(
(names, i, arr) => arr.indexOf(names) === i,
)
}
Child component
<template>
<div>
{{ name }}
</div>
</template>
<script>
export default {
name: "result",
props: {
result: Object,
},
data: function () {
return {
name: this.results.result.name
}
}
Your <Result> component in the Parent template is passing in the items of Result in a loop, but you have not modified Result, hence why the original non-unique array of names is passed in. Create and pass down a computed property instead which should be a modified version of Result in the format you want. A computed property can update/reformat automatically whenever its dependencies (the original Result) changes. Whenever the computed property updates so will the prop
<Result
v-for="result in modifiedResults"
:result="result"
:key="result.docNum"
/>
computed: {
modifiedResults() {
// unfinished code for returning modified Results containing array of unique names
return [...this.Results].map(r => {
let uniquenames = getUniqueNames(r.names)
return { ...r, names: uniquenames }
})
},
},
as the comment says you'll need to finish/change the computed property to return exactly what you want. I can't do it as I'm not completely sure how all the different arrays and object properties in your code fit together, but computed property is definitely the way to go!

How to fill the form in smartclient?

Lets say there is such form:
{
this._dynamicForm =
<DynamicForm dataSource={InvoiceAdHocChargeDataSource.sc()} useAllDataSourceFields={true}>
<FormFieldItem name="feeCode"
required={true}
/>
<FormFieldItem name="description"
required={true}
/>
<FormFieldItem name="price"
required={true}
width={100}
/>
<FormFieldItem name="quantity"
required={true}
width={100}
/>
<FormFieldItem name="vatCode"
required={true}
width={100}
/>
<FormFieldItem name="remarks"/>
</DynamicForm>
}
How it should be filled with current values? Like I have table of records, click on the table - I want the data from the table row to be in the form fields.
I have done that on table click it reacts, gives me console.log output. But what next?
Cannot find in documenation and quick start quide.
So the main thing I was missing was
layout._dynamicForm.editRecord(record);
full method:
protected onInit(layout: page.invoicing.tabs.InvoiceAdHocChargesTabLayout) {
const onSelectionUpdated = suppressibleFunction((record: InvoiceAdHocChargeDataSource.Record | null) => {
if (record) {
console.log('adhoc on updated' , record);
layout._dynamicForm.editRecord(record);
}
});
layout._listGridRecords.selectionUpdated = onSelectionUpdated
this.tabEvents.onSelectedChanged.add(() => this.load())
//
this.editorEventsRecords.editStarted.add(({record}) => {
// this is running on invoice line double click
console.log('edit started // this is running on invoice line double click');
console.log(record);
this.invoice = record
this.load()
})
}
suppressibleFunction:
export function suppressibleFunction(f: (...args) => any) {
let suppress = false
return Object.assign(
(...args) => suppress ? undefined : f.apply(this, args),
{
suppressWith: <T>(f: () => T): T => {
suppress = true
const value = f()
suppress = false
return value
}
}
)
}

Why query not updated value but get initial value in Function with ExtReact

I have form fn
code here
#update code. I initialization
query = {
field: '',
email: '',
}
not initialization url
export function HomePage() {
const [query, setQuery] = useState({
field: '',
email: '',
});
const handleChaneValue = (value) => {
// not received data on change
console.log('query', query);
setQuery({
...query,
[value.sender.name]: value.newValue
})
}
console.log('query2', query);
query2 received data on change
console.log('query2', query);
#Update code: I add
return (
<div>
<Container
padding={10}
platformConfig={{
desktop: {
maxWidth: 400,
}
}}
>
<FormPanel
padding={10}
shadow
defaults={{
errorTarget: 'under'
}}
margin="0 0 20 0"
title="Using Validators"
>
<Textfield
required
label="Required Field"
requiredMessage="This field is required."
errorTarget="under"
name="field"
onChange={handleChaneValue}
/>
<EmailField
label="Email"
validators="email"
errorTarget="under"
name="email"
onChange={(e) => handleChaneValue(e)}
/>
<Urlfield
label="URL"
validators={{
type: 'url',
message: 'Website url example http://'
}}
errorTarget="under"
name="url"
onChange={handleChaneValue}
/>
<Container layout="center">
<Button
ui="confirm"
text="BTN submit"
handler={handleClick}
style={{border: '1px solid black' }}/>
</Container>
</FormPanel>
</Container>
</div>
)
}
export default HomePage;
When I change value in TextField. Query is
query2: {field: 'abc'}
But i change value in Email field. Query is not give old value "{filed: 'abc'}" throught I use ES6 three dot.
query2 : {email: 'xyz'}
and Query in funciton always initialization
query: {}
image change value
#Update image: when I change value Url. fn handleChangeValue get initialization query
query: {
field: '',
email: '',
}
does not value query updated.
here issues were you did not initialize the value of textfield and emailfield I updated code pls check now
export function HomePage() {
const [query, setQuery] = useState({
field:'',
email:''
});
const handleChaneValue = (value) => {
// not received data on change
console.log("query", query);
setQuery({
...query,
[value.sender.name]: value.newValue,
});
};
console.log("query2", query);
return (
<div>
<Container
padding={10}
platformConfig={{
desktop: {
maxWidth: 400,
},
}}
>
<FormPanel
padding={10}
shadow
defaults={{
errorTarget: "under",
}}
margin="0 0 20 0"
title="Using Validators"
>
<Textfield
required
label="Required Field"
requiredMessage="This field is required."
errorTarget="under"
name="field"
onChange={handleChaneValue}
value={query.field}
/>
<EmailField
label="Email"
validators="email"
errorTarget="under"
name="email"
value={query.email}
onChange={handleChaneValue}
/>
<Container layout="center">
<Button
ui="confirm"
text="BTN submit"
handler={handleClick}
style={{ border: "1px solid black" }}
/>
</Container>
</FormPanel>
</Container>
</div>
);
}
export default HomePage;
I found the solution to the problem. I using framework ExtReact so when run function, states re-initialized initil value. To solve this problem you can useRef and refer here.
enter link description here

Fetch sum of column

I am using React-Admin to create little app for evidence of tools and who borrowed them.
I have table of tools with id, code, name, state atd.
using List to render it.
<List {...props} filters={<ToolFilter />}>
<Datagrid rowClick="edit">
<TextField source="id" />
<TextField source="code" label="Kód" />
<TextField source="name" />
<TextField source="state" />
<NumberField source="free" />
<DateField source="add_time" />
<EditButton />
</Datagrid>
</List>
Than I have table B_tools, that holds data of borrowed tools.
it has id, userId, toolId, durationOfBorrow
What I want to do is to add column to the tools list, that SUMs durationOfBorrow from B_tool table for every tool and render it to list of tools.
for example if I have B_tool table:
id userId toolId durationOfBorrow
1 1 1 7
2 1 1 7
3 2 2 2
4 1 2 2
I need list of tools to look like this:
id code name state durationOfBorrow
1 123 Drill 1 14 (7+7)
2 456 Wrench 1 4 (2+2)
I tried use Querying The API With fetch from React-Admin documentation.
I have prepared route app.get('/api/tools/borrowcount:id', toolController.borrowCount); that shoud return sum of column:
borrowCount(req, res) {
//console.log(req);
const filtry = JSON.parse(req.query.filter);
console.log(filtry);
const options = {
attributes: [
[sequelize.fn('sum', sequelize.col('time')), 'total_time'],
],
raw: true,
where: ({
}),
order:
[]
,
};
if (typeof filtry.id !== "undefined") {
options.where.id = filtry.id;
}
return B_tool
.findAll(options)
//console.log(user);
//res.status(201).send(test);
.then(b_tool => {
//console.log(user.rows);
res.status(200).send(b_tool);
})
.catch(error => res.status(400).send(error));
},
But dont know how to implement it to show it in the tools list.
I got it.
I created element BorrowCount that useQuery api/toolsborrow/:id and returns total_time.
import React from 'react';
import { useQuery, Loading, Error } from 'react-admin';
const BorrowCount = ({ record }) => {
const { data, loading, error } = useQuery({
type: 'getOne',
resource: 'toolsborrow',
payload: { id: record.id }
});
if (loading) return <Loading />;
if (error) return <Error />;
if (!data) return null;
console.log(data);
return (
data[0].total_time
)
};
export default BorrowCount;
In API I call new controller:
app.get('/api/toolsborrow/:id', toolController.borrowCount);
And controller returns SUM of time column where ToolId is id of tool row:
borrowCount(req, res) {
const options = {
attributes: [
[Sequelize.fn('sum', Sequelize.col('time')), 'total_time'],
],
raw: true,
where: ({
ToolId: req.params.id
}),
order:
[]
,
};
return B_tool
.findAll(options)
.then(b_tool => {
res.status(200).send(b_tool);
})
.catch(error => res.status(400).send(error));
},
Finally, I just use this new element BorrowCount in List of tools:
<List {...props} filters={<ToolFilter />}>
<Datagrid rowClick="edit">
<TextField source="id" />
<TextField source="code" label="Kód" />
<TextField source="name" />
<TextField source="state" />
<BorrowCount label="Total_sum"/>
<NumberField source="free" />
<DateField source="add_time" />
<EditButton />
</Datagrid>
</List>
If someone has more elegant solution, please leave it here.

How can I call a method in Vue while the rendering takes place

This so far works as expected. This means, while rendering, the Json data is 'transformed' correctly to a csv String as the Attribute value.
Json:
"functions": {
"function": [
{
"name": "foo"
},
{
"name": "bar"
}
]
},
Html:
<input allowedfunctions="foo,bar" />
Vue - templating:
<input
:allowedfunctions="data.functions ?
data.functions.function.map(function(e) { return e.name }, data.functions.function).join(',') :
''"
/>
Now, I want to refactor and make this more readable.
So, how can I call a method while the rendering takes place?
Something like this would be awesome:
<input :allowedfunctions="getAllowedFunctions(data.functions);" />
Now it is working as expected.
Due to the comments, here I had to remove the semicolon:
<input :allowedfunctions="getAllowedFunctions(data.functions)" />
And further, the function must be implemented as 'method' and not as 'computed':
methods: {
getAllowedFunctions(functions: any) {
if (functions) {
return functions.function.map(function(e: any) { return e.name }, functions.function).join(',');
}
}
}