Skip to content

Instantly share code, notes, and snippets.

@lkatney
Last active July 22, 2018 22:55
Show Gist options
  • Save lkatney/e9a19d066a446138bbc914a66d182213 to your computer and use it in GitHub Desktop.
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
//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});
})
}
}
})
})();
//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();
});
}
})();
/*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;
}
//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
};
});
})();
<!-- 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>
<!-- 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>
//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);
}
}
})();
<!-- 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