UNPKG

test-scribe

Version:

Your sidekick for analog machine development. Run machines interactively and generate automated test scripts based on the outcome.

412 lines (280 loc) 10.7 kB
/** * Custom Dropdown Directive * ------------------------------------------------------------------------ * */ angular.module('homepage') .directive('customDropdown', ['negotiateKeyboardEvent', function (negotiateKeyboardEvent) { return { template: '<div><textarea class="hidden" style="position: absolute; left: -9999px;"></textarea><ul class="contents"></ul><div class="resting-state"><span></span><i class="fa fa-angle-down"></i></div></div>', restrict: 'EAC', scope: { // e.g. "'[{id: 2385235, name: \'Turtle Ship\'}, ...]'" choices: '=', // e.g. 3 // the index of the choice to select by default // if unspecified, this will use 0 (the empty choice) selectedIndex: '=', // e.g. true // if true, an empty choice will be prepended to the other choices, // allowing the user to make a null choice. allowEmpty: '=', // The placeholder text to show // (If allowEmpty is true, will also be the empty choice) placeholder: '=', // e.g. "'displayName'" keyToDisplay: '=', // iconKey: '=', disabled: '=', // e.g. "someFunction" onSelect: "=", // e.g. "someFunction" onDeselect: "=", // an empty control object // this directive will expose a function called 'set' on this object actions: '=' }, link: function ($scope, $el, attrs) { var $ = angular.element; // On render // ================================================== if (!$scope.choices) { throw new Error('`No `choices` array was provided to custom-dropdown directive!`'); } if (!$scope.keyToDisplay) { throw new Error('`No `key-to-display` (string) was provided to custom-dropdown directive-- I need to know which key you want to show in the dropdown.`'); } if (!$scope.onSelect) { throw new Error('`No `on-select` function was provided to custom-dropdown directive!`'); } // Clone choices to avoid mysterious quantum entanglement. var machineChoices = _.cloneDeep($scope.choices); // Default `allowEmpty` to false. var allowEmpty = $scope.allowEmpty; if (_.isUndefined(allowEmpty)) { allowEmpty = false; } // Default `selectedIndex` to 0 var selectedIndex = +$scope.selectedIndex; // Get reference to $ul. var $ul = $el.find('.contents'); // Get reference to $restingState. var $restingState = $el.find('.resting-state'); // Then add the placeholder. var placeholder = $scope.placeholder || '--'; $el.find('.resting-state >span').text(placeholder); // Maintain state- remembering whether choices are currently visible. var currentlyVisible; // Build the empty choice for use below. var emptyChoice = {}; emptyChoice[$scope.keyToDisplay] = placeholder; // Immediately hide list of options hideListOfOptions(); // Add the array of choices var choices = []; // Then add the empty state as the first item if (allowEmpty) { choices.push(emptyChoice); } // Then add in the other stuff provided by the user. choices = choices.concat($scope.choices); // The last selected choice (defaults to the empty choice) var selectedChoice = choices[selectedIndex]; // The array index of the 'hovered-over' choice // (can be manipulated with arrow keys) var imminentChoiceIndex = selectedIndex; // Render each choice as a new <li> inside of our <ul>. _.each(choices, function (choice){ var label = choice[$scope.keyToDisplay]; var tpl; var renderedHTML; var $li; // if (iconClass) { // var iconClass = choice[$scope.iconKey]; // tpl = '<li class="choice"><i class="<%= iconClass %>"></i><%= label %></li>'; // renderedHTML = (_.template(tpl))({ label: label }); // $li = $(renderedHTML); // $ul.append($li); // } // else { tpl = '<li class="choice"><%= label %></li>'; renderedHTML = (_.template(tpl))({ label: label }); $li = $(renderedHTML); $ul.append($li); // } }); // Render the currently selected choice if(selectedChoice) { renderSelectedChoice(selectedChoice); } if (!selectedIndex || _.isNaN(selectedIndex)) { selectedIndex = 0; } else { // If it was specified and valid, increment the `selectedIndex` by one // to make it behave as expected (since we prepend an empty choice) // But only if `allowEmpty` is enabled. if (allowEmpty) { selectedIndex++; } } // Render the 'imminent' choice renderImminentChoice($ul.children().eq(selectedIndex)); // Set up helper functions which are called by events // or while rendering this directive. // ================================================== function showListOfOptions() { $ul.show(); // $restingState.hide(); currentlyVisible = true; } function hideListOfOptions() { $ul.hide(); // $restingState.show(); currentlyVisible = false; } function renderSelectedChoice(choice) { $el.find('.resting-state >span').text(choice[$scope.keyToDisplay]); } function renderImminentChoice($li) { $el.find('li.choice').removeClass('imminent'); $li.addClass('imminent'); } // Bind any relevant DOM events // ================================================== // The hidden textarea used to emulate normal browser behavior. var $hiddenTextarea = $el.find('textarea.hidden'); // The choices (<li>s) in our select dropdown var $choices = $el.find('li.choice'); $restingState = $el.find('.resting-state'); /** * When clicked, either hide or show the list of options... */ $el.on('mousedown', function (e) { // Prevent default behavior of mousedown // (i.e. blurring selected form fields) e.preventDefault(); if ( currentlyVisible ) { return $hiddenTextarea.blur(); } $hiddenTextarea.focus(); }); /** * When focused, display the list of options... */ $hiddenTextarea.on('focus', function (e){ if($scope.disabled) { return; } showListOfOptions(); $restingState.addClass('focused'); }); /** * When blurred, hide the list of options... */ $hiddenTextarea.on('blur', function (e){ hideListOfOptions(); $restingState.removeClass('focused'); }); /** * When a key is pressed, check for special keystrokes... */ $hiddenTextarea.on('keydown', function(e) { return negotiateKeyboardEvent(e, { '<ESC>': function(e) { $hiddenTextarea.blur(); }, '<UP_ARROW>': function(e) { // It is possible to have the textarea focused but not showing the list of options. // If this is the case, show it. if ( !currentlyVisible ) { showListOfOptions(); } // Move up (wrap around) imminentChoiceIndex -= 1; if (imminentChoiceIndex < 0) { imminentChoiceIndex = choices.length - 1; } // Render the imminent choice var $imminentChoice = $choices.eq(imminentChoiceIndex); renderImminentChoice($imminentChoice); }, '<DOWN_ARROW>': function(e) { // It is possible to have the textarea focused but not showing the list of options. // If this is the case, show it. if ( !currentlyVisible ) { showListOfOptions(); } // Move down (wrap around) imminentChoiceIndex += 1; if (imminentChoiceIndex > (choices.length - 1)) { imminentChoiceIndex = 0; } // Render the imminent choice var $imminentChoice = $choices.eq(imminentChoiceIndex); renderImminentChoice($imminentChoice); }, '<RETURN>': function(e) { var $imminentChoice = $choices.eq(imminentChoiceIndex); var selectedIndex = $imminentChoice.index(); selectedChoice = choices[selectedIndex]; // Render the selected choice renderSelectedChoice(selectedChoice); // Hide the dropdown list // (but don't blur to allow for subsequently changing the selected // choice using arrow keys- this is the default browser behavior) hideListOfOptions(); // Call `onSelect` $scope.onSelect({ choice: selectedChoice }); } }); }); /** * When a new selection is made, set the provided `result` * scope variable. Also hide the list of options. */ $choices.on('mousedown', function (e){ var $selectedElement = $(e.currentTarget); var selectedIndex = $selectedElement.index(); selectedChoice = choices[selectedIndex]; // Render the selected choice renderSelectedChoice(selectedChoice); // Call `onSelect` $scope.onSelect({ choice: selectedChoice }); }); /** * When hovering over a choice with the mouse... */ $choices.hover(function onMouseEnter (e){ var $selectedElement = $(e.currentTarget); var imminentIndex = $selectedElement.index(); imminentChoiceIndex = imminentIndex; // Render the imminent choice renderImminentChoice($selectedElement); }, function onMouseLeave (e){ }); // Expose functions on the provided control object (`actions`) // ================================================== // If not provided, no biggie. if (!_.isObject($scope.actions)) { return; } /** * Set the dropdown to select the choice at the specified index. */ $scope.actions.set = function(index) { selectedChoice = choices[index]; // Render the selected choice renderSelectedChoice(selectedChoice); }; /** * Get the value of the dropdown. */ $scope.actions.get = function() { if(selectedChoice) { return selectedChoice; } else return undefined; }; } }; }]);