├── .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 |
35 | 36 |
37 |
38 | Upload Files 39 | 40 |
41 | 42 |
43 |
44 | 45 |
46 | 47 |
48 | 49 |
50 | 51 |
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 |
50 |
51 | Upload Files 52 | 53 |
54 | 55 |
56 |
57 | 58 |
59 | 60 |
61 | 62 |
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 | ![Screenshot 1](https://github.com/arian/mootools-form-upload/raw/master/screenshot.png) 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 --------------------------------------------------------------------------------