4imble
4imble

Reputation: 14416

Aurelia binding to a function with parameter and forcing update

I have bound an isSelected(item) function to my button which will toggle the class to show if it is selected or not, however this function will not update on changes.

I can not use a computed property as it takes a parameter.

Is there any way to make this work with a bound function and getting it to update ideally with a @computedFrom decorator or the like?

Example here:

https://codesandbox.io/s/aurelia-typescript-sandbox-8oksr?fontsize=14

You will notice that person 2 is bound correctly via the function, but clicking the other items will not update the UI.

-

reason for doing this

The reason I want to do this is a result of a somewhat complex source array. Rather than the persons array I have given as an example here. My real domain is closer to a list of boxes with items that can have other boxes (potentially infinitely) where items can be selected at any level.

Upvotes: 2

Views: 1279

Answers (2)

pavelsevcik
pavelsevcik

Reputation: 514

multi select utilizing signaler

import "./app.css";
import {BindingSignaler} from 'aurelia-templating-resources';
import { inject } from "aurelia-framework";

@inject(BindingSignaler)
export class App {
  message = "Hello World!";
  message2 = "Hello World2!";
  people = [{ name: "Person 1" }, { name: "Person 2" }, { name: "Person 3" }];
  selections = [];

  constructor(private signaler: BindingSignaler) {
    this.selections.push(this.people[1]);
  }

  selectPerson(person) {
    this.selections.push(person);
    this.signaler.signal('select-signal')
  }

  color(person) {
    return this.selections.includes(person) ? 'green' : 'red';
  }
}
<template>
  <h1>People</h1>

  <div repeat.for="person of selections">
    ${person.name}
  </div>

  <button
    class="btn-gradient mini ${color(person) & signal:'select-signal' }"
    click.delegate="selectPerson(person)"
    repeat.for="person of people"
  >
    ${person.name}
  </button>
</template>

single select

add selected into class, assign person in it on change and then use selected === person as condition

import "./app.css";

export class App {
  message = "Hello World!";
  message2 = "Hello World2!";
  people = [{ name: "Person 1" }, { name: "Person 2" }, { name: "Person 3" }];
  selections = [];

  // add selected
  selected = null;

  constructor() {

    // use selectPerson
    this.selectPerson(this.people[1]);
  }

  selectPerson(person) {
    this.selections.push(person);

    // assign person to selected
    this.selected = person;
  }
}

<template>
  <h1>People</h1>

  <div repeat.for="person of selections">
    ${person.name}
  </div>

  <!-- use `selected === person` as condition -->
  <button
    class="btn-gradient mini ${selected === person ? 'green': 'red'}"
    click.delegate="selectPerson(person)"
    repeat.for="person of people"
  >
    ${person.name}
  </button>
</template>

Upvotes: 0

RHarris
RHarris

Reputation: 11187

Update 2

So I ran across this issue on github. One of the answers indicated that passing an observed item into a method automatically makes the method observable. Currently, you're only passing person into isSelected(). But, person isn't being changed. I think you can accomplish what you're looking for by changing your isSelected() method like so (notice the change in the call to isSelected in the class binding of the button):

vm.ts

public isSelected(person, length){
   return this.selections.find(item => item.name === person.name);
}

view.html

<button
    class="btn-gradient mini ${isSelected(person, selections.length) ? 'green': 'red'}"
    click.delegate="selectPerson(person)"
    repeat.for="person of people">
    ${person.name}
  </button>

Example: https://codesandbox.io/s/aurelia-typescript-sandbox-oelt7?fontsize=14

Original Post

I'm struggling with the same issue with trying to implement an isSelected() method for controlling a selected indicator class. I've looked into @computedFrom.

I may be wrong on this, but from what I've seen @computedFrom can only be used with un-paramaterized getters.

@computedFrom('firstName', 'lastName')
get fullName() { return `${firstName} ${lastName}`}

So the problem with what we're wanting to do is that we need to pass in an index or an item to our method -- which breaks our ability to use @computedFrom.

An alternative, which I don't really like ... but it does work, is to add an isSelected property to each of your person objects. Then your code would look something like this:

vm.ts

selectPerson(person){
    person.isSelected = !person.isSelected;  //De-selects if already selected
}

view.html

<button
class="btn-gradient mini ${person.isSelected ? 'green': 'red'}"
click.delegate="selectPerson(person)"
repeat.for="person of people">${person.name}</button>

(or, as was recently suggested to me, wrap your person object in a wrapper class)

public class SelectableWrapper {
   constructor(public person : Person, public isSelected : boolean){}
}

Update 1

To address the issue of displaying the list of selected items (as well as "coloring" the selected items), you could do the following (in addition to what I've already shown):

vm.ts

//Remove selections property and add it as a getter
get selections(){
   return this.people.filter(p => p.isSelected);
}

view.html

<div repeat.for = "person of selections">
   ${person.name}
</div>

Example here: https://codesandbox.io/s/aurelia-typescript-sandbox-u92nk?fontsize=14

Upvotes: 1

Related Questions