(function () {
//Angular module that has some generic, useful functionality, mainly directives
var mod = angular.module('usefulstuff', []);
mod.directive('checkList', function () {
return {
scope: {
list: '=checkList',
value: '@'
},
link: function (scope, elem, attrs) {
var handler = function (setup) {
var checked = elem.prop('checked');
var index = 0;
if (scope.list)
index = scope.list.indexOf(scope.value);
if (checked && index === -1) {
if (setup)
elem.prop('checked', false);
else
scope.list.push(scope.value);
} else if (!checked && index !== -1) {
if (setup)
elem.prop('checked', true);
else
scope.list.splice(index, 1);
}
};
var setupHandler = handler.bind(null, true);
var changeHandler = handler.bind(null, false);
elem.on('change', function () {
scope.$apply(changeHandler);
});
scope.$watch('list', setupHandler, true);
}
};
});
mod.directive('shouldMatch', function () {
return {
require: 'ngModel',
restrict: 'A',
scope: {
shouldMatch: '='
},
link: function (scope, elem, attrs, ctrl) {
scope.$watch(function () {
var modelValue = ctrl.$modelValue || ctrl.$$invalidModelValue;
return (ctrl.$pristine && angular.isUndefined(modelValue)) || scope.shouldMatch === modelValue;
}, function (currentValue) {
ctrl.$setValidity('shouldMatch', currentValue);
});
}
};
});
//directive to validate a password
mod.directive('valPassword', function () {
return {
require: 'ngModel',
restrict: 'A',
link: function (scope, el, attrs, ctrl) {
var uppercaseRegex = /[A-Z]+/;
var lowercaseRegex = /[a-z]+/;
var integerRegex = /[0-9]+/;
//when the model changes
scope.$watch(function () {
return ctrl.$viewValue;
// add errors contextually
}, function (currentValue) {
if (integerRegex.test(currentValue))
ctrl.$setValidity('integer', true);
else
ctrl.$setValidity('integer', false);
if (lowercaseRegex.test(currentValue))
ctrl.$setValidity('lowercase', true);
else
ctrl.$setValidity('lowercase', false);
if (uppercaseRegex.test(currentValue))
ctrl.$setValidity('uppercase', true);
else
ctrl.$setValidity('uppercase', false);
});
}
}
});
// directive that adds a confirm modal when clicked
mod.directive('ngReallyClick', ['$modal',
function($modal) {
var modalInstanceCtrl = function ($scope, $modalInstance) {
$scope.hasBeenClicked = false;
$scope.ok = function () {
if ($modalInstance) {
$scope.hasBeenClicked = true;
$modalInstance.close();
}
};
$scope.cancel = function () {
if ($modalInstance) {
$scope.hasBeenClicked = true;
$modalInstance.dismiss('cancel');
}
};
};
return {
restrict: 'A',
scope: {
ngReallyClick: "&", //declare a function binding for directive
item: "=" //the current item for the directive
},
link: function(scope, element, attrs) {
element.bind('click', function() {
var message = attrs.ngReallyMessage;
var title = attrs.ngReallyTitle;
var modalHtml = '
' + message + '
';
if (title) {
var modalHtml = '' + message + '
';
}
modalHtml += '';
var modalInstance = $modal.open({
template: modalHtml,
controller: modalInstanceCtrl,
size: 'sm',
windowClass: 'center-modal'
});
modalInstance.result.then(function () {
scope.ngReallyClick({ item: scope.item }); //call the function though function binding
}, function () {
//Modal dismissed
});
});
}
}
}
]);
//directive to make the element draggable
mod.directive('draggable', function () {
return function (scope, element) {
// this gives us the native JS object
var el = element[0];
el.draggable = true;
el.addEventListener(
'dragstart',
function (e) {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('Text', this.id);
this.classList.add('drag');
return false;
},
false
);
el.addEventListener(
'dragend',
function (e) {
this.classList.remove('drag');
return false;
},
false
);
}
});
//directive to make the element droppable
mod.directive('droppable', function () {
return {
scope: {
drop: '&' // parent
},
link: function (scope, element) {
// again we need the native object
var el = element[0];
el.addEventListener(
'dragover',
function (e) {
e.dataTransfer.dropEffect = 'move';
// allows us to drop
if (e.preventDefault) e.preventDefault();
this.classList.add('over');
return false;
},
false
);
el.addEventListener(
'dragenter',
function (e) {
this.classList.add('over');
return false;
},
false
);
el.addEventListener(
'dragleave',
function (e) {
this.classList.remove('over');
return false;
},
false
);
el.addEventListener(
'drop',
function (e) {
// Stops some browsers from redirecting.
if (e.stopPropagation) e.stopPropagation();
this.classList.remove('over');
var item = document.getElementById(e.dataTransfer.getData('Text'));
this.appendChild(item);
// call the drop passed drop function
scope.$apply('drop()');
return false;
},
false
);
}
}
});
mod.directive('autoHeight', function () {
return {
restrict: 'A',
link: function (scope, element, $attrs) {
$(window).on('scroll', function () {
if (element[0].contentWindow) {
var iFrameHeight = element[0].contentWindow.document.getElementById('showcase-view').offsetHeight + 'px';
element.css('height', iFrameHeight);
}
});
}
}
});
mod.directive("whenScrolled", function ($window) {
return {
restrict: 'A',
link: function (scope, elem, attrs) {
raw = elem[0];
var checkBounds = function (evt) {
var rectObject = raw.getBoundingClientRect();
if ($window.innerHeight > rectObject.bottom) {
scope.loading = true;
scope.$apply(attrs.whenScrolled);
}
};
angular.element($window).bind('scroll load', checkBounds);
}
};
});
mod.directive("whenScrolledMobile", function ($window) {
return {
restrict: 'A',
link: function (scope, elem, attrs) {
raw = elem[0];
var checkBounds = function (evt) {
var rectObject = raw.getBoundingClientRect();
if ($window.innerHeight > rectObject.bottom) {
scope.loading = true;
scope.$apply(attrs.whenScrolledMobile);
}
};
angular.element($window).bind('scroll load', checkBounds);
}
};
});
//mod.directive('whenScrolled', function () {
// return {
// restrict: 'A',
// link: function (scope, elm, attr) {
// var itemContainer = $('#items');
// $(window).scroll(function () {
// if ($(window).scrollTop() >= $(document).height() - $(window).height() * 1.5) {
// scope.$apply(attr.whenScrolled);
// }
// });
// itemContainer.bind('scroll', function (e) {
// if (itemContainer.offset().top > 100) {
// scope.$apply(attr.whenScrolled);
// }
// });
// }
// }
//});
mod.directive('whenScrolledMobile', function () {
return {
restrict: 'A',
link: function (scope, elm, attr) {
$(window).scroll(function () {
if ($(window).scrollTop() >= $(document).height() - $(window).height() * 2)
scope.$apply(attr.whenScrolledMobile);
});
}
}
});
//only update the model when the element has blurred - e.g. don't update the model linked to a text box until after the user has moused away from it
mod.directive('ngChangeOnBlur', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, elm, attrs, ngModelCtrl) {
if (attrs.type === 'radio' || attrs.type === 'checkbox')
return;
var expressionToCall = attrs.ngChangeOnBlur;
var oldValue = null;
elm.bind('focus', function() {
scope.$apply(function() {
oldValue = elm.val();
});
});
elm.bind('blur', function () {
scope.$apply(function () {
var newValue = elm.val();
if (newValue !== oldValue) {
scope.$eval(expressionToCall);
}
//alert('changed ' + oldValue);
});
});
}
};
});
mod.directive('cloudinaryVideoPreview', ['$sce', function ($sce) {
var directive = {
restrict: 'E',
scope: {
publicId: '=',
w: '@',
h: '@',
cloudinaryVideoBaseUrl:'='
},
template: '',
link: link
};
return directive;
function link(scope, element, attrs) {
scope.$watch(attrs.cloudinaryVideoBaseUrl, function (newValue, oldValue) {
if (newValue) {
var baseVideoUrl = scope.cloudinaryVideoBaseUrl + 'w_' + scope.w + ',' + 'c_fill' + '/' + scope.publicId;
scope.sources = [
{ src: $sce.trustAsResourceUrl(baseVideoUrl + ".webm"), type: "video/webm" },
{ src: $sce.trustAsResourceUrl(baseVideoUrl + ".mp4"), type: "video/mp4" },
{ src: $sce.trustAsResourceUrl(baseVideoUrl + ".ogv"), type: "video/ogg" }
];
scope.posterUrl = baseVideoUrl + '.jpg';
}
});
}
}]);
mod.directive("vgSrc", function () {
return {
restrict: "A",
link: {
pre: function (scope, elem, attr) {
var element = elem;
var sources;
var canPlay;
function changeSource() {
if (sources) {
for (var i = 0, l = sources.length; i < l; i++) {
canPlay = element[0].canPlayType(sources[i].type);
if (canPlay === "maybe" || canPlay === "probably") {
element.attr("src", sources[i].src);
element.attr("type", sources[i].type);
break;
}
}
}
if (canPlay === "")
scope.$emit("onVideoError", { type: "Can't play file" });
}
scope.$watch(attr.vgSrc, function (newValue, oldValue) {
if (!sources || newValue !== oldValue) {
sources = newValue;
changeSource();
}
});
}
}
}
});
//Scroll to href attribute
mod.directive('scrollOnClick', function () {
return {
restrict: 'A',
link: function (scope, $elm, attrs) {
var idToScroll = attrs.href;
$elm.on('click', function () {
var $target;
if (idToScroll) {
$target = $(idToScroll);
} else {
$target = $elm;
}
$("body").animate({ scrollTop: $target.offset().top }, "slow");
});
}
}
});
mod.directive('resize', function ($window) {
return {
restrict: 'EA',
scope: {
numberOfItems: '='
},
link: function (scope, element, attrs) {
var w = angular.element($window);
scope.getWindowDimensions = function () {
return {
'w': w.width()
};
};
scope.$watch(scope.getWindowDimensions, function (dragContainer, oldValue, lastCss) {
// Deduct container padding
scope.windowWidth = dragContainer.w - 30;
scope.itemWidth = 150;
scope.lastWidth = scope.windowWidth - (scope.numberOfItems * scope.itemWidth);
scope.$parent.lastCss = {
"border": 'none',
"border-right": scope.lastWidth + 'px solid transparent',
"width": 'auto',
"background-size": '150px'
};
}, true);
w.bind('resize', function () {
//scope.$apply();
});
}
};
});
//Auto play video onclick (video ifrmae id needs to be in href attribute can be used in conjunction with scrollOnClick
mod.directive('playOnClick', function () {
return {
restrict: 'A',
link: function (scope, $elm, attrs) {
var video = attrs.href;
$elm.on('click', function (e) {
var evt = e || window.event;
var $target;
if (video) {
$target = $(video);
} else {
$target = $elm;
}
$(video)[0].src += "&autoplay=1";
evt.preventDefault();
});
}
}
});
// Toggle tag and framework class
mod.directive('toggleClass', function () {
return {
restrict: 'A',
link: function (scope, element, attr) {
element.bind('click', function () {
$("ul li").removeClass(attr.toggleClass);
$("a").removeClass(attr.toggleClass);
$(".timeline-header-title").removeClass(attr.toggleClass);
$(this).addClass(attr.toggleClass);
});
}
};
});
// Remove tag and framework class
mod.directive('removeClass', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.bind('click', function () {
$(".tagged").removeClass(attrs.removeClass);
});
}
};
});
//directive to determine if the selected file is a video
mod.directive('fileIsVideo', function () {
return {
require: 'ngModel',
restrict: 'A',
scope: {
fileIsVideo: "="
},
link: function (scope, elem, attrs, ctrl) {
//use regex to test if the file is the correct type
var imageRegex = /(mp4|webm|ogv|3gp|flv|quicktime|mov)/i;
scope.$watch(function () {
var modelValue = ctrl.$viewValue;
if (!modelValue)
return true;
return imageRegex.test(modelValue);
}, function (currentValue) {
scope.fileIsVideo = currentValue;
});
}
};
});
//directive to determine if the selected file is a badge
mod.directive('fileIsBadge', function () {
return {
require: 'ngModel',
restrict: 'A',
scope: {
fileIsBadge: "="
},
link: function (scope, elem, attrs, ctrl) {
//use regex to test if the file is the correct type
var imageRegex = /png/i;
scope.$watch(function () {
var modelValue = ctrl.$viewValue;
if (!modelValue)
return true;
var result = imageRegex.test(modelValue);
return result;
}, function (currentValue) {
scope.fileIsBadge = currentValue;
});
}
};
});
//directive to determine if the selected file is an image
mod.directive('fileIsImage', function () {
return {
require: 'ngModel',
restrict: 'A',
scope: {
fileIsImage: "="
},
link: function (scope, elem, attrs, ctrl) {
//use regex to test if the file is the correct type
var imageRegex = /(jpg|png|gif|bmp|jpeg)/i;
scope.$watch(function () {
var modelValue = ctrl.$viewValue;
if (!modelValue)
return true;
return imageRegex.test(modelValue);
}, function (currentValue) {
scope.fileIsImage = currentValue;
});
}
};
});
mod.directive("starRating", function () {
return {
restrict: "EA",
templateUrl: modulesSharedResourcesUrl + "Modules/UsefulStuff/Templates/starrating.html?version=270122",
scope: {
max: "=?", //optional: default is 5
onRatingSelected: "&?",
readonly: "=?"
},
require: 'ngModel',
link: function (scope, elem, attrs, ngModelController) {
if (scope.max == undefined)
scope.max = 5;
function updateStars() {
scope.stars = [];
for (var i = 0; i < scope.max; i++) {
scope.stars.push({
filled: i < ngModelController.$viewValue
});
}
};
scope.toggle = function (index) {
if (scope.readonly == undefined || scope.readonly == false) {
updateModel(index + 1);
if (scope.onRatingSelected) {
scope.onRatingSelected({
rating: index + 1
});
}
}
};
scope.$watch("max", function (oldVal, newVal) {
if (newVal !== undefined)
updateStars();
});
// update the model then the view
function updateModel(value) {
// call $parsers pipeline then update $modelValue
ngModelController.$setViewValue(value);
// update the local view
ngModelController.$render();
}
// when model change, update our view (just update the div content)
ngModelController.$render = function () {
updateStars();
};
}
};
});
//a directive that adds max word count validation to a form element
mod.directive('maxWordCount', function () {
return {
// limit usage to argument only
restrict: 'A',
// require NgModelController, i.e. require a controller of ngModel directive
require: 'ngModel',
// create linking function and pass in our NgModelController as a 4th argument
link: function (scope, element, attr, ctrl) {
var initialValidity = attr.minWordCount === '0' || attr.wordValue === 'true';
ctrl.$setValidity('maxWordCount', initialValidity);
ctrl.$parsers.unshift(function (viewValue) {
if (!viewValue) {
ctrl.$setValidity('maxWordCount', true);
return '';
}
var maxWordCount = attr.maxWordCount;
if (maxWordCount == 0) {
ctrl.$setValidity('maxWordCount', true);
return viewValue;
}
var text = viewValue;
if (attr.stripHtml === 'true') {
var htmlRegex = /(<([^>]+)>)/ig;
text = viewValue.replace(htmlRegex, "");
}
var regex = /\s+/gi;
var wordCount = text.trim().replace(regex, ' ').split(' ').length;
if (maxWordCount >= wordCount) {
ctrl.$setValidity('maxWordCount', true);
return viewValue;
} else {
ctrl.$setValidity('maxWordCount', false);
return undefined;
}
});
}
};
});
//a directive to give a dynamic name to a form element - normally form element names are static which don't work in all cases
mod.directive('dynamicName', function ($compile, $parse) {
return {
restrict: 'A',
terminal: true,
priority: 100000,
link: function (scope, elem) {
//get the dynamic name
var name = $parse(elem.attr('dynamic-name'))(scope);
// $interpolate() will support things like 'skill'+skill.id where parse will not
//remove the dynamic-name attribute - we don't need it now
elem.removeAttr('dynamic-name');
//add a normal name attribute
elem.attr('name', name);
$compile(elem)(scope);
}
};
});
mod.directive('minWordCount', function () {
return {
// limit usage to argument only
restrict: 'A',
// require NgModelController, i.e. require a controller of ngModel directive
require: 'ngModel',
// create linking function and pass in our NgModelController as a 4th argument
link: function (scope, element, attr, ctrl) {
var initialValidity = attr.minWordCount === '0' || attr.wordValue==='true';
ctrl.$setValidity('minWordCount', initialValidity);
ctrl.$parsers.unshift(function (viewValue) {
if (viewValue === null || viewValue===undefined) {
ctrl.$setValidity('minWordCount', true);
return '';
}
var minWordCount = attr.minWordCount;
if (minWordCount == 0) {
ctrl.$setValidity('minWordCount', true);
return viewValue;
}
var regex = /\s+/gi;
var text = viewValue;
if (attr.stripHtml==='true') {
var htmlRegex = /(<([^>]+)>)/ig;
text = viewValue.replace(htmlRegex, "");
}
var wordCount = text.trim().replace(regex, ' ').split(' ').length;
if (minWordCount <= wordCount) {
ctrl.$setValidity('minWordCount', true);
return viewValue;
} else {
ctrl.$setValidity('minWordCount', false);
return undefined;
}
});
}
};
});
mod.directive("dateToIso", function () {
var linkFunction = function (scope, element, attrs, ngModelCtrl) {
ngModelCtrl.$parsers.push(function (datepickerValue) {
return moment(datepickerValue).format("YYYY-MM-DD");
});
};
return {
restrict: "A",
require: "ngModel",
link: linkFunction
};
});
// Get Window Width
//mod.directive('deviceWidth', function ($window, $rootScope) {
// return {
// restrict: 'EA',
// link: function (scope, element, attrs) {
// var deviceWidth = angular.element($window);
// // We will also get the height of the creen to enable draggable scrolling on the film strip
// scope.deviceHeight = deviceWidth.height();
// scope.deviceWidth = deviceWidth.width();
// scope.desktopWidth = window.screen.width;
// if (scope.deviceWidth < 460) {
// scope.isMobile = true;
// } else if (scope.deviceWidth < 768) {
// scope.isTablet = true;
// } else if (scope.desktopWidth < 1440 || scope.deviceWidth < 1440) {
// scope.isSmallDesktop = true;
// scope.isDesktop = true;
// } else {
// scope.isDesktop = true;
// }
// }
// };
//});
mod.directive('listScroll', function ($window, $rootScope) {
return {
restrict: 'EA',
link: function (scope, element, attrs) {
$('#scroll').click(function () {
$('#myshowcases-list').animate({
scrollTop: '+=100'
}, 100);
});
}
};
});
//directive to determine if the file is valid (file field input)
mod.directive('validFile', ['$timeout', function ($timeout) {
return {
require: 'ngModel',
link: function (scope, el, attrs, ngModel) {
el.bind('change', function () {
$timeout(function () {
scope.$apply(function () {
ngModel.$setViewValue(el.val());
ngModel.$render();
});
});
});
}
}
}]);
//a common date formatting filter
mod.filter('dateFormat', function ($filter) {
return function (input) {
if (input == null)
return "";
var date = $filter('date')(new Date(input), 'MMM dd yyyy');
return date.toUpperCase();
};
});
//a common time formatting filter
mod.filter('time', function ($filter) {
return function (input) {
if (input == null)
return "";
var date = $filter('date')(new Date(input), 'HH:mm:ss');
return date.toUpperCase();
};
});
mod.filter('truncate', function () {
return function (text, length, end) {
if (!text)
return '';
if (isNaN(length))
length = 10;
if (end === undefined || end === null)
end = "..."; //add ellipsis
if (text.length <= length || text.length - end.length <= length)
return text;
else
return String(text).substring(0, length - end.length) + end;
};
});
//format datetime
mod.filter('datetime', function ($filter) {
return function (input) {
if (input == null)
return "";
var date = $filter('date')(new Date(input),
'MMM dd yyyy - HH:mm:ss');
return date.toUpperCase();
};
});
//capitolise a string value
mod.filter('capitalise', function () {
return function (input) {
return (!!input) ? input.replace(/([^\W_]+[^\s]*) */g, function (txt) {
var split = txt.split('-');
var result = '';
for (var i = 0; i < split.length; i++) {
if (split[i])
result = result + split[i].charAt(0).toUpperCase() + split[i].substr(1).toLowerCase();
if (split.length !== i)
result = result + ' ';
}
return result;
}) : '';
};
});
mod.filter('startFrom', function () {
return function (input, start) {
start = +start; //parse to int
if (input) {
return input.slice(start);
} else {
return;
}
}
});
//filter items by associated tags
mod.filter('tagfilter', function () {
return function (templates, tagFilter) {
if (!templates || templates === undefined)
return null;
if (!tagFilter)
return templates;
var filtered = [];
if (tagFilter === 'absolutely no tags whatsoever' && templates !== undefined) {
for (var i = 0; i < items.length; i++) {
var item = templates[i];
if (item.tags) {
if (item.tags.length === 0)
filtered.push(item);
}
}
} else {
for (var i = 0; i < templates.length; i++) {
var item = templates[i];
if (item.tags) {
for (var j = 0; j < item.tags.length; j++) {
var text = item.tags[j];
if (text === tagFilter)
filtered.push(item);
}
}
}
}
return filtered;
};
});
//filter items by associated tags
mod.filter('badgeTagFilter', function () {
return function (templates, tagFilter) {
if (!templates || templates === undefined)
return null;
if (!tagFilter)
return templates;
var filtered = [];
if (tagFilter === 'absolutely no tags whatsoever' && templates !== undefined) {
for (var i = 0; i < items.length; i++) {
var item = templates[i];
if (item.tags) {
if (item.tags.length === 0)
filtered.push(item);
}
}
} else {
for (var i = 0; i < templates.length; i++) {
var item = templates[i];
if (item.tags) {
for (var j = 0; j < item.tags.length; j++) {
var text = item.tags[j];
if (text === tagFilter)
filtered.push(item);
}
}
}
}
return filtered;
};
});
// Template style renderer
mod.directive('styler', ['$compile', function ($compile) {
return {
restrict: 'E',
link: function postLink(scope, element) {
if (element.html()) {
var template = $compile('');
element.replaceWith(template(scope));
}
}
};
}]);
//display utc date as a locale ready date (according to browser settings)
mod.filter('utcdate', ['$filter', function ($filter) {
return function (input, format) {
if (!angular.isDefined(format))
format = 'MMM d, y h:mm:ss a';
var date = new Date(input);
var utc = new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds());
return $filter('date')(utc, format);
};
}]);
mod.factory('usefulService', [usefulService]);
function usefulService() {
var service = {
dynamicSort: dynamicSort
};
return service;
function dynamicSort(property) {
var sortOrder = 1;
//a '-' character is used to denote sort order
if (property[0] === "-") {
sortOrder = -1;
property = property.substr(1);
}
return function (a, b) {
var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
return result * sortOrder;
}
}
}
})();
(function (module) {
//file helper functions so that file functionality works nicely with Angular, promises etc.
var fileReader = function ($q, $log) {
var onLoad = function (reader, deferred, scope) {
return function () {
scope.$apply(function () {
deferred.resolve(reader.result);
});
};
};
var onError = function (reader, deferred, scope) {
return function () {
scope.$apply(function () {
deferred.reject(reader.result);
});
};
};
var onProgress = function (reader, scope) {
return function (event) {
scope.$broadcast("fileProgress",
{
total: event.total,
loaded: event.loaded
});
};
};
var getReader = function (deferred, scope) {
var reader = new FileReader();
reader.onload = onLoad(reader, deferred, scope);
reader.onerror = onError(reader, deferred, scope);
reader.onprogress = onProgress(reader, scope);
return reader;
};
//read a file as a binrary string
var readAsBinaryString = function (file, scope) {
var deferred = $q.defer();
var reader = getReader(deferred, scope);
if (file)
reader.readAsBinaryString(file);
return deferred.promise;
};
//read a file as a data url
var readAsDataURL = function (file, scope) {
var deferred = $q.defer();
var reader = getReader(deferred, scope);
reader.readAsDataURL(file);
return deferred.promise;
};
//read a text file
var readAsText = function (file, scope) {
var deferred = $q.defer();
var reader = getReader(deferred, scope);
if (file)
reader.readAsText(file);
return deferred.promise;
};
return {
readAsDataUrl: readAsDataURL,
readAsText: readAsText,
readAsBinaryString: readAsBinaryString
};
};
module.factory("fileReader",
["$q", "$log", fileReader]);
}(angular.module("usefulstuff")));