angular.module('biomed.directives', []) .directive("bioNavbar", function ($location) { return { restrict: 'A', link: function ($scope, element, attrs, controller) { $scope.$watch(function () { return $location.path(); }, function (newValue, oldValue) { element.find('li[data-match-route]').each(function (k, li) { var $li = angular.element(li); var pattern = $li.attr('data-match-route'); var regex = new RegExp('^' + pattern + '$', ['i']); if (regex.test(newValue)) { $li.addClass('active'); } else { $li.removeClass('active'); } }); }); } } }) .directive('infiniteScroll', function ($window) { return { link: function (scope, element, attrs) { var offset = parseInt(attrs.threshold) || 0; var e = angular.element($window); e.bind("scroll", function () { if (scope.canLoad && e.height() + e.scrollTop() >= $(document).height() - offset) { scope.$apply(attrs.infiniteScroll); } }); } } }) .directive('tabbable', function () { return { restrict: 'C', compile: function (element) { var navTabs = angular.element(''), tabContent = angular.element('
'); tabContent.append(element.contents()); element.append(navTabs).append(tabContent); }, controller: ['$scope', '$element', function ($scope, $element) { var navTabs = $element.contents().eq(0), ngModel = $element.controller('ngModel') || {}, tabs = [], selectedTab; ngModel.$render = function () { var $viewValue = this.$viewValue; if (selectedTab ? (selectedTab.value != $viewValue) : $viewValue) { if (selectedTab) { selectedTab.paneElement.removeClass('active'); selectedTab.tabElement.removeClass('active'); selectedTab = null; } if ($viewValue) { for (var i = 0, ii = tabs.length; i < ii; i++) { if ($viewValue == tabs[i].value) { selectedTab = tabs[i]; break; } } if (selectedTab) { selectedTab.paneElement.addClass('active'); selectedTab.tabElement.addClass('active'); } } } }; this.addPane = function (element, attr) { var li = angular.element('
  • '), a = li.find('a'), tab = { paneElement: element, paneAttrs: attr, tabElement: li }; tabs.push(tab); attr.$observe('value', update)(); attr.$observe('title', function () { update(); a.text(tab.title); })(); attr.$observe('visible', function () { update(); tab.tabElement[0].style.display = (tab.visible === "false") ? 'none' : 'block'; })(); function update() { console.log(attr.visible); tab.title = attr.title; tab.value = attr.value || attr.title; tab.visible = attr.visible; if (!ngModel.$setViewValue && (!ngModel.$viewValue || tab == selectedTab)) { // we are not part of angular ngModel.$viewValue = tab.value; } ngModel.$render(); } navTabs.append(li); li.bind('click', function (event) { event.preventDefault(); event.stopPropagation(); if (ngModel.$setViewValue) { $scope.$apply(function () { ngModel.$setViewValue(tab.value); ngModel.$render(); }); } else { // we are not part of angular ngModel.$viewValue = tab.value; ngModel.$render(); } }); return function () { tab.tabElement.remove(); for (var i = 0, ii = tabs.length; i < ii; i++) { if (tab == tabs[i]) { tabs.splice(i, 1); } } }; } }] }; }) .directive('tabPane', function () { return { require: '^tabbable', restrict: 'C', link: function (scope, element, attrs, tabsCtrl) { element.bind('$remove', tabsCtrl.addPane(element, attrs)); } }; }) .directive('datepicker', function ($timeout) { var isTouch = 'ontouchstart' in window && !window.navigator.userAgent.match(/PhantomJS/i); return { restrict: 'AC', require: '?ngModel', link: function postLink(scope, element, attrs, controller) { var format = 'MM-DD-YYYY'; // Handle date validity according to dateFormat if (controller) { controller.$formatters.unshift(function (value) { return moment(value).format(format); }); controller.$parsers.unshift(function (viewValue) { if (angular.isDate(viewValue)) { return viewValue; } else { var date = moment(viewValue); if (date.isValid()) { return date; } else { return undefined; } } }); } // Use native interface for touch devices // if(isTouch && element.prop('type') === 'text') { // // element.prop('type', 'date'); // element.on('change', function(ev) { // scope.$apply(function () { // controller.$setViewValue(moment(element.val()).toDate()); // }); // }); // } else { // If we have a controller (i.e. ngModelController) then wire it up if (controller) { element.on('changeDate', function (ev) { scope.$apply(function () { controller.$setViewValue(moment(element.val()).toDate()); }); }); } // Popover GarbageCollection var $popover = element.closest('.popover'); if ($popover) { $popover.on('hide', function (e) { var datepicker = element.data('datepicker'); if (datepicker) { datepicker.picker.remove(); element.data('datepicker', null); } }); } // Create datepicker element.attr('data-toggle', 'datepicker'); element.datepicker({ autoclose: true, format: 'mm-dd-yyyy', forceParse: attrs.forceParse || false }); // } // Support add-on var component = element.siblings('[data-toggle="datepicker"]'); if (component.length) { component.on('click', function () { element.trigger('focus'); }); } } }; }) .directive('techschedule', function () { return { restrict: 'E', scope: { schedule: '=', date: '=', onEntryClick: '&' }, templateUrl: '/partials/techSchedule.html', replace: true, link: function ($scope, element, attrs) { var x, rangeDate, rangeStart, rangeEnd; function setupScale() { x = d3.scale.linear() .range([0, 100]) .domain([420, 1140]) .clamp(true); } setupScale(); var color = d3.scale.category20(); var hourWidth = 100 / 12; $scope.hourMarkers = []; for (var i = 7; i < 19; i++) { $scope.hourMarkers.push({ date: moment({hour: i}).toDate(), style: { left: x(i * 60) + '%', width: hourWidth + '%' } }); } $scope.$watch('schedule', function (newVal, oldVal) { generateDate(); }); $scope.$watch('date', function (newVal, oldVal) { setupScale(); }); function generateDate() { var range = moment($scope.date); var data = {}; var current = range.format('ddd MMM Do YYYY'); for (var i = -7; i < 22; i++) { var day = range.clone().add(i, 'days'); var key = day.format('MM-DD-YYYY'); var label = day.format('ddd MMM Do YYYY'); data[key] = { order: i, current: current == label, label: label, values: [] }; } var c = 0; angular.forEach($scope.schedule, function (workorder) { var start = moment(workorder.scheduling.start); var startMinutes = start.diff(start.clone().startOf('day'), 'minutes'); var end = moment(workorder.scheduling.end); var endMinutes = end.diff(end.clone().startOf('day'), 'minutes'); var length = end.diff(start, 'days') + 1; console.log('length: ' + length + ' start: ' + startMinutes + ' end: ' + endMinutes); var backgroundColor = color(c++); for (var i = 0; i < length; i++) { var adjStart, adjEnd; var key = start.clone().add(i, 'days').format('MM-DD-YYYY'); if (i == 0) { adjStart = startMinutes; } else { adjStart = 420; } if (i == length - 1) { adjEnd = endMinutes; } else { adjEnd = 1320; } if (data[key]) { data[key].values.push({ style: { backgroundColor: color(c), left: x(adjStart) + '%', width: (x(adjEnd) - x(adjStart)) + '%' }, workorder: workorder }); } } return; angular.forEach(workorder.techs, function (tech) { var key = tech.name.first + ' ' + tech.name.last; if (!data[key]) return; var start = moment(workorder.scheduling.start); var end = moment(workorder.scheduling.end); data[key].values.push({ style: { backgroundColor: color(key), left: x(start) + "%", width: (x(end) - x(start)) + "%" }, workorder: workorder }); }) }); var dataArray = []; for (var o in data) { dataArray.push(data[o]); } $scope.data = dataArray; } } }; }) .filter('pretty', function () { return function (input) { return "\n" + angular.toJson(input, true); } }) .directive('techpicker', function () { return { restrict: 'E', scope: { users: '=', schedule: '=', date: '=', onEntryClick: '&' }, templateUrl: '/partials/techPicker.html', replace: true, link: function ($scope, element, attrs) { var x, rangeDate, rangeStart, rangeEnd; function setupScale() { rangeDate = moment($scope.date).startOf('day'); rangeStart = moment(rangeDate).add('hours', 7); rangeEnd = moment(rangeDate).add('hours', 19); x = d3.time.scale() .range([0, 100]) .domain([rangeStart.toDate(), rangeEnd.toDate()]) .clamp(true); } setupScale(); var color = d3.scale.category20(); var totalHours = moment.duration(rangeEnd - rangeStart).hours(); var hourWidth = 100 / totalHours; $scope.hourMarkers = []; for (var i = 0; i < totalHours; i++) { var date = moment(rangeStart).add('hours', i).toDate(); $scope.hourMarkers.push({ date: date, style: { left: x(date) + "%", width: hourWidth + "%" } }); } $scope.$watch('users', function (newVal, oldVal) { generateDate(); }); $scope.$watch('schedule', function (newVal, oldVal) { generateDate(); }); $scope.$watch('date', function (newVal, oldVal) { setupScale(); }); function generateDate() { var data = {}; var labels = []; angular.forEach($scope.users, function (user) { var key = user.name.first + ' ' + user.name.last; labels.push(key); data[key] = { id: user._id, values: [] }; }); labels.sort(); color.domain(labels); angular.forEach($scope.schedule, function (workorder) { angular.forEach(workorder.techs, function (tech) { var key = tech.name.first + ' ' + tech.name.last; if (!data[key]) return; var start = moment(workorder.scheduling.start); var end = moment(workorder.scheduling.end); data[key].values.push({ style: { backgroundColor: color(key), left: x(start) + "%", width: (x(end) - x(start)) + "%" }, workorder: workorder }); }) }); $scope.data = data; } } }; }) .value('uiSelect2Config', {}) .directive('uiSelect2', ['uiSelect2Config', '$timeout', function (uiSelect2Config, $timeout) { var options = {}; if (uiSelect2Config) { angular.extend(options, uiSelect2Config); } return { require: 'ngModel', priority: 1, compile: function (tElm, tAttrs) { var watch, repeatOption, repeatAttr, isSelect = tElm.is('select'), isMultiple = angular.isDefined(tAttrs.multiple); // Enable watching of the options dataset if in use if (tElm.is('select')) { repeatOption = tElm.find('optgroup[ng-repeat], optgroup[data-ng-repeat], option[ng-repeat], option[data-ng-repeat]'); if (repeatOption.length) { repeatAttr = repeatOption.attr('ng-repeat') || repeatOption.attr('data-ng-repeat'); watch = jQuery.trim(repeatAttr.split('|')[0]).split(' ').pop(); } } return function (scope, elm, attrs, controller) { // instance-specific options var opts = angular.extend({}, options, scope.$eval(attrs.uiSelect2)); /* Convert from Select2 view-model to Angular view-model. */ var convertToAngularModel = function (select2_data) { var model; if (opts.simple_tags) { model = []; angular.forEach(select2_data, function (value, index) { model.push(value.id); }); } else { model = select2_data; } return model; }; /* Convert from Angular view-model to Select2 view-model. */ var convertToSelect2Model = function (angular_data) { var model = []; if (!angular_data) { return model; } if (opts.simple_tags) { model = []; angular.forEach( angular_data, function (value, index) { model.push({'id': value, 'text': value}); }); } else { model = angular_data; } return model; }; if (isSelect) { // Use instead delete opts.multiple; delete opts.initSelection; } else if (isMultiple) { opts.multiple = true; } if (controller) { // Watch the model for programmatic changes controller.$render = function () { if (isSelect) { elm.select2('val', controller.$modelValue); } else { if (isMultiple) { if (!controller.$modelValue) { elm.select2('data', []); } else if (angular.isArray(controller.$modelValue)) { elm.select2('data', controller.$modelValue); } else { elm.select2('val', controller.$modelValue); } } else { if (angular.isObject(controller.$modelValue)) { elm.select2('data', controller.$modelValue); } else { elm.select2('val', controller.$modelValue); } } } }; // Watch the options dataset for changes if (watch) { scope.$watch(watch, function (newVal, oldVal, scope) { if (!newVal) return; // Delayed so that the options have time to be rendered $timeout(function () { elm.select2('val', controller.$viewValue); // Refresh angular to remove the superfluous option elm.trigger('change'); }); }); } if (!isSelect) { // Set the view and model value and update the angular template manually for the ajax/multiple select2. elm.bind("change", function () { scope.$apply(function () { controller.$setViewValue(elm.select2('data')); }); }); if (opts.initSelection) { var initSelection = opts.initSelection; opts.initSelection = function (element, callback) { initSelection(element, function (value) { controller.$setViewValue(value); callback(value); }); }; } } } attrs.$observe('disabled', function (value) { elm.select2(value && 'disable' || 'enable'); }); if (attrs.ngMultiple) { scope.$watch(attrs.ngMultiple, function (newVal) { elm.select2(opts); }); } // Set initial value since Angular doesn't elm.val(scope.$eval(attrs.ngModel)); // Initialize the plugin late so that the injected DOM does not disrupt the template compiler $timeout(function () { elm.select2(opts); // Not sure if I should just check for !isSelect OR if I should check for 'tags' key if (!opts.initSelection && !isSelect) controller.$setViewValue(elm.select2('data')); }); }; } }; }) .directive('dropzone', function () { return { scope: { dropzone: '=', existing: '=', prefix: '@' }, controller: function ($scope, $element, $attrs) { var config, dropzone; config = $scope.dropzone; dropzone = new Dropzone($element[0], config.options); angular.forEach(config.eventHandlers, function (handler, event) { dropzone.on(event, handler); }); $scope.$watch('existing', function () { var existing = $scope.existing; var prefix = "http://atlanticbiomedical.com/images/"; if ($scope.prefix) { prefix = prefix + $scope.prefix; } if (existing) { for (var i = 0; i < existing.length; i++) { var file = {name: existing[i], size: 0, accepted: true, filename: existing[i]}; dropzone.options.addedfile.call(dropzone, file); dropzone.options.thumbnail.call(dropzone, file, prefix + existing[i]); dropzone.files.push(file); } } }); } }; }) .directive('abUnique', function (Clients, $timeout) { return { restrict: 'A', require: 'ngModel', link: function (scope, element, attrs, ngModel) { var stop_timeout; return scope.$watch(function () { return ngModel.$modelValue; }, function (name) { $timeout.cancel(stop_timeout); if (!name) { ngModel.$setValidity('unique', true); } stop_timeout = $timeout(function () { var keyProperty = scope.$eval(attrs.abUnique); if (name) { Clients.isUnique({ key: keyProperty.key, field: keyProperty.field, value: name }, function (result) { console.log('unique = ' + result.isUnique); ngModel.$setValidity('unique', result.isUnique); }); } }, 200); }); } }; }) .directive('abDeviceUnique', function (Devices, $timeout) { return { restrict: 'A', require: 'ngModel', link: function (scope, element, attrs, ngModel) { var stop_timeout; return scope.$watch(function () { return ngModel.$modelValue; }, function (name) { $timeout.cancel(stop_timeout); if (!name) { ngModel.$setValidity('unique', true); } stop_timeout = $timeout(function () { var keyProperty = scope.$eval(attrs.abDeviceUnique); if (name) { Devices.isUnique({ key: keyProperty.key, field: keyProperty.field, value: name }, function (result) { console.log('unique = ' + result.isUnique); ngModel.$setValidity('unique', result.isUnique); }); } }, 200); }); } }; })