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
JavaScript
/**
* 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;
};
}
};
}]);