I need to retrieve data from the server then I display this data in a table and if a user clicks on a button in the table he can download the file
Here is my data :
files = [ {name : "file1", created_at: "29/01/2021"},
{name : "file2", created_at: "20/03/2021"}]
table headers :
const tableColumns = [
{ title: "name", field: "name" },
{
title: "Date",
field: "created_at",
type: "date",
dateSetting: { locale: "en-GB" },
filterComponent: (props) => <CustomDatePicker {...props} />
}
];
My array :
{file && (
<Fragment>
<MaterialTable
columns={tableColumns}
data={file}
title="Material Table - Custom Filter Component"
options={{search: false, filtering: true }}
actions={[
{
icon: 'save',
tooltip: 'Save User',
onClick: () => window.open('http://localhost:5000/files/' + file.name, '_blank').focus()
}
]}
/>
</Fragment>)}
I get the error: ENOENT: no such file or directory, stat '/Users/admin//files/undefined'"}
how can i use in react js the link of each file, i tried to use the map function on my data but it doesn't work
Even it works on your computer, the code won't work in a public server. because no one knows what is http://localhost:5000.
In order to get that line working, you need to find a resources that can be reached in your browser, ex. google.com
And then give it a shot and see if you still run into that error.
NOTE: client app such as a website can't work with a physical file in your computer. That is not allowed, but once you publish to the web, it becomes another URL which could store a file.
Related
Here is the issue I am having. I am trying to have the Ag-grid render it's html output using NextJS getServerSideProps. However, when I view the source code, it doesn't appear to have any of the HTML rendered for SEO purposes. If I go ahead and output the "staff" array to a div then the HTML output is viewable in the source code so at least I know the function is working. Is there something I need to do to have AGGridReact render its contents?
export default function Home({ staff }) {
const gridRef = useRef();
const defaultColDef = {
resizable: true,
sortable: true,
};
const [columnDefs] = useState([
{ headerName: 'First Name', field: 'first_name' },
{ headerName: 'Last Name', field: 'last_name' },
{ headerName: 'Job Title', field: 'job_title' },
{ field: 'office' },
{ field: 'email' },
{ field: 'phone' },
]);
return (
<>
<main>
<div style={{ height: '600px' }}>
<AgGridReact
id='staff_grid'
ref={gridRef}
rowData={staff}
defaultColDef={defaultColDef}
columnDefs={columnDefs}
rowSelection={'single'}
style={{ height: '100%', width: '100%' }}
></AgGridReact>
</div>
</main>
</>
);
}
// This gets called on every request
export async function getServerSideProps() {
const staff = [];
for (let id = 1; id <= 3; id++) {
staff.push({
id: id,
first_name: 'first' + id,
last_name: 'last' + id,
email: 'member' + id + '#company.com',
phone: '12345' + id,
office: 'place' + id,
job_title: 'Worker ' + id,
});
}
// Pass data to the page via props
return { props: { staff } };
}
TLDR: Diving deep into the ag-grid's node_modules abyss and into their documentation, I found that their grid component is being injected into the DOM (client-side) once an "AG Grid" wrapper component has been mounted. Therefore, this is a client-side only component.
Debugging
When I request the Next page from Postman, I see an empty div where the grid should be:
But when I request the page from the browser, I see the grid:
An even easier way to determine that this is a client-side only component would be to assign the grid a debug prop:
<AgGridReact
debug
rowData={staff}
columnDefs={columnDefs}
rowSelection="single"
/>
We see AG Grid debug logs in the browser (Notice the Rendered on Client message):
But, we don't see any AG Grid debug logs on the server (Notice the Rendered on Server message):
More investigation
I thought I found a server-side rendering solution via their Row Models, but unfortunately it's not referring to the table being SSR'd, but the data being lazy loaded via dynamically fetching data from a server. My guess as to why this table is client-side only is that AG Grid doesn't use a native table, but instead a bunch of div elements with custom styles to represent a table. Since the server doesn't have a DOM (eg, can't access document nor window), calculating these dynamic styles wouldn't be possible.
Alternatives
If you're creating this table for an enterprise and it's absolutely vital to have this page SSR'd for SEO, then I'd recommend having some sort of bot detection in Next's middleware and within gSSP. Then pass an isBot prop to the component and conditionally render a native table (styling won't matter since it's mainly used for SEO). We do something similar for our web application where search results need to be baked into the page on the server, but can be lazy-loaded client-side for a snappier UX.
Here's a working demo. You can change the User-Agent using your browser's tools or by changing it within the request headers.
A more comprehensive bot list can be found here.
What a user sees:
What a bot sees:
I've created a custom dialog with a dropzone in order to upload files and images to our server. By default the dropzone renders with 'Drop an image here' / 'Browse for an image'.
As part of the tinymce editor options file_picker_type allows setting of the default dropzone and I wondered how to access these options for a stand-alone dropzone?
My tinymce options include:
tinymce.init({
file_picker_types: 'file image'
});
My upload dialog config looks something like this:
const upload_config = {
title: 'Upload files',
size: 'medium',
body: {
type: 'panel',
{
type: 'dropzone',
name: 'file_drop',
types: 'file image' //// <----- something like this???
}
]
},
buttons: [
{
type: 'cancel',
name: 'closeButton',
text: 'Cancel',
},
{
type: 'submit',
name: 'submitButton',
text: 'Upload',
buttonType: 'primary'
}
],
onSubmit: (api) => {
// handle upload here
}
};
Anybody managed to fathom this? The tinymce docs are in no way comprehensive as they simply say "A dropzone is a composite component that catches drag and drops items or lets the user browse that can send a list of files for processing and receive the result." but give no details on how to actually do that with a stand alone dropzone.
I've managed to make it all work, but this is the last hurdle.
Thanks in advance
Are you trying to override the default image/file upload dialog with the custom one? I'm asking because I see the file_picker_types config option. To do so, you will need to use the file_picker_callback.
If it's something else and you are not trying to override the default dialog, use this interactive example as a base. You will need to:
a) Register the custom button with a setup function:
setup: function (editor) {
editor.ui.registry.addButton('custom-upload', {
icon: 'upload',
onAction: function () {
editor.windowManager.open(upload_config)
}
})
},
b) Add this button to the toolbar via toolbar: 'custom-upload'. This way, pressing the button will open the dialog based on the upload_config variable.
c) You've made a mistake in the panel config. I guess, you forgot to create the items array at the beginning. Instead of
type: 'panel',
{
type: 'dropzone',
name: 'file_drop',
types: 'file image' //// <----- something like this???
}
]
There should be:
type: 'panel',
items: [
{
type: 'dropzone',
name: 'file_drop',
label: 'Dropzone',
}
]
I've created this fiddle as a quick demo: https://fiddle.tiny.cloud/3iiaab
P.S. The dropzone component does not support file types. But you can check the file types after they are uploaded.
I'm trying to build a custom panel option editor in web app called Grafana, but am running into an error I suspect is no more than a React syntax issue.
195:15 error Component definition is missing display name react/display-name
export const optionsBuilder = (builder: PanelOptionsEditorBuilder<SVGOptions>) => {
return builder
.addBooleanSwitch({
category: ['SVG Document'],
path: 'svgAutoComplete',
name: 'Enable SVG AutoComplete',
description: 'Enable editor autocompletion, optional as it can be buggy on large documents',
})
.addCustomEditor({
category: ['SVG Document'],
path: 'svgSource',
name: 'SVG Document',
description: `Editor for SVG Document, while small tweaks can be made here, we recommend using a dedicated
Graphical SVG Editor and simply pasting the resulting XML here`,
id: 'svgSource',
defaultValue: props_defaults.svgNode,
editor: (props) => {
const grafanaTheme = config.theme.name;
return (
<MonacoEditor
language="xml"
theme={grafanaTheme === 'Grafana Light' ? 'vs-light' : 'vs-dark'}
value={props.value}
onChange={props.onChange}
/>
);
},
})
};
To use a custom panel option editor, use the addCustomEditor on the OptionsUIBuilder object in your module.ts file. Configure the editor to use by setting the editor property to the SimpleEditor component.
The tutorial in the Grafana Docs explains more about what I'm doing, but I believe the issue is just with the arrow function I use at line 195.
Is there a different way I should be retrieving my editor property?
I am using Fluent UI DetailsList. My table looks like below:
I need filters below every column (text or drop-down) as shown below:
Please let me know if this is possible? Or maybe a way to display custom header (using html) ?
This actually turned out to be easier than I thought it'd be...
If you're ok with clicking the column header to reveal the choices (vs having the dropdown directly under the title) then this can be achieved using the ContextualMenu component in conjunction with DetailsList. I got it working by tweaking from the variable row height example in the official docs: https://developer.microsoft.com/en-us/fluentui#/controls/web/detailslist/variablerowheights.
Add a ContextualMenu underneath your DetailsList:
<DetailsList
items={items}
columns={columns}
/>
{this.state.contextualMenuProps && <ContextualMenu {...this.state.contextualMenuProps} />}
Inside your column definition, set the hasDropdown action so the user gets a UI indicator that they can/should click the header, and call a contextMenu method (note I'm using onColumnContextMenu as well as onColumnClick so it doesn't matter if they left or right click the header:
{
key: 'dept',
name: 'Department',
fieldName: 'dept',
minWidth: 125,
maxWidth: 200,
onColumnContextMenu: (column, ev) => {
this.onColumnContextMenu(column, ev);
},
onColumnClick: (ev, column) => {
this.onColumnContextMenu(column, ev);
},
columnActionsMode: ColumnActionsMode.hasDropdown,
}
When the onColumnContextMenu method gets invoked, we need to build the context menu properties that will get consumed by the ContextualMenu component. Note the dismissal method as well, which clears out the state so the menu is hidden.
private onContextualMenuDismissed = (): void => {
this.setState({
contextualMenuProps: undefined,
});
}
private onColumnContextMenu = (column: IColumn, ev: React.MouseEvent<HTMLElement>): void => {
if (column.columnActionsMode !== ColumnActionsMode.disabled) {
this.setState({
contextualMenuProps: this.getContextualMenuProps(ev, column),
});
}
};
Finally, inside of getContextualMenuProps you need to determine what the options should be for the user to click. In this example, I'm simply giving sort options (you'll need to add an onClick handler to actually do something when the user clicks the item), but I'll use the column to determine what those items should actually be and paint the filters into the items collection so the user can select one to filter.
private getContextualMenuProps = (ev: React.MouseEvent<HTMLElement>, column: IColumn): IContextualMenuProps => {
const items: IContextualMenuItem[] = [
{
key: 'aToZ',
name: 'A to Z',
iconProps: { iconName: 'SortUp' },
canCheck: true,
checked: column.isSorted && !column.isSortedDescending,
},
{
key: 'zToA',
name: 'Z to A',
iconProps: { iconName: 'SortDown' },
canCheck: true,
checked: column.isSorted && column.isSortedDescending,
}
];
return {
items: items,
target: ev.currentTarget as HTMLElement,
directionalHint: DirectionalHint.bottomLeftEdge,
gapSpace: 10,
isBeakVisible: true,
onDismiss: this.onContextualMenuDismissed,
}
}
Note the target on the ContextualMenuProps object, which is what tells the ContextualMenu where to lock itself onto (in this case, the column header that you clicked to instantiate the menu.
Detail list filter for each column without context menu -
https://codesandbox.io/s/rajesh-patil74-jzuiy?file=/src/DetailsList.CustomColumns.Example.tsx
For instance - Providing filter in text field associated with each column will apply filter on color column.
I am trying to dynamically render images from a local folder within my project
The object structure looks like this
{
id: 2,
text: 'How would you describe your level of influence in management of the farm?',
type: 'cardChoice',
choices: [
{
id: 1,
text: 'I am the primary decision maker',
questionId: 'Hm?',
value: 'I am the primary decision maker',
image: '1.jpg',
},
{
id: 2,
text: 'I hent',
questionId: 'Hrm?',
value: 'I',
image: '2.jpg',
},
{
id: 3,
text: 'I arm',
questionId: '?',
value: 'Irm',
image: '3.jpg',
},
],
},
In my component I select the folder that contains the images const baseUrl = '../../assets/images/CNA'
After that, in my return I try to render the images
<img src={`${baseUrl}'${questionChoice.image}'`} alt={questionChoice.text} />
The page renders, but my image isn't loading and it's showing my alt instead
Heres my full component
const CardChoiceQuestions = ({ cardChoiceArray, currentAnswer, updateCurrent, submitAnswer }) => {
const { id, value } = currentAnswer
const baseUrl = '../../assets/images/CNA'
return (
<ButtonContainer>
{cardChoiceArray.map(questionChoice => {
return (
<Button
active={questionChoice.id === id}
type="button"
key={questionChoice.id}
onClick={() => {
const answer = { id: questionChoice.id, value: questionChoice.value }
updateCurrent(answer)
submitAnswer()
}}
>
<p>{questionChoice.text}</p>
<img src={`${baseUrl}${questionChoice.image}`} alt={questionChoice.text} />
</Button>
)
})}
</ButtonContainer>
)
}
I don't have my laptop in front of me but a few things I noticed. Do you need a slash "/" after your base url? Also, the string concatenation should be completed in one set of brackets after the $ sign. Not sure if that's the issue try a few console.log(string path) amd verify it is going where you think it is. It looks like the path may be wrong. You may be better off conditional rendering images as opposed to building a dynamic url but either way it should render on change.
<img src={`${baseUrl}/${questionChoice.image}`} alt={questionChoice.text} />
use like this
Leaving this here in case someone comes across this...
<img src={require(`../../assets/images/CNA/${questionChoice.image}`)} alt={questionChoice.text} />
Not sure why I can't use ${baseUrl} but this works for now.