Skip to content

Commit

Permalink
feat(GDB-11566) Introduce multi-select dropdown directive
Browse files Browse the repository at this point in the history
## WHAT:
- Adds a new directive multiselectDropdown along with its template
- Includes styles in workbench-custom.css to display a multi-select dropdown with checkboxes.
- Updates webpack.config.common.js to copy the new template to the correct build location.

## WHY:
- Needed a simple and reusable multi-select component for selecting multiple items

## HOW:
- Created multiselect-dropdown.directive.js
- Added custom styling and positioning rules
- Updated webpack.config.common.js to ensure the template files are copied when building the app
  • Loading branch information
teodossidossev authored and svilenvelikov committed Feb 5, 2025
1 parent f6d5688 commit 91cf392
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 0 deletions.
47 changes: 47 additions & 0 deletions src/css/workbench-custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -409,3 +409,50 @@
.help-info b {
font-weight: 500;
}

.dynamic-form .dropdown-menu.multi-select {
background-color: #fff !important;
border: 1px solid rgba(0,0,0,.15) !important;
border-radius: 0.25rem !important;
position: absolute;
left: 0;
top: 100%;
z-index: 1000;
display: none;
max-height: 200px;
overflow-y: auto;
}

.dynamic-form .dropdown.multi-select > button {
font-weight: 400 !important;
}

.dynamic-form .dropdown-item.multi-select {
color: var(--gray-color) !important;
font-weight: initial !important;
}

.dynamic-form .open > .dropdown-menu.multi-select {
display: block;
opacity: 1;
margin-top: -1px;
}

.dynamic-form .dropdown-menu.multi-select .dropdown-item.multi-select {
color: var(--gray-color) !important;
font-weight: initial !important;
}

.dynamic-form button.form-control.dropdown-toggle.multi-select.text-left {
position: relative;
}

.dynamic-form button.form-control.dropdown-toggle.multi-select.text-left::after {
margin-left: 0 !important;
position: absolute;
right: 3px;
top: 50%;
transform: translateY(-50%);
color: initial;
font-size: smaller !important;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/**
* @function multiselectDropdown
* @description
* A directive for rendering a multi-select dropdown.
*
* This directive displays a list of options with checkboxes that allows users
* to select multiple items. Each option should be an instance of the
* SelectOptionsModel, which encapsulates the display label, value,
* selection state, and any additional data. Clicking the toggle button opens/closes
* the dropdown; clicking outside the dropdown closes it.
*
* @example
* ### HTML Usage:
* ```html
* <multiselect-dropdown
* ng-model="selectedOptions"
* options="myOptions"
* dropdown-label="'Choose Items'">
* </multiselect-dropdown>
* ```
*
* ### Controller Example:
* ```javascript
* angular.module('myApp', [])
* .controller('ExampleController', function($scope) {
* $scope.myOptions = [
* new SelectOptionsModel({ label: 'Option A', value: 'A' }),
* new SelectOptionsModel({ label: 'Option B', value: 'B' }),
* new SelectOptionsModel({ label: 'Option C', value: 'C' })
* ];
* });
* ```
*
* @param {Array.<SelectOptionsModel>} options - The array of options displayed in the dropdown.
* @param {string=} [dropdownLabel='Select...'] - (Optional) The placeholder text if no items are selected.
*
* @returns {Object} The directive definition object.
*/
angular.module('graphdb.framework.core.directives.multiselect-dropdown', [])
.directive('multiselectDropdown', ['$translate', function ($translate) {
return {
restrict: 'E',
require: 'ngModel',
scope: {
options: '=',
dropdownLabel: '=?'
},
templateUrl: 'js/angular/core/directives/multiselect-dropdown/templates/multiselect-dropdown.html',
link: function($scope, element, attrs, ngModel) {
// =========================
// Public variables
// =========================
$scope.isOpen = false;

// =========================
// Private methods
// =========================
// Close the dropdown if the user clicks outside it.
const handleDocumentClick = (event) => {
if (!element[0].contains(event.target)) {
$scope.$apply(() => {
$scope.isOpen = false;
});
}
};

const updateModel = () => {
const selected = $scope.options.filter(function(option) {
return option.selected;
});
ngModel.$setViewValue(selected);
}

// =========================
// Public methods
// =========================
$scope.toggleDropdown = function () {
$scope.isOpen = !$scope.isOpen;
};

/**
* Toggle the selection state of an option.
* @param {SelectOptionsModel} option
*/
$scope.toggleSelection = function (option) {
option.selected = !option.selected;
updateModel();
};

/**
* Build the display text for the dropdown toggle.
* Iterates the options and joins the labels of selected options.
* @returns {string}
*/
$scope.getSelectedText = function () {
const selectedOptions = $scope.options.filter((option) => option.selected);
if (selectedOptions.length === 0) {
return $scope.dropdownLabel || $translate.instant('common.select');
}
return selectedOptions.map((option) => option.label).join(', ');
};

/**
* If ngModel provides an initial value, update the corresponding option selections.
*/
ngModel.$render = function () {
if (angular.isArray(ngModel.$viewValue)) {
$scope.options.forEach(function(option) {
option.selected = ngModel.$viewValue.some(function(selectedOption) {
return selectedOption.value === option.value;
});
});
}
};

// =========================
// Subscriptions
// =========================
document.addEventListener('click', handleDocumentClick);
$scope.$on('$destroy', () => {
document.removeEventListener('click', handleDocumentClick);
});
}
};
}]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<div class="dropdown multi-select" ng-class="{'open': isOpen}">
<button type="button"
class="form-control dropdown-toggle multi-select text-left"
ng-click="toggleDropdown()">
{{ dropdownLabel || getSelectedText() }}
</button>
<ul class="dropdown-menu multi-select" role="menu">
<li class="dropdown-item multi-select"
ng-repeat="option in options track by $index"
ng-click="toggleSelection(option)">
<input type="checkbox" ng-model="option.selected">
{{option.label}}
</li>
</ul>
</div>
5 changes: 5 additions & 0 deletions webpack.config.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,11 @@ module.exports = {
to: 'js/angular/core/directives/graphql-playground/templates',
transform: replaceVersion
},
{
from: 'src/js/angular/core/directives/multiselect-dropdown/templates',
to: 'js/angular/core/directives/multiselect-dropdown/templates',
transform: replaceVersion
},
{
from: 'src/js/angular/templates',
to: 'js/angular/templates'
Expand Down

0 comments on commit 91cf392

Please sign in to comment.