├── .jshintrc
├── Demos
├── Upload.html
├── add.png
├── delete.png
├── files.php
└── styles.css
├── README.md
├── Source
├── Form.MultipleFileInput.js
├── Form.Upload.js
└── Request.File.js
├── package.yml
└── screenshot.png
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "mootools": true,
3 | "browser": true
4 | }
5 |
--------------------------------------------------------------------------------
/Demos/Upload.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Multiple Upload
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
30 |
31 |
32 |
33 |
34 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/Demos/add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arian/mootools-form-upload/bdc927d62819625908f14b574b3fa63ebe167e16/Demos/add.png
--------------------------------------------------------------------------------
/Demos/delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arian/mootools-form-upload/bdc927d62819625908f14b574b3fa63ebe167e16/Demos/delete.png
--------------------------------------------------------------------------------
/Demos/files.php:
--------------------------------------------------------------------------------
1 | ';
4 |
5 | print_r($_FILES);
6 |
7 | print_r($_POST);
8 |
9 |
--------------------------------------------------------------------------------
/Demos/styles.css:
--------------------------------------------------------------------------------
1 |
2 | /* some general styles */
3 |
4 | body {
5 | font: 0.9em 'Ubuntu', arial, sans-serif;
6 | }
7 |
8 | #wrapper {
9 | margin: 100px auto;
10 | width: 50%;
11 | }
12 |
13 | fieldset {
14 | border: 0;
15 | }
16 |
17 | form {
18 | border-top: 1px #545a74 solid;
19 | border-bottom: 1px #545a74 solid;
20 | background: #e2e5f3;
21 | margin: 20px 0;
22 | padding: 10px;
23 | }
24 |
25 | fieldset legend {
26 | color: #666;
27 | margin: 0;
28 | font-size: 1.5em;
29 | font-weight: normal;
30 | padding-bottom: 20px;
31 | }
32 |
33 | .formRow {
34 | overflow: auto;
35 | border-bottom: 1px #bcbcbc solid;
36 | padding: 5px 0;
37 | max-width: 100%;
38 | }
39 |
40 | .formRow label {
41 | padding: 4px;
42 | margin: 0 3px 0 0;
43 | border-right: 1px #bcbcbc solid;
44 | }
45 |
46 |
47 | /* Drag & Drop file uploading */
48 |
49 | .droppable {
50 | border: #121a3f 1px solid;
51 | border-radius: 3px;
52 | background: #545A74;
53 | color: white;
54 | padding: 20px;
55 | margin: 10px;
56 | clear: both;
57 | text-align: center;
58 | }
59 |
60 | .droppable.hover {
61 | background: #121a3f;
62 | }
63 |
64 | .uploadList {
65 | margin: 0;
66 | padding: 0;
67 | list-style: none;
68 | }
69 |
70 | .uploadItem {
71 | overflow: hidden;
72 | border-bottom: #BCBCBC 1px solid;
73 | margin: 0 20px;
74 | padding: 3px;
75 | }
76 |
77 | .uploadItem span {
78 | overflow: hidden;
79 | width: 150px;
80 | float: left;
81 | display: block;
82 | }
83 |
84 | a.addInputRow,
85 | a.delInputRow,
86 | .uploadItem a {
87 | display: inline-block;
88 | background: url(add.png) no-repeat;
89 | height: 16px;
90 | width: 16px;
91 | text-indent: -999px;
92 | }
93 |
94 | .uploadItem a {
95 | float: left;
96 | display: block;
97 | padding-left: 20px;
98 | background-image: url(delete.png);
99 | }
100 |
101 | a.delInputRow {
102 | background-image: url(delete.png);
103 | }
104 |
105 | .progress {
106 | margin: 5px 0;
107 | height: 15px;
108 | border-radius: 3px;
109 | background: #545A74;
110 | }
111 |
112 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Multiple File Upload with HTML5
2 | ===============================
3 |
4 | Upload your files the HTML5 way, drag and drop from your computer, multiple
5 | files the ajaxy way!
6 |
7 | It works about the same as uploading attachments to gmail.
8 |
9 | - HTML5 Drag and Drop files from your computer
10 | - HTML5 multiple attribute files for selecting multiple files
11 | - Uploading files through XMLHttpRequest with FormData
12 | - Progress bar
13 | - Graceful degradation for IE / Opera
14 |
15 |
16 | How To Use
17 | -----------
18 |
19 | Include the JavaScript files in your page, for example with the `
37 |
38 |
39 |
40 |
41 | To get the magic running just initiate the Form.Upload class:
42 |
43 | #JS
44 | new Form.Upload('url');
45 |
46 | This is for the html structure like
47 |
48 | #HTML
49 |
63 |
64 | ### Events
65 |
66 | The `Form.Upload` class has only one event: `onComplete`. This is only used
67 | in the HTML5 uploader
68 |
69 | ### Graceful Degradation
70 |
71 | Note that in the HTML the input is wrapped in a `div.formRow` element. This
72 | is for the graceful degradation in older browsers. The Form.Upload class will
73 | clone the first parent that maches `.formRow` of the `input#url` element. Then
74 | it will append the clones after that `div.formRow` element.
75 |
76 | By default it will just upload the files and go to the page given in the action
77 | attribute, like any other form post. With the `isModern()` method you could
78 | check whether it's using the HTML5 possibilities or the legacy upload.
79 |
80 | **Example**
81 |
82 | #JS
83 |
84 | var upload = new Form.Upload('url', {
85 | onComplete: function(){
86 | alert('Completed uploading the Files');
87 | }
88 | });
89 |
90 | if (!upload.isModern()){
91 | // using iFrameFormRequest from the forge to upload the files
92 | // in an IFrame.
93 | new iFrameFormRequest('myForm', {
94 | onComplete: function(response){
95 | alert('Completed uploading the files');
96 | }
97 | });
98 | }
99 |
100 | ### Using the files separately
101 |
102 | The Form.Upload Class is just a useful class to use when you quickly want to
103 | build the upload form. However the `Form.MultipleFileInput` and `File.Request`
104 | classes can be used separately too!
105 |
106 | #### Form.MultipleFileInput
107 |
108 | This is a class that transforms a simple `input[type="file"]` into a multiple
109 | file input field with even drag and drop!
110 |
111 | Checkout the source for all options and events. That's not very spectacular.
112 |
113 | #### Request.File
114 |
115 | This is a class that extends Request and brings file uploading to Request.
116 | Some credits should go to fellow MooTools Developer Djamil Legato for proving
117 | this class.
118 |
119 | In this version it is a very stripped down version and not all Request options
120 | might work. In 95% of the cases you don't really need them though, so it doesn't
121 | matter really.
122 |
123 | #### Combining Form.MultipleFileInput and Request.File
124 |
125 | When the user has selected all the files, they can be uploaded.
126 | To bridge those to classes, usually Form.Upload can be used.
127 |
128 | However if you want more control, the following can be done:
129 |
130 | #JS
131 | // the input element, the list (ul) and the drop zone element.
132 | var input, list, drop;
133 | // Form.MultipleFileInput instance
134 | var inputFiles = new Form.MultipleFileInput(input, list, drop, {
135 | onDragenter: drop.addClass.pass('hover', drop),
136 | onDragleave: drop.removeClass.pass('hover', drop),
137 | onDrop: drop.removeClass.pass('hover', drop)
138 | });
139 |
140 | // Request instance;
141 | var request = new Request.File({
142 | url: 'files.php'
143 | // onSuccess
144 | // onProgress
145 | });
146 |
147 | myForm.addEvent('submit', function(event){
148 | event.preventDefault();
149 | inputFiles.getFiles().each(function(file){
150 | request.append('url[]' , file);
151 | });
152 | request.send();
153 | });
154 |
155 | In the last loop, we loop over the [File](http://www.w3.org/TR/FileAPI/)
156 | instances and we append them to the request:
157 |
158 | request.append((string) fieldname, (File) file);
159 |
160 |
161 | Screenshots
162 | -----------
163 |
164 | 
165 |
--------------------------------------------------------------------------------
/Source/Form.MultipleFileInput.js:
--------------------------------------------------------------------------------
1 | /*
2 | ---
3 |
4 | name: Form.MultipleFileInput
5 | description: Create a list of files that has to be uploaded
6 | license: MIT-style license.
7 | authors: Arian Stolwijk
8 | requires: [Element.Event, Class, Options, Events]
9 | provides: Form.MultipleFileInput
10 |
11 | ...
12 | */
13 |
14 | Object.append(Element.NativeEvents, {
15 | dragenter: 2, dragleave: 2, dragover: 2, dragend: 2, drop: 2
16 | });
17 |
18 | if (!this.Form) this.Form = {};
19 |
20 | Form.MultipleFileInput = new Class({
21 |
22 | Implements: [Options, Events],
23 |
24 | options: {
25 | itemClass: 'uploadItem'/*,
26 | onAdd: function(file){},
27 | onRemove: function(file){},
28 | onEmpty: function(){},
29 | onDragenter: function(event){},
30 | onDragleave: function(event){},
31 | onDragover: function(event){},
32 | onDrop: function(event){}*/
33 | },
34 |
35 | _files: [],
36 |
37 | initialize: function(input, list, drop, options){
38 | input = this.element = document.id(input);
39 | list = this.list = document.id(list);
40 | drop = this.drop = document.id(drop);
41 |
42 | this.setOptions(options);
43 |
44 | var name = input.get('name');
45 | if (name.slice(-2) != '[]') input.set('name', name + '[]');
46 | input.set('multiple', true);
47 |
48 | this.inputEvents = {
49 | change: function(event){
50 | Array.each(input.files, this.add, this);
51 | this.fireEvent('change', event);
52 | }.bind(this)
53 | };
54 |
55 | this.dragEvents = drop && (typeof document.body.draggable != 'undefined') ? {
56 | dragenter: this.fireEvent.bind(this, 'dragenter'),
57 | dragleave: this.fireEvent.bind(this, 'dragleave'),
58 | dragend: this.fireEvent.bind(this, 'dragend'),
59 | dragover: function(event){
60 | event.preventDefault();
61 | this.fireEvent('dragover', event);
62 | }.bind(this),
63 | drop: function(event){
64 | event.preventDefault();
65 | var dataTransfer = event.event.dataTransfer;
66 | if (dataTransfer) Array.each(dataTransfer.files, this.add, this);
67 | this.fireEvent('drop', event);
68 | }.bind(this)
69 | } : null;
70 |
71 | this.attach();
72 | },
73 |
74 | attach: function(){
75 | this.element.addEvents(this.inputEvents);
76 | if (this.dragEvents) this.drop.addEvents(this.dragEvents);
77 | },
78 |
79 | detach: function(){
80 | this.input.removeEvents(this.inputEvents);
81 | if (this.dragEvents) this.drop.removeEvents(this.dragEvents);
82 | },
83 |
84 | add: function(file){
85 | this._files.push(file);
86 | var self = this;
87 | new Element('li', {
88 | 'class': this.options.itemClass
89 | }).grab(new Element('span', {
90 | text: file.name
91 | })).grab(new Element('a', {
92 | text: 'x',
93 | href: '#',
94 | events: {click: function(e){
95 | e.preventDefault();
96 | self.remove(file);
97 | }}
98 | })).inject(this.list);
99 | this.fireEvent('add', file);
100 | return this;
101 | },
102 |
103 | remove: function(file){
104 | var index = this._files.indexOf(file);
105 | if (index == -1) return this;
106 | this._files.splice(index, 1);
107 | this.list.childNodes[index].destroy();
108 | this.fireEvent('remove', file);
109 | if (!this._files.length) this.fireEvent('empty');
110 | return this;
111 | },
112 |
113 | getFiles: function(){
114 | return this._files;
115 | }
116 |
117 | });
118 |
--------------------------------------------------------------------------------
/Source/Form.Upload.js:
--------------------------------------------------------------------------------
1 | /*
2 | ---
3 |
4 | name: Form.Upload
5 | description: Create a multiple file upload form
6 | license: MIT-style license.
7 | authors: Arian Stolwijk
8 | requires: [Form.MultipleFileInput, Request.File]
9 | provides: Form.Upload
10 |
11 | ...
12 | */
13 |
14 | (function(){
15 | "use strict";
16 |
17 | if (!this.Form) this.Form = {};
18 | var Form = this.Form;
19 |
20 | Form.Upload = new Class({
21 |
22 | Implements: [Options, Events],
23 |
24 | options: {
25 | dropMsg: 'Please drop your files here',
26 | fireAtOnce: false,
27 | onComplete: function(){
28 | // reload
29 | window.location.href = window.location.href;
30 | }
31 | },
32 |
33 | initialize: function(input, options){
34 | input = this.input = document.id(input);
35 |
36 | this.setOptions(options);
37 |
38 | // Our modern file upload requires FormData to upload
39 | if ('FormData' in window) this.modernUpload(input);
40 | else this.legacyUpload(input);
41 | },
42 |
43 | modernUpload: function(input){
44 |
45 | this.modern = true;
46 |
47 | var form = input.getParent('form');
48 | if (!form) return;
49 |
50 | var self = this;
51 |
52 | var drop = new Element('div.droppable', {
53 | text: this.options.dropMsg
54 | }).inject(input, 'after');
55 |
56 | var list = new Element('ul.uploadList').inject(drop, 'after');
57 |
58 | var progress = new Element('div.progress')
59 | .setStyle('display', 'none').inject(list, 'after');
60 |
61 | var uploadReq = new Request.File({
62 | url: form.get('action'),
63 | onRequest: progress.setStyles.pass({display: 'block', width: 0}, progress),
64 | onProgress: function(event){
65 | var loaded = event.loaded, total = event.total;
66 | progress.setStyle('width', parseInt(loaded / total * 100, 10).limit(0, 100) + '%');
67 | },
68 | onComplete: function(){
69 | progress.setStyle('width', '100%');
70 | self.fireEvent('complete', Array.slice(arguments));
71 | this.reset();
72 | }
73 | });
74 |
75 | var inputname = input.get('name');
76 |
77 | var inputFiles = new Form.MultipleFileInput(input, list, drop, {
78 | onDragenter: drop.addClass.pass('hover', drop),
79 | onDragleave: drop.removeClass.pass('hover', drop),
80 | onDrop: function(){
81 | drop.removeClass.pass('hover', drop);
82 | if (self.options.fireAtOnce){
83 | self.submit(inputFiles, inputname, uploadReq);
84 | }
85 | },
86 | onChange: function(){
87 | if (self.options.fireAtOnce){
88 | self.submit(inputFiles, inputname, uploadReq);
89 | }
90 | }
91 | });
92 |
93 | form.addEvent('submit', function(event){
94 | if (event) event.preventDefault();
95 | self.submit(inputFiles, inputname, uploadReq);
96 | });
97 |
98 | self.reset = function() {
99 | var files = inputFiles.getFiles();
100 | for (var i = 0; i < files.length; i++){
101 | inputFiles.remove(files[i]);
102 | }
103 | };
104 | },
105 |
106 | submit: function(inputFiles, inputname, uploadReq){
107 | inputFiles.getFiles().each(function(file){
108 | uploadReq.append(inputname , file);
109 | });
110 | uploadReq.send();
111 | },
112 |
113 | legacyUpload: function(input){
114 |
115 | var rows = [];
116 |
117 | var row = input.getParent('.formRow');
118 | var rowClone = row.clone(true, true);
119 | var add = function(event){
120 | event.preventDefault();
121 |
122 | var newRow = rowClone.clone(true, true),
123 | inputID = String.uniqueID(),
124 | label = newRow.getElement('label');
125 |
126 | newRow.getElement('input').set('id', inputID).grab(new Element('a.delInputRow', {
127 | text: 'x',
128 | events: {click: function(event){
129 | event.preventDefault();
130 | newRow.destroy();
131 | }}
132 | }), 'after');
133 |
134 | if (label) label.set('for', inputID);
135 | newRow.inject(row, 'after');
136 | rows.push(newRow);
137 | };
138 |
139 | new Element('a.addInputRow', {
140 | text: '+',
141 | events: {click: add}
142 | }).inject(input, 'after');
143 |
144 | this.reset = function() {
145 | for (var i = 0; i < rows.length; i++){
146 | rows[i].destroy();
147 | }
148 | rows = [];
149 | };
150 |
151 | },
152 |
153 | isModern: function(){
154 | return !!this.modern;
155 | }
156 |
157 | });
158 |
159 | }).call(window);
160 |
--------------------------------------------------------------------------------
/Source/Request.File.js:
--------------------------------------------------------------------------------
1 | /*
2 | ---
3 |
4 | name: Request.File
5 | description: Uploading files with FormData
6 | license: MIT-style license.
7 | authors: [Arian Stolwijk, Djamil Legato]
8 | requires: [Request]
9 | provides: Request.File
10 | credits: https://gist.github.com/a77b537e729aff97429c
11 |
12 | ...
13 | */
14 |
15 | (function(){
16 |
17 | var progressSupport = ('onprogress' in new Browser.Request());
18 |
19 | Request.File = new Class({
20 |
21 | Extends: Request,
22 |
23 | options: {
24 | emulation: false,
25 | urlEncoded: false
26 | },
27 |
28 | initialize: function(options){
29 | this.xhr = new Browser.Request();
30 | this.formData = new FormData();
31 | this.setOptions(options);
32 | this.headers = this.options.headers;
33 | },
34 |
35 | append: function(key, value){
36 | this.formData.append(key, value);
37 | return this.formData;
38 | },
39 |
40 | reset: function(){
41 | this.formData = new FormData();
42 | },
43 |
44 | send: function(options){
45 | if (!this.check(options)) return this;
46 |
47 | this.options.isSuccess = this.options.isSuccess || this.isSuccess;
48 | this.running = true;
49 |
50 | var xhr = this.xhr;
51 | if (progressSupport){
52 | xhr.onloadstart = this.loadstart.bind(this);
53 | xhr.onprogress = this.progress.bind(this);
54 | xhr.upload.onprogress = this.progress.bind(this);
55 | }
56 |
57 | xhr.open('POST', this.options.url, true);
58 | xhr.onreadystatechange = this.onStateChange.bind(this);
59 |
60 | Object.each(this.headers, function(value, key){
61 | try {
62 | xhr.setRequestHeader(key, value);
63 | } catch (e){
64 | this.fireEvent('exception', [key, value]);
65 | }
66 | }, this);
67 |
68 | this.fireEvent('request');
69 | xhr.send(this.formData);
70 |
71 | if (!this.options.async) this.onStateChange();
72 | if (this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this);
73 | return this;
74 | }
75 |
76 | });
77 |
78 | })();
79 |
80 |
--------------------------------------------------------------------------------
/package.yml:
--------------------------------------------------------------------------------
1 | name: Form.Upload
2 | author: astolwijk
3 | current: 0.3
4 | category: utilities
5 | tags: [upload, html5, file, "drag and drop"]
6 | license: MIT
7 | description: HTML5 Upload for multiple files
8 | demo: http://aryweb.nl/projects/mootools-form-upload/Demos/Upload.html
9 |
10 | sources:
11 | - "Source/Form.MultipleFileInput.js"
12 | - "Source/Request.File.js"
13 | - "Source/Form.Upload.js"
14 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arian/mootools-form-upload/bdc927d62819625908f14b574b3fa63ebe167e16/screenshot.png
--------------------------------------------------------------------------------