Search code examples
javascripttypescriptaurelia

Aurelia - Change the color of the selected row in a table


I am using Aurelia v4.6.1 with Typescript in a.Net core project. I have a component that retrieves data from an external source and displays it in a table using a repeat.for attribute.

Now I want to highlight the row that has been selected (clicked). On my view model I have a documents property of type Document[] that holds all the documents displayed in the grid, and I have a selectedDocument property of type Document that should hold the last selected document. This is set in a click event on the row setSelected.

My view is as follows:

<template>
<require from="../../resources/valueconverters/itemIdFormat"></require>
<require from="./documentData.css"></require>
<h1>Patient Documents</h1>

<p>This component demonstrates fetching data from DME.</p>

<p if.bind="!documents"><em>Loading...</em></p>

<table if.bind="documents" class="table">
    <thead>
        <tr>
            <th>Id</th>
            <th>Title</th>
            <th>Patient</th>
        </tr>
    </thead>
    <tbody>
        <tr repeat.for="document of documents" click.trigger="setSelected(document)" class="${($document.title == $selectedDocument.title) ? 'selected' : 'notSelected'}">
            <td>${ document.itemId | itemIdFormat }</td>
            <td>${ document.title }</td>
            <td>${ document.patient.lastName}, ${ document.patient.firstName }</td>
        </tr>
    </tbody>
</table>

My ViewModel class is:

import { HttpClient } from 'aurelia-fetch-client';
import { BindingEngine, inject } from 'aurelia-framework';

@inject(HttpClient, BindingEngine)
export class DocumentData {
public documents: Document[];
public selectedDocument: Document;
public bindingEngine

constructor(http: HttpClient, bindingEngine) {
    this.bindingEngine = bindingEngine;
    this.selectedDocument = null;
    let subscription = this.bindingEngine
        .propertyObserver(this, 'selectedDocument')
        .subscribe(this.selectedDocumentChanged);


    http.fetch('/api/Data/PatientDocuments')
        .then(result => result.json() as Promise<Document[]>)
        .then(data => {
            this.documents = data;
        });
}

setSelected(selected: Document) {
    this.selectedDocument = selected;
}

selectedDocumentChanged(newValue, oldValue) {
   // this.documents.forEach(function (d) {
   //     d.isCurrent = d === newValue;
   // })
}
}

As you can see in the view html, I have set the class attribute on the table row element with:

class="${($document.title == $selectedDocument.title) ? 'selected' : 'notSelected'}"

However for some reason this always returns true so all rows are highlighted.

So I tried this instead:

class="${$document.isCurrent ? 'selected' : 'notSelected'}"

Then I used the binding engine subscribe function on the selectedDocument property, setting it to run selectedDocumentChanged method, to set the isCurrent property on each document in the list manually whenever the selectedDocument property is changed. Notice however that this is commented out. This is because the this variable is not in scope in the subscriber changed method so I can't use it.

So I am a bit stuck. What is the correct way to highlight the selected row? I guess it might be possible using a nested component repeated for each row, but I would prefer to be able to achieve this all in this component.


Solution

  • Firstly - the scope issue you're getting with the selectedDocumentChanged() method is because you're using the Binding Engine. If you use the observable decorator instead like below, this will no longer be out of scope;

    import {observable} from "aurelia-framework";
    
    export class DocumentData {
    
        @observable selectedDocument;
    
        // Your other code
    
        selectedDocumentChanged(newVal, oldVal) {
            this.documents.forEach(function (d) { // no longer out of scope
                d.isCurrent = d === newValue;
            })
        }
    }
    

    Secondly - in your template you don't need to prefix your properties with a $. This is only used when you want to use string interpolation within your templates. If you're looping through, or comparing properties then you can just use them as normal. For example;

    class="${document.isCurrent ? 'selected' : 'notSelected'}"
    

    or;

    class="${(document.title == selectedDocument.title) ? 'selected' : 'notSelected'}"