Skip to content

Instantly share code, notes, and snippets.

@djvs
Created June 23, 2014 19:30
Show Gist options
  • Save djvs/7c82f7395624d85c7e03 to your computer and use it in GitHub Desktop.
Save djvs/7c82f7395624d85c7e03 to your computer and use it in GitHub Desktop.
// Version (see package.json)
// AngularJS simple file upload directive
// this directive uses an iframe as a target
// to enable the uploading of files without
// losing focus in the ng-app.
//
// <div ng-app="app">
// <div ng-controller="mainCtrl">
// <form ng-attr-action="/uploads"
// ng-upload="completed(content)">
// ng-upload-loading="loading()"
// <input type="file" name="avatar"></input>
// <input type="submit" value="Upload"
// ng-disabled="$isUploading"></input>
// </form>
// </div>
// </div>
//
// angular.module('app', ['ngUpload'])
// .controller('mainCtrl', function($scope) {
// $scope.loading = function() {
// console.log('loading...');
// }
// $scope.completed = function(content) {
// console.log(content);
// };
// });
//
angular.module('ngUpload', [])
.directive('uploadSubmit', ["$parse", function($parse) {
// Utility function to get the closest parent element with a given tag
function getParentNodeByTagName(element, tagName) {
element = angular.element(element);
var parent = element.parent();
tagName = tagName.toLowerCase();
if ( parent && parent[0].tagName.toLowerCase() === tagName ) {
return parent;
} else {
return !parent ? null : getParentNodeByTagName(parent, tagName);
}
}
return {
restrict: 'AC',
link: function(scope, element, attrs) {
element.bind('click', function($event) {
// prevent default behavior of click
if ($event) {
$event.preventDefault();
$event.stopPropagation();
}
if (element.attr('disabled')) { return; }
var form = getParentNodeByTagName(element, 'form');
form.triggerHandler('submit');
form[0].submit();
});
}
};
}])
.directive('ngUpload', ["$log", "$parse", "$document",
function ($log, $parse, $document) {
var iframeID = 1;
// Utility function to get meta tag with a given name attribute
function getMetaTagWithName(name) {
var head = $document.find('head');
var match;
angular.forEach(head.find('meta'), function(element) {
if ( element.getAttribute('name') === name ) {
match = element;
}
});
return angular.element(match);
}
return {
restrict: 'AC',
link: function (scope, element, attrs) {
// Give each directive instance a new id
iframeID++;
function setLoadingState(state) {
scope.$isUploading = state;
}
var options = {};
// Options (just 1 for now)
// Each option should be prefixed with 'upload-options-' or 'uploadOptions'
// {
// // add the Rails CSRF hidden input to form
// enableRailsCsrf: bool
// }
var fn = attrs.ngUpload ? $parse(attrs.ngUpload) : null;
var errorCatcher = attrs.errorCatcher ? $parse(attrs.errorCatcher) : null;
var loading = attrs.ngUploadLoading ? $parse(attrs.ngUploadLoading) : null;
if ( attrs.hasOwnProperty( "uploadOptionsConvertHidden" ) ) {
// Allow blank or true
options.convertHidden = attrs.uploadOptionsConvertHidden != "false";
}
if ( attrs.hasOwnProperty( "uploadOptionsEnableRailsCsrf" ) ) {
// allow for blank or true
options.enableRailsCsrf = attrs.uploadOptionsEnableRailsCsrf != "false";
}
if ( attrs.hasOwnProperty( "uploadOptionsBeforeSubmit" ) ) {
options.beforeSubmit = $parse(attrs.uploadOptionsBeforeSubmit);
}
element.attr({
'target': 'upload-iframe-' + iframeID,
'method': 'post',
'enctype': 'multipart/form-data',
'encoding': 'multipart/form-data'
});
var iframe = angular.element(
'<iframe name="upload-iframe-' + iframeID + '" ' +
'border="0" width="0" height="0" ' +
'style="width:0px;height:0px;border:none;display:none">'
);
// If enabled, add csrf hidden input to form
if ( options.enableRailsCsrf ) {
var input = angular.element("<input />");
input.attr("class", "upload-csrf-token");
input.attr("type", "hidden");
input.attr("name", getMetaTagWithName('csrf-param').attr('content'));
input.val(getMetaTagWithName('csrf-token').attr('content'));
element.append(input);
}
element.after(iframe);
setLoadingState(false);
// Start upload
element.bind('submit', function uploadStart() {
var formController = scope[attrs.name];
// if form is invalid don't submit (e.g. keypress 13)
if(formController && formController.$invalid) return false;
// perform check before submit file
if (options.beforeSubmit) { return options.beforeSubmit(); }
// bind load after submit to prevent initial load triggering uploadEnd
iframe.bind('load', uploadEnd);
// If convertHidden option is enabled, set the value of hidden fields to the eval of the ng-model
if (options.convertHidden) {
angular.forEach(element.find('input'), function(el) {
var _el = angular.element(el);
if (_el.attr('ng-model') &&
_el.attr('type') &&
_el.attr('type') == 'hidden') {
_el.attr('value', scope.$eval(_el.attr('ng-model')));
}
});
}
if (!scope.$$phase) {
scope.$apply(function() {
if (loading) loading(scope);
setLoadingState(true);
});
} else {
if (loading) loading(scope);
setLoadingState(true);
}
});
// Finish upload
function uploadEnd() {
// unbind load after uploadEnd to prevent another load triggering uploadEnd
iframe.unbind('load');
if (!scope.$$phase) {
scope.$apply(function() {
setLoadingState(false);
});
} else {
setLoadingState(false);
}
// Get iframe body contents
try {
var bodyContent = (iframe[0].contentDocument ||
iframe[0].contentWindow.document).body;
var content;
try {
content = angular.fromJson(bodyContent.innerText || bodyContent.textContent);
} catch (e) {
// Fall back to html if json parse failed
content = bodyContent.innerHTML;
$log.warn('Response is not valid JSON');
}
// if outside a digest cycle, execute the upload response function in the active scope
// else execute the upload response function in the current digest
if (!scope.$$phase) {
scope.$apply(function () {
fn(scope, { content: content});
});
} else {
fn(scope, { content: content});
}
} catch (error) {
if ( errorCatcher ){
if (!scope.$$phase) {
scope.$apply(function () {
errorCatcher(scope, { error: error});
});
} else {
errorCatcher(scope, { error: error});
}
}
}
}
}
};
}]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment