├── LICENSE ├── README.md ├── bower.json ├── dist ├── mdr-file.css ├── mdr-file.js ├── mdr-file.min.css └── mdr-file.min.js ├── examples ├── index.html └── php │ └── upload.php ├── gulpfile.js ├── package.json └── src ├── mdr-file.css └── mdr-file.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 modulr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular File 2 | Angular File is an Angularjs component that can upload files via XMLHttpRequest, provides drag & drop support. 3 | 4 | - Backend and Frontend [examples](https://github.com/Modulr/mdr-angular-file/tree/master/examples) 5 | 6 | ![](http://modulr.io/img/preview/mdr-angular-file.png) 7 | 8 | ##Features 9 | 10 | - Uses the native Angularjs scope for data binding 11 | - Drag & Drop 12 | - Multiple or single files uploads 13 | - Image preview 14 | - Update progress 15 | - Large files support 16 | 17 | 18 | ##Requirements 19 | 20 | - [Angularjs](https://angularjs.org/) 21 | - [Bootstrap 3.](http://getbootstrap.com/) 22 | 23 | ##Quick start 24 | 25 | Several quick start options are available: 26 | 27 | - [Download the latest release](https://github.com/Modulr/mdr-angular-file/archive/master.zip) 28 | - Clone the repo: `git clone https://github.com/Modulr/mdr-angular-file.git`. 29 | - Install with [Bower](http://bower.io/): `bower install mdr-angular-file`. 30 | - Install with [npm](https://www.npmjs.com): `npm install mdr-angular-file`. 31 | 32 | ##What's included 33 | 34 | ``` 35 | mdr-angular-file/ 36 | dist/ 37 | ├── mdr-file.css 38 | ├── mdr-file.min.css 39 | ├── mdr-file.js 40 | └── mdr-file.min.js 41 | ``` 42 | 43 | ##Documentation 44 | 45 | ####Usage 46 | 47 | ######Load CSS 48 | 49 | ```html 50 | 51 | ``` 52 | 53 | ######Load JS 54 | 55 | ```html 56 | 57 | ``` 58 | 59 | ######Code 60 | 61 | ```js 62 | angular.module('MyApp', ['mdr.file']) 63 | ``` 64 | 65 | ######HTML View or Templates 66 | 67 | > Basic Directive 68 | 69 | ```html 70 | 71 | ``` 72 | 73 | > Complete Directive (All attributes) 74 | 75 | ```html 76 | 77 | ``` 78 | 79 | ####API 80 | 81 | ######Attributes 82 | 83 | Attribute | Type | Description 84 | --- | --- | --- 85 | url | `string` | *Is the path on the server where the file will be uploaded.* **Note:** *The parameter received on the server is* `file` 86 | model | `object` | *It is the scope model where will be received to response the server.* 87 | data | `object` | *Data to be sent to the server.* 88 | headers | `object` | *Send headers to the server.* 89 | size | `number` | *Max size in MB to file.* 90 | limit | `number` | *Max number files to upload.* 91 | formats | `string,array` | *Extensions permitted to the file.* 92 | multiple | `boolean` | *If required to upload a multiple file is marked as true.* 93 | disabled | `boolean` | *If required disable the component is marked as true.* 94 | text | `string` | *Text into area drag and drop.* 95 | 96 | ##How to contribute 97 | 98 | All contributions are very welcome, We love it. There are several ways to help out: 99 | 100 | - Create an [issue](https://github.com/Modulr/mdr-angular-file/issues) on GitHub, if you have found a bug 101 | - Write test cases for open bug issues 102 | - Write patches for open bug/feature issues, preferably with test cases included 103 | - Contribute to the documentation 104 | 105 | There are a few guidelines that we need contributors to follow so that we have a chance of keeping on top of things. 106 | 107 | If you want to making changes Better avoid working directly on the `master` branch, to avoid conflicts if you pull in updates from origin, so, if make your contribution under the branch [`dev`](https://github.com/Modulr/mdr-angular-file/tree/dev), into folder `src/`. 108 | 109 | ##Community 110 | 111 | - Implementation help may be found at Stack Overflow (tagged [`mdr-file`](http://stackoverflow.com/questions/tagged/mdr-file)). 112 | 113 | ##Creators 114 | 115 | [@AlfredoBarronC](https://twitter.com/AlfredoBarronC) 116 | 117 | ## Copyright and license 118 | 119 | Code and documentation (c) Copyright 2015 Modulr. Code published under [license MIT](https://github.com/Modulr/mdr-angular-file/blob/master/LICENSE) 120 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdr-angular-file", 3 | "version": "1.0.5", 4 | "description": "File Upload Drag and Drop", 5 | "main": [ 6 | "dist/mdr-file.css", 7 | "dist/mdr-file.js" 8 | ], 9 | "authors": [ 10 | "Alfredo Barron" 11 | ], 12 | "license": "MIT", 13 | "keywords": [ 14 | "angular", 15 | "bootstrap", 16 | "file", 17 | "upload", 18 | "draganddrop", 19 | "directive", 20 | "component" 21 | ], 22 | "moduleType": [], 23 | "homepage": "", 24 | "ignore": [ 25 | "**/.*" 26 | ], 27 | "dependencies": { 28 | "bootstrap": "~3.3.5", 29 | "angular": "~1.4.7" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /dist/mdr-file.css: -------------------------------------------------------------------------------- 1 | .mdr-file-dad { 2 | position: relative; 3 | padding: 15px; 4 | min-height: 200px; 5 | background-color: #fff; 6 | border: 4px dashed #9E9E9E; 7 | color: #9E9E9E; 8 | overflow: hidden; 9 | -webkit-transition: all .2s ease-in-out; 10 | -moz-transition: all .2s ease-in-out; 11 | -ms-transition: all .2s ease-in-out; 12 | -o-transition: all .2s ease-in-out; 13 | transition: all .2s ease-in-out; 14 | } 15 | .mdr-file-dad:hover { 16 | border-color: #858585; 17 | color: #858585; 18 | } 19 | .mdr-file-dad.disabled:hover { 20 | border-color: #9E9E9E; 21 | color: #9E9E9E; 22 | } 23 | 24 | .mdr-file-dad input[type=file] { 25 | position: absolute; 26 | top: 0; 27 | left: 0; 28 | width: 100%; 29 | height: 100%; 30 | font-size: 1000em; 31 | cursor: pointer; 32 | opacity: 0; 33 | filter: alpha(opacity=0); 34 | } 35 | .mdr-file-dad.disabled:hover input[type=file] { 36 | cursor: auto; 37 | } 38 | 39 | .mdr-file-dad .mdr-file-dad-text { 40 | position: absolute; 41 | top: 0; 42 | left: 0; 43 | width: 100%; 44 | height: 100%; 45 | color: #9E9E9E; 46 | display:-webkit-flex; 47 | display:flex; 48 | -webkit-justify-content:center; 49 | justify-content:center; 50 | -webkit-align-items:center; 51 | align-items:center; 52 | } 53 | .mdr-file-dad:hover .mdr-file-dad-text h3 { 54 | color: #858585; 55 | } 56 | .mdr-file-dad.disabled:hover .mdr-file-dad-text h3 { 57 | color: #9E9E9E; 58 | } 59 | .mdr-file-dad .mdr-file-dad-text h3 { 60 | text-align: center; 61 | margin: 0; 62 | color: #9E9E9E; 63 | } 64 | .mdr-file-dad .mdr-file-dad-text h3 span { 65 | display: block; 66 | font-size: 3em; 67 | margin-bottom: 15px; 68 | } 69 | 70 | .mdr-file-dad .mdr-file-dad-content { 71 | position: inherit; 72 | background-color: #fff; 73 | margin-top: -15px; 74 | margin-bottom: -15px; 75 | } 76 | .mdr-file-dad .mdr-file-dad-content button { 77 | display: none; 78 | margin-top: 10px; 79 | margin-right: 15px; 80 | outline: none; 81 | } 82 | .mdr-file-dad .mdr-file-dad-content > div{ 83 | padding-top: 15px; 84 | padding-bottom: 15px; 85 | } 86 | .mdr-file-dad .mdr-file-dad-content .thumbnail { 87 | border-radius: 0; 88 | } 89 | .mdr-file-dad .mdr-file-dad-content .thumbnail img { 90 | opacity: 0.8; 91 | filter: alpha(opacity=0.8); 92 | } 93 | .mdr-file-dad .mdr-file-dad-content .thumbnail span { 94 | font-size: 6em; 95 | opacity: 0.8; 96 | filter: alpha(opacity=0.8); 97 | } 98 | .mdr-file-dad .mdr-file-dad-content .thumbnail .progress { 99 | width: 100%; 100 | height: 5px; 101 | border-radius: 0; 102 | margin-bottom: 0; 103 | margin-top: 10px; 104 | } 105 | .mdr-file-dad .mdr-file-dad-content .thumbnail .caption p { 106 | margin: 0; 107 | } 108 | .mdr-file-dad .mdr-file-dad-content .thumbnail .caption p span{ 109 | font-size: 12px; 110 | } 111 | .mdr-file-dad .mdr-file-dad-content .thumbnail .caption p.text-danger{ 112 | font-size: 11px; 113 | } 114 | -------------------------------------------------------------------------------- /dist/mdr-file.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | angular 5 | .module('mdr.file', []) 6 | .directive('mdrFile', ['$compile', function($compile){ 7 | /** 8 | * @param url {string} 9 | * @param model {object} 10 | * @param data {object} 11 | * @param headers {object} 12 | * @param size {number} 13 | * @param limit {number} 14 | * @param formats {array, string} 15 | * @param text {string} 16 | * @param multiple {boolean} 17 | * @param disabled {boolean} 18 | */ 19 | 20 | var linker = function(scope, element, attrs) 21 | { 22 | if (scope.multiple) { 23 | element.find('input').attr('multiple', 'multiple'); 24 | } 25 | 26 | if (scope.disabled) { 27 | element.find('.mdr-file-dad').addClass('disabled'); 28 | } 29 | 30 | $compile(element.contents())(scope); 31 | }; 32 | 33 | return { 34 | restrict: 'E', 35 | link: linker, 36 | controller: 'FileCtrl', 37 | scope: { 38 | url: '@', 39 | headers: '=', 40 | model: '=', 41 | data: '=', 42 | size: '=', 43 | limit: '=', 44 | formats: '=', 45 | disabled: '=', 46 | multiple: '=', 47 | text: '@' 48 | }, 49 | template: 50 | '
' + 51 | '
' + 52 | '

{{text}}

' + 53 | '
' + 54 | '' + 55 | '
'+ 56 | '' + 57 | '
' + 58 | '
' 59 | }; 60 | 61 | }]) 62 | .controller('FileCtrl', ['$scope', '$element', '$attrs', function($scope, $element, $attrs) { 63 | 64 | // OPTIONS 65 | $scope.text = 'Drag or click here'; 66 | $scope.count = { 67 | send: 0, 68 | complete: 0, 69 | invalid: 0, 70 | error: 0 71 | }; 72 | 73 | /** Drag and Drop 74 | |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 75 | */ 76 | $element.bind('dragenter', function (e){ 77 | e.stopPropagation(); 78 | e.preventDefault(); 79 | var parent = $(e.target).parent(); 80 | parent.addClass('mdr-file-dad-text-hover'); 81 | }); 82 | 83 | $element.bind('dragleave', function (e){ 84 | e.stopPropagation(); 85 | e.preventDefault(); 86 | var parent = $(e.target).parent(); 87 | parent.removeClass('mdr-file-dad-text-hover'); 88 | }); 89 | 90 | $element.bind('dragover', function (e){ 91 | e.stopPropagation(); 92 | e.preventDefault(); 93 | }); 94 | 95 | $element.bind('drop', function (e){ 96 | e.stopPropagation(); 97 | e.preventDefault(); 98 | 99 | var parent = $(e.target).parent(); 100 | parent.removeClass('mdr-file-dad-text-hover'); 101 | // Se obtienen los archivos 102 | var files = e.originalEvent.dataTransfer.files; 103 | // Se envian los archivos 104 | uploadFiles(files); 105 | }); 106 | 107 | /** Events 108 | * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 109 | */ 110 | $scope.clearContent = function() 111 | { 112 | $('#fileId_'+ $scope.$id +' .mdr-file-dad-content div').fadeOut('slow', function() { $(this).remove(); }); 113 | $("#fileId_"+ $scope.$id +' .mdr-file-dad-content button').fadeOut('slow'); 114 | }; 115 | 116 | $scope.upload = function(element) 117 | { 118 | // Se obtienen los archivos 119 | var files = element.files; 120 | // Se envian los archivos 121 | uploadFiles(files); 122 | }; 123 | 124 | /** Methods 125 | * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 126 | */ 127 | function uploadFiles(files) 128 | { 129 | $scope.count = { 130 | send: 0, 131 | complete: 0, 132 | invalid: 0, 133 | error: 0 134 | }; 135 | 136 | // Si es multiple 137 | if( validMultiple(files) ) { 138 | // Si no exede el limite de archivos 139 | if ( validLimit(files) ) { 140 | // Se envian los archivos 141 | $.each(files, function(k, v){ 142 | // Se envia el archivo 143 | uploadFile(k,v); 144 | }); 145 | } 146 | } 147 | $('#fileId_'+ $scope.$id +' input').replaceWith($('#fileId_'+ $scope.$id +' input').val('').clone(true)); 148 | } 149 | 150 | // Se sube file por file 151 | function uploadFile(k,v) 152 | { 153 | // SE INSTANCIA EL XHR 154 | var xhr = new XMLHttpRequest(); 155 | // Se abre el xhr 156 | xhr.open('POST', $scope.url, true); 157 | // Se agregan los headers al xhr 158 | if ($scope.headers !== undefined) { 159 | var headers = $scope.headers; 160 | for (var header in headers) { 161 | xhr.setRequestHeader(header, headers[header]); 162 | } 163 | } 164 | // SE INSTANCIA EL FILEREAD 165 | // Lee los atributos del file 166 | var reader = new FileReader(); 167 | // Lee la url temporal del file 168 | reader.readAsDataURL(v); 169 | // Cuando se carga el file 170 | reader.onload = function (e) { 171 | // Se valida que el archivo sea valido 172 | var validFile = isValid(v); 173 | // Se crea el preview 174 | createPreview(v, k, e.target.result, validFile.icon, validFile.messages); 175 | // Se valida el tipo y el tamaño 176 | if (validFile.resp) { 177 | // Se envia el formData al server 178 | $scope.$apply(function () { 179 | $scope.count.send++; 180 | }); 181 | // SE INSTANCIA EL FORM DATA 182 | var formData = new FormData(); 183 | // Se agrega el modelo data al formData 184 | if ($scope.data !== undefined) { 185 | for (var ke in $scope.data) { 186 | formData.append(ke, $scope.data[ke]); 187 | } 188 | } 189 | // Se agrega el file en el formData 190 | formData.append('file', v); 191 | xhr.send(formData); 192 | } else { 193 | $scope.$apply(function () { 194 | $scope.count.invalid++; 195 | }); 196 | // Se aborta el envio de formData al server 197 | xhr.abort(); 198 | } 199 | }; 200 | xhr.addEventListener("loadstart", loadStart, false); 201 | xhr.addEventListener("progress", updateProgress, false); 202 | xhr.addEventListener("load", transferComplete, false); 203 | xhr.addEventListener("error", transferFailed, false); 204 | xhr.addEventListener("abort", transferCanceled, false); 205 | // Caundo inicia la carga del archivo 206 | function loadStart(){ 207 | //console.log('Load start'); 208 | } 209 | function updateProgress (e) { 210 | if (e.lengthComputable) { 211 | var percentage = (e.loaded / e.total) * 100; 212 | $('#fileId_'+ $scope.$id + ' .mdr-file-dad-content .preview-'+ k +' .progress .progress-bar').css('width', percentage + '%'); 213 | } 214 | } 215 | function transferComplete (e) { 216 | $scope.$apply(function () { 217 | $scope.count.complete++; 218 | }); 219 | if (xhr.status == 200) { 220 | $scope.$apply(function () { 221 | $scope.model = JSON.parse(xhr.response); 222 | }); 223 | $('#fileId_'+ $scope.$id +' .mdr-file-dad-content .preview-'+ k).fadeOut('slow', function() { $(this).remove(); }); 224 | } else { 225 | $scope.$apply(function () { 226 | $scope.count.error++; 227 | }); 228 | console.log("Server error "+xhr.status+", an error occurred while transferring the file."); 229 | } 230 | } 231 | function transferFailed (e) { 232 | console.log("An error occurred while transferring the file."); 233 | } 234 | function transferCanceled (e) { 235 | console.log("The transfer has been canceled by the user."); 236 | } 237 | } 238 | 239 | 240 | // SE CREA EL PREVIEW 241 | function createPreview(v,k,url,icon,messages) 242 | { 243 | // Se obtiene el tamaño del archivo 244 | var humanSize = bytesToSize(v.size); 245 | 246 | var img = null; 247 | if (icon) { 248 | img = ''; 249 | } else { 250 | img = ''; 251 | } 252 | 253 | var preview = 254 | '
' + 255 | '
' + 256 | img + 257 | '
' + 258 | '
' + 259 | '
' + 260 | '
' + 261 | '

'+ v.name +'

' + 262 | '

'+ humanSize +'

' + 263 | '
' + 264 | '
' + 265 | '
'; 266 | 267 | $("#fileId_"+ $scope.$id +' .mdr-file-dad-content').append($(preview).fadeIn('slow')); 268 | 269 | if (messages !== undefined) { 270 | messages.forEach(function(msg){ 271 | $("#fileId_"+ $scope.$id +' .mdr-file-dad-content .preview-'+ k +' .thumbnail .caption').append('

'+ msg +'

'); 272 | }); 273 | } 274 | 275 | } 276 | 277 | 278 | // VALID FUNCTIONS 279 | function isValid(file) 280 | { 281 | var messages = []; 282 | var icon = false; 283 | 284 | var ext = validExt(file); 285 | var size = validSize(file); 286 | 287 | if (ext.icon || size.icon) { 288 | icon = true; 289 | } 290 | 291 | if (!ext.resp) { 292 | messages.push(ext.msg); 293 | } 294 | if (!size.resp) { 295 | messages.push(size.msg); 296 | } 297 | 298 | if ( ext.resp && size.resp ) { 299 | return { resp: true, icon: icon }; 300 | } 301 | 302 | return { resp: false, icon: icon, messages: messages}; 303 | } 304 | 305 | 306 | function validExt(file) 307 | { 308 | // Img previe or icon 309 | var icon = true; 310 | 311 | // Se decide si se va mostrar el preview o un icono 312 | if (file.type == 'image/jpeg' || file.type == 'image/png' || file.type == 'image/svg+xml' || file.type == 'image/gif') { 313 | icon = false; 314 | } 315 | 316 | if ($scope.formats === undefined) { 317 | return { resp: true, icon: icon, msg: '' }; 318 | } 319 | 320 | // Extencion del archivo 321 | var fileExtension = file.name.substring(file.name.lastIndexOf('.') + 1).toLowerCase(); 322 | // extensiones aceptadas 323 | var formats = $scope.formats; 324 | 325 | // Si los formatos son de tipo string se convierte en array (stringToArray) 326 | if (typeof formats == 'string') { 327 | formats = formats.split(','); 328 | } 329 | 330 | // Si existe la extencion entre las extenciones validas 331 | for (var i = 0; i < formats.length; i++) { 332 | if (fileExtension == formats[i].trim()) { 333 | return { resp: true, icon: icon, msg: ''}; 334 | } 335 | } 336 | 337 | return { resp: false, icon: icon, msg: 'File type '+ fileExtension +' not allowed' }; 338 | } 339 | 340 | 341 | function validSize(file) 342 | { 343 | // Img previe or icon 344 | var icon = false; 345 | // Tamaño del archivo 346 | var size = file.size; 347 | 348 | // Se decide si se va mostrar el preview o un icono 349 | if (size > (5 * 1000) * 1024) { 350 | icon = true; 351 | } 352 | 353 | if ($scope.size === undefined) { 354 | return { resp: true, icon: icon }; 355 | } 356 | 357 | // Tamaño maximo 358 | var maxSize = ($scope.size * 1000) * 1024; 359 | 360 | 361 | if (size < maxSize) { 362 | return { resp: true, icon: icon}; 363 | } 364 | 365 | return { resp: false, icon: icon, msg: 'File exceeds size '+ $scope.size +'MB'}; 366 | } 367 | 368 | function validLimit(files) 369 | { 370 | if ($scope.limit === undefined) { 371 | return true; 372 | } 373 | if ($scope.limit < files.length) { 374 | alert('Max files upload is '+ $scope.limit); 375 | return false; 376 | } 377 | return true; 378 | } 379 | 380 | // Se valida si el imput es multiple 381 | function validMultiple(files) 382 | { 383 | if (files.length == 1) { 384 | return true; 385 | } else if (files.length > 1) { 386 | if ($scope.multiple) { 387 | return true; 388 | } 389 | } 390 | alert('One file for time'); 391 | return false; 392 | } 393 | 394 | // Convierte los bits en 'Bytes', 'KB', 'MB', 'GB', 'TB' 395 | function bytesToSize(bytes) 396 | { 397 | var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; 398 | if (bytes === 0) return 'n/a'; 399 | var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); 400 | if (i === 0) return bytes + ' ' + sizes[i]; 401 | return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i]; 402 | } 403 | 404 | /* 405 | |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 406 | | watch 407 | |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 408 | */ 409 | $scope.$watchCollection('count', function(newValue, oldValue) 410 | { 411 | if (newValue.send == newValue.complete && (newValue.invalid > 0 || newValue.error > 0)) { 412 | $("#fileId_"+ $scope.$id +' .mdr-file-dad-content button').fadeIn('slow'); 413 | } 414 | }); 415 | 416 | 417 | }]); 418 | 419 | })(); 420 | -------------------------------------------------------------------------------- /dist/mdr-file.min.css: -------------------------------------------------------------------------------- 1 | .mdr-file-dad{position:relative;padding:15px;min-height:200px;background-color:#fff;border:4px dashed #9E9E9E;color:#9E9E9E;overflow:hidden;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-ms-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.mdr-file-dad:hover{border-color:#858585;color:#858585}.mdr-file-dad.disabled:hover{border-color:#9E9E9E;color:#9E9E9E}.mdr-file-dad input[type=file]{position:absolute;top:0;left:0;width:100%;height:100%;font-size:1000em;cursor:pointer;opacity:0;filter:alpha(opacity=0)}.mdr-file-dad.disabled:hover input[type=file]{cursor:auto}.mdr-file-dad .mdr-file-dad-text{position:absolute;top:0;left:0;width:100%;height:100%;color:#9E9E9E;display:-webkit-flex;display:flex;-webkit-justify-content:center;justify-content:center;-webkit-align-items:center;align-items:center}.mdr-file-dad:hover .mdr-file-dad-text h3{color:#858585}.mdr-file-dad.disabled:hover .mdr-file-dad-text h3{color:#9E9E9E}.mdr-file-dad .mdr-file-dad-text h3{text-align:center;margin:0;color:#9E9E9E}.mdr-file-dad .mdr-file-dad-text h3 span{display:block;font-size:3em;margin-bottom:15px}.mdr-file-dad .mdr-file-dad-content{position:inherit;background-color:#fff;margin-top:-15px;margin-bottom:-15px}.mdr-file-dad .mdr-file-dad-content button{display:none;margin-top:10px;margin-right:15px;outline:0}.mdr-file-dad .mdr-file-dad-content>div{padding-top:15px;padding-bottom:15px}.mdr-file-dad .mdr-file-dad-content .thumbnail{border-radius:0}.mdr-file-dad .mdr-file-dad-content .thumbnail img{opacity:.8;filter:alpha(opacity=.8)}.mdr-file-dad .mdr-file-dad-content .thumbnail span{font-size:6em;opacity:.8;filter:alpha(opacity=.8)}.mdr-file-dad .mdr-file-dad-content .thumbnail .progress{width:100%;height:5px;border-radius:0;margin-bottom:0;margin-top:10px}.mdr-file-dad .mdr-file-dad-content .thumbnail .caption p{margin:0}.mdr-file-dad .mdr-file-dad-content .thumbnail .caption p span{font-size:12px}.mdr-file-dad .mdr-file-dad-content .thumbnail .caption p.text-danger{font-size:11px} -------------------------------------------------------------------------------- /dist/mdr-file.min.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";angular.module("mdr.file",[]).directive("mdrFile",["$compile",function(e){var t=function(t,n,i){t.multiple&&n.find("input").attr("multiple","multiple"),t.disabled&&n.find(".mdr-file-dad").addClass("disabled"),e(n.contents())(t)};return{restrict:"E",link:t,controller:"FileCtrl",scope:{url:"@",headers:"=",model:"=",data:"=",size:"=",limit:"=",formats:"=",disabled:"=",multiple:"=",text:"@"},template:'

{{text}}

'}}]).controller("FileCtrl",["$scope","$element","$attrs",function(e,t,n){function i(t){e.count={send:0,complete:0,invalid:0,error:0},c(t)&&s(t)&&$.each(t,function(e,t){r(e,t)}),$("#fileId_"+e.$id+" input").replaceWith($("#fileId_"+e.$id+" input").val("").clone(!0))}function r(t,n){function i(){}function r(n){if(n.lengthComputable){var i=n.loaded/n.total*100;$("#fileId_"+e.$id+" .mdr-file-dad-content .preview-"+t+" .progress .progress-bar").css("width",i+"%")}}function l(n){e.$apply(function(){e.count.complete++}),200==c.status?(e.$apply(function(){e.model=JSON.parse(c.response)}),$("#fileId_"+e.$id+" .mdr-file-dad-content .preview-"+t).fadeOut("slow",function(){$(this).remove()})):(e.$apply(function(){e.count.error++}),console.log("Server error "+c.status+", an error occurred while transferring the file."))}function d(e){console.log("An error occurred while transferring the file.")}function s(e){console.log("The transfer has been canceled by the user.")}var c=new XMLHttpRequest;if(c.open("POST",e.url,!0),void 0!==e.headers){var p=e.headers;for(var f in p)c.setRequestHeader(f,p[f])}var u=new FileReader;u.readAsDataURL(n),u.onload=function(i){var r=o(n);if(a(n,t,i.target.result,r.icon,r.messages),r.resp){e.$apply(function(){e.count.send++});var l=new FormData;if(void 0!==e.data)for(var d in e.data)l.append(d,e.data[d]);l.append("file",n),c.send(l)}else e.$apply(function(){e.count.invalid++}),c.abort()},c.addEventListener("loadstart",i,!1),c.addEventListener("progress",r,!1),c.addEventListener("load",l,!1),c.addEventListener("error",d,!1),c.addEventListener("abort",s,!1)}function a(t,n,i,r,a){var o=p(t.size),l=null;l=r?'':'';var d='
'+l+'

'+t.name+'

'+o+"

";$("#fileId_"+e.$id+" .mdr-file-dad-content").append($(d).fadeIn("slow")),void 0!==a&&a.forEach(function(t){$("#fileId_"+e.$id+" .mdr-file-dad-content .preview-"+n+" .thumbnail .caption").append('

'+t+"

")})}function o(e){var t=[],n=!1,i=l(e),r=d(e);return(i.icon||r.icon)&&(n=!0),i.resp||t.push(i.msg),r.resp||t.push(r.msg),i.resp&&r.resp?{resp:!0,icon:n}:{resp:!1,icon:n,messages:t}}function l(t){var n=!0;if(("image/jpeg"==t.type||"image/png"==t.type||"image/svg+xml"==t.type||"image/gif"==t.type)&&(n=!1),void 0===e.formats)return{resp:!0,icon:n,msg:""};var i=t.name.substring(t.name.lastIndexOf(".")+1).toLowerCase(),r=e.formats;"string"==typeof r&&(r=r.split(","));for(var a=0;a512e4&&(n=!0),void 0===e.size)return{resp:!0,icon:n};var r=1e3*e.size*1024;return r>i?{resp:!0,icon:n}:{resp:!1,icon:n,msg:"File exceeds size "+e.size+"MB"}}function s(t){return void 0===e.limit?!0:e.limit1&&e.multiple?!0:(alert("One file for time"),!1)}function p(e){var t=["Bytes","KB","MB","GB","TB"];if(0===e)return"n/a";var n=parseInt(Math.floor(Math.log(e)/Math.log(1024)));return 0===n?e+" "+t[n]:(e/Math.pow(1024,n)).toFixed(1)+" "+t[n]}e.text="Drag or click here",e.count={send:0,complete:0,invalid:0,error:0},t.bind("dragenter",function(e){e.stopPropagation(),e.preventDefault();var t=$(e.target).parent();t.addClass("mdr-file-dad-text-hover")}),t.bind("dragleave",function(e){e.stopPropagation(),e.preventDefault();var t=$(e.target).parent();t.removeClass("mdr-file-dad-text-hover")}),t.bind("dragover",function(e){e.stopPropagation(),e.preventDefault()}),t.bind("drop",function(e){e.stopPropagation(),e.preventDefault();var t=$(e.target).parent();t.removeClass("mdr-file-dad-text-hover");var n=e.originalEvent.dataTransfer.files;i(n)}),e.clearContent=function(){$("#fileId_"+e.$id+" .mdr-file-dad-content div").fadeOut("slow",function(){$(this).remove()}),$("#fileId_"+e.$id+" .mdr-file-dad-content button").fadeOut("slow")},e.upload=function(e){var t=e.files;i(t)},e.$watchCollection("count",function(t,n){t.send==t.complete&&(t.invalid>0||t.error>0)&&$("#fileId_"+e.$id+" .mdr-file-dad-content button").fadeIn("slow")})}])}(); -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Angular File 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

Angular File

18 |

File Upload Drag and Drop

19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /examples/php/upload.php: -------------------------------------------------------------------------------- 1 | div{ 83 | padding-top: 15px; 84 | padding-bottom: 15px; 85 | } 86 | .mdr-file-dad .mdr-file-dad-content .thumbnail { 87 | border-radius: 0; 88 | } 89 | .mdr-file-dad .mdr-file-dad-content .thumbnail img { 90 | opacity: 0.8; 91 | filter: alpha(opacity=0.8); 92 | } 93 | .mdr-file-dad .mdr-file-dad-content .thumbnail span { 94 | font-size: 6em; 95 | opacity: 0.8; 96 | filter: alpha(opacity=0.8); 97 | } 98 | .mdr-file-dad .mdr-file-dad-content .thumbnail .progress { 99 | width: 100%; 100 | height: 5px; 101 | border-radius: 0; 102 | margin-bottom: 0; 103 | margin-top: 10px; 104 | } 105 | .mdr-file-dad .mdr-file-dad-content .thumbnail .caption p { 106 | margin: 0; 107 | } 108 | .mdr-file-dad .mdr-file-dad-content .thumbnail .caption p span{ 109 | font-size: 12px; 110 | } 111 | .mdr-file-dad .mdr-file-dad-content .thumbnail .caption p.text-danger{ 112 | font-size: 11px; 113 | } 114 | -------------------------------------------------------------------------------- /src/mdr-file.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | angular 5 | .module('mdr.file', []) 6 | .directive('mdrFile', ['$compile', function($compile){ 7 | /** 8 | * @param url {string} 9 | * @param model {object} 10 | * @param data {object} 11 | * @param headers {object} 12 | * @param size {number} 13 | * @param limit {number} 14 | * @param formats {array, string} 15 | * @param text {string} 16 | * @param multiple {boolean} 17 | * @param disabled {boolean} 18 | */ 19 | 20 | var linker = function(scope, element, attrs) 21 | { 22 | if (scope.multiple) { 23 | element.find('input').attr('multiple', 'multiple'); 24 | } 25 | 26 | if (scope.disabled) { 27 | element.find('.mdr-file-dad').addClass('disabled'); 28 | } 29 | 30 | $compile(element.contents())(scope); 31 | }; 32 | 33 | return { 34 | restrict: 'E', 35 | link: linker, 36 | controller: 'FileCtrl', 37 | scope: { 38 | url: '@', 39 | headers: '=', 40 | model: '=', 41 | data: '=', 42 | size: '=', 43 | limit: '=', 44 | formats: '=', 45 | disabled: '=', 46 | multiple: '=', 47 | text: '@' 48 | }, 49 | template: 50 | '
' + 51 | '
' + 52 | '

{{text}}

' + 53 | '
' + 54 | '' + 55 | '
'+ 56 | '' + 57 | '
' + 58 | '
' 59 | }; 60 | 61 | }]) 62 | .controller('FileCtrl', ['$scope', '$element', '$attrs', function($scope, $element, $attrs) { 63 | 64 | // OPTIONS 65 | $scope.text = 'Drag or click here'; 66 | $scope.count = { 67 | send: 0, 68 | complete: 0, 69 | invalid: 0, 70 | error: 0 71 | }; 72 | 73 | /** Drag and Drop 74 | |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 75 | */ 76 | $element.bind('dragenter', function (e){ 77 | e.stopPropagation(); 78 | e.preventDefault(); 79 | var parent = $(e.target).parent(); 80 | parent.addClass('mdr-file-dad-text-hover'); 81 | }); 82 | 83 | $element.bind('dragleave', function (e){ 84 | e.stopPropagation(); 85 | e.preventDefault(); 86 | var parent = $(e.target).parent(); 87 | parent.removeClass('mdr-file-dad-text-hover'); 88 | }); 89 | 90 | $element.bind('dragover', function (e){ 91 | e.stopPropagation(); 92 | e.preventDefault(); 93 | }); 94 | 95 | $element.bind('drop', function (e){ 96 | e.stopPropagation(); 97 | e.preventDefault(); 98 | 99 | var parent = $(e.target).parent(); 100 | parent.removeClass('mdr-file-dad-text-hover'); 101 | // Se obtienen los archivos 102 | var files = e.originalEvent.dataTransfer.files; 103 | // Se envian los archivos 104 | uploadFiles(files); 105 | }); 106 | 107 | /** Events 108 | * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 109 | */ 110 | $scope.clearContent = function() 111 | { 112 | $('#fileId_'+ $scope.$id +' .mdr-file-dad-content div').fadeOut('slow', function() { $(this).remove(); }); 113 | $("#fileId_"+ $scope.$id +' .mdr-file-dad-content button').fadeOut('slow'); 114 | }; 115 | 116 | $scope.upload = function(element) 117 | { 118 | // Se obtienen los archivos 119 | var files = element.files; 120 | // Se envian los archivos 121 | uploadFiles(files); 122 | }; 123 | 124 | /** Methods 125 | * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 126 | */ 127 | function uploadFiles(files) 128 | { 129 | $scope.count = { 130 | send: 0, 131 | complete: 0, 132 | invalid: 0, 133 | error: 0 134 | }; 135 | 136 | // Si es multiple 137 | if( validMultiple(files) ) { 138 | // Si no exede el limite de archivos 139 | if ( validLimit(files) ) { 140 | // Se envian los archivos 141 | $.each(files, function(k, v){ 142 | // Se envia el archivo 143 | uploadFile(k,v); 144 | }); 145 | } 146 | } 147 | $('#fileId_'+ $scope.$id +' input').replaceWith($('#fileId_'+ $scope.$id +' input').val('').clone(true)); 148 | } 149 | 150 | // Se sube file por file 151 | function uploadFile(k,v) 152 | { 153 | // SE INSTANCIA EL XHR 154 | var xhr = new XMLHttpRequest(); 155 | // Se abre el xhr 156 | xhr.open('POST', $scope.url, true); 157 | // Se agregan los headers al xhr 158 | if ($scope.headers !== undefined) { 159 | var headers = $scope.headers; 160 | for (var header in headers) { 161 | xhr.setRequestHeader(header, headers[header]); 162 | } 163 | } 164 | // SE INSTANCIA EL FILEREAD 165 | // Lee los atributos del file 166 | var reader = new FileReader(); 167 | // Lee la url temporal del file 168 | reader.readAsDataURL(v); 169 | // Cuando se carga el file 170 | reader.onload = function (e) { 171 | // Se valida que el archivo sea valido 172 | var validFile = isValid(v); 173 | // Se crea el preview 174 | createPreview(v, k, e.target.result, validFile.icon, validFile.messages); 175 | // Se valida el tipo y el tamaño 176 | if (validFile.resp) { 177 | // Se envia el formData al server 178 | $scope.$apply(function () { 179 | $scope.count.send++; 180 | }); 181 | // SE INSTANCIA EL FORM DATA 182 | var formData = new FormData(); 183 | // Se agrega el modelo data al formData 184 | if ($scope.data !== undefined) { 185 | for (var ke in $scope.data) { 186 | formData.append(ke, $scope.data[ke]); 187 | } 188 | } 189 | // Se agrega el file en el formData 190 | formData.append('file', v); 191 | xhr.send(formData); 192 | } else { 193 | $scope.$apply(function () { 194 | $scope.count.invalid++; 195 | }); 196 | // Se aborta el envio de formData al server 197 | xhr.abort(); 198 | } 199 | }; 200 | xhr.addEventListener("loadstart", loadStart, false); 201 | xhr.addEventListener("progress", updateProgress, false); 202 | xhr.addEventListener("load", transferComplete, false); 203 | xhr.addEventListener("error", transferFailed, false); 204 | xhr.addEventListener("abort", transferCanceled, false); 205 | // Caundo inicia la carga del archivo 206 | function loadStart(){ 207 | //console.log('Load start'); 208 | } 209 | function updateProgress (e) { 210 | if (e.lengthComputable) { 211 | var percentage = (e.loaded / e.total) * 100; 212 | $('#fileId_'+ $scope.$id + ' .mdr-file-dad-content .preview-'+ k +' .progress .progress-bar').css('width', percentage + '%'); 213 | } 214 | } 215 | function transferComplete (e) { 216 | $scope.$apply(function () { 217 | $scope.count.complete++; 218 | }); 219 | if (xhr.status == 200) { 220 | $scope.$apply(function () { 221 | $scope.model = JSON.parse(xhr.response); 222 | }); 223 | $('#fileId_'+ $scope.$id +' .mdr-file-dad-content .preview-'+ k).fadeOut('slow', function() { $(this).remove(); }); 224 | } else { 225 | $scope.$apply(function () { 226 | $scope.count.error++; 227 | }); 228 | console.log("Server error "+xhr.status+", an error occurred while transferring the file."); 229 | } 230 | } 231 | function transferFailed (e) { 232 | console.log("An error occurred while transferring the file."); 233 | } 234 | function transferCanceled (e) { 235 | console.log("The transfer has been canceled by the user."); 236 | } 237 | } 238 | 239 | 240 | // SE CREA EL PREVIEW 241 | function createPreview(v,k,url,icon,messages) 242 | { 243 | // Se obtiene el tamaño del archivo 244 | var humanSize = bytesToSize(v.size); 245 | 246 | var img = null; 247 | if (icon) { 248 | img = ''; 249 | } else { 250 | img = ''; 251 | } 252 | 253 | var preview = 254 | '
' + 255 | '
' + 256 | img + 257 | '
' + 258 | '
' + 259 | '
' + 260 | '
' + 261 | '

'+ v.name +'

' + 262 | '

'+ humanSize +'

' + 263 | '
' + 264 | '
' + 265 | '
'; 266 | 267 | $("#fileId_"+ $scope.$id +' .mdr-file-dad-content').append($(preview).fadeIn('slow')); 268 | 269 | if (messages !== undefined) { 270 | messages.forEach(function(msg){ 271 | $("#fileId_"+ $scope.$id +' .mdr-file-dad-content .preview-'+ k +' .thumbnail .caption').append('

'+ msg +'

'); 272 | }); 273 | } 274 | 275 | } 276 | 277 | 278 | // VALID FUNCTIONS 279 | function isValid(file) 280 | { 281 | var messages = []; 282 | var icon = false; 283 | 284 | var ext = validExt(file); 285 | var size = validSize(file); 286 | 287 | if (ext.icon || size.icon) { 288 | icon = true; 289 | } 290 | 291 | if (!ext.resp) { 292 | messages.push(ext.msg); 293 | } 294 | if (!size.resp) { 295 | messages.push(size.msg); 296 | } 297 | 298 | if ( ext.resp && size.resp ) { 299 | return { resp: true, icon: icon }; 300 | } 301 | 302 | return { resp: false, icon: icon, messages: messages}; 303 | } 304 | 305 | 306 | function validExt(file) 307 | { 308 | // Img previe or icon 309 | var icon = true; 310 | 311 | // Se decide si se va mostrar el preview o un icono 312 | if (file.type == 'image/jpeg' || file.type == 'image/png' || file.type == 'image/svg+xml' || file.type == 'image/gif') { 313 | icon = false; 314 | } 315 | 316 | if ($scope.formats === undefined) { 317 | return { resp: true, icon: icon, msg: '' }; 318 | } 319 | 320 | // Extencion del archivo 321 | var fileExtension = file.name.substring(file.name.lastIndexOf('.') + 1).toLowerCase(); 322 | // extensiones aceptadas 323 | var formats = $scope.formats; 324 | 325 | // Si los formatos son de tipo string se convierte en array (stringToArray) 326 | if (typeof formats == 'string') { 327 | formats = formats.split(','); 328 | } 329 | 330 | // Si existe la extencion entre las extenciones validas 331 | for (var i = 0; i < formats.length; i++) { 332 | if (fileExtension == formats[i].trim()) { 333 | return { resp: true, icon: icon, msg: ''}; 334 | } 335 | } 336 | 337 | return { resp: false, icon: icon, msg: 'File type '+ fileExtension +' not allowed' }; 338 | } 339 | 340 | 341 | function validSize(file) 342 | { 343 | // Img previe or icon 344 | var icon = false; 345 | // Tamaño del archivo 346 | var size = file.size; 347 | 348 | // Se decide si se va mostrar el preview o un icono 349 | if (size > (5 * 1000) * 1024) { 350 | icon = true; 351 | } 352 | 353 | if ($scope.size === undefined) { 354 | return { resp: true, icon: icon }; 355 | } 356 | 357 | // Tamaño maximo 358 | var maxSize = ($scope.size * 1000) * 1024; 359 | 360 | 361 | if (size < maxSize) { 362 | return { resp: true, icon: icon}; 363 | } 364 | 365 | return { resp: false, icon: icon, msg: 'File exceeds size '+ $scope.size +'MB'}; 366 | } 367 | 368 | function validLimit(files) 369 | { 370 | if ($scope.limit === undefined) { 371 | return true; 372 | } 373 | if ($scope.limit < files.length) { 374 | alert('Max files upload is '+ $scope.limit); 375 | return false; 376 | } 377 | return true; 378 | } 379 | 380 | // Se valida si el imput es multiple 381 | function validMultiple(files) 382 | { 383 | if (files.length == 1) { 384 | return true; 385 | } else if (files.length > 1) { 386 | if ($scope.multiple) { 387 | return true; 388 | } 389 | } 390 | alert('One file for time'); 391 | return false; 392 | } 393 | 394 | // Convierte los bits en 'Bytes', 'KB', 'MB', 'GB', 'TB' 395 | function bytesToSize(bytes) 396 | { 397 | var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; 398 | if (bytes === 0) return 'n/a'; 399 | var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); 400 | if (i === 0) return bytes + ' ' + sizes[i]; 401 | return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i]; 402 | } 403 | 404 | /* 405 | |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 406 | | watch 407 | |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 408 | */ 409 | $scope.$watchCollection('count', function(newValue, oldValue) 410 | { 411 | if (newValue.send == newValue.complete && (newValue.invalid > 0 || newValue.error > 0)) { 412 | $("#fileId_"+ $scope.$id +' .mdr-file-dad-content button').fadeIn('slow'); 413 | } 414 | }); 415 | 416 | 417 | }]); 418 | 419 | })(); 420 | --------------------------------------------------------------------------------