Skip to content

Commit

Permalink
Fixed #1036, #1040, added ng-model-options and image drop from page
Browse files Browse the repository at this point in the history
  • Loading branch information
Danial Farid authored and Danial Farid committed Oct 2, 2015
1 parent 60e1d99 commit 1d3dee5
Show file tree
Hide file tree
Showing 18 changed files with 507 additions and 284 deletions.
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,16 @@ At least one of the `ngf-select` or `ngf-drop` are mandatory for the plugin to l
<div|button|input type="file"|ngf-select|ngf-drop...
ngf-select="" or "upload($files, $file, $event)" // called when files are selected or cleared
ngf-drop="" or "upload($files, $file, $event)" // called when files being dropped
// You can have ng-model or ngf-change instead of specifying function for ngf-drop and ngf-select
ng-model="myFiles" // binds the selected/dropped file or files to the scope model
// You can use ng-model or ngf-change instead of specifying function for ngf-drop and ngf-select
ng-model="myFiles" // binds the valid selected/dropped file or files to the scope model
// could be an array or single file depending on ngf-multiple and ngf-keep values.
ngf-change="upload($files, $file, $newFiles, $duplicateFiles, $event)"
// called when files are selected, dropped, or cleared
ng-disabled="boolean" // disables this element
ng-model-options="{updateOn: {'change', 'click', 'drop', 'paste'}, allowInvalid: false, debounce: 0}" // angular
// model options. You can disable resetting on 'click' (old ngf-reset-on-click option),
// updating on some other events like 'paste', allowing invalid files in the model and
// debouncing the model update to the later time in miliseconds. See angular ng-model-options for more info.
ngf-select-disabled="boolean" // default false, disables file select on this element
ngf-drop-disabled="boolean" // default false, disables file drop on this element
ngf-multiple="boolean" // default false, allows selecting multiple files
Expand All @@ -166,10 +170,6 @@ At least one of the `ngf-select` or `ngf-drop` are mandatory for the plugin to l

*ngf-capture="'camera'" or "'other'" // allows mobile devices to capture using camera
*accept="image/*" // standard HTML accept attribute for the browser specific popup window filtering
*ngf-reset-on-click="boolean" default true, will reset the model value to empty when you click on the
// upload button. This allows removing files when you cancel the popup and
// selecting the same file again. Chrome will empty the files if you cancel the popup by
// default but not the other browsers, so test cross browser is using this.

+ngf-allow-dir="boolean" // default true, allow dropping files only for Chrome webkit browser
+ngf-drag-over-class="{pattern: 'image/*', accept:'acceptClass', reject:'rejectClass', delay:100}"
Expand All @@ -187,7 +187,6 @@ At least one of the `ngf-select` or `ngf-drop` are mandatory for the plugin to l
// quality (optional between 0.1 and 1.0)

//validations:
ngf-valid-only="boolean" // default false, if true only valid files will be assigned to model or change functions.
ngf-pattern="'.pdf,.jpg,video/*,!.jog'" // comma separated wildcard to filter file names and types allowed
// you can exclude specific files by ! at the beginning.
// validate error name: pattern
Expand All @@ -206,8 +205,9 @@ At least one of the `ngf-select` or `ngf-drop` are mandatory for the plugin to l
// validate error name: validateFn
ngf-validate-async-fn="validate($file)" // custom validation function, return a promise that resolve to
// boolean or string containing the error. validate error name: validateAsyncFn
ngf-validate-force="boolean" // default false, if true file.$erro will be set if the dimension or duration
// values for validations cannot be calculated for example image load error or unsupported video by browser
ngf-validate-force="boolean" // default false, if true file.$error will be set if the dimension or duration
// values for validations cannot be calculated for example image load error or unsupported video by the browser.
// by default it would assume the file is valid if the duration or dimension cannot be calculated by the browser.
ngf-validate-later="boolean" // default false, if true model will be set and change will be called before validation

>Upload/Drop</div>
Expand Down Expand Up @@ -257,12 +257,12 @@ var upload = Upload.upload({
{picFile: Upload.rename(file, 'profile.jpg'), title: title} send file with picFile key and profile.jpg file name*/
data: {key: file, otherInfo: uploadInfo},
/*
This is to accomudate server implementations expecting nested data object keys in .key or [key] format.
This is to accommodate server implementations expecting nested data object keys in .key or [key] format.
Example: data: {rec: {name: 'N', pic: file}} sent as: rec[name] -> N, rec[pic] -> file
data: {rec: {name: 'N', pic: file}, objectKey: '.k'} sent as: rec.name -> N, rec.pic -> file */
objectKey: '[k]' or '.k' // default is '[k]'
/*
This is to accomudate server implementations expecting array data object keys in '[i]' or '[]' or
This is to accommodate server implementations expecting array data object keys in '[i]' or '[]' or
''(multiple entries with same key) format.
Example: data: {rec: [file[0], file[1], ...]} sent as: rec[0] -> file[0], rec[1] -> file[1],...
data: {rec: {rec: [f[0], f[1], ...], arrayKey: '[]'} sent as: rec[] -> f[0], rec[] -> f[1],...*/
Expand Down
26 changes: 14 additions & 12 deletions demo/src/main/webapp/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,13 @@ <h3>
<fieldset>
<legend>Play with options</legend>
<div class="up-buttons">
<div ngf-select ngf-drop ng-model="files"
<div ngf-select ngf-drop ng-model="files" ngf-model-invalid="invalidFiles"
ng-model-options="modelOptionsObj"
ngf-multiple="multiple" ngf-pattern="pattern" accept="{{acceptSelect}}"
ng-disabled="disabled" ngf-capture="capture"
ngf-drag-over-class="{accept:'dragover', reject:'dragover-err', delay:100}"
ngf-validate="validateObj" ngf-valid-only="validOnly"
ngf-keep="keep" ngf-keep-distinct="keepDistinct" ngf-reset-on-click="resetOnClick"
ngf-drag-over-class="dragOverClassObj"
ngf-validate="validateObj"
ngf-keep="keep" ngf-keep-distinct="keepDistinct"
ngf-allow-dir="allowDir" class="drop-box" ngf-drop-available="dropAvailable">Select File,
<span ng-show="dropAvailable">Drop File or Paste Image</span>
</div>
Expand All @@ -103,6 +104,8 @@ <h3>
<label>ngf-capture (for mobile): <input type="text" ng-model="capture"></label><br/>
<label>ngf-pattern (validate file model): <input type="text" ng-model="pattern"></label><br/>
<label>ngf-validate: <input type="text" ng-model="validate" size="49"></label><br/>
<label>ngf-drag-over-class (chrome): <input size="31" type="text" ng-model="dragOverClass"></label><br/>
<label>ng-model-options: <input type="text" size="43" ng-model="modelOptions"></label><br/>
<label><input type="checkbox" ng-model="multiple">ngf-multiple (allow multiple files)</label><br/>
<label><input type="checkbox" ng-model="disabled">ng-disabled</label><br/>
<label><input type="checkbox" ng-model="allowDir">ngf-allow-dir (allow directory drop Chrome
Expand All @@ -111,9 +114,6 @@ <h3>
ng-model)</label><br/>
<label><input type="checkbox" ng-model="keepDistinct">ngf-keep-distinct (do not allow
duplicates)</label><br/>
<label><input type="checkbox" ng-model="validOnly">ngf-valid-only (only valid files assigned to
model)</label><br/>
<label><input type="checkbox" ng-model="resetOnClick">ngf-reset-on-click</label><br/>
<label>Upload resumable chunk size: <input type="text" ng-model="chunkSize"></label><br/>
</div>

Expand All @@ -128,12 +128,9 @@ <h3>
</div>
</div>
</div>
<ul style="clear:both" ng-show="files.length > 0" class="response">
<ul style="clear:both" class="response">
<li class="sel-file" ng-repeat="f in files">
<div ng-show="f.$error">Invalid File: {{f.$error}} {{f.$errorParam}}, {{f.name}} - size: {{f.size}}B - type:
{{f.type}}
</div>
<div ng-hide="f.$error">
<div>
<img ngf-thumbnail="!f.$error && f" class="thumb">
<span class="progress" ng-show="f.progress >= 0">
<div style="width:{{f.progress}}%">{{f.progress}}%</div>
Expand Down Expand Up @@ -167,6 +164,11 @@ <h3>
</div>
</div>
</li>
<li class="sel-file" ng-repeat="f in invalidFiles">
<div>Invalid File: {{f.$error}} {{f.$errorParam}}, {{f.name}} - size: {{f.size}}B - type:
{{f.type}}
</div>
</li>
</ul>

<br/>
Expand Down
142 changes: 93 additions & 49 deletions demo/src/main/webapp/js/ng-file-upload-all.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* progress, resize, thumbnail, preview, validation and CORS
* FileAPI Flash shim for old browsers not supporting FormData
* @author Danial <danial.farid@gmail.com>
* @version 8.0.6
* @version 9.0.0
*/

(function () {
Expand Down Expand Up @@ -427,7 +427,7 @@ if (!window.FileReader) {
* AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort,
* progress, resize, thumbnail, preview, validation and CORS
* @author Danial <danial.farid@gmail.com>
* @version 8.0.6
* @version 9.0.0
*/

if (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) {
Expand All @@ -448,7 +448,7 @@ if (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) {

var ngFileUpload = angular.module('ngFileUpload', []);

ngFileUpload.version = '8.0.6';
ngFileUpload.version = '9.0.0';

ngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, $q, $timeout) {
var upload = this;
Expand Down Expand Up @@ -658,14 +658,21 @@ ngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http,
formData.append(key, file, file.ngfName || file.name);
} else {
if (angular.isObject(val)) {
for (var k in val) {
if (val.hasOwnProperty(k)) {
var objectKey = config.objectKey == null ? '[i]' : config.objectKey;
if (val.length && parseInt(k) > -1) {
objectKey = config.arrayKey == null ? objectKey : config.arrayKey;
if (val.$$ngfCircularDetection) throw 'ngFileUpload: Circular reference in config.data. Make sure specified data for Upload.upload() has no circular reference: ' + key;

val.$$ngfCircularDetection = true;
try {
for (var k in val) {
if (val.hasOwnProperty(k)) {
var objectKey = config.objectKey == null ? '[i]' : config.objectKey;
if (val.length && parseInt(k) > -1) {
objectKey = config.arrayKey == null ? objectKey : config.arrayKey;
}
addFieldToFormData(formData, val[k], key + objectKey.replace(/[ik]/g, k));
}
addFieldToFormData(formData, val[k], key + objectKey.replace(/[ik]/g, k));
}
} finally {
delete val.$$ngfCircularDetection;
}
} else {
formData.append(key, val);
Expand Down Expand Up @@ -785,18 +792,22 @@ ngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', 'UploadResize'
}
};

upload.shouldUpdateOn = function(type, attr, scope) {
var modelOptions = upload.attrGetter('ngModelOptions', attr, scope);
if (modelOptions && modelOptions.updateOn) {
return modelOptions.updateOn.split(' ').indexOf(type) > -1;
}
return true;
};

upload.updateModel = function (ngModel, attr, scope, fileChange, files, evt, noDelay) {
var newFiles = files, dupFiles = [];

function update() {
function update(files, invalidFiles) {
var file = files && files.length ? files[0] : null;
if (ngModel) {
var singleModel = !upload.attrGetter('ngfMultiple', attr, scope) && !upload.attrGetter('multiple', attr) && !keep;
$parse(upload.attrGetter('ngModel', attr)).assign(scope, singleModel ? file : files);
}
var ngfModel = upload.attrGetter('ngfModel', attr);
if (ngfModel) {
$parse(ngfModel).assign(scope, files);
ngModel.$setViewValue(singleModel ? file : files);
}

if (fileChange) {
Expand All @@ -805,11 +816,18 @@ ngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', 'UploadResize'
$file: file,
$newFiles: newFiles,
$duplicateFiles: dupFiles,
$invalidFiles: invalidFiles,
$event: evt
});
}
// scope apply changes
var invalidModel = upload.attrGetter('ngfModelInvalid', attr);
if (invalidModel) {
$timeout(function() {
$parse(invalidModel).assign(scope, invalidFiles);
});
}
$timeout(function () {
// scope apply changes
});
}

Expand Down Expand Up @@ -876,23 +894,28 @@ ngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', 'UploadResize'
}

if (noDelay) {
update();
} else if (upload.validate(files, ngModel, attr, scope, upload.attrGetter('ngfValidateLater', attr), function () {
if (upload.attrGetter('ngfValidOnly', attr, scope) === true) {
var valids = [];
angular.forEach(files, function(file) {
if (!file.$error) {
valids.push(file);
}
});
files = valids;
}
resize(files, function () {
$timeout(function () {
update();
update(files, []);
} else {
if (upload.validate(files, ngModel, attr, scope, upload.attrGetter('ngfValidateLater', attr), function () {
var options = upload.attrGetter('ngModelOptions', attr, scope);
if (!options || !options.allowInvalid) {
var valids = [], invalids = [];
angular.forEach(files, function (file) {
if (file.$error) {
invalids.push(file);
} else {
valids.push(file);
}
});
files = valids;
}
resize(files, function () {
$timeout(function () {
update(files, invalids);
}, options && options.debounce ? options.debounce.change || options.debounce : 0);
});
});
}));
}));
}

// cleaning object url memories
var l = prevFiles.length;
Expand Down Expand Up @@ -927,7 +950,7 @@ ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload',
/** @namespace attr.ngfSelect */
/** @namespace attr.ngfChange */
/** @namespace attr.ngModel */
/** @namespace attr.ngfModel */
/** @namespace attr.ngModelOptions */
/** @namespace attr.ngfMultiple */
/** @namespace attr.ngfCapture */
/** @namespace attr.ngfValidate */
Expand All @@ -946,11 +969,14 @@ ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload',
}

function changeFn(evt) {
var fileList = evt.__files_ || (evt.target && evt.target.files), files = [];
for (var i = 0; i < fileList.length; i++) {
files.push(fileList[i]);
if (upload.shouldUpdateOn('change', attr, scope)) {
var fileList = evt.__files_ || (evt.target && evt.target.files), files = [];
for (var i = 0; i < fileList.length; i++) {
files.push(fileList[i]);
}
upload.updateModel(ngModel, attr, scope, fileChangeAttr(),
files.length ? files : null, evt);
}
upload.updateModel(ngModel, attr, scope, fileChangeAttr(), files.length ? files : null, evt);
}

var unwatches = [];
Expand Down Expand Up @@ -1040,7 +1066,7 @@ ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload',
var fileElem = elem;

function resetModel(evt) {
if (attrGetter('ngfResetOnClick', scope) !== false && fileElem.val()) {
if (upload.shouldUpdateOn('click', attr, scope) && fileElem.val()) {
fileElem.val(null);
upload.updateModel(ngModel, attr, scope, fileChangeAttr(), null, evt, true);
}
Expand Down Expand Up @@ -1855,13 +1881,13 @@ ngFileUpload.service('UploadResize', ['UploadValidate', '$q', '$timeout', functi
}]);

(function () {
ngFileUpload.directive('ngfDrop', ['$parse', '$timeout', '$location', 'Upload',
function ($parse, $timeout, $location, Upload) {
ngFileUpload.directive('ngfDrop', ['$parse', '$timeout', '$location', 'Upload', '$http',
function ($parse, $timeout, $location, Upload, $http) {
return {
restrict: 'AEC',
require: '?ngModel',
link: function (scope, elem, attr, ngModel) {
linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $location, Upload);
linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $location, Upload, $http);
}
};
}]);
Expand All @@ -1886,7 +1912,7 @@ ngFileUpload.service('UploadResize', ['UploadValidate', '$q', '$timeout', functi
};
}]);

function linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $location, upload) {
function linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $location, upload, $http) {
var available = dropAvailable();

var attrGetter = function (name, scope, params) {
Expand Down Expand Up @@ -1951,18 +1977,36 @@ ngFileUpload.service('UploadResize', ['UploadValidate', '$q', '$timeout', functi
}, dragOverDelay || 1);
}, false);
elem[0].addEventListener('drop', function (evt) {
if (isDisabled()) return;
if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;
evt.preventDefault();
if (stopPropagation(scope)) evt.stopPropagation();
if (actualDragOverClass) elem.removeClass(actualDragOverClass);
actualDragOverClass = null;
extractFiles(evt, function (files) {
upload.updateModel(ngModel, attr, scope, attrGetter('ngfChange') || attrGetter('ngfDrop'), files, evt);
}, attrGetter('ngfAllowDir', scope) !== false,
attrGetter('multiple') || attrGetter('ngfMultiple', scope));
var html = (evt.dataTransfer && evt.dataTransfer.getData && evt.dataTransfer.getData('text/html'));
if (html) {
var url;
html.replace(/<img .*src *=\"([^\"]*)\"/, function (m, src) {
url = src;
});
if (url) {
$http({url: url, method: 'get', responseType: 'arraybuffer'}).then(function (resp) {
var arrayBufferView = new Uint8Array(resp.data);
var type = resp.headers('content-type') || 'image/jpg';
var blob = new Blob([arrayBufferView], {type: type});
//var split = type.split('[/;]');
//blob.name = url.substring(0, 150).replace(/\W+/g, '') + '.' + (split.length > 1 ? split[1] : 'jpg');
upload.updateModel(ngModel, attr, scope, attrGetter('ngfChange') || attrGetter('ngfDrop'), [blob], evt);
});
}
} else {
extractFiles(evt, function (files) {
upload.updateModel(ngModel, attr, scope, attrGetter('ngfChange') || attrGetter('ngfDrop'), files, evt);
}, attrGetter('ngfAllowDir', scope) !== false,
attrGetter('multiple') || attrGetter('ngfMultiple', scope));
}
}, false);
elem[0].addEventListener('paste', function (evt) {
if (isDisabled()) return;
if (isDisabled() || !upload.shouldUpdateOn('paste', attr, scope)) return;
var files = [];
var clipboard = evt.clipboardData || evt.originalEvent.clipboardData;
if (clipboard && clipboard.items) {
Expand All @@ -1989,7 +2033,7 @@ ngFileUpload.service('UploadResize', ['UploadValidate', '$q', '$timeout', functi
var pattern = obj.pattern || attrGetter('ngfPattern', scope, {$event: evt});
var len = items.length;
while (len--) {
if (items[len].kind !== 'file' || !upload.validatePattern(items[len], pattern)) {
if (!upload.validatePattern(items[len], pattern)) {
dClass = obj.reject;
break;
} else {
Expand Down
2 changes: 1 addition & 1 deletion demo/src/main/webapp/js/ng-file-upload-all.min.js

Large diffs are not rendered by default.

Loading

0 comments on commit 1d3dee5

Please sign in to comment.