@darwino/darwino-react-bootstrap
Version:
A set of Javascript classes and utilities
225 lines (206 loc) • 7.69 kB
JSX
/*
* (c) Copyright Darwino Inc. 2014-2017.
*/
import React, {Component} from "react";
import PropTypes from 'prop-types';
import { Button, FormControl, FormGroup, ControlLabel } from 'react-bootstrap';
import Dialog from "./Dialog"
import { Richtext } from '@darwino/darwino';
const { renderAttachmentUrl, cleanAttachmentName } = Richtext;
import DocumentForm from './DocumentForm';
const OP_NOOP = 0
const OP_CREATED = 1
const OP_UPDATED = 2
const OP_DELETED = 3
class FieldAttachments extends Component {
// Context to read from the parent - router
static contextTypes = {
documentForm: PropTypes.object
};
static defaultProps = {
colName: true,
colLength: true,
colMime: true,
colDelete: true
};
constructor(props,context) {
super(props,context);
if(!(context.documentForm instanceof DocumentForm)) {
throw new Error('AttachmentTable must be inside a component within a DocumentForm');
}
this.dragEnter = this.dragEnter.bind(this);
this.dragLeave = this.dragLeave.bind(this);
this.dragOver = this.dragOver.bind(this);
this.drop = this.drop.bind(this);
this.state = {dropAction: false};
}
onUpload(att) {
return !this.props.onUpload || this.props.onUpload(att)
}
onDelete(att) {
return !this.props.onDelete || this.props.onDelete(att)
}
_indexOf(name) {
const {value} = this.props.input;
for(let i=0; i<value.length; i++) {
if(value[i].name==name) return i;
}
return -1;
}
deleteFile(att) {
if(!this.onDelete(att)) {
return;
}
const {onChange,value} = this.props.input;
const newAtt = [...value]
const idx = this._indexOf(att.name)
if(att.op==OP_CREATED) {
newAtt.splice(idx, 1);
} else {
newAtt[idx] = {...att, op:OP_DELETED}
}
onChange(newAtt)
}
fileSelected(_file) {
const {richTextfield} = this.props;
let file = _file || this.refs.upload.files[0]
this.refs.upload.value = "" // Reset the value
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
let att = [...this.props.input.value] // We get the new value here...
let newAttachment = {
op: OP_CREATED,
name: (richTextfield ? (richTextfield+"^^") : "")+file.name, // Create file attachment
length: file.size,
mimeType: file.type,
}
if(!this.onUpload(newAttachment)) {
return;
}
// We should wait until the document is saved, performance wise
let content = reader.result
newAttachment.content = content.substring(content.indexOf(',')+1)
const idx = this._indexOf(newAttachment.name)
if(idx>=0) {
att[idx] = newAttachment
newAttachment.op = OP_UPDATED
} else {
att.push(newAttachment)
}
this.props.input.onChange(att)
}
reader.onerror = (error) => {
alert("Error reading attachment "+file.name)
};
}
renderUpload() {
const {disabled,buttonLabel} = this.props
return (
<div>
{<input ref="upload" type="file" id="input" onChange={(e) => this.fileSelected()} style={{display:"none"}} />}
<Button disabled={disabled} onClick={() =>this.refs.upload.click()}>{buttonLabel||"Choose File..."}</Button>
</div>
)
}
renderTable() {
const {richTextfield,onChange,readOnly} = this.props;
const {colName, colLength, colMime, colDelete} = this.props
const attachments = this.props.input.value
return (
<table className="table table-condensed table-striped table-bordered table-attachments">
<thead>
<tr>
{colName && <th>Name</th>}
{colLength && <th>Size</th>}
{colMime && <th>Type</th>}
{colDelete && !readOnly &&<th></th>}
</tr>
</thead>
<tbody>{attachments && this.renderAttachmentRows(this.filterAttachments(attachments, richTextfield), onChange)}</tbody>
</table>
)
}
filterAttachments(attachments,richTextfield) {
const inline = this.props.inline
return attachments.filter((att,idx) => {
if(att.op==OP_DELETED) {
return false;
}
if(richTextfield) {
const attName = att.name.toLowerCase()
if( attName.indexOf(richTextfield+"^^") == 0 // File attachement
|| (inline && attName.indexOf(richTextfield+"||") == 0)) { // Inline attachment
return true;
}
return false;
}
return true;
})
}
renderAttachmentRows(attachments) {
const { disabled, readOnly } = this.props
const { colName, colLength, colMime, colDelete } = this.props
const { databaseId, storeId, unid } = this.context.documentForm.state;
return attachments.map((att) => {
return (<tr key={att.name}>
{colName && <td>
{att.op!=OP_CREATED ?
<a href={renderAttachmentUrl(databaseId, storeId, unid, att.name)} target="_blank">
{cleanAttachmentName(att.name)}
</a>
:
<a style={{pointerEvents: 'none'}} disabled="disabled">
{cleanAttachmentName(att.name)}
</a>
}
</td>}
{colLength && <td>{att.length}</td>}
{colMime && <td>{att.mimeType}</td>}
{colDelete && !readOnly &&
<td>
<Button disabled={disabled} bsStyle="link" onClick={()=>{this.deleteFile(att)}}><span className="glyphicon glyphicon-trash" aria-hidden="true"></span></Button>
</td>
}
</tr>
)
})
}
dragEnter(e) {
e.preventDefault();
this.setState({dropAction: true});
}
dragLeave(e) {
e.preventDefault();
this.setState({dropAction: false});
}
dragOver(e) {
e.preventDefault(); e.stopPropagation()
try {
e.dataTransfer.dropEffect = 'copy'
} catch (err) {}
return false
}
drop(e) {
const { disabled, readOnly } = this.props
if(!disabled && !readOnly) {
e.stopPropagation();e.preventDefault();
var dt = e.dataTransfer;
var files = dt.files;
for(let i=0; i<files.length; i++) {
this.fileSelected(files[i]);
}
}
this.setState({dropAction: false});
}
render() {
const {multiple, readOnly } = this.props
return (
<div onDragEnter={this.dragEnter} onDragLeave={this.dragLeave} onDragOver={this.dragOver} onDrop={this.drop}>
{!readOnly && this.renderUpload()}
{this.renderTable()}
</div>
)
}
}
export default FieldAttachments