├── .gitignore ├── Gruntfile.js ├── README.md ├── bower.json ├── demo ├── index.html └── upload.php ├── dropper.jquery.json ├── jquery.fs.dropper.css ├── jquery.fs.dropper.js ├── jquery.fs.dropper.min.css ├── jquery.fs.dropper.min.js ├── package.json └── src ├── jquery.fs.dropper-config.less ├── jquery.fs.dropper-styles.less ├── jquery.fs.dropper.js └── jquery.fs.dropper.less /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*global module:false*/ 2 | 3 | // Less 4 | 5 | module.exports = function(grunt) { 6 | 7 | grunt.initConfig({ 8 | pkg: grunt.file.readJSON('package.json'), 9 | meta: { 10 | banner: '/* \n' + 11 | ' * <%= pkg.name %> v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> \n' + 12 | ' * <%= pkg.description %> \n' + 13 | ' * <%= pkg.homepage %> \n' + 14 | ' * \n' + 15 | ' * Copyright <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>; <%= pkg.license %> Licensed \n' + 16 | ' */\n' 17 | }, 18 | // JS Hint 19 | jshint: { 20 | options: { 21 | globals: { 22 | 'jQuery': true, 23 | '$' : true 24 | }, 25 | browser: true, 26 | curly: true, 27 | eqeqeq: true, 28 | forin: true, 29 | freeze: true, 30 | immed: true, 31 | latedef: true, 32 | newcap: true, 33 | noarg: true, 34 | nonew: true, 35 | smarttabs: true, 36 | sub: true, 37 | undef: true, 38 | validthis: true 39 | }, 40 | files: [ 41 | 'src/<%= pkg.codename %>.js' 42 | ] 43 | }, 44 | // Copy 45 | copy: { 46 | main: { 47 | files: [ 48 | { 49 | src: 'src/<%= pkg.codename %>.js', 50 | dest: '<%= pkg.codename %>.js' 51 | } 52 | ] 53 | } 54 | }, 55 | // Uglify 56 | uglify: { 57 | options: { 58 | report: 'min' 59 | }, 60 | target: { 61 | files: { 62 | '<%= pkg.codename %>.min.js': [ '<%= pkg.codename %>.js' ] 63 | } 64 | } 65 | }, 66 | // jQuery Manifest 67 | jquerymanifest: { 68 | options: { 69 | source: grunt.file.readJSON('package.json'), 70 | overrides: { 71 | name: '<%= pkg.id %>', 72 | keywords: '<%= pkg.keywords %>', 73 | homepage: '<%= pkg.homepage %>', 74 | docs: '<%= pkg.homepage %>', 75 | demo: '<%= pkg.homepage %>', 76 | download: '<%= pkg.repository.url %>', 77 | bugs: '<%= pkg.repository.url %>/issues', 78 | dependencies: { 79 | jquery: '>=1.7' 80 | } 81 | } 82 | } 83 | }, 84 | // LESS 85 | less: { 86 | main: { 87 | files: { 88 | '<%= pkg.codename %>.css': 'src/<%= pkg.codename %>.less' 89 | } 90 | }, 91 | min: { 92 | options: { 93 | report: 'min', 94 | cleancss: true 95 | }, 96 | files: { 97 | '<%= pkg.codename %>.min.css': 'src/<%= pkg.codename %>.less' 98 | } 99 | } 100 | }, 101 | // Auto Prefixer 102 | autoprefixer: { 103 | options: { 104 | borwsers: [ '> 1%', 'last 5 versions', 'Firefox ESR', 'Opera 12.1', '>= ie 8' ] 105 | }, 106 | no_dest: { 107 | src: '*.css' 108 | } 109 | }, 110 | // Banner 111 | usebanner: { 112 | options: { 113 | position: 'top', 114 | banner: '<%= meta.banner %>' 115 | }, 116 | files: { 117 | src: [ 118 | '<%= pkg.codename %>.css', 119 | '<%= pkg.codename %>.js', 120 | '<%= pkg.codename %>.min.css', 121 | '<%= pkg.codename %>.min.js' 122 | ] 123 | } 124 | }, 125 | //Bower sync 126 | sync: { 127 | all: { 128 | options: { 129 | sync: [ 'name', 'version', 'description', 'author', 'license', 'homepage' ], 130 | overrides: { 131 | main: [ 132 | '<%= pkg.codename %>.js', 133 | '<%= pkg.codename %>.css' 134 | ], 135 | ignore: [ "*.jquery.json" ] 136 | } 137 | } 138 | } 139 | } 140 | }); 141 | 142 | // Load tasks 143 | grunt.loadNpmTasks('grunt-contrib-jshint'); 144 | grunt.loadNpmTasks('grunt-contrib-copy'); 145 | grunt.loadNpmTasks('grunt-contrib-uglify'); 146 | grunt.loadNpmTasks('grunt-jquerymanifest'); 147 | grunt.loadNpmTasks('grunt-contrib-less'); 148 | grunt.loadNpmTasks('grunt-autoprefixer'); 149 | grunt.loadNpmTasks('grunt-banner'); 150 | grunt.loadNpmTasks('grunt-npm2bower-sync'); 151 | 152 | // Readme 153 | grunt.registerTask('buildReadme', 'Build Formstone README.md file.', function () { 154 | var pkg = grunt.file.readJSON('package.json'), 155 | extra = grunt.file.exists('src/README.md') ? '\n\n---\n\n' + grunt.file.read('src/README.md') : ''; 156 | destination = "README.md", 157 | markdown = '

Development of this plugin has ended. Please upgrade to the new Formstone.


\n\n' + 158 | 'Built with Grunt \n' + 159 | '# ' + pkg.name + ' \n\n' + 160 | pkg.description + ' \n\n' + 161 | '- [Demo](' + pkg.demo + ') \n' + 162 | '- [Documentation](' + pkg.homepage + ') \n\n' + 163 | '#### Bower Support \n' + 164 | '`bower install ' + pkg.name + '` ' + 165 | extra; 166 | 167 | grunt.file.write(destination, markdown); 168 | grunt.log.writeln('File "' + destination + '" created.'); 169 | }); 170 | 171 | // Default task. 172 | grunt.registerTask('default', [ 'jshint', 'copy', 'uglify', 'jquerymanifest', 'less', 'autoprefixer', 'usebanner', 'sync', 'buildReadme' ]); 173 | 174 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Development of this plugin has ended. Please upgrade to the new Formstone.


2 | 3 | Built with Grunt 4 | # Dropper 5 | 6 | A jQuery plugin for simple drag and drop uploads. Part of the Formstone Library. 7 | 8 | - [Demo](http://classic.formstone.it/components/Dropper/demo/index.html) 9 | - [Documentation](http://classic.formstone.it/dropper/) 10 | 11 | #### Bower Support 12 | `bower install Dropper` -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dropper", 3 | "version": "1.0.1", 4 | "description": "A jQuery plugin for simple drag and drop uploads. Part of the Formstone Library.", 5 | "author": { 6 | "name": "Ben Plum", 7 | "email": "mr@benplum.com", 8 | "url": "http://www.benplum.com" 9 | }, 10 | "license": "MIT", 11 | "homepage": "http://classic.formstone.it/dropper/", 12 | "main": [ 13 | "jquery.fs.dropper.js", 14 | "jquery.fs.dropper.css" 15 | ], 16 | "ignore": [ 17 | "*.jquery.json" 18 | ] 19 | } -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Dropper Demo 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 44 | 45 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 134 |
135 |
136 |
137 |

Dropper Demo

138 |
139 | View Documentation 140 |
141 | 142 | 143 | 144 | 145 |

Basic

146 |

Dropper will create a simple 'drop zone' for file uploads:

147 | 148 |
$(".target").dropper({
149 | 	action: "upload.php"
150 | });
151 |
<div class="target"></div>
152 | 153 |
Demo
154 |
155 |
156 | 157 |
158 |
Complete
159 |
    160 |
161 |
Queued
162 |
    163 |
164 |
165 |
166 | 167 | 168 |

Uploads

169 |

Dropper does not store or manipulate uploaded files on the server, it simply facilitates the asynchronous upload process from the front end.

170 | 171 | 172 | 173 |
174 |
175 | 176 | 181 | 182 | -------------------------------------------------------------------------------- /demo/upload.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dropper.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dropper", 3 | "version": "1.0.1", 4 | "title": "Dropper", 5 | "author": { 6 | "name": "Ben Plum", 7 | "email": "mr@benplum.com", 8 | "url": "http://www.benplum.com" 9 | }, 10 | "licenses": [ 11 | { 12 | "type": "MIT", 13 | "url": "http://opensource.org/licenses/MIT" 14 | } 15 | ], 16 | "dependencies": { 17 | "jquery": ">=1.7" 18 | }, 19 | "description": "A jQuery plugin for simple drag and drop uploads. Part of the Formstone Library.", 20 | "keywords": [ 21 | "upload", 22 | "form", 23 | "ajax", 24 | "javascript", 25 | "jquery", 26 | "formstone" 27 | ], 28 | "docs": "http://classic.formstone.it/dropper/", 29 | "demo": "http://classic.formstone.it/dropper/", 30 | "download": "https://github.com/FormstoneClassic/Dropper", 31 | "bugs": "https://github.com/FormstoneClassic/Dropper/issues", 32 | "homepage": "http://classic.formstone.it/dropper/" 33 | } -------------------------------------------------------------------------------- /jquery.fs.dropper.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Dropper v1.0.1 - 2015-04-04 3 | * A jQuery plugin for simple drag and drop uploads. Part of the Formstone Library. 4 | * http://classic.formstone.it/dropper/ 5 | * 6 | * Copyright 2015 Ben Plum; MIT Licensed 7 | */ 8 | 9 | .dropper { 10 | overflow: hidden; 11 | } 12 | .dropper, 13 | .dropper *, 14 | .dropper *:before, 15 | .dropper *:after { 16 | box-sizing: border-box; 17 | } 18 | .dropper-dropzone { 19 | background: #ffffff; 20 | border: 3px dashed #cccccc; 21 | border-radius: 0; 22 | color: #666666; 23 | cursor: pointer; 24 | font-size: 14px; 25 | margin: 0; 26 | padding: 25px; 27 | text-align: center; 28 | } 29 | .dropper.dropping .dropper-dropzone, 30 | .no-touch .dropper:hover .dropper-dropzone { 31 | background: #eeeeee; 32 | border-color: #999999; 33 | color: #333333; 34 | } 35 | .dropper-input { 36 | position: absolute; 37 | left: 100%; 38 | opacity: 0; 39 | } 40 | .no-opacity .dropper-input { 41 | left: -999px; 42 | } 43 | -------------------------------------------------------------------------------- /jquery.fs.dropper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Dropper v1.0.1 - 2015-04-04 3 | * A jQuery plugin for simple drag and drop uploads. Part of the Formstone Library. 4 | * http://classic.formstone.it/dropper/ 5 | * 6 | * Copyright 2015 Ben Plum; MIT Licensed 7 | */ 8 | 9 | ;(function ($, window) { 10 | "use strict"; 11 | 12 | var supported = (window.File && window.FileReader && window.FileList); 13 | 14 | /** 15 | * @options 16 | * @param action [string] "Where to submit uploads" 17 | * @param label [string] <'Drag and drop files or click to select'> "Dropzone text" 18 | * @param maxQueue [int] <2> "Number of files to simultaneously upload" 19 | * @param maxSize [int] <5242880> "Max file size allowed" 20 | * @param postData [object] "Extra data to post with upload" 21 | * @param postKey [string] <'file'> "Key to upload file as" 22 | */ 23 | 24 | var options = { 25 | action: "", 26 | label: "Drag and drop files or click to select", 27 | maxQueue: 2, 28 | maxSize: 5242880, // 5 mb 29 | postData: {}, 30 | postKey: "file" 31 | }; 32 | 33 | /** 34 | * @events 35 | * @event start.dropper "" 36 | * @event complete.dropper "" 37 | * @event fileStart.dropper "" 38 | * @event fileProgress.dropper "" 39 | * @event fileComplete.dropper "" 40 | * @event fileError.dropper "" 41 | */ 42 | 43 | var pub = { 44 | 45 | /** 46 | * @method 47 | * @name defaults 48 | * @description Sets default plugin options 49 | * @param opts [object] <{}> "Options object" 50 | * @example $.dropper("defaults", opts); 51 | */ 52 | defaults: function(opts) { 53 | options = $.extend(options, opts || {}); 54 | 55 | return (typeof this === 'object') ? $(this) : true; 56 | } 57 | }; 58 | 59 | /** 60 | * @method private 61 | * @name _init 62 | * @description Initializes plugin 63 | * @param opts [object] "Initialization options" 64 | */ 65 | function _init(opts) { 66 | var $items = $(this); 67 | 68 | if (supported) { 69 | // Settings 70 | opts = $.extend({}, options, opts); 71 | 72 | // Apply to each element 73 | for (var i = 0, count = $items.length; i < count; i++) { 74 | _build($items.eq(i), opts); 75 | } 76 | } 77 | 78 | return $items; 79 | } 80 | 81 | /** 82 | * @method private 83 | * @name _build 84 | * @description Builds each instance 85 | * @param $nav [jQuery object] "Target jQuery object" 86 | * @param opts [object] <{}> "Options object" 87 | */ 88 | function _build($dropper, opts) { 89 | opts = $.extend({}, opts, $dropper.data("dropper-options")); 90 | 91 | var html = ""; 92 | 93 | html += '
'; 94 | html += opts.label; 95 | html += '
'; 96 | html += ' 1) { 98 | html += ' multiple'; 99 | } 100 | html += '>'; 101 | 102 | $dropper.addClass("dropper") 103 | .append(html); 104 | 105 | var data = $.extend({ 106 | $dropper: $dropper, 107 | $input: $dropper.find(".dropper-input"), 108 | queue: [], 109 | total: 0, 110 | uploading: false 111 | }, opts); 112 | 113 | $dropper.on("click.dropper", ".dropper-dropzone", data, _onClick) 114 | .on("dragenter.dropper", data, _onDragEnter) 115 | .on("dragover.dropper", data, _onDragOver) 116 | .on("dragleave.dropper", data, _onDragOut) 117 | .on("drop.dropper", ".dropper-dropzone", data, _onDrop) 118 | .data("dropper", data); 119 | 120 | data.$input.on("change.dropper", data, _onChange); 121 | } 122 | 123 | /** 124 | * @method private 125 | * @name _onClick 126 | * @description Handles click to dropzone 127 | * @param e [object] "Event data" 128 | */ 129 | function _onClick(e) { 130 | e.stopPropagation(); 131 | e.preventDefault(); 132 | 133 | var data = e.data; 134 | 135 | data.$input.trigger("click"); 136 | } 137 | 138 | /** 139 | * @method private 140 | * @name _onChange 141 | * @description Handles change to hidden input 142 | * @param e [object] "Event data" 143 | */ 144 | function _onChange(e) { 145 | e.stopPropagation(); 146 | e.preventDefault(); 147 | 148 | var data = e.data, 149 | files = data.$input[0].files; 150 | 151 | if (files.length) { 152 | _handleUpload(data, files); 153 | } 154 | } 155 | 156 | /** 157 | * @method private 158 | * @name _onDragEnter 159 | * @description Handles dragenter to dropzone 160 | * @param e [object] "Event data" 161 | */ 162 | function _onDragEnter(e) { 163 | e.stopPropagation(); 164 | e.preventDefault(); 165 | 166 | var data = e.data; 167 | 168 | data.$dropper.addClass("dropping"); 169 | } 170 | 171 | /** 172 | * @method private 173 | * @name _onDragOver 174 | * @description Handles dragover to dropzone 175 | * @param e [object] "Event data" 176 | */ 177 | function _onDragOver(e) { 178 | e.stopPropagation(); 179 | e.preventDefault(); 180 | 181 | var data = e.data; 182 | 183 | data.$dropper.addClass("dropping"); 184 | } 185 | 186 | /** 187 | * @method private 188 | * @name _onDragOut 189 | * @description Handles dragout to dropzone 190 | * @param e [object] "Event data" 191 | */ 192 | function _onDragOut(e) { 193 | e.stopPropagation(); 194 | e.preventDefault(); 195 | 196 | var data = e.data; 197 | 198 | data.$dropper.removeClass("dropping"); 199 | } 200 | 201 | /** 202 | * @method private 203 | * @name _onDrop 204 | * @description Handles drop to dropzone 205 | * @param e [object] "Event data" 206 | */ 207 | function _onDrop(e) { 208 | e.preventDefault(); 209 | 210 | var data = e.data, 211 | files = e.originalEvent.dataTransfer.files; 212 | 213 | data.$dropper.removeClass("dropping"); 214 | 215 | _handleUpload(data, files); 216 | } 217 | 218 | /** 219 | * @method private 220 | * @name _handleUpload 221 | * @description Handles new files 222 | * @param data [object] "Instance data" 223 | * @param files [object] "File list" 224 | */ 225 | function _handleUpload(data, files) { 226 | var newFiles = []; 227 | 228 | for (var i = 0; i < files.length; i++) { 229 | var file = { 230 | index: data.total++, 231 | file: files[i], 232 | name: files[i].name, 233 | size: files[i].size, 234 | started: false, 235 | complete: false, 236 | error: false, 237 | transfer: null 238 | }; 239 | 240 | newFiles.push(file); 241 | data.queue.push(file); 242 | } 243 | 244 | if (!data.uploading) { 245 | $(window).on("beforeunload.dropper", function(){ 246 | return 'You have uploads pending, are you sure you want to leave this page?'; 247 | }); 248 | 249 | data.uploading = true; 250 | } 251 | 252 | data.$dropper.trigger("start.dropper", [ newFiles ]); 253 | 254 | _checkQueue(data); 255 | } 256 | 257 | /** 258 | * @method private 259 | * @name _checkQueue 260 | * @description Checks and updates file queue 261 | * @param data [object] "Instance data" 262 | */ 263 | function _checkQueue(data) { 264 | var transfering = 0, 265 | newQueue = []; 266 | 267 | // remove lingering items from queue 268 | for (var i in data.queue) { 269 | if (data.queue.hasOwnProperty(i) && !data.queue[i].complete && !data.queue[i].error) { 270 | newQueue.push(data.queue[i]); 271 | } 272 | } 273 | 274 | data.queue = newQueue; 275 | 276 | for (var j in data.queue) { 277 | if (data.queue.hasOwnProperty(j)) { 278 | if (!data.queue[j].started) { 279 | var formData = new FormData(); 280 | 281 | formData.append(data.postKey, data.queue[j].file); 282 | 283 | for (var k in data.postData) { 284 | if (data.postData.hasOwnProperty(k)) { 285 | formData.append(k, data.postData[k]); 286 | } 287 | } 288 | 289 | _uploadFile(data, data.queue[j], formData); 290 | } 291 | 292 | transfering++; 293 | 294 | if (transfering >= data.maxQueue) { 295 | return; 296 | } else { 297 | i++; 298 | } 299 | } 300 | } 301 | 302 | if (transfering === 0) { 303 | $(window).off("beforeunload.dropper"); 304 | 305 | data.uploading = false; 306 | 307 | data.$dropper.trigger("complete.dropper"); 308 | } 309 | } 310 | 311 | /** 312 | * @method private 313 | * @name _uploadFile 314 | * @description Uploads file 315 | * @param data [object] "Instance data" 316 | * @param file [object] "Target file" 317 | * @param formData [object] "Target form" 318 | */ 319 | function _uploadFile(data, file, formData) { 320 | if (file.size >= data.maxSize) { 321 | file.error = true; 322 | data.$dropper.trigger("fileError.dropper", [ file, "Too large" ]); 323 | 324 | _checkQueue(data); 325 | } else { 326 | file.started = true; 327 | file.transfer = $.ajax({ 328 | url: data.action, 329 | data: formData, 330 | type: "POST", 331 | contentType:false, 332 | processData: false, 333 | cache: false, 334 | xhr: function() { 335 | var $xhr = $.ajaxSettings.xhr(); 336 | 337 | if ($xhr.upload) { 338 | $xhr.upload.addEventListener("progress", function(e) { 339 | var percent = 0, 340 | position = e.loaded || e.position, 341 | total = e.total; 342 | 343 | if (e.lengthComputable) { 344 | percent = Math.ceil(position / total * 100); 345 | } 346 | 347 | data.$dropper.trigger("fileProgress.dropper", [ file, percent ]); 348 | }, false); 349 | } 350 | 351 | return $xhr; 352 | }, 353 | beforeSend: function(e) { 354 | data.$dropper.trigger("fileStart.dropper", [ file ]); 355 | }, 356 | success: function(response, status, jqXHR) { 357 | file.complete = true; 358 | data.$dropper.trigger("fileComplete.dropper", [ file, response ]); 359 | 360 | _checkQueue(data); 361 | }, 362 | error: function(jqXHR, status, error) { 363 | file.error = true; 364 | data.$dropper.trigger("fileError.dropper", [ file, error ]); 365 | 366 | _checkQueue(data); 367 | } 368 | }); 369 | } 370 | } 371 | 372 | $.fn.dropper = function(method) { 373 | if (pub[method]) { 374 | return pub[method].apply(this, Array.prototype.slice.call(arguments, 1)); 375 | } else if (typeof method === 'object' || !method) { 376 | return _init.apply(this, arguments); 377 | } 378 | return this; 379 | }; 380 | 381 | $.dropper = function(method) { 382 | if (method === "defaults") { 383 | pub.defaults.apply(this, Array.prototype.slice.call(arguments, 1)); 384 | } 385 | }; 386 | })(jQuery, window); -------------------------------------------------------------------------------- /jquery.fs.dropper.min.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Dropper v1.0.1 - 2015-04-04 3 | * A jQuery plugin for simple drag and drop uploads. Part of the Formstone Library. 4 | * http://classic.formstone.it/dropper/ 5 | * 6 | * Copyright 2015 Ben Plum; MIT Licensed 7 | */ 8 | 9 | .dropper{overflow:hidden}.dropper,.dropper *,.dropper :before,.dropper :after{box-sizing:border-box}.dropper-dropzone{background:#fff;border:3px dashed #ccc;border-radius:0;color:#666;cursor:pointer;font-size:14px;margin:0;padding:25px;text-align:center}.dropper.dropping .dropper-dropzone,.no-touch .dropper:hover .dropper-dropzone{background:#eee;border-color:#999;color:#333}.dropper-input{position:absolute;left:100%;opacity:0}.no-opacity .dropper-input{left:-999px} -------------------------------------------------------------------------------- /jquery.fs.dropper.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Dropper v1.0.1 - 2015-04-04 3 | * A jQuery plugin for simple drag and drop uploads. Part of the Formstone Library. 4 | * http://classic.formstone.it/dropper/ 5 | * 6 | * Copyright 2015 Ben Plum; MIT Licensed 7 | */ 8 | 9 | !function(a,b){"use strict";function c(b){var c=a(this);if(n){b=a.extend({},o,b);for(var e=0,f=c.length;f>e;e++)d(c.eq(e),b)}return c}function d(b,c){c=a.extend({},c,b.data("dropper-options"));var d="";d+='
',d+=c.label,d+="
",d+='1&&(d+=" multiple"),d+=">",b.addClass("dropper").append(d);var k=a.extend({$dropper:b,$input:b.find(".dropper-input"),queue:[],total:0,uploading:!1},c);b.on("click.dropper",".dropper-dropzone",k,e).on("dragenter.dropper",k,g).on("dragover.dropper",k,h).on("dragleave.dropper",k,i).on("drop.dropper",".dropper-dropzone",k,j).data("dropper",k),k.$input.on("change.dropper",k,f)}function e(a){a.stopPropagation(),a.preventDefault();var b=a.data;b.$input.trigger("click")}function f(a){a.stopPropagation(),a.preventDefault();var b=a.data,c=b.$input[0].files;c.length&&k(b,c)}function g(a){a.stopPropagation(),a.preventDefault();var b=a.data;b.$dropper.addClass("dropping")}function h(a){a.stopPropagation(),a.preventDefault();var b=a.data;b.$dropper.addClass("dropping")}function i(a){a.stopPropagation(),a.preventDefault();var b=a.data;b.$dropper.removeClass("dropping")}function j(a){a.preventDefault();var b=a.data,c=a.originalEvent.dataTransfer.files;b.$dropper.removeClass("dropping"),k(b,c)}function k(c,d){for(var e=[],f=0;f=c.maxQueue)return;f++}0===d&&(a(b).off("beforeunload.dropper"),c.uploading=!1,c.$dropper.trigger("complete.dropper"))}function m(b,c,d){c.size>=b.maxSize?(c.error=!0,b.$dropper.trigger("fileError.dropper",[c,"Too large"]),l(b)):(c.started=!0,c.transfer=a.ajax({url:b.action,data:d,type:"POST",contentType:!1,processData:!1,cache:!1,xhr:function(){var d=a.ajaxSettings.xhr();return d.upload&&d.upload.addEventListener("progress",function(a){var d=0,e=a.loaded||a.position,f=a.total;a.lengthComputable&&(d=Math.ceil(e/f*100)),b.$dropper.trigger("fileProgress.dropper",[c,d])},!1),d},beforeSend:function(a){b.$dropper.trigger("fileStart.dropper",[c])},success:function(a,d,e){c.complete=!0,b.$dropper.trigger("fileComplete.dropper",[c,a]),l(b)},error:function(a,d,e){c.error=!0,b.$dropper.trigger("fileError.dropper",[c,e]),l(b)}}))}var n=b.File&&b.FileReader&&b.FileList,o={action:"",label:"Drag and drop files or click to select",maxQueue:2,maxSize:5242880,postData:{},postKey:"file"},p={defaults:function(b){return o=a.extend(o,b||{}),"object"==typeof this?a(this):!0}};a.fn.dropper=function(a){return p[a]?p[a].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof a&&a?this:c.apply(this,arguments)},a.dropper=function(a){"defaults"===a&&p.defaults.apply(this,Array.prototype.slice.call(arguments,1))}}(jQuery,window); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dropper", 3 | "id": "dropper", 4 | "codename": "jquery.fs.dropper", 5 | "version": "1.0.1", 6 | "description": "A jQuery plugin for simple drag and drop uploads. Part of the Formstone Library.", 7 | "keywords": [ 8 | "upload", 9 | "form", 10 | "ajax", 11 | "javascript", 12 | "jquery", 13 | "formstone" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/FormstoneClassic/Dropper" 18 | }, 19 | "homepage": "http://classic.formstone.it/dropper/", 20 | "demo": "http://classic.formstone.it/components/Dropper/demo/index.html", 21 | "license": "MIT", 22 | "author": { 23 | "name": "Ben Plum", 24 | "email": "mr@benplum.com", 25 | "url": "http://www.benplum.com" 26 | }, 27 | "devDependencies": { 28 | "grunt-autoprefixer": "^1.0.1", 29 | "grunt-banner": "^0.2.3", 30 | "grunt-contrib-copy": "^0.6.0", 31 | "grunt-contrib-jshint": "~0.7.2", 32 | "grunt-contrib-less": "^0.11.4", 33 | "grunt-contrib-uglify": "~0.2.7", 34 | "grunt-jquerymanifest": "~0.1.3", 35 | "grunt-npm2bower-sync": "~0.3.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/jquery.fs.dropper-config.less: -------------------------------------------------------------------------------- 1 | 2 | // Use these variables when compiling directly 3 | 4 | // Base 5 | 6 | @dropper-background: #fff; // #fff 7 | @dropper-border: 3px dashed #ccc; // 3px dashed #ccc 8 | @dropper-border-radius: 0; // 0 9 | 10 | @dropper-font-size: 14px; // 14px 11 | @dropper-text-color: #666; // #666 12 | 13 | @dropper-margin: 0; // 0 14 | @dropper-padding: 25px; // 25px 15 | 16 | // Dropping 17 | 18 | @dropper-dropping-background: #eee; // #eee 19 | @dropper-dropping-border-color: #999; // #999 20 | @dropper-dropping-text-color: #333; // #333 -------------------------------------------------------------------------------- /src/jquery.fs.dropper-styles.less: -------------------------------------------------------------------------------- 1 | 2 | .dropper { 3 | overflow: hidden; 4 | 5 | &, 6 | & *, 7 | & *:before, 8 | & *:after { 9 | box-sizing: border-box; 10 | } 11 | 12 | // .dropper-dropzone 13 | 14 | &-dropzone { 15 | background: @dropper-background; 16 | border: @dropper-border; 17 | border-radius: @dropper-border-radius; 18 | color: @dropper-text-color; 19 | cursor: pointer; 20 | font-size: @dropper-font-size; 21 | margin: @dropper-margin; 22 | padding: @dropper-padding; 23 | text-align: center; 24 | } 25 | 26 | &.dropping &-dropzone, 27 | .no-touch &:hover &-dropzone { 28 | background: @dropper-dropping-background; 29 | border-color: @dropper-dropping-border-color; 30 | color: @dropper-dropping-text-color; 31 | } 32 | 33 | // .dropper-input 34 | 35 | &-input { 36 | position: absolute; 37 | left: 100%; 38 | opacity: 0; 39 | 40 | // IE8 Opacity Check 41 | 42 | .no-opacity & { 43 | left: -999px; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/jquery.fs.dropper.js: -------------------------------------------------------------------------------- 1 | ;(function ($, window) { 2 | "use strict"; 3 | 4 | var supported = (window.File && window.FileReader && window.FileList); 5 | 6 | /** 7 | * @options 8 | * @param action [string] "Where to submit uploads" 9 | * @param label [string] <'Drag and drop files or click to select'> "Dropzone text" 10 | * @param maxQueue [int] <2> "Number of files to simultaneously upload" 11 | * @param maxSize [int] <5242880> "Max file size allowed" 12 | * @param postData [object] "Extra data to post with upload" 13 | * @param postKey [string] <'file'> "Key to upload file as" 14 | */ 15 | 16 | var options = { 17 | action: "", 18 | label: "Drag and drop files or click to select", 19 | maxQueue: 2, 20 | maxSize: 5242880, // 5 mb 21 | postData: {}, 22 | postKey: "file" 23 | }; 24 | 25 | /** 26 | * @events 27 | * @event start.dropper "" 28 | * @event complete.dropper "" 29 | * @event fileStart.dropper "" 30 | * @event fileProgress.dropper "" 31 | * @event fileComplete.dropper "" 32 | * @event fileError.dropper "" 33 | */ 34 | 35 | var pub = { 36 | 37 | /** 38 | * @method 39 | * @name defaults 40 | * @description Sets default plugin options 41 | * @param opts [object] <{}> "Options object" 42 | * @example $.dropper("defaults", opts); 43 | */ 44 | defaults: function(opts) { 45 | options = $.extend(options, opts || {}); 46 | 47 | return (typeof this === 'object') ? $(this) : true; 48 | } 49 | }; 50 | 51 | /** 52 | * @method private 53 | * @name _init 54 | * @description Initializes plugin 55 | * @param opts [object] "Initialization options" 56 | */ 57 | function _init(opts) { 58 | var $items = $(this); 59 | 60 | if (supported) { 61 | // Settings 62 | opts = $.extend({}, options, opts); 63 | 64 | // Apply to each element 65 | for (var i = 0, count = $items.length; i < count; i++) { 66 | _build($items.eq(i), opts); 67 | } 68 | } 69 | 70 | return $items; 71 | } 72 | 73 | /** 74 | * @method private 75 | * @name _build 76 | * @description Builds each instance 77 | * @param $nav [jQuery object] "Target jQuery object" 78 | * @param opts [object] <{}> "Options object" 79 | */ 80 | function _build($dropper, opts) { 81 | opts = $.extend({}, opts, $dropper.data("dropper-options")); 82 | 83 | var html = ""; 84 | 85 | html += '
'; 86 | html += opts.label; 87 | html += '
'; 88 | html += ' 1) { 90 | html += ' multiple'; 91 | } 92 | html += '>'; 93 | 94 | $dropper.addClass("dropper") 95 | .append(html); 96 | 97 | var data = $.extend({ 98 | $dropper: $dropper, 99 | $input: $dropper.find(".dropper-input"), 100 | queue: [], 101 | total: 0, 102 | uploading: false 103 | }, opts); 104 | 105 | $dropper.on("click.dropper", ".dropper-dropzone", data, _onClick) 106 | .on("dragenter.dropper", data, _onDragEnter) 107 | .on("dragover.dropper", data, _onDragOver) 108 | .on("dragleave.dropper", data, _onDragOut) 109 | .on("drop.dropper", ".dropper-dropzone", data, _onDrop) 110 | .data("dropper", data); 111 | 112 | data.$input.on("change.dropper", data, _onChange); 113 | } 114 | 115 | /** 116 | * @method private 117 | * @name _onClick 118 | * @description Handles click to dropzone 119 | * @param e [object] "Event data" 120 | */ 121 | function _onClick(e) { 122 | e.stopPropagation(); 123 | e.preventDefault(); 124 | 125 | var data = e.data; 126 | 127 | data.$input.trigger("click"); 128 | } 129 | 130 | /** 131 | * @method private 132 | * @name _onChange 133 | * @description Handles change to hidden input 134 | * @param e [object] "Event data" 135 | */ 136 | function _onChange(e) { 137 | e.stopPropagation(); 138 | e.preventDefault(); 139 | 140 | var data = e.data, 141 | files = data.$input[0].files; 142 | 143 | if (files.length) { 144 | _handleUpload(data, files); 145 | } 146 | } 147 | 148 | /** 149 | * @method private 150 | * @name _onDragEnter 151 | * @description Handles dragenter to dropzone 152 | * @param e [object] "Event data" 153 | */ 154 | function _onDragEnter(e) { 155 | e.stopPropagation(); 156 | e.preventDefault(); 157 | 158 | var data = e.data; 159 | 160 | data.$dropper.addClass("dropping"); 161 | } 162 | 163 | /** 164 | * @method private 165 | * @name _onDragOver 166 | * @description Handles dragover to dropzone 167 | * @param e [object] "Event data" 168 | */ 169 | function _onDragOver(e) { 170 | e.stopPropagation(); 171 | e.preventDefault(); 172 | 173 | var data = e.data; 174 | 175 | data.$dropper.addClass("dropping"); 176 | } 177 | 178 | /** 179 | * @method private 180 | * @name _onDragOut 181 | * @description Handles dragout to dropzone 182 | * @param e [object] "Event data" 183 | */ 184 | function _onDragOut(e) { 185 | e.stopPropagation(); 186 | e.preventDefault(); 187 | 188 | var data = e.data; 189 | 190 | data.$dropper.removeClass("dropping"); 191 | } 192 | 193 | /** 194 | * @method private 195 | * @name _onDrop 196 | * @description Handles drop to dropzone 197 | * @param e [object] "Event data" 198 | */ 199 | function _onDrop(e) { 200 | e.preventDefault(); 201 | 202 | var data = e.data, 203 | files = e.originalEvent.dataTransfer.files; 204 | 205 | data.$dropper.removeClass("dropping"); 206 | 207 | _handleUpload(data, files); 208 | } 209 | 210 | /** 211 | * @method private 212 | * @name _handleUpload 213 | * @description Handles new files 214 | * @param data [object] "Instance data" 215 | * @param files [object] "File list" 216 | */ 217 | function _handleUpload(data, files) { 218 | var newFiles = []; 219 | 220 | for (var i = 0; i < files.length; i++) { 221 | var file = { 222 | index: data.total++, 223 | file: files[i], 224 | name: files[i].name, 225 | size: files[i].size, 226 | started: false, 227 | complete: false, 228 | error: false, 229 | transfer: null 230 | }; 231 | 232 | newFiles.push(file); 233 | data.queue.push(file); 234 | } 235 | 236 | if (!data.uploading) { 237 | $(window).on("beforeunload.dropper", function(){ 238 | return 'You have uploads pending, are you sure you want to leave this page?'; 239 | }); 240 | 241 | data.uploading = true; 242 | } 243 | 244 | data.$dropper.trigger("start.dropper", [ newFiles ]); 245 | 246 | _checkQueue(data); 247 | } 248 | 249 | /** 250 | * @method private 251 | * @name _checkQueue 252 | * @description Checks and updates file queue 253 | * @param data [object] "Instance data" 254 | */ 255 | function _checkQueue(data) { 256 | var transfering = 0, 257 | newQueue = []; 258 | 259 | // remove lingering items from queue 260 | for (var i in data.queue) { 261 | if (data.queue.hasOwnProperty(i) && !data.queue[i].complete && !data.queue[i].error) { 262 | newQueue.push(data.queue[i]); 263 | } 264 | } 265 | 266 | data.queue = newQueue; 267 | 268 | for (var j in data.queue) { 269 | if (data.queue.hasOwnProperty(j)) { 270 | if (!data.queue[j].started) { 271 | var formData = new FormData(); 272 | 273 | formData.append(data.postKey, data.queue[j].file); 274 | 275 | for (var k in data.postData) { 276 | if (data.postData.hasOwnProperty(k)) { 277 | formData.append(k, data.postData[k]); 278 | } 279 | } 280 | 281 | _uploadFile(data, data.queue[j], formData); 282 | } 283 | 284 | transfering++; 285 | 286 | if (transfering >= data.maxQueue) { 287 | return; 288 | } else { 289 | i++; 290 | } 291 | } 292 | } 293 | 294 | if (transfering === 0) { 295 | $(window).off("beforeunload.dropper"); 296 | 297 | data.uploading = false; 298 | 299 | data.$dropper.trigger("complete.dropper"); 300 | } 301 | } 302 | 303 | /** 304 | * @method private 305 | * @name _uploadFile 306 | * @description Uploads file 307 | * @param data [object] "Instance data" 308 | * @param file [object] "Target file" 309 | * @param formData [object] "Target form" 310 | */ 311 | function _uploadFile(data, file, formData) { 312 | if (file.size >= data.maxSize) { 313 | file.error = true; 314 | data.$dropper.trigger("fileError.dropper", [ file, "Too large" ]); 315 | 316 | _checkQueue(data); 317 | } else { 318 | file.started = true; 319 | file.transfer = $.ajax({ 320 | url: data.action, 321 | data: formData, 322 | type: "POST", 323 | contentType:false, 324 | processData: false, 325 | cache: false, 326 | xhr: function() { 327 | var $xhr = $.ajaxSettings.xhr(); 328 | 329 | if ($xhr.upload) { 330 | $xhr.upload.addEventListener("progress", function(e) { 331 | var percent = 0, 332 | position = e.loaded || e.position, 333 | total = e.total; 334 | 335 | if (e.lengthComputable) { 336 | percent = Math.ceil(position / total * 100); 337 | } 338 | 339 | data.$dropper.trigger("fileProgress.dropper", [ file, percent ]); 340 | }, false); 341 | } 342 | 343 | return $xhr; 344 | }, 345 | beforeSend: function(e) { 346 | data.$dropper.trigger("fileStart.dropper", [ file ]); 347 | }, 348 | success: function(response, status, jqXHR) { 349 | file.complete = true; 350 | data.$dropper.trigger("fileComplete.dropper", [ file, response ]); 351 | 352 | _checkQueue(data); 353 | }, 354 | error: function(jqXHR, status, error) { 355 | file.error = true; 356 | data.$dropper.trigger("fileError.dropper", [ file, error ]); 357 | 358 | _checkQueue(data); 359 | } 360 | }); 361 | } 362 | } 363 | 364 | $.fn.dropper = function(method) { 365 | if (pub[method]) { 366 | return pub[method].apply(this, Array.prototype.slice.call(arguments, 1)); 367 | } else if (typeof method === 'object' || !method) { 368 | return _init.apply(this, arguments); 369 | } 370 | return this; 371 | }; 372 | 373 | $.dropper = function(method) { 374 | if (method === "defaults") { 375 | pub.defaults.apply(this, Array.prototype.slice.call(arguments, 1)); 376 | } 377 | }; 378 | })(jQuery, window); -------------------------------------------------------------------------------- /src/jquery.fs.dropper.less: -------------------------------------------------------------------------------- 1 | 2 | @import "jquery.fs.dropper-config.less"; 3 | @import "jquery.fs.dropper-styles.less"; --------------------------------------------------------------------------------