0

I am trying to build a custom element to manage simple lists, renaming the items and changing their order. Unfortunately I'm noticing some weird behavior that's actually really hard to pin down.

  1. Typing into the inputs does not appear to be recognized as changes for Aurelia to update the item
  2. When typing/changing one item after page load and then changing its position in array via those methods, the index of the item seems lost (turns into -1). If the item isn't changed via input field, the index in the array is recognized correctly and sorting works.

Are there any known issues with arrays, binding and maybe even child elements? What are the battle tested approached to get the desired behavior? Thanks a lot!

Parent Element

...   
<list items.bind="list"></list>
...   

List Element

<template>
<div class="input-group" repeat.for="item of items">
     <input value.bind="item" class="input"  type="text" placeholder="Item" autofocus>
     <a click.delegate="deleteItem(item)">X</a>
     <a click.delegate="moveItemUp(item)">^</a>
     <a click.delegate="moveItemDown(item)">v</a>
</div>
<a click.delegate="addItem()">Add Item</a>

List JS

 export class List {

  @bindable({defaultBindingMode: bindingMode.twoWay}) items;

  constructor() {}

  addItem() {
    this.items.push('new')
  }

  deleteItem(item) {
    let i = this.items.indexOf(item)
    this.items.splice(i, 1)
  }

  moveItemUp(item) {
    let i = this.items.indexOf(item)
    if (i === 0) return
    let temp = item
    this.items.splice(i, 1)
    this.items.splice(i - 1, 0, temp)
  }

  moveItemDown(item) {
    let i = this.items.indexOf(item)
    if (i === this.items.length) return
    let temp = item
    this.items.splice(i, 1)
    this.items.splice(i, 0, temp)
  }

}
Dwayne Charrington
  • 6,524
  • 7
  • 41
  • 63
Seltsam
  • 854
  • 1
  • 7
  • 27

2 Answers2

3

repeat.for has several contextual variables you can leverage of. [Documentation]

Gist demo: https://gist.run/?id=1c8f78d8a774cc859c9ee2b1ee2c97f3

  • Current item's correct position can be determined by using $index contextual variable instead of items.indexOf(item).
  • Databound values of inputs will be preserved by passing item to items.slice(newIndex, item).

If you need to observe array changes, CollectionObserver could be a great fit for that. More details here: Observing Objects and Arrays in Aurelia.

list.js

import { bindable, bindingMode } from 'aurelia-framework';

export class List {

  @bindable({defaultBindingMode: bindingMode.twoWay}) items;

  constructor() {} 

  addItem() {
    this.items.push(`New Item ${this.items.length + 1}`);
  }

  deleteItem(i) {
    this.items.splice(i, 1);
  }

  moveItemUp(i, item) {
    if (i === 0) 
      return;

    this.moveItem(i, i - 1, item);
  }

  moveItemDown(i, item) {
    if (i === this.items.length - 1) 
      return;

    this.moveItem(i, i + 1, item);
  }

  moveItem(oldIndex, newIndex, item) {
      this.items.splice(oldIndex, 1);
      this.items.splice(newIndex, 0, item);
  }

}

list.html

<template>
    <div class="input-group" repeat.for="item of items">
         <input value.bind="item" class="input"  type="text" placeholder="Item" autofocus> | 
         <a click.delegate="deleteItem($index)"><i class="fa fa-close"></i></a> | 
         <a click.delegate="moveItemUp($index, item)"><i class="fa fa-arrow-up"></i></a> | 
         <a click.delegate="moveItemDown($index, item)"><i class="fa fa-arrow-down"></i></a>
    </div>
    <a click.delegate="addItem()">Add Item</a>
</template>
Marton Sagi
  • 1,247
  • 1
  • 8
  • 10
1

I believe this has to do with the immutability of strings. That is, strings can't be modified, so when you modify a value in the textbox, the array element is actually replaced instead of modified. That's why you're losing the binding.

Here's a gist that demonstrates it working correctly when binding to a list of objects.

https://gist.run/?id=22d186d866ac08bd4a198131cc5b4913

Joseph Gabriel
  • 8,339
  • 3
  • 39
  • 53