Alexander Mikhalchenko
Alexander Mikhalchenko

Reputation: 4565

Aurelia: always call method in the view (problems after upgrade)

We've upgraded Aurelia (in particular aurelia-framework to 1.0.6, aurelia-bindong to 1.0.3) and now we're facing some binding issues.

There's a list of elements with computed classes, and we had a method int the custom element that contained the list:

getClass(t) {
    return '...' + 
           (this.selected.indexOf(t) !== -1
                ? 'disabled-option' :
                : ''
           ) + (t === this.currentTag 
                ? 'selected-option' 
                : ''
           );
}

And class.one-way="$parent.getClass(t)" for the list element, everything was OK.

After the upgrade it simply stopped to work, so whenever the selected (btw it's bindable) or currentTag properties were modified, the getClass method just wasn't called.

I partially solved this by moving this logic to the view:

class="${$parent.getClass(t) + (selected.indexOf(t) !== -1 ? 'disabled-option' : '') (t === $parent.currentTag ? 'selected-option' : '')}"

I know that looks, well... bad, but that made t === $parent.currentTag work, but the disabled-option class still isn't applied.

So, the question is:

How do I force Aurelia to call methods in attributes in the view?

P.S.

I understand that it might cause some performance issues.

Small note:

I can not simply add a selected attribute to the list element since I don't to somehow modify the data that comes to the custom element and I basically want my code to work properly without making too many changes.

UPD

I ended up with this awesome solution by Fabio Luz with this small edit:

UPD Here's a way to interpret this awesome solution by Fabio Luz.

export class SelectorObjectClass {
    constructor(el, tagger){
        Object.assign(this, el);
        this.tagger = tagger;
    }

    get cssClass(){
        //magic here
    }
}

and

this.shown = this.shown(e => new SelectorObjectClass(e, this));

But I ended up with this (defining an extra array).

Upvotes: 9

Views: 1401

Answers (3)

Alexander Mikhalchenko
Alexander Mikhalchenko

Reputation: 4565

A great solution was offered by Fabio but it caused issues (the data that was two-way bound to the custom element (result of the selection) wasn't of the same type as the input and so on). This definitely can be fixed but it would take a significant amount of time and result in rewriting tests, etc. Alternatively, yeah, we could put the original object as some property blah-blah-blah...

Anyway:

There's another solution, less elegant but much faster to implement.

  1. Let's declare an extra array

    @bindable shownProperties = [];
    
  2. Inject ObserverLocator

  3. Observe the selected array

    this.obsLoc.getArrayObserver(this.selected)
        .subscribe(() => this.selectedArrayChanged);
    
  4. Update the shownProperties

    isSelected(t) {
        return this.selected.indexOf(t) !== -1;
    }
    
    selectedArrayChanged(){
        for(var i = 0; i < this.shown.length; i++){
            this.shownProperties[i] = {
                selected: this.isSelected(this.shown[i])
            }
        }
    } 
    
  5. And, finally, in the view:

    class="... ${shownProperties[$index].selected ? 'disabled-option' : '')} ..."
    

So, the moral of the story:

Don't use methods in the view like I did :)

Upvotes: 1

Fabio
Fabio

Reputation: 11990

You have to use a property instead of a function. Like this:

//pay attention at the "get" before function name
get getClass() {
    //do your magic here
    return 'a b c d e';
}

HTML:

<div class.bind="getClass"></div>

EDIT

I know that it might be an overkill, but it is the nicest solution I found so far:

Create a class for your objects:

export class MyClass {
   constructor(id, value) {
      this.id = id;
      this.value = value;
   }

   get getClass() {
      //do your magic here
      return 'your css classes';
   }
}

Use the above class to create the objects of the array:

let shown = [];
shown[1] = new MyClass('someId', 'someValue');
shown[2] = new MyClass('someId', 'someValue');

Now, you will be able to use getClass property:

<div repeat.for="t of shown" class.bind="t.getClass">...</div>

Hope it helps!

Upvotes: 2

Egor Malkevich
Egor Malkevich

Reputation: 1536

It looks pretty sad.

I miss understand your point for computing class in html. Try that code, it should help you.

computedClass(item){
  return `
     ${this.getClass(item)}
     ${~selected.indexOf(item) ? 'disabled-option': ''}
     ${item === this.currentTag ? 'selected-option' : ''}
  `;
}

Your code not working cause you miss else option at first if state :/

Update: To toggle attribute state try selected.bind="true/false"

Good luck, Egor

Upvotes: 1

Related Questions