| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http: *www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| 'use strict'; |
| |
| /** |
| * @ngdoc directive |
| * @name ocwUiApp.directive:predictiveFileBrowserInput |
| * @description |
| * # predictiveFileBrowserInput |
| */ |
| angular.module('ocwUiApp') |
| .directive('predictiveFileBrowserInput', function() { |
| var link = function($scope, $elem, $attrs) { |
| $scope.autocomplete = []; |
| |
| // Set id to use this directive correctly in multiple places |
| /* |
| This had been written as $elem.context.id, but $elem is an object (jQuery.fn.init) |
| and the object did not have a context or id attribute. This was |
| throwing an error to the console and the list of files was not being displayed. |
| Replaced with $attrs.id. |
| */ |
| $scope.id = 'autoCompletePath' + $attrs.id; |
| |
| /* |
| * We need a place to dump our auto-completion options |
| */ |
| $($elem).parent().append('<ul id="' + $scope.id +'"><ul>'); |
| |
| // Handle user clicks on auto-complete path options |
| $(document).on('click', '#' +$scope.id+ ' 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); |
| // Need to trigger the input box's "input" event so Angular updates the model! |
| $elem.trigger('input'); |
| |
| // 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 { |
| $('#' +$scope.id+ ' 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 + '/dir/list/' + path, {}, |
| function(data) { |
| data = data['listing'] |
| $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 + '/dir/list/' + path, {}, |
| function(data) { |
| data = data['listing'] |
| $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) { |
| $('#' +$scope.id+ ' 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. |
| $('#' +$scope.id+ ' 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 |
| $('#' +$scope.id+ ' 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) { |
| $('#' +$scope.id+ '').append($('<li>').html($('<span>').text(v))); |
| }); |
| }; |
| }; |
| |
| return { |
| link: link, |
| scope: true, |
| restrict: 'A' |
| }; |
| }); |