react-jsonschema-form
react-jsonschema-form copied to clipboard
Material-ui - field type `data-url`
Prerequisites
- [x ] I have read the documentation;
- [x] In the case of a bug report, I understand that providing a SSCCE example is tremendously useful to the maintainers.
- [x] Ideally, I'm providing a sample JSFiddle or a shared playground link demonstrating the issue.
Description
I am using the material-ui theme, which I love to have available. Only no proper data-url field Widget is present in Material-ui.
Because of this the title
property is not rendered for material-ui, type: data-url
.
Is this on purpose and am I missing some functionality?
Version
├─ @rjsf/[email protected] └─ @rjsf/[email protected]
Any feedback possible on this issue?
I forgot to provide playground link:
In the playground https://rjsf-team.github.io/react-jsonschema-form/ when choosing the example named 'Files' and 'material-ui' as theme, the problem is visible.
Also semantic-ui and fluent-ui experience this problem.
The screenshots show Material-UI and Bootstrap 4. In Bootstrap the tile field is rendered, in Material-UI nog title field is present.
Any feedback on how to solve this?
I've been having this problem as well, I've been able to use the "ui:help" helper text as a work around in some places, but it would be great to have the label show up for file inputs.
Hello @hoetmaaiers did you solve this issue. Also do you know how to change 'choose file' and 'no files selected' ?? I am planning to use 'Select your photo' instead
No sorry, I went with using the basic HTML form and add some basic css to this form to match the application.
A true solution, update on the material-theme or some hints on how to solve the bug would still be great.
I ended up creating a custom widget using the functions from the core package FileWidget
import React, { useState, useRef, useEffect } from "react";
import { WidgetProps } from "@rjsf/core";
import FormLabel from "@material-ui/core/FormLabel";
import List from "@material-ui/core/List";
import Input from "@material-ui/core/Input";
import ListItemText from "@material-ui/core/ListItemText";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItem from "@material-ui/core/ListItem";
import DescriptionIcon from "@material-ui/icons/Description";
import { dataURItoBlob } from "../../../core/src/utils";
interface FileInfo {
name: string;
size: number;
type: string;
}
interface Props {
filesInfo?: FileInfo[];
}
function addNameToDataURL(dataURL: any, name: any) {
return dataURL.replace(";base64", `;name=${encodeURIComponent(name)};base64`);
}
function processFile(file: any) {
const { name, size, type } = file;
return new Promise((resolve, reject) => {
const reader = new window.FileReader();
reader.onerror = reject;
reader.onload = event => {
resolve({
dataURL: addNameToDataURL(event.target?.result, name),
name,
size,
type,
});
};
reader.readAsDataURL(file);
});
}
function processFiles(files: any) {
return Promise.all([].map.call(files, processFile));
}
function extractFileInfo(dataURLs: any[]) {
return dataURLs
.filter(dataURL => typeof dataURL !== "undefined")
.map(dataURL => {
const { blob, name } = dataURItoBlob(dataURL);
return {
name: name,
size: blob.size,
type: blob.type,
};
});
}
const FilesInfo = ({ filesInfo }: Props) => {
if (!filesInfo || filesInfo.length === 0) {
return null;
}
return (
<List id="file-info">
{filesInfo.map((fileInfo: any, key: any) => {
const { name, size, type } = fileInfo;
return (
<ListItem key={key}>
<ListItemIcon>
<DescriptionIcon />
</ListItemIcon>
<ListItemText primary={name} secondary={`${type}, ${size} bytes`} />
</ListItem>
);
})}
</List>
);
};
const FileWidget = ({
id,
schema,
options,
value,
required,
disabled,
readonly,
label,
multiple,
autofocus,
onChange,
}: WidgetProps) => {
const [state, setState] = useState<FileInfo[]>();
const inputRef = useRef();
useEffect(() => {
const values = Array.isArray(value) ? value : [value];
const initialFilesInfo: FileInfo[] = extractFileInfo(values);
if (initialFilesInfo.length > 0) {
setState(initialFilesInfo);
}
}, [value]);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
processFiles(event.target.files).then((filesInfo: any) => {
setState(filesInfo);
const values = filesInfo.map((fileInfo: any) => fileInfo.dataURL);
if (multiple) {
onChange(values);
} else {
onChange(values[0]);
}
});
};
return (
<>
<FormLabel required={required} htmlFor={id}>
{label || schema.title}
</FormLabel>
<Input
ref={inputRef.current}
id={id}
type="file"
disabled={readonly || disabled}
onChange={handleChange}
autoFocus={autofocus}
multiple={multiple}
accept={options.accept as string}
/>
<FilesInfo filesInfo={state} />
</>
);
};
export default FileWidget;
was going to make a PR but was having issues installing packages for this repo (might be too much for my laptop)
My custom FileWidget
import React, { Component } from "react"; import PropTypes from "prop-types";
import AttachFile from "@material-ui/icons/AttachFile"; import { Fab, Button } from "@material-ui/core";
import { dataURItoBlob, shouldRender } from "@rjsf/core/lib/utils";
function addNameToDataURL(dataURL, name) {
return dataURL.replace(";base64", ;name=${encodeURIComponent(name)};base64
);
}
function processFile(file) { const { name, size, type } = file; return new Promise((resolve, reject) => { const reader = new window.FileReader(); reader.onerror = reject; reader.onload = event => { resolve({ dataURL: addNameToDataURL(event.target.result, name), name, size, type, }); }; reader.readAsDataURL(file); }); }
function processFiles(files) { return Promise.all([].map.call(files, processFile)); }
function FilesInfo(props) { const { filesInfo } = props; if (filesInfo.length === 0) { return null; } return ( <ul className="file-info"> {filesInfo.map((fileInfo, key) => { const { name, size, type } = fileInfo; return (
function extractFileInfo(dataURLs) { return dataURLs .filter(dataURL => typeof dataURL !== "undefined") .map(dataURL => { const { blob, name } = dataURItoBlob(dataURL); return { name: name, size: blob.size, type: blob.type, }; }); }
class FileWidget extends Component { constructor(props) { super(props); const { value } = props; const values = Array.isArray(value) ? value : [value]; this.state = { values, filesInfo: extractFileInfo(values) }; }
shouldComponentUpdate(nextProps, nextState) { return shouldRender(this, nextProps, nextState); }
onChange = event => { const { multiple, onChange } = this.props; processFiles(event.target.files).then(filesInfo => { const state = { values: filesInfo.map(fileInfo => fileInfo.dataURL), filesInfo, }; this.setState(state, () => { if (multiple) { onChange(state.values); } else { onChange(state.values[0]); } }); }); };
render() { const { multiple, id, readonly, disabled, autofocus, options, label } = this.props; console.log('this.props', this.props) const { filesInfo } = this.state; return (
<label htmlFor={id}>
<input
style={{ display: "none" }}
ref={ref => (this.inputRef = ref)}
id={id}
name={id}
type="file"
disabled={readonly || disabled}
onChange={this.onChange}
defaultValue=""
autoFocus={autofocus}
multiple={multiple}
accept={options.accept}
/>
<Button color="primary" variant="contained" component="span">
<AttachFile /> {label}
</Button>{" "}
</label>
</p>
<FilesInfo filesInfo={filesInfo} />
</div>
);
} }
FileWidget.defaultProps = { autofocus: false, };
if (process.env.NODE_ENV !== "production") { FileWidget.propTypes = { multiple: PropTypes.bool, value: PropTypes.oneOfType([ PropTypes.string, PropTypes.arrayOf(PropTypes.string), ]), autofocus: PropTypes.bool, }; }
export default FileWidget;
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Please leave a comment if this is still an issue for you. Thank you.
Still an issue for me after installing latest react-jsonschema-form through npm. gh-pages showcase also affected.
@heath-freenome regarding "needs playground link":
It was already mentioned by @hoetmaaiers in their 3rd post in this issue:
I forgot to provide playground link:
In the playground https://rjsf-team.github.io/react-jsonschema-form/ when choosing the example named 'Files' and 'material-ui' as theme, the problem is visible.
Currently, the playground URL gives 404, but I assume it will still be a problem when it comes back online, as no fix was referenced yet.