Wednesday, September 27, 2017

a hacky example of dressing up a third-party control in an Angular 4 app

angular2-datatable has DefaultSorter controls for sorting the headings of tables of data. They look like this:

<mfDefaultSorter by="BirthDate">Birth Date</mfDefaultSorter>

 
 

When you click one of these controls, an up arrow or a down arrow will appear by the control's name. (an up arrow first) But, what if you want an icon with both up and down arrows to appear by the control before it is ever touched as if you say "You can touch me. I like being touched." to distinguish the sortable columns with a DefaultSorter from a column that is not sortable? Well, I found a way to jam my own logic on top of the third-party control without editing the third-party control which is generally a bad idea as it sabotages the ability to ever upgrade the third-party control or at least makes doing so prohibitively painful. To start with, I jam in a span with a CSS class to make the icon like so:

<mfDefaultSorter by="BirthDate">Birth Date<span class="whatever"></span></mfDefaultSorter>

 
 

Alright, we need our own click event at the DefaultSorter and the span tag inside needs to become a ViewChild variable back at the component that governs the template into which the HTML for the DefaultSorters sit.

<mfDefaultSorter by="BirthDate" (click)="dressUpDefaultSorters($event)">Birth Date<span #birthDate class="whatever"></span></mfDefaultSorter>

 
 

For argument sake let's say there are three sortable columns leading to three ViewChild variables like so:

@ViewChild('birthDate') birthDate: ElementRef;
@ViewChild('deathDate') deathDate: ElementRef;
@ViewChild('barMitzvah') barMitzvah: ElementRef;

 
 

Alright the dressUpDefaultSorters method in the component looks like so:

dressUpDefaultSorters(event:any):void{
   let classForDoubleArrowIcon:string = "whatever";
   [this.birthDate, this.deathDate, this.barMitzvah].forEach((defaultSorter:ElementRef)=>{
      defaultSorter.nativeElement.className = classForDoubleArrowIcon;
   });
   let nodes = event.toElement.childNodes;
   if (nodes.length == 0){
      if (event.toElement.className == classForDoubleArrowIcon){
         event.toElement.className = "";
      }
   }
   event.toElement.childNodes.forEach((node:any)=>{
      if(node.className == classForDoubleArrowIcon){
         node.className = "";
      }
   }); }

 
 

Alright, one tacky thing about DefaultSorters is that if you click one, the arrow disappears from the others that have been clicked so we need to put the double arrow back on the other columns which is the first thing we do above. Then we determine if one clicked upon the DefaultSorter itself or the span within the DefaultSorter and apply the whatever class as applicable. Nasty? Maybe so.

 
 

Addendum 9/29/2017: Alright, since writing what is above I have found a mistake and, no, it's not that the last curly brace isn't on its own line though that sucks too. The problem is that if you click on the standalone up or down arrows that are provided with the DefaultSorter tags that you are clicking on a span inside the tag and not the tag itself and my code does not account for that. There is a fix and it's not pretty. The dressUpDefaultSorters method is going to need a second input like so:

<mfDefaultSorter by="BirthDate" (click)="dressUpDefaultSorters($event,'birthDate')">Birth Date<span #birthDate class="whatever"></span></mfDefaultSorter>

 
 

We need to use the magic string like so:

dressUpDefaultSorters(event:any, name:string):void{
   let classForDoubleArrowIcon:string = "whatever";
   let columns:Array<ElementRef> = [];
   if (name != 'birthDate') columns.push(this.birthDate);
   if (name != 'deathDate') columns.push(this.deathDate);
   if (name != 'barMitzvah') columns.push(this.barMitzvah);
   columns.forEach((defaultSorter:ElementRef)=>{
      defaultSorter.nativeElement.className = classForDoubleArrowIcon;
   });
   let nodes = event.toElement.childNodes;
   if (nodes.length == 0){
      if (event.toElement.className == classForDoubleArrowIcon){
         event.toElement.className = "";
      }
   }
   event.toElement.childNodes.forEach((node:any)=>{
      if(node.className == classForDoubleArrowIcon){
         node.className = "";
      }
   });
}

No comments:

Post a Comment