Last active
July 22, 2018 22:55
-
-
Save lkatney/e9a19d066a446138bbc914a66d182213 to your computer and use it in GitHub Desktop.
Angular1 directive/component 'FilePicker' to handle file selection(taking pictures, choosing photos and files) from device
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//file.directive.js | |
(function() { | |
'use strict'; | |
angular | |
.module('starter.services') | |
.directive("file",function(){ | |
return { | |
scope: { | |
onFilesSelection: '&', | |
}, | |
link: function(scope,el){ | |
el.bind("change", function(e){ | |
var files = (e.srcElement || e.target).files; | |
scope.onFilesSelection({files : files}); | |
}) | |
} | |
} | |
}) | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//filepicker.controller.js | |
(function() { | |
'use strict'; | |
angular | |
.module('starter.controllers') | |
.controller('FilePickerCtrl', FilePickerCtrl); | |
FilePickerCtrl.$inject = ['$rootScope', '$scope', '$timeout', '$ionicModal', '$window']; | |
function FilePickerCtrl($rootScope, $scope, $timeout, $ionicModal, $window) { | |
var filePicker = this; | |
var clickTimeout; | |
filePicker.preview = []; | |
var counter = 1; | |
$rootScope['unsavedImages'] = {}; | |
//number of images to show | |
if($scope.numimages){ | |
$scope.numimages = parseInt($scope.numimages); | |
} | |
//unique key to represent each and every section and avoid override images | |
filePicker.key = $scope.belongsto + '_' + $scope.imagenames; | |
function loadPreviewImages(){ | |
filePicker.preview = []; | |
if($scope.previewfiles){ | |
_.each($scope.previewfiles, function(file){ | |
if(file.fileName.toLowerCase().includes($scope.imagenames.toLowerCase()) || $scope.imagenames.toLowerCase().includes('imagesgallery')){ | |
filePicker.preview.push({base64 : file.result}); | |
} | |
}); | |
}else{ | |
$scope.previewfiles = []; | |
} | |
//check for any local images | |
//needed if you moved away from directive, may be different tab on same page | |
//values will not be retained. To retain them , store them in $rootScope | |
if(!$rootScope['unsavedImages']){ | |
$rootScope['unsavedImages'] = {}; | |
} | |
if($rootScope['unsavedImages'] && $rootScope['unsavedImages'][filePicker.key]){ | |
if(!angular.isArray($scope.files)){ | |
$scope.files = []; | |
} | |
_.each($rootScope['unsavedImages'][filePicker.key], function(fileInfo){ | |
$scope.files.push(fileInfo); | |
filePicker.preview.push({base64 : fileInfo.base64, isDelete : true}); | |
}); | |
} | |
} | |
$scope.$watch('previewfiles', function(newValue, oldValue) { | |
if (newValue !== oldValue) { | |
loadPreviewImages(); | |
} | |
}, true); | |
//full screen modal to show images from thumbnails | |
$ionicModal.fromTemplateUrl('shared/filePicker/fullscreen.html', { | |
scope: $scope, | |
animation: 'slide-in-up' | |
}).then(function(modal) { | |
filePicker.modal = modal; | |
}); | |
filePicker.openModal = function() { | |
filePicker.modal.show(); | |
}; | |
filePicker.closeModal = function() { | |
filePicker.modal.hide(); | |
}; | |
filePicker.openSelection = function(){ | |
clickTimeout = $timeout(function() { | |
document.querySelectorAll('[key="'+filePicker.key+'"]')[0].click(); | |
}, 100); | |
} | |
filePicker.removeImage = function(index){ | |
filePicker.preview.splice(index, 1); | |
$scope.files.splice(index - $scope.previewfiles.length, 1); | |
filePicker.closeModal(); | |
$scope.onFileChange({ deleted : true}); | |
} | |
filePicker.previewImage = function(imageData, imageSelectedIndex){ | |
filePicker.imageData = imageData; | |
filePicker.imageSelectedIndex = imageSelectedIndex; | |
filePicker.openModal(); | |
} | |
function b64toBlob(b64Data, contentType, sliceSize) { | |
contentType = contentType || ''; | |
sliceSize = sliceSize || 512; | |
var byteCharacters = atob(b64Data); | |
var byteArrays = []; | |
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) { | |
var slice = byteCharacters.slice(offset, offset + sliceSize); | |
var byteNumbers = new Array(slice.length); | |
for (var i = 0; i < slice.length; i++) { | |
byteNumbers[i] = slice.charCodeAt(i); | |
} | |
var byteArray = new Uint8Array(byteNumbers); | |
byteArrays.push(byteArray); | |
} | |
var blob = new Blob(byteArrays, {type: contentType}); | |
return blob; | |
} | |
function getBlob(base64){ | |
/** Process the type1 base64 string **/ | |
var myBaseString = base64; | |
// Split the base64 string in data and contentType | |
var block = myBaseString.split(";"); | |
// Get the content type | |
var contentType = block[0].split(":")[1];// In this case "image/png" | |
// get the real base64 content of the file | |
var content = block[1].split(",")[1];// In this case "iVBORw0KGg...." | |
return b64toBlob(content,contentType); | |
} | |
function saveImageBeforeWrite(fileInfo){ | |
if(!$rootScope['unsavedImages'][filePicker.key]){ | |
$rootScope['unsavedImages'][filePicker.key] = []; | |
} | |
$rootScope['unsavedImages'][filePicker.key].push(fileInfo); | |
} | |
function getExtension(filename){ | |
return filename.substring(filename.lastIndexOf('.')+1, filename.length) || filename; | |
} | |
function setFileForProcessing(file, base64){ | |
var fileInfo = { | |
blob : getBlob(base64), | |
base64 : base64, | |
name : filePicker.key + '_' + counter++ + '.'+ getExtension(file.name), | |
size : file.size, | |
type : file.type | |
}; | |
if($scope.numimages == 1){ | |
$scope.files[0] = fileInfo; | |
filePicker.preview[0] = {base64 : base64, isDelete : true}; | |
}else{ | |
$scope.files.push(fileInfo); | |
filePicker.preview.push({base64 : base64, isDelete : true}); | |
} | |
saveImageBeforeWrite(fileInfo); | |
$scope.onFileChange({ added : true}); | |
} | |
function previewFile(file){ | |
var fileReader = new FileReader(); | |
fileReader.onload = function(e){ | |
onResult(file, fileReader.result); | |
} | |
fileReader.readAsDataURL(file); | |
} | |
//workaround to bind the scope properly on file select in case | |
//number of this FilePicker directives are there on same page | |
var deregisterPreviewFileEvent = $scope.$on('preview-file-added', function(event, args){ | |
if(args.belongsto === filePicker.key){ | |
var base64 = args.base64; | |
var file = args.file; | |
setFileForProcessing(file, base64); | |
$scope.$apply(); | |
} | |
}); | |
function onResult(file, base64) { | |
$rootScope.$broadcast('preview-file-added', {file : file, base64 : base64, belongsto : filePicker.key}); | |
} | |
filePicker.onFilesSelection = function(files){ | |
_.each(files, function(file){ | |
previewFile(file); | |
}); | |
} | |
$scope.$on('$destroy', function() { | |
deregisterPreviewFileEvent(); | |
$timeout.cancel(clickTimeout); | |
filePicker.modal.remove(); | |
}); | |
} | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*filepicker.css*/ | |
.file-picker, .file-picker div{ | |
display: inline; | |
} | |
.file-picker img{ | |
border: 1px solid #ddd; | |
border-radius: 4px; | |
padding: 5px; | |
margin: 5px; | |
} | |
.file-picker .images img{ | |
height: 100px; | |
} | |
.file-picker .single-image img{ | |
max-width: 87%; | |
} | |
.file-picker img:hover, .file-picker img:active{ | |
box-shadow: 0 0 2px 1px rgba(0, 140, 186, 0.5); | |
} | |
.file-picker input[type=file]{ | |
font-size: 0; | |
display: none; | |
} | |
.file-picker .button.ion-camera{ | |
color: grey !important; | |
padding: 0 !important; | |
margin: 15px 3px; | |
} | |
.file-picker .ion-camera::before{ | |
font-size: 50px !important; | |
} | |
/********* FULL SCREEN ************ | |
**********************************/ | |
.transparent { | |
background: #00000042 !important; | |
} | |
.image-modal { | |
width: 100% !important; | |
height: 100%; | |
top: 0 !important; | |
left: 0 !important; | |
} | |
.fullscreen-image { | |
max-width: 90%; | |
max-height: 100%; | |
bottom: 0; | |
left: 0; | |
margin: auto; | |
overflow: auto; | |
position: fixed; | |
right: 0; | |
top: 0; | |
} | |
.fullscreen-buttons{ | |
margin: 30px; | |
z-index: 25; | |
cursor: pointer; | |
position: absolute; | |
right: 1px | |
} | |
.button.fullscreen-button{ | |
color: #FFF; | |
padding: 0 15px !important; | |
} | |
.button.fullscreen-button::before{ | |
font-size: 40px; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//filepicker.directive.js | |
(function() { | |
'use strict'; | |
angular | |
.module('starter.services') | |
.directive('filePicker', function() { | |
return { | |
restrict: 'E', | |
scope : { | |
files : '=', // files attribute that needs to be filled by directive with blob | |
previewfiles : '=', //already existing images/files to preview | |
belongsto : '=', // name of record, specifically for SALESFORCE if this images need to be linked with records | |
imagenames : '@', //Naming convention of images | |
numimages : '@', // number of images component needs to handle | |
onFileChange : '&', //fire an event once files get read properly and added to $scope.files parameter | |
}, | |
controller : 'FilePickerCtrl', // name of controller | |
controllerAs : 'filePicker', // vm for this controller | |
templateUrl : 'shared/filePicker/filepicker.html' // path to html | |
}; | |
}); | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- filepicker.html --> | |
<div class="file-picker"> | |
<!-- filePicker.preview shows all images selected --> | |
<div ng-repeat="imageData in filePicker.preview track by $index" ng-class="{'images' : !numimages || numimages > 1, 'single-image' : numimages == 1 }"> | |
<a ng-click="filePicker.previewImage(imageData, $index)"> | |
<img ng-src="{{imageData.base64}}"/> | |
</a> | |
</div> | |
<div class="file-button"> | |
<!-- stylish button to cover old fashion input type file --> | |
<a class="button button-icon icon ion-camera" ng-click="filePicker.openSelection()"></a> | |
<-- seperate buttons to handle single image or num of images --> | |
<input type="file" name="file" file on-files-selection="filePicker.onFilesSelection(files)" id="file-input" key="{{filePicker.key}}" multiple="true" ng-if="!numimages || numimages > 1"></input> | |
<input type="file" name="file" file on-files-selection="filePicker.onFilesSelection(files)" id="file-input" key="{{filePicker.key}}" ng-if="numimages == 1"></input> | |
</div> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- filepicker.html --> | |
<div class="modal image-modal transparent"> | |
<div class="fullscreen-buttons"> | |
<a class="button button-icon icon ion-ios-trash fullscreen-button" ng-click="filePicker.removeImage(filePicker.imageSelectedIndex)" ng-show="filePicker.imageData.isDelete"></a> | |
<a class="button button-icon icon ion-close fullscreen-button" ng-click="filePicker.closeModal()"></a> | |
</div> | |
<ion-pane class="transparent"> | |
<img ng-src="{{filePicker.imageData.base64}}" class="fullscreen-image"/> | |
</ion-pane> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//usage.controller.js | |
(function () { | |
'use strict'; | |
angular.module('starter.controllers').controller('FilesCtrl', FilesCtrl); | |
FilesCtrl.$inject = ['$scope']; | |
function FilesCtrl($scope) { | |
$scope.files = []; | |
$scope.preview = []; | |
$scope.belongs = 'myFiles' | |
$scope.onFileChange = function(added, deleted){ | |
console.log('-->added', added); | |
} | |
} | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- usage.html --> | |
<ion-content class="padding"> | |
<file-picker | |
files="files" | |
previewfiles="preview" | |
belongsto="belongs" | |
imagenames="ImageGallery" | |
on-file-change="onFileChange(added, deleted)" > | |
</file-picker> | |
</ion-content> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment