使用Aurelia repeat.for时,如何从列表中启用多个选择?

时间:2017-02-15 20:05:13

标签: aurelia aurelia-binding

我有一个视图,允许用户查看项目列表并选择一个项目,然后启用一些按钮,如下所示:

查看模型

export class ListViewCustomElement{
   @bindable rows= []

   selectedRow= null

   deleteRow() {
     let event = new CustomEvent('delete-row',{
       item: selectedItem,
       bubbles: true
     }

     this.element.dispatchEvent(event)
}

查看

<!-- delete button enabled when user selected -->
<i class="button fa fa-times" if.bind="selectedRow" click.delegate="deleteRow()"></i>

<div repeat.for="row of rows" click.delegate="selectedRow = row">
    <i class="fa ${selectedRow == row ? 'fa-check-circle' : 'fa-check-circle-o'}"></i>
    ${row.item1} ${row.item2}
</div>

parentview

<list-view rows.bind="users" delete-item.delegate="deleteUser($event)"></list-view>

现在我需要允许选择多个用户。如果已选择该用户,我仍然需要显示每行的图标。此外,我的deleteRow()函数需要在事件中传递selectedRows列表,而不仅仅是单个用户。

最简单的方法是为每个行元素添加一个select属性,但我不能这样做,因为它会弄脏我的数据对象。

ATTEMPT 1

查看模型

//added to the above vm
selectedRows = []

selectClick(indexRef){
    let index = this.selectedRows.indexOf(indexRef)
    if(index < 0){
       this.selectedRows.push(indexRef)
    }
    else{
       this.selectedRows.splice(index,1)
    }
}

查看

<!--Changed to call new function -->
<div ... click.delegate="selectClick($index)">
    <i class="fa ${selectedRows.indexOf($index) < 0 ? 'fa-check-circle-o' : 'fa-circle-check'}"></i>

除了视图中的图标外,这一切都有效 - 在从selectedRows添加和删除项目时,indexOf不会被评估

ATTEMPT 2

查看

<div ref="rowItems">
   <div ref="rowItems[$index].unselected" repeat.for="row of rows" 
       click.delegate="rowItems[$index].unselected = !rowItems[$index].unselected">
      <i class="fa ${rowItems[$index].unselected ? 'fa-check-circle-o' : 'fa-check-circle'}"></i>

但是,这种方法很难启用/禁用删除按钮,并且当父视图实际删除所选记录时也会导致问题,因为视图更新但rowItems属性的未选定标志未重置

关于实现我之后的最佳方式的任何想法?

2 个答案:

答案 0 :(得分:0)

也许我错过了你的问题,但我认为你只想要这样的东西:

<div repeat.for="row of rows" click.delegate="row.isSelected = !row.isSelected">
    <i class="fa ${row.isSelected ? 'fa-check-circle' : 'fa-check-circle-o'}"></i>
    ${row.item1} ${row.item2}
</div>

如果行对象没有isSelected属性,那么最初它会解析为undefined,这是假的。用户点击一次后,!会将其强制转换为bool值为true,进一步点击将切换该布尔值。

我假设您希望在课程的稍后阶段对选择做些什么。然后你可以使用javascript的数组过滤功能来获取所有选中的项目:

var selectedRows = rows.filter(function(row) { return row.isSelected; });

<强>更新: 对不起,我以前误解了你的约束。我同意Eliran Malka。我认为在这些场景中的一般方法是,人们会将他们的数据从服务接收到他们的视图模型中,然后将数据操作到视图特定的对象中,这些对象将被绑定到视图。如果需要保存这些对象中的某些数据,那么会有一些东西可以将这些视图对象转换回服务对象。因此isSelected属性不会使您的数据变得肮脏。

话虽如此,根据您需要跟踪这些变化的内容和原因,有一些不同的选择,复杂程度不同。

我能用您提供的信息看到的最简单的方法与第一种方法类似。如果这还不够,请告诉我,我们可以尝试其他选择。

查看

<div repeat.for="row of rows" click.delegate="selectedIndices[$index] = !selectedIndices[$index]">
    <i class="fa ${${selectedIndices[$index] ? 'fa-check-circle' : 'fa-check-circle-o'}"></i>
    ${row.item1} ${row.item2}
</div>

<强>视图模型

selectedIndices = {}

答案 1 :(得分:0)

所以这就是我最终解决这个问题的方法。

<强>视图模型

import {BindingEngine} from 'aurelia-binding'
@inject(Element,BindingEngine)

...

selectedItems = {}
selectedItemsCount = 0   //used to enable/disable buttons

constructor(element,bindingEngine){
    this.element = element
    this.bindingEngine = bindingEngine

    this.psub = this.bindingEngine
       .propertyObserver(this,"rows")
       .subscribe((data) => {
            if(this.asub) this.asub.dispose();

            if(!Array.isArray(data)) return;

            this.asub = this.bindingEngine
              .collectionObserver(this.rows)
              .subscribe(changes => {
                  if(changes[0].removed.length){
                      for(let item in this.selectedItems){
                          if(!this.rows.find(v => {return v.id == item})){
                             this.selectedItemsCount -= (this.selectedItems[item] ? 1 : 0)
                             delete this.selectedItems[item]
                          }
                      }
                  }
              })
        })
}    

selectClick(id){
    this.selectedItems[id] = !this.selectedItems[id]

    let total = 0
    for(let item in this.selectedItems){
       total += this.selectedItems[item] ? 1 : 0
    }

    this.selectedItemsCount = total
}

deleteRow(item){
   let responseItem = null
   if(!item){
       responseItem = []
       responseItem = this.rows.filter(v => {return this.selectedItems[v.id]})
   }
   else responseItem = item;

   let event = new CustomEvent('delete-row',{
      detail: {item: responseItem, isList: Array.isArray(responseItem)},
      bubbles: true
   })

   this.element.dispatchEvent(event)
}

首先,实际选择的代码类似于peinearydevelopment所建议的 - 所以感谢让我朝着正确的方向前进。但是,我需要&#34;全球&#34;选择或取消选择项目时,启用/禁用删除按钮(列表外部)。这是selectClick函数中selectedItemsCount更新的目的。

然而,更大的问题是,如果我选择5个项目并单击删除,则父视图(拥有行列表)负责决定是否应删除行并实际删除它们。发生这种情况时,我的行已更新,但我的selectedItemsCount却没有。如果将项目添加到列表中也是如此。

所以,这就是构造函数中属性和集合观察者代码的目的。它确保随时添加或删除集合中的项目,清除selectedItems列表并调整selectedItemsCount

注意:在许多情况下,propertyObserver代码可能不是必需的,但在我的情况下,在调用构造函数时始终不会加载行 - 也不会在视图附加时加载 - 并且数据加载器没有将新数据推送到现有阵列中,而是替换它。替换杀死了我的collectionObserver附加到的数组,因此有必要注意并附加一个新的collectionObserver。

查看

<!-- delete button enabled when user selected -->
<i class="button fa fa-times" if.bind="selectedItemsCount" click.delegate="deleteRow()"></i>

<div repeat.for="row of rows">
    <i class="fa ${selectedItems[row.id] ? 'fa-check-circle' : 'fa-check-circle-o'}"></i>
${row.item1} ${row.item2}
</div>

我确信Aurelia团队会有更优雅的方式来处理这一切;但希望这对其他人也有帮助。

再次感谢peinearydevelopment和Eliran的投入。我同意你们的看法,即每行添加一个isSelected标志将是一个更多更简单的解决方案。