blob: 46a7627fb4140f523fb7b9893ec21fa05f599483 [file] [log] [blame]
'use strict';
// Directive for dealing with the Leaflet map
angular.module('rcmes').
directive('sap', function($rootScope) {
return {
restrict: 'E',
replace: true,
template: '<div></div>',
link: function(scope, element, attrs) {
$rootScope.map = L.map(attrs.id, {
center: [40, 0],
zoom: 2,
scrollWheelZoom: false,
});
//create a CloudMade tile layer and add it to the map
L.tileLayer('http://{s}.tile.cloudmade.com/57cbb6ca8cac418dbb1a402586df4528/997/256/{z}/{x}/{y}.png', {
maxZoom: 4, minZoom: 2,
}).addTo($rootScope.map);
}
};
}).
// Directive for inserting bootstrap modals
directive('bootstrapModal', function($timeout) {
var link = function(scope, elem, attrs) {
var escapeEvent;
var openModal;
var closeModal;
escapeEvent = function(e) {
if (e.which == 27)
closeModal();
}
openModal = function(event, toggleBackground, toggleKeyboardEscape) {
// Grab the current modal tag based on the modalId attribute in the bootstrapModal tag
var modal = $('#' + attrs.modalId);
// Make all the modal's children of class "close" call the appropriate function for closing!
$('.close', modal).bind('click', closeModal);
modal.modal({
show: true,
backdrop: toggleBackground,
keyboard: toggleKeyboardEscape,
});
};
closeModal = function(event) {
$('#' + attrs.modalId).modal('hide');
};
// We need to bind the close and open modal events so outside elements can trigger the modal.
// This has to wait until the template has been fully inserted, so just wait a bit of time
// before we set them. I'm sure there's a better way of handling this...
$timeout(function() {
$('#' + attrs.modalId).
bind('modalOpen', openModal).
bind('modalClose', closeModal);
}, 100);
};
return {
link: link,
replace: true,
restrict: 'E',
scope: {
modalId: '@'
},
template: '<div id="{{modalId}}" class="modal hide fade" tabindex="-1"><div ng-transclude></div></div>',
transclude: true
};
}).
// This directive process bootstrap-modal-open attributes. This lets the user bind the opening of a specific modal
// as well as specifying how that modal should open.
directive('bootstrapModalOpen', function() {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
// Default to showing the background if the user didn't specify a value for this.
var hasBackground = (attrs.background === undefined ? true : attrs.background);
// Enable keyboard closing of modal with escape key.
var hasKeyboardEscape = (attrs.keyboard === undefined ? true : attrs.keyboard);
$(elem).bind('click', function() {
$('#' + attrs.bootstrapModalOpen).trigger('modalOpen', [hasBackground, hasKeyboardEscape]);
});
}
};
}).
// Setup a text input that the user will use to input a path to a local file.
directive('predictiveFileBrowserInput', function() {
var link = function($scope, $elem, $attrs) {
$scope.autocomplete = [];
/*
* We need a place to dump our auto-completion options
*/
$($elem).parent().append('<ul id="autoCompletePath"><ul>');
// Handle user clicks on auto-complete path options
$(document).on('click', '#autoCompletePath li span', function(e) {
// Set the input text box's value to that of the user selected path
var val = $(e.target).text();
$($elem).val(val);
// If the user selected a directory, find more results..
if (val[val.length - 1] == '/') {
$scope.fetchFiles($($elem).val());
// Otherwise, remove the auto-complete options...
} else {
$('#autoCompletePath li').remove();
}
});
/*
* Handle key-down events on the input box
*
* We need to ignore <TAB> presses here so we can auto-complete with <TAB>.
* If we don't ignore here then <TAB> will move the user to the next field
* in the form and our common-prefix-fill won't work later.
*/
$($elem).keydown(function(e) {
var code = e.keyCode || e.which;
var BACKSPACE = 8,
TAB = 9;
if (code == TAB)
return false;
});
/*
* Handle key-up events on the input box
*/
$($elem).keyup(function(e) {
var code = e.keyCode || e.which;
var BACKSPACE = 8,
TAB = 9,
FORWARD_SLASH = 191;
if (code === FORWARD_SLASH) {
// Fetch new directory information from the server.
$scope.fetchFiles($(this).val());
} else if (code === TAB) {
// Attempt to auto-fill for the user.
$scope.handleTabPress();
} else if (code == BACKSPACE) {
// Need to properly handle the removal of directory information
// and the displaying of auto-complete options
$scope.handleBackSpace();
} else {
// Filter auto-complete options based on user input..
$scope.handleMiscKeyPress();
}
// This is being used so we can handle backspacing. The user might hold
// down the backspace key or select a section of text and delete. This allows
// us to compare the result to its prior state, which makes handling
// backspaces easier.
$scope.lastInputContents = $elem.val();
});
/*
* Grab additional path information from the web-server
*
* Params:
* path - The path to get a directory listing of.
*/
// TODO Make this use $HTTP
$scope.fetchFiles = function(path) {
$.get($scope.baseURL + '/getDirInfo/' + path, {},
function(data) {
$scope.setNewData(data);
$scope.updateAutoComplete();
}, 'json');
};
/*
* Grab additional path information from the web-server and filter the
* results based on the current input text.
*
* Params:
* path - The path to get a directory listing of.
*
* This is needed to handle deletion of selected text. It is possible that
* the user will select text and delete only part of a word. The results
* need to be filtered based on this partial input.
*/
// TODO Why isn't this using $http?!?!?! Because I copy and pasted!!!!
$scope.fetchFilesAndFilter = function(path) {
$.get($scope.baseURL + '/getDirInfo/' + path, {},
function(data) {
$scope.setNewData(data);
$scope.filterResults();
$scope.updateAutoComplete();
}, 'json');
};
/*
* Handle directory data from the server.
*
* We store the entire directory information along with the remaining
* possible options given the users current input. This lets us avoid
* unnecessary calls to the server for directory information every time
* the user deletes something.
*/
$scope.setNewData = function(data) {
$scope.autocomplete = data.sort();
$scope.possibleCompletes = $scope.autocomplete;
};
/*
* Handle <TAB> presses.
*
* Attempt to auto-complete options when the user presses <TAB>.
*/
$scope.handleTabPress = function() {
// If there's only one option available there's no points in trying to
// find a common prefix! Just set the value!
if ($scope.possibleCompletes.length === 1) {
$elem.val($scope.possibleCompletes[0]);
// Make sure more options are displayed if a directory was selected.
$scope.checkForMoreOptions();
$scope.updateAutoComplete();
return;
}
// Find the greatest common prefix amongst the remaining choices and set
// the input text to it.
var prefix = $scope.getLargestCommonPrefix($scope.possibleCompletes);
$elem.val(prefix);
$scope.updateAutoComplete();
};
/*
* Handle Backspacing and option displaying.
*
* The auto-complete options needs to be displayed correctly when the user
* removes directory information.
*/
$scope.handleBackSpace = function() {
var curInputVal = $elem.val();
// If the user deletes everything in the input box all we need to do
// is make sure that the auto-complete options aren't displayed.
if (curInputVal.length === 0) {
$('#autoCompletePath li').remove();
return;
}
// Figure out how much text the user removed from the input box.
var lengthDiff = $scope.lastInputContents.length - curInputVal.length;
// Grab the removed text.
var removedText = $scope.lastInputContents.substr(-lengthDiff);
// If the user deleted over a directory we need to fetch information on the
// previous directory for auto-completion.
if (removedText.indexOf('/') !== -1) {
var lastSlashIndex = curInputVal.lastIndexOf('/');
// If the remaining path still contains a directory...
if (lastSlashIndex !== -1) {
// Grab the section of the path that points to a valid directory,
// fetch the listing, and update the results.
var pathToSearch = curInputVal.slice(0, lastSlashIndex + 1);
$scope.fetchFilesAndFilter(pathToSearch);
} else {
// Delete the old auto-complete information in the case where the user
// completely removed path information.
$('#autoCompletePath li').remove();
}
} else {
// Otherwise, we just need to filter results based on the remaining input.
$scope.filterResults();
$scope.updateAutoComplete();
}
};
/*
* Handle all other key presses in the input box
*
* Filter the auto-complete options as the user types to ensure that only options
* which are possible given the current input text are still displayed.
*/
$scope.handleMiscKeyPress = function() {
// Safely exit when there are no options available.
if ($scope.autocomplete === [])
return;
// Otherwise, filter the results.
$scope.filterResults();
$scope.updateAutoComplete();
};
/*
* When a path is auto-completed with <TAB> we need to check to see if it points
* to a directory. If it does, we still need to fetch results!
*/
$scope.checkForMoreOptions = function() {
var path = $elem.val();
if (path[path.length - 1] === '/') {
$scope.fetchFiles(path);
}
};
/*
* Calculate the greatest common prefix of the passed options.
*
* Params:
* Options - An array of strings in which the greatest common prefix
* should be found
*
* Returns:
* The greatest common prefix of the strings.
*
*
* Note - For us, there will always be a prefix of at least '/' since this can't
* possible be called without the users entering a starting directory. As a result,
* we don't explicitly handle the case where there is 0 length common prefix.
*/
$scope.getLargestCommonPrefix = function(options) {
var index = 1;
var shortestString = options.reduce(function(a, b) { return a.length < b.length ? a : b; });
var longestString = options.reduce(function(a, b) { return a.length > b.length ? a : b; });
var substringToCheck = shortestString[0];
while (longestString.indexOf(substringToCheck) !== -1) {
substringToCheck = shortestString.slice(0, ++index);
}
return longestString.slice(0, index - 1);
};
/*
* Filter the auto-complete options based on the current input.
*/
$scope.filterResults = function() {
$scope.possibleCompletes = $scope.autocomplete.filter(function(item, index, array) {
return (~item.indexOf($($elem).val()));
});
$scope.possibleCompletes.sort();
};
/*
* Update the display of auto-complete options.
*/
$scope.updateAutoComplete = function() {
// Remove all the existing options
$('#autoCompletePath li').remove();
// We don't need to show anything if the user has completely selected
// the only existing option available.
if ($scope.possibleCompletes.length === 1) {
if ($scope.possibleCompletes[0] === $elem.val()) {
return;
}
}
// Display all the possible completes
$.each($scope.possibleCompletes, function(i, v) {
$('#autoCompletePath').append($('<li>').html($('<span>').text(v)));
});
};
};
return {
link: link,
scope: true,
restrict: 'A'
};
});