├── 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 | 
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 | '
' +
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:''}}]).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='";$("#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 | '
' +
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 |
--------------------------------------------------------------------------------