Skip to content

Instantly share code, notes, and snippets.

@andhdo
Last active April 23, 2017 05:27
Show Gist options
  • Save andhdo/8a88afa4390295c5e5f203c152cb0244 to your computer and use it in GitHub Desktop.
Save andhdo/8a88afa4390295c5e5f203c152cb0244 to your computer and use it in GitHub Desktop.
Meteor Uniforms FileUpload widget using ostrio:files (aldeed autoform replacement in react)

the idea of the project is to do the upload with ostrio (to 'xfile' collection objects) and then bind the identifier of the collection to another model (to 'document' objects).

this is the layout in the project:

  • /app/imports/ui/components/uniforms/UploadField.jsx

this is the usage in a project (not finished yet, but it could be a guide):

VIEW / UI

  • /app/imports/startup/client/routes.jsx: Contains router declaration to view
  • /app/imports/ui/views/documents/documentsHome.jsx : Renders the view
  • /app/imports/ui/views/documents/partials/documentsCreate.jsx : Contains the fragment with the form, inside the view

MODEL / BACKEND

  • /app/imports/api/xfiles/xfiles.js: this is the file collection declaration (container of uploads)
  • /app/imports/api/documents/documents.js: this is the model that contains the usage of uploads inside the business context model
  • /app/imports/startup/server/register-api.js: api registration

you need to add some dependencies to the meteor project:

meteor add aldeed:collection2 meteor add ostrio:files meteor add ostrio:meteor-root

and some for nodejs

meteor npm install --save uniforms meteor npm install --save uniforms-bootstrap3 meteor npm install --save react-bootstrap meteor npm install --save react-dropzone meteor npm install --save prop-types meteor npm install --save react-s-alert

import { Mongo } from 'meteor/mongo';
import { SimpleSchema } from 'meteor/aldeed:simple-schema';
// import { Xfiles } from './xfiles.js';
import { UploadField } from '../../ui/components/uniforms/UploadField.jsx';
import { Xfiles } from './../xfiles/xfiles.js';
const Documents = new Mongo.Collection('Documents');
const DocumentsSchema = new SimpleSchema({
"original" : {
type: Object,
blackbox: true,
label: "archivo",
optional: true,
uniforms: {
component: UploadField,
collection: Xfiles
}
},
"nombre" : {
type: String,
label: "nombre",
max: 1024,
optional: false,
},
});
Documents.attachSchema( DocumentsSchema );
Documents.allow({
insert: (userId, doc) => {
// only allow posting if you are logged in
return !! userId;
},
update: (userId, doc) => {
// only allow posting if you are logged in
return !! userId;
},
remove: (userId, doc) => {
// only allow posting if you are logged in
return !! userId;
}
});
export { Documents, DocumentsSchema };
import React, { PropTypes } from 'react';
import { browserHistory } from 'react-router';
//
import { Meteor } from 'meteor/meteor';
import { createContainer } from 'meteor/react-meteor-data';
//
import { Documents, DocumentsSchema } from '../../../../api/documents/documents.js';
//
// Choose your theme
import {AutoForm} from 'uniforms-bootstrap3';
import {QuickForm} from 'uniforms-bootstrap3';
import {AutoField} from 'uniforms-bootstrap3';
//import {QuickForm} from 'uniforms';
import {SubmitField} from 'uniforms-bootstrap3';
import {ErrorsField} from 'uniforms-bootstrap3';
import {UploadField} from '../../../components/uniforms/UploadField.jsx';
const DocumentsCreateForm = () => {
return (
<AutoForm
schema={DocumentsSchema}
onSubmit={doc => {
console.log(doc);
Meteor.call('documents.insert', doc);
}}
onSubmitSuccess={()=>alert('Promise resolved')}
onSubmitFailure={()=>alert('Promise rejected')}
>
<UploadField name="original" />
<AutoField name="nombre" />
<ErrorsField />
<SubmitField />
</AutoForm>
)
}
/*
<AutoForm
schema={DocusetsSchema}
onSubmit={doc => {
console.log(doc);
Meteor.call('docusets.insert', doc);
}}
onSubmitSuccess={()=>alert('Promise resolved')}
onSubmitFailure={()=>alert('Promise rejected')}
/>
*/
class DocumentsCreatePartial extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div>
<DocumentsCreateForm />
</div>
);
}
}
/*
<AutoForm
schema={Docusets._collection.simpleSchema()}
onSubmit={doc => console.log(doc)}
>
<AutoField name="nombre" />
<SubmitField />
</AutoForm>
*/
DocumentsCreatePartial.propTypes = {
currentUser: PropTypes.object,
};
export default createContainer(() => {
return {
currentUser: Meteor.user(),
};
}, DocumentsCreatePartial);
import React, { PropTypes } from 'react';
import { browserHistory } from 'react-router';
//
import { Meteor } from 'meteor/meteor';
import { createContainer } from 'meteor/react-meteor-data';
//
import ContentHeader from '../content_header.jsx';
import DocumentsCreatePartial from './partials/documentsCreate.jsx';
class DocumentsHome extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
// i just want to show browser url in '/dashboard'.
// So, this component is repeated after index rendered <Dashboard />
browserHistory.push('/documents/home');
}
render() {
return(
<div className="content-wrapper">
{/* Content Wrapper. Contains page content */}
{/* Content Header (Page header) */}
<ContentHeader name="Desde Archivo" description="Importar" breadcrumbIcon="fa fa-archive" breadcrumb="Home" />
{/* Main content */}
<section className="content">
<DocumentsCreatePartial/>
{/* Your Page Content Here */}
<span>statistics</span>
</section>
{/* /.content */}
{/* /.content-wrapper */}
</div>
);
}
}
DocumentsHome.propTypes = {
currentUser: PropTypes.object,
};
export default createContainer(() => {
return {
currentUser: Meteor.user(),
};
}, DocumentsHome);
// import this file in /app/imports/startup/server/index.js file with
// this declaration => import './register-api.js';
import '../../api/xfiles/xfiles.js';
import '../../api/xfiles/methods.js';
//
import '../../api/documents/documents.js';
import '../../api/documents/methods.js';
import React from 'react';
import { Router, Route, browserHistory } from 'react-router';
//
import DocumentsHome from '../../ui/views/documents/documentsHome.jsx';
...
export const Routes = () => (
<Router history={ browserHistory }>
<Route path="/" component={ Index }>
<Route path="/documents" >
<Route path="home" component={ DocumentsHome }/>
</Route>
</Route>
</Router>
);
import React from 'react';
import { PropTypes } from 'react';
//
import { Meteor } from 'meteor/meteor';
import {_} from 'meteor/underscore';
//
import { ReactMeteorData } from 'meteor/react-meteor-data';
import { createContainer } from 'meteor/react-meteor-data';
//
import { BaseField } from 'uniforms';
import { connectField } from 'uniforms'; //'uniforms/connectField'
//
import { Xfiles } from '../../../api/xfiles/xfiles.js';
//
import Dropzone from 'react-dropzone';
import Alert from 'react-s-alert';
//
const UploadFieldComponent = React.createClass({
mixins: [ReactMeteorData],
//constructor(props) {
// super(props);
// this.state = this.getInitialState();
//}
getInitialState(){
return {
uploading: [],
progress: 0,
inProgress: false,
uploadState: null,
uploadError: null
}
},
getMeteorData() {
return {};
},
setUploadProgress(progress) {
this.setState({
progress: progress
});
},
setUploadState(uploadState) {
this.setState({
uploadState: uploadState
});
},
setUploadError(error){
this.setState({
uploadError: error
});
},
uploadFile(self, file, _onUploadDone) {
if (file) {
let uploadInstance = Xfiles.insert({
file: file,
meta: {
locator: self.props.fileLocator,
userId: Meteor.userId() // Optional, used to check on server for file tampering
},
streams: 'dynamic',
chunkSize: 'dynamic',
allowWebWorkers: true // If you see issues with uploads, change this to false
}, false);
self.setState({
uploading: uploadInstance, // Keep track of this instance to use below
inProgress: true // Show the progress bar now
});
// These are the event functions, don't need most of them, it shows where we are in the process
uploadInstance.on('progress', function (progress, fileObj) {
console.log('Upload Percentage: ' + progress);
// Update our progress bar
self.setUploadProgress(progress);
});
uploadInstance.on('start', function () {
console.log('Starting');
self.setUploadState('uploading');
});
uploadInstance.on('end', function (error, fileObj) {
self.setUploadState(null);
self.setUploadError(error);
_onUploadDone(error, fileObj);
});
uploadInstance.on('error', function (error, fileObj) {
console.log('Error during upload: ' + error);
self.setState({
uploadError: {
message: error
}
})
});
uploadInstance.on('uploaded', function (error, fileObj) {
console.log('uploaded: ', fileObj);
/*
// Remove the filename from the upload box
self.refs['fileinput'].value = '';
// Reset our state for the next file
self.setState({
uploading: [],
progress: 0,
inProgress: false
});
*/
});
uploadInstance.start(); // Must manually start the upload
}
},
setValue(fileObj) {
const valueData = {
xfileId : fileObj._id,
xfileCollection : fileObj._collectionName
};
// set data to model
this.props.onChange(valueData);
},
onUploadDone(err,fileObj){
console.log('On end File Object: ', fileObj);
if(fileObj) {
const valueData = {
xfileId : fileObj._id,
xfileCollection : fileObj._collectionName
};
this.setValue(fileObj);
Alert.success('success; uploaded=' + fileObj.name );
/*
this.props.value.xfileId = fileObj._id;
this.props.value.xfileCollection = fileObj._collectionName;
console.log('this.props.value=',this.props.value);
*/
} else {
Alert.error(error.message);
console.error(error);
throw new Meteor.Error('upload-file-fail', error);
}
},
uploadIt(e){
"use strict";
e.preventDefault();
let self = this;
if (e.currentTarget.files && e.currentTarget.files[0]) {
// We upload only one file, in case
// there was multiple files selected
var file = e.currentTarget.files[0];
self.uploadFile(self, file, self.onUploadDone);
}
},
onDrop(acceptedFiles, rejectedFiles) {
// only take one
const file = acceptedFiles[0];
let self = this;
self.uploadFile(self, file, self.onUploadDone);
},
// This is our progress bar, bootstrap styled
// Remove this function if not needed
showUploads() {
console.log('**********************************', this.state.uploading);
console.log('showing',this.props.xfileId);
console.log('showing',this.props.xfileCollection);
if (!_.isEmpty(this.state.uploading)) {
return <div>
{this.state.uploading.file.name}
<div className="progress progress-bar-default">
<div style={{width: this.state.progress + '%'}} aria-valuemax="100"
aria-valuemin="0"
aria-valuenow={this.state.progress || 0} role="progressbar"
className="progress-bar">
<span className="sr-only">{this.state.progress}% Complete (success)</span>
<span>{this.state.progress}%</span>
</div>
</div>
{/*
{this.state.inProgress ?
<div>Uploading... {this.state.progress}</div> :
}
<div>
{this.value ? <textarea>{this.value}</textarea> : null}
</div>
<div>
{this.state.uploadError ? <p>{this.state.uploadError.message}</p> : null}
</div>
---------
{uploadState === 'uploading' ?
<div>Uploading... {uploadProgress}</div> :
<div>
{value ? <CollectionImage aspectRatio={aspectRatio} imageId={value} /> : null}
</div>
}
---------
{uploadError ? <p>{uploadError.message}</p> : null}
*/}
</div>
}
},
showWidget() {
const renderType = 'dropzoneWidget';//'fileWidget';
if('fileWidget' == renderType) {
widget = (
<input type="file"
id={this.props.id}
name={this.props.name}
disabled={this.state.inProgress}
ref="fileinput"
onChange={this.uploadIt}
/>
);
} else if('buttonWidget' == renderType ) {
widget = (
<section className="upload-container">
<input type="file"
id={this.props.id}
name={this.props.name}
disabled={this.state.inProgress}
ref="fileinput"
onChange={this.uploadIt}
style={{display:'none'}}
/>
<div className='buttons'>
<a onClick={this.handleClick.bind(this)}>
{ this.state.uploading ? <T>upload.uploading</T> : <T>upload.chooseFile</T> }
</a> <span className='font-smaller'>{this.renderSpecs()}</span>
</div>
</section>
);
} else if('dropzoneWidget' == renderType ) {
widget = (
<Dropzone onDrop={this.onDrop} multiple={false} >
<div className="fake-upload">
<i className="fa fa-cloud-upload"></i>
<h5>SELECCIONAR ARCHIVO</h5>
</div>
</Dropzone>
);
}
return widget;
},
render() {
return (
<div>
<div className="row">
<div className="col-md-12">
<p>{this.props.label && (
<span>{this.props.label}</span>
)}</p>
<div className="upload-field">
{this.showWidget()}
</div>
</div>
</div>
<div className="row m-t-sm m-b-sm">
<div className="col-md-6">
{this.showUploads()}
</div>
<div className="col-md-6">
</div>
</div>
</div>
)
},
});
UploadFieldComponent.propTypes = {
value: PropTypes.object
};
/*
export default createContainer(() => {
return {
currentUser: Meteor.user(),
};
}, connectField(UploadField));
*/
export const UploadField = connectField(UploadFieldComponent, {
mapProps: props => {
//props.xfileId = props.value.xfileId || '';
//props.xfileCollection = props.value.xfileCollection || '';
return props;
},
});
// Definition of the collection object
// ostrio implementation
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { FilesCollection } from 'meteor/ostrio:files';
//import meteorProjectPath from 'meteor-project-path';
export const Xfiles = new Meteor.Files({ // Meteor.Files FilesCollection
debug: true, // Change to `true` for debugging
storagePath: ( Meteor.settings.FS_STORE_RELATIVE ) ? ( Meteor.absolutePath +'/' + Meteor.settings.FS_STORE_FILESYSTEM_PATH ) : (Meteor.settings.FS_STORE_FILESYSTEM_PATH) , //'assets/app/uploads/uploadedFiles',
collectionName: 'Xfiles',
allowClientCode: false, // Disallow remove files from Client
onBeforeUpload: function (file) {
// Allow upload files under 10MB, and only in png/jpg/jpeg formats
if (file.size <= 1024*1024*20 && /png|jpg|jpeg|pdf|doc/i.test(file.extension)) {
return true;
} else {
return 'Please upload image, with size equal or less than 20MB';
}
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment