pyc-website

main website for pyc inc.

git clone https://9o.is/git/pyc-website.git

angular-file-upload.js

(22368B)


      1 /*
      2  angular-file-upload v0.5.7
      3  https://github.com/nervgh/angular-file-upload
      4 */
      5 (function(angular, factory) {
      6     if (typeof define === 'function' && define.amd) {
      7         define('angular-file-upload', ['angular'], function(angular) {
      8             return factory(angular);
      9         });
     10     } else {
     11         return factory(angular);
     12     }
     13 }(angular || null, function(angular) {
     14 var app = angular.module('angularFileUpload', []);
     15 
     16 // It is attached to an element that catches the event drop file
     17 app.directive('ngFileDrop', ['$fileUploader', function ($fileUploader) {
     18     'use strict';
     19 
     20     return {
     21         // don't use drag-n-drop files in IE9, because not File API support
     22         link: !$fileUploader.isHTML5 ? angular.noop : function (scope, element, attributes) {
     23             element
     24                 .bind('drop', function (event) {
     25                     var dataTransfer = event.dataTransfer ?
     26                         event.dataTransfer :
     27                         event.originalEvent.dataTransfer; // jQuery fix;
     28                     if (!dataTransfer) return;
     29                     event.preventDefault();
     30                     event.stopPropagation();
     31                     scope.$broadcast('file:removeoverclass');
     32                     scope.$emit('file:add', dataTransfer.files, scope.$eval(attributes.ngFileDrop));
     33                 })
     34                 .bind('dragover', function (event) {
     35                     var dataTransfer = event.dataTransfer ?
     36                         event.dataTransfer :
     37                         event.originalEvent.dataTransfer; // jQuery fix;
     38 
     39                     event.preventDefault();
     40                     event.stopPropagation();
     41                     dataTransfer.dropEffect = 'copy';
     42                     scope.$broadcast('file:addoverclass');
     43                 })
     44                 .bind('dragleave', function (event) {
     45                     if (event.target === element[0]) {
     46                         scope.$broadcast('file:removeoverclass');
     47                     }
     48                 });
     49         }
     50     };
     51 }])
     52 
     53 // It is attached to an element which will be assigned to a class "ng-file-over" or ng-file-over="className"
     54 app.directive('ngFileOver', function () {
     55     'use strict';
     56 
     57     return {
     58         link: function (scope, element, attributes) {
     59             scope.$on('file:addoverclass', function () {
     60                 element.addClass(attributes.ngFileOver || 'ng-file-over');
     61             });
     62             scope.$on('file:removeoverclass', function () {
     63                 element.removeClass(attributes.ngFileOver || 'ng-file-over');
     64             });
     65         }
     66     };
     67 });
     68 // It is attached to <input type="file"> element like <ng-file-select="options">
     69 app.directive('ngFileSelect', ['$fileUploader', function($fileUploader) {
     70     'use strict';
     71 
     72     return {
     73         link: function(scope, element, attributes) {
     74             if(!$fileUploader.isHTML5) {
     75                 element.removeAttr('multiple');
     76             }
     77 
     78             element.bind('change', function() {
     79                 var data = $fileUploader.isHTML5 ? this.files : this;
     80                 var options = scope.$eval(attributes.ngFileSelect);
     81 
     82                 scope.$emit('file:add', data, options);
     83 
     84                 if($fileUploader.isHTML5 && element.attr('multiple')) {
     85                     element.prop('value', null);
     86                 }
     87             });
     88 
     89             element.prop('value', null); // FF fix
     90         }
     91     };
     92 }]);
     93 app.factory('$fileUploader', ['$compile', '$rootScope', '$http', '$window', function($compile, $rootScope, $http, $window) {
     94     'use strict';
     95 
     96     /**
     97      * Creates a uploader
     98      * @param {Object} params
     99      * @constructor
    100      */
    101     function Uploader(params) {
    102         angular.extend(this, {
    103             scope: $rootScope,
    104             url: '/',
    105             alias: 'file',
    106             queue: [],
    107             headers: {},
    108             progress: null,
    109             autoUpload: false,
    110             removeAfterUpload: false,
    111             method: 'POST',
    112             filters: [],
    113             formData: [],
    114             isUploading: false,
    115             queueLimit: Number.MAX_VALUE,
    116             withCredentials: false,
    117             _nextIndex: 0,
    118             _timestamp: Date.now()
    119         }, params);
    120 
    121         // add default filters
    122         this.filters.unshift(this._queueLimitFilter);
    123         this.filters.unshift(this._emptyFileFilter);
    124 
    125         this.scope.$on('file:add', function(event, items, options) {
    126             event.stopPropagation();
    127             this.addToQueue(items, options);
    128         }.bind(this));
    129 
    130         this.bind('beforeupload', Item.prototype._beforeupload);
    131         this.bind('in:progress', Item.prototype._progress);
    132         this.bind('in:success', Item.prototype._success);
    133         this.bind('in:cancel', Item.prototype._cancel);
    134         this.bind('in:error', Item.prototype._error);
    135         this.bind('in:complete', Item.prototype._complete);
    136         this.bind('in:progress', this._progress);
    137         this.bind('in:complete', this._complete);
    138     }
    139 
    140     Uploader.prototype = {
    141         /**
    142          * Link to the constructor
    143          */
    144         constructor: Uploader,
    145 
    146         /**
    147          * Returns "true" if item is DOMElement or a file with size > 0
    148          * @param {File|Input} item
    149          * @returns {Boolean}
    150          * @private
    151          */
    152         _emptyFileFilter: function(item) {
    153             return angular.isElement(item) ? true : !!item.size;
    154         },
    155 
    156         /**
    157          * Returns "true" if the limit has not been reached
    158          * @returns {Boolean}
    159          * @private
    160          */
    161         _queueLimitFilter: function() {
    162             return this.queue.length < this.queueLimit;
    163         },
    164 
    165         /**
    166          * Registers a event handler
    167          * @param {String} event
    168          * @param {Function} handler
    169          * @return {Function} unsubscribe function
    170          */
    171         bind: function(event, handler) {
    172             return this.scope.$on(this._timestamp + ':' + event, handler.bind(this));
    173         },
    174 
    175         /**
    176          * Triggers events
    177          * @param {String} event
    178          * @param {...*} [some]
    179          */
    180         trigger: function(event, some) {
    181             arguments[0] = this._timestamp + ':' + event;
    182             this.scope.$broadcast.apply(this.scope, arguments);
    183         },
    184 
    185         /**
    186          * Checks a support the html5 uploader
    187          * @returns {Boolean}
    188          * @readonly
    189          */
    190         isHTML5: !!($window.File && $window.FormData),
    191 
    192         /**
    193          * Adds items to the queue
    194          * @param {FileList|File|HTMLInputElement} items
    195          * @param {Object} [options]
    196          */
    197         addToQueue: function(items, options) {
    198             var length = this.queue.length;
    199             var list = 'length' in items ? items : [items];
    200 
    201             angular.forEach(list, function(file) {
    202                 // check a [File|HTMLInputElement]
    203                 var isValid = !this.filters.length ? true : this.filters.every(function(filter) {
    204                     return filter.call(this, file);
    205                 }, this);
    206 
    207                 // create new item
    208                 var item = new Item(angular.extend({
    209                     url: this.url,
    210                     alias: this.alias,
    211                     headers: angular.copy(this.headers),
    212                     formData: angular.copy(this.formData),
    213                     removeAfterUpload: this.removeAfterUpload,
    214                     withCredentials: this.withCredentials,
    215                     method: this.method,
    216                     uploader: this,
    217                     file: file
    218                 }, options));
    219 
    220                 if(isValid) {
    221                     this.queue.push(item);
    222                     this.trigger('afteraddingfile', item);
    223                 } else {
    224                     this.trigger('whenaddingfilefailed', item);
    225                 }
    226             }, this);
    227 
    228             if(this.queue.length !== length) {
    229                 this.trigger('afteraddingall', this.queue);
    230                 this.progress = this._getTotalProgress();
    231             }
    232 
    233             this._render();
    234             this.autoUpload && this.uploadAll();
    235         },
    236 
    237         /**
    238          * Remove items from the queue. Remove last: index = -1
    239          * @param {Item|Number} value
    240          */
    241         removeFromQueue: function(value) {
    242             var index = this.getIndexOfItem(value);
    243             var item = this.queue[index];
    244             if (item.cancel) item.cancel();
    245             if (item._destroy) item._destroy();
    246             this.queue.splice(index, 1);
    247             this.progress = this._getTotalProgress();
    248         },
    249 
    250         /**
    251          * Clears the queue
    252          */
    253         clearQueue: function() {
    254             while(this.queue.length) {
    255                 this.queue[this.queue.length - 1].remove();
    256             }
    257         },
    258 
    259         /**
    260          * Uploads a item from the queue
    261          * @param {Item|Number} value
    262          */
    263         uploadItem: function(value) {
    264             var index = this.getIndexOfItem(value);
    265             var item = this.queue[index];
    266             var transport = this.isHTML5 ? '_xhrTransport' : '_iframeTransport';
    267 
    268             item.index = item.index || this._nextIndex++;
    269             item.isReady = true;
    270 
    271             if(this.isUploading) return;
    272 
    273             this.isUploading = true;
    274             this[transport](item);
    275         },
    276 
    277         /**
    278          * Cancels uploading of item from the queue
    279          * @param {Item|Number} value
    280          */
    281         cancelItem: function(value) {
    282             var index = this.getIndexOfItem(value);
    283             var item = this.queue[index];
    284             var prop = this.isHTML5 ? '_xhr' : '_form';
    285             if (item[prop]) item[prop].abort();
    286         },
    287 
    288         /**
    289          * Uploads all not uploaded items of queue
    290          */
    291         uploadAll: function() {
    292             var items = this.getNotUploadedItems().filter(function(item) {
    293                 return !item.isUploading;
    294             });
    295             items.forEach(function(item) {
    296                 item.index = item.index || this._nextIndex++;
    297                 item.isReady = true;
    298             }, this);
    299             items.length && this.uploadItem(items[0]);
    300         },
    301 
    302         /**
    303          * Cancels all uploads
    304          */
    305         cancelAll: function() {
    306             this.getNotUploadedItems().forEach(function(item) {
    307                 this.cancelItem(item);
    308             }, this);
    309         },
    310 
    311         /**
    312          * Returns a index of item from the queue
    313          * @param {Item|Number} value
    314          * @returns {Number}
    315          */
    316         getIndexOfItem: function(value) {
    317             return angular.isNumber(value) ? value : this.queue.indexOf(value);
    318         },
    319 
    320         /**
    321          * Returns not uploaded items
    322          * @returns {Array}
    323          */
    324         getNotUploadedItems: function() {
    325             return this.queue.filter(function(item) {
    326                 return !item.isUploaded;
    327             });
    328         },
    329 
    330         /**
    331          * Returns items ready for upload
    332          * @returns {Array}
    333          */
    334         getReadyItems: function() {
    335             return this.queue
    336                 .filter(function(item) {
    337                     return item.isReady && !item.isUploading;
    338                 })
    339                 .sort(function(item1, item2) {
    340                     return item1.index - item2.index;
    341                 });
    342         },
    343 
    344         /**
    345          * Updates angular scope
    346          * @private
    347          */
    348         _render: function() {
    349             if (!this.scope.$$phase) this.scope.$digest();
    350         },
    351 
    352         /**
    353          * Returns the total progress
    354          * @param {Number} [value]
    355          * @returns {Number}
    356          * @private
    357          */
    358         _getTotalProgress: function(value) {
    359             if(this.removeAfterUpload) {
    360                 return value || 0;
    361             }
    362 
    363             var notUploaded = this.getNotUploadedItems().length;
    364             var uploaded = notUploaded ? this.queue.length - notUploaded : this.queue.length;
    365             var ratio = 100 / this.queue.length;
    366             var current = (value || 0) * ratio / 100;
    367 
    368             return Math.round(uploaded * ratio + current);
    369         },
    370 
    371         /**
    372          * The 'in:progress' handler
    373          * @private
    374          */
    375         _progress: function(event, item, progress) {
    376             var result = this._getTotalProgress(progress);
    377             this.trigger('progressall', result);
    378             this.progress = result;
    379             this._render();
    380         },
    381 
    382         /**
    383          * The 'in:complete' handler
    384          * @private
    385          */
    386         _complete: function() {
    387             var item = this.getReadyItems()[0];
    388             this.isUploading = false;
    389 
    390             if(angular.isDefined(item)) {
    391                 this.uploadItem(item);
    392                 return;
    393             }
    394 
    395             this.trigger('completeall', this.queue);
    396             this.progress = this._getTotalProgress();
    397             this._render();
    398         },
    399 
    400         /**
    401          * The XMLHttpRequest transport
    402          * @private
    403          */
    404         _xhrTransport: function(item) {
    405             var xhr = item._xhr = new XMLHttpRequest();
    406             var form = new FormData();
    407             var that = this;
    408 
    409             this.trigger('beforeupload', item);
    410 
    411             item.formData.forEach(function(obj) {
    412                 angular.forEach(obj, function(value, key) {
    413                     form.append(key, value);
    414                 });
    415             });
    416 
    417             form.append(item.alias, item.file);
    418 
    419             xhr.upload.onprogress = function(event) {
    420                 var progress = event.lengthComputable ? event.loaded * 100 / event.total : 0;
    421                 that.trigger('in:progress', item, Math.round(progress));
    422             };
    423 
    424             xhr.onload = function() {
    425                 var response = that._transformResponse(xhr.response);
    426                 var event = that._isSuccessCode(xhr.status) ? 'success' : 'error';
    427                 that.trigger('in:' + event, xhr, item, response);
    428                 that.trigger('in:complete', xhr, item, response);
    429             };
    430 
    431             xhr.onerror = function() {
    432                 that.trigger('in:error', xhr, item);
    433                 that.trigger('in:complete', xhr, item);
    434             };
    435 
    436             xhr.onabort = function() {
    437                 that.trigger('in:cancel', xhr, item);
    438                 that.trigger('in:complete', xhr, item);
    439             };
    440 
    441             xhr.open(item.method, item.url, true);
    442 
    443             xhr.withCredentials = item.withCredentials;
    444 
    445             angular.forEach(item.headers, function(value, name) {
    446                 xhr.setRequestHeader(name, value);
    447             });
    448 
    449             xhr.send(form);
    450         },
    451 
    452         /**
    453          * The IFrame transport
    454          * @private
    455          */
    456         _iframeTransport: function(item) {
    457             var form = angular.element('<form style="display: none;" />');
    458             var iframe = angular.element('<iframe name="iframeTransport' + Date.now() + '">');
    459             var input = item._input;
    460             var that = this;
    461 
    462             if (item._form) item._form.replaceWith(input); // remove old form
    463             item._form = form; // save link to new form
    464 
    465             this.trigger('beforeupload', item);
    466 
    467             input.prop('name', item.alias);
    468 
    469             item.formData.forEach(function(obj) {
    470                 angular.forEach(obj, function(value, key) {
    471                     form.append(angular.element('<input type="hidden" name="' + key + '" value="' + value + '" />'));
    472                 });
    473             });
    474 
    475             form.prop({
    476                 action: item.url,
    477                 method: 'POST',
    478                 target: iframe.prop('name'),
    479                 enctype: 'multipart/form-data',
    480                 encoding: 'multipart/form-data' // old IE
    481             });
    482 
    483             iframe.bind('load', function() {
    484                 // fixed angular.contents() for iframes
    485                 var html = iframe[0].contentDocument.body.innerHTML;
    486                 var xhr = {response: html, status: 200, dummy: true};
    487                 var response = that._transformResponse(xhr.response);
    488                 that.trigger('in:success', xhr, item, response);
    489                 that.trigger('in:complete', xhr, item, response);
    490             });
    491 
    492             form.abort = function() {
    493                 var xhr = {status: 0, dummy: true};
    494                 iframe.unbind('load').prop('src', 'javascript:false;');
    495                 form.replaceWith(input);
    496                 that.trigger('in:cancel', xhr, item);
    497                 that.trigger('in:complete', xhr, item);
    498             };
    499 
    500             input.after(form);
    501             form.append(input).append(iframe);
    502 
    503             form[0].submit();
    504         },
    505 
    506         /**
    507          * Checks whether upload successful
    508          * @param {Number} status
    509          * @returns {Boolean}
    510          * @private
    511          */
    512         _isSuccessCode: function(status) {
    513             return (status >= 200 && status < 300) || status === 304;
    514         },
    515 
    516         /**
    517          * Transforms the server response
    518          * @param {*} response
    519          * @returns {*}
    520          * @private
    521          */
    522         _transformResponse: function(response) {
    523             $http.defaults.transformResponse.forEach(function(transformFn) {
    524                 response = transformFn(response);
    525             });
    526             return response;
    527         }
    528     };
    529 
    530 
    531     /**
    532      * Create a item
    533      * @param {Object} [params]
    534      * @constructor
    535      */
    536     function Item(params) {
    537         // fix for old browsers
    538         if(!Uploader.prototype.isHTML5) {
    539             var input = angular.element(params.file);
    540             var clone = $compile(input.clone())(params.uploader.scope);
    541             var value = input.val();
    542 
    543             params.file = {
    544                 lastModifiedDate: null,
    545                 size: null,
    546                 type: 'like/' + value.slice(value.lastIndexOf('.') + 1).toLowerCase(),
    547                 name: value.slice(value.lastIndexOf('/') + value.lastIndexOf('\\') + 2)
    548             };
    549 
    550             params._input = input;
    551             clone.prop('value', null); // FF fix
    552             input.css('display', 'none').after(clone); // remove jquery dependency
    553         }
    554 
    555         angular.extend(this, {
    556             isReady: false,
    557             isUploading: false,
    558             isUploaded: false,
    559             isSuccess: false,
    560             isCancel: false,
    561             isError: false,
    562             progress: null,
    563             index: null
    564         }, params);
    565     }
    566 
    567 
    568     Item.prototype = {
    569         /**
    570          * Link to the constructor
    571          */
    572         constructor: Item,
    573         /**
    574          * Removes a item
    575          */
    576         remove: function() {
    577             this.uploader.removeFromQueue(this);
    578         },
    579         /**
    580          * Uploads a item
    581          */
    582         upload: function() {
    583             this.uploader.uploadItem(this);
    584         },
    585         /**
    586          * Cancels uploading
    587          */
    588         cancel: function() {
    589             this.uploader.cancelItem(this);
    590         },
    591         /**
    592          * Destroys form and input
    593          * @private
    594          */
    595         _destroy: function() {
    596             if (this._form) this._form.remove();
    597             if (this._input) this._input.remove();
    598             delete this._form;
    599             delete this._input;
    600         },
    601         /**
    602          * The 'beforeupload' handler
    603          * @param {Object} event
    604          * @param {Item} item
    605          * @private
    606          */
    607         _beforeupload: function(event, item) {
    608             item.isReady = true;
    609             item.isUploading = true;
    610             item.isUploaded = false;
    611             item.isSuccess = false;
    612             item.isCancel = false;
    613             item.isError = false;
    614             item.progress = 0;
    615         },
    616         /**
    617          * The 'in:progress' handler
    618          * @param {Object} event
    619          * @param {Item} item
    620          * @param {Number} progress
    621          * @private
    622          */
    623         _progress: function(event, item, progress) {
    624             item.progress = progress;
    625             item.uploader.trigger('progress', item, progress);
    626         },
    627         /**
    628          * The 'in:success' handler
    629          * @param {Object} event
    630          * @param {XMLHttpRequest} xhr
    631          * @param {Item} item
    632          * @param {*} response
    633          * @private
    634          */
    635         _success: function(event, xhr, item, response) {
    636             item.isReady = false;
    637             item.isUploading = false;
    638             item.isUploaded = true;
    639             item.isSuccess = true;
    640             item.isCancel = false;
    641             item.isError = false;
    642             item.progress = 100;
    643             item.index = null;
    644             item.uploader.trigger('success', xhr, item, response);
    645         },
    646         /**
    647          * The 'in:cancel' handler
    648          * @param {Object} event
    649          * @param {XMLHttpRequest} xhr
    650          * @param {Item} item
    651          * @private
    652          */
    653         _cancel: function(event, xhr, item) {
    654             item.isReady = false;
    655             item.isUploading = false;
    656             item.isUploaded = false;
    657             item.isSuccess = false;
    658             item.isCancel = true;
    659             item.isError = false;
    660             item.progress = 0;
    661             item.index = null;
    662             item.uploader.trigger('cancel', xhr, item);
    663         },
    664         /**
    665          * The 'in:error' handler
    666          * @param {Object} event
    667          * @param {XMLHttpRequest} xhr
    668          * @param {Item} item
    669          * @param {*} response
    670          * @private
    671          */
    672         _error: function(event, xhr, item, response) {
    673             item.isReady = false;
    674             item.isUploading = false;
    675             item.isUploaded = true;
    676             item.isSuccess = false;
    677             item.isCancel = false;
    678             item.isError = true;
    679             item.progress = 100;
    680             item.index = null;
    681             item.uploader.trigger('error', xhr, item, response);
    682         },
    683         /**
    684          * The 'in:complete' handler
    685          * @param {Object} event
    686          * @param {XMLHttpRequest} xhr
    687          * @param {Item} item
    688          * @param {*} response
    689          * @private
    690          */
    691         _complete: function(event, xhr, item, response) {
    692             item.uploader.trigger('complete', xhr, item, response);
    693             item.removeAfterUpload && item.remove();
    694         }
    695     };
    696 
    697     return {
    698         create: function(params) {
    699             return new Uploader(params);
    700         },
    701         isHTML5: Uploader.prototype.isHTML5
    702     };
    703 }])
    704 
    705     return app;
    706 }));