image
Blog Post • development

ngSelect Customizations: Draggable Panel

September 19, 2018by Evan Jenkins 4 min read
Blog Post • development
ngSelect Customizations: Draggable Panel
Back to top

I was tasked with an issue to build a component that would require several ng-select filters all displayed in a grid. After I had finished that, there were several design changes that the product owner wanted to see, to make the select boxes much more usable for the end-user. One of the design changes was the ability to resize the select dropdown so that you could see long strings in the dropdown by dragging the corner of the dropdown to make it wider.


This post will be part 1 of some of the customizations I had to make to the ng-select module to make it more usable for our end-users. Today I'm writing about how I made the ng-select dropdown resize by dragging the dropdown box wider. If you want to follow along with my explanation, go download the code on Github and setup the project locally on your machine.

User story As a user, when I toggle a dropdown I want to be able to expand the dropdown to see longer words without them being cut-off or flowing to a second line.

Ng-select does a good job of allowing you to customize the select options. For this example I have created a template for the select that has an image and the name within the dropdown.

I created a select-item component to place each of the grid's select lists. This is required as you'll read later on to be able to access the panel component within ng-select. First step to get the dragging open to work, is to add a new anchor in the footer template so that the user would be able to click and drag the panel open to reveal more of the drop downs.

<!-- select-item.component.html -->
<ng-select [items]="data" bindLabel="name" bindValue="id">
  <ng-template ng-option-tmp let-item="item" let-index="index">
    <div class="option-template">
      <img [src]="item.picture" />
      <h4>{{ item.name }}</h4>
    </div>
  </ng-template>
  <ng-template ng-footer-tmp>
    <!-- Anchor used to drag open -->
    <a (mousedown)="dragDropdown()" class="dropdown-opener"></a>
  </ng-template>
</ng-select>

The next step was getting the id value of the panel drop down that gets created for each of the ng-select components.

Accessing data from a child component

If you take a look within the ng-select module at the ng-select.component.d.ts file you see that there is a dropdownId value. This is the id of the panel drop down item. There are other ways to dive deeper into the child component's children, but for this case I will access this via @ViewChild. Also, add in members to assign the drop down id, panel reference, panel width, panel height, dragging boolean, and X/Y coordinates.

// select-item.component.ts
// Resizing variables.
dropdownId = '';
panel: any;
panelWidth = 0;
panelHeight = 0;
panelX = 0;
panelY = 0;
draggingCorner = false;
constructor(private elRef: ElementRef) { }

@ViewChild(NgSelectComponent)
set panelId(v: NgSelectComponent) {
  this.dropdownId = v.dropdownId;
}

In the select-item.component.html template the footer anchor has a method for use on mousedown. Create this method that will be used to get the panel's reference, set some heights and widths, as well as set the flag on the draggingCorner boolean.

// select-item.component.ts
/**
 * Dropdown dragger mousedown event.
 */
dragDropdown() {
  // Find the panel
  this.panel = this.elRef.nativeElement.querySelector(`#${this.dropdownId}`);
  if (!this.panelWidth && !this.panelHeight) {
    // Set some inline styles
    this.panelWidth = this.panel.clientWidth;
    this.panelHeight = this.panel.clientHeight;
    this.panel.style.width = this.panelWidth;
    this.panel.style.height = this.panelHeight;
  }
  // Find the panels X & Y coordinates.
  const rect = this.panel.getBoundingClientRect();
  this.panelX = rect.x;
  this.panelY = rect.y;
  this.draggingCorner = true; // Very important to know when to stop resizing.
}

The Dragging Code

The last step to get this working is to track the mouse movement as well as stopping the dragging from happening when the user's mouse is back up. To do this add two HostListener's; one for the document's mousemove and another for the document's mouseup events.

// select-item.component.ts
/**
 * Listen after the dragger has been clicked.
 * @param {MouseEvent} event
 */
@HostListener('document:mousemove', ['$event'])
onCornerMove(event: MouseEvent) {
  // Prevent this from happening while the user isn't holding
  // the dragger anchor.
  if (!this.draggingCorner) {
    return;
  }

  // Get the coordinates of the panel
  const offsetX = event.clientX - this.panelX;
  const offsetY = event.clientY - this.panelY;

  // Adding a resized class to allow the rest of the items to show, css below.
  if (!this.panel.classList.contains('resized') && this.panelHeight < offsetY) {
    this.panel.classList.add('resized');
  }

  // Do not allow the dropdown to get smaller
  // than the initial dropdown size.
  if (this.panelWidth < offsetX) {
    this.panel.style.width = offsetX + 'px';
  }

  if (this.panelHeight < offsetY) {
    this.panel.style.height = offsetY + 'px';
  }
}

/**
 * Allow to stop listening to mousemove event.
 * @param {MouseEvent} event
 */
@HostListener('document:mouseup', ['$event'])
onCornerRelease(event: MouseEvent) {
  this.draggingCorner = false; // Tell the component to stop setting inline styles.
}

Using the resized class added above when the user has made the panel dropdown taller, gives the opportunity to set the panel options height since the ng-select module has set the max-height and the options would stay only as tall as what is set in it's css.

::ng-deep .ng-dropdown-panel.resized .scroll-host.ng-dropdown-panel-items {
  max-height: 100%;
}

Take a look at this example Github project to see this in action. I'll post more of the examples to this repo as well in future blog posts.

The ng-select panel should now have the ability to be dragged wider and taller to reveal all of the information for the user to see! This was only one customization that I had to make to the ng-select component. Some of the other customizations that I made are:

  • Parent / Child selections with dropdowns in the list
  • Search results returning parent tree

If you would like to read about the rest of the problems and solutions for ng-select let us know in the comments below or tweet us!

Authored by