Aurelia ValueConverter fromView 与 multi-select 混淆

Aurelia ValueConverter fromView confusion with multi-select

我正在尝试在多 select 表单的上下文中使用和理解 Aurelia ValueConverter。我认为很简单的事情,结果对我来说是一个挑战。

我有一个用于创建新交易的表格,该表格通过多 select 输入字段分配了多个类别。我已将表单的输出绑定到 new_deal.categorizations(在数据库中,交易通过分类有类别)。

现在在创建时,通过 'brute force' 方法,我将每个类别 ID 转换为 {category_id: id} 对象,然后再发布到 API。

仅记录 POST 输出的示例:

  create(){
      var categorizations = this.new_deal.categorizations;
      this.new_deal.categorizations = categorizations.map(function (e) {
          return {category_id: e}
      });
      logger.debug ('POST: ', JSON.stringify(this.new_deal));
  }

示例输出:

POST:  {"name":"new deal","categorizations":[{"category_id":"1"},{"category_id":"2"}]}

但我认为最好通过 ValueConverter 来完成。

Plunker 是 here 的完整代码,但它基本上是:

app.js:

export class App {
  constructor(){
    this.categories = [{id: 1, name: 'test1'}, {id: 2, name: 'test2'}];
    this.new_deal = {
        name:       'new deal',
        categorizations: null,
    };
  }

  create(){
      var categorizations = this.new_deal.categorizations;
      this.new_deal.categorizations = categorizations.map(function (e) {return {category_id: e}});
      logger.debug ('POST: ', JSON.stringify(this.new_deal));
  }

  create2(){
      logger.debug ('POST: ', JSON.stringify(this.new_deal));
  }

}

export class CategoryToIDValueConverter {
    fromView(id) {
        return id ? id: null;
    }
}

和app.html:

<template>
  <h1>Testing ValueConverter</h1>
   <h3 >New Brute Force Deal</h3>
     <form role="form">
       <label>Name</label>
          <input type="text" placeholder="Ex. Buy One Get One Free" value.bind="new_deal.name">
       <label>Categories</label>
          <select value.bind="new_deal.categorizations" multiple size="2">
              <option repeat.for="category of categories" value.bind="category.id">${category.name}</option>
          </select>
       </form>
       <button type="submit" click.delegate="create()">Save</button>

  <h3>New ValueConverter Deal</h3>
    <form role="form">
      <label>Name</label>
          <input type="text" placeholder="Ex. Buy One Get One Free" value.bind="new_deal.name">
      <label>Categories</label>
          <select class="form-control" value.bind="new_deal.categorizations | categoryToID" multiple size="2">
            <option repeat.for="category of categories" value.bind="category.id">${category.name}</option>
          </select>
    </form>
    <button class="btn btn-success" type="submit" click.delegate="create2()">Save</button>
</template>

有了这个,我得到了

的输出
POST:  {"name":"new deal","categorizations":["1","2"]}

在 app.js 的 fromView 中,我认为我可以更改:

return id ? id: null;

到return一个对象而不是一个单独的值:

return id ? {category_id: id} : null

但这会导致此错误:

Uncaught Error: Only null or Array instances can be bound to a multi-select.

经进一步检查,id 似乎以数组形式进入 fromView...

所以我将 fromView 修改为:

    fromView(id) {
        if(id){
          var categorizations = [];
          id.forEach(function(cat_id){
            categorizations.push({category_id: cat_id})
          });
          logger.debug(categorizations);
          logger.debug(Object.prototype.toString.call(categorizations));
          return categorizations;
        } else { return null; }
    }
}

试图期待一个数组,然后构建一个分类对象数组到 return,但是正如您在 this Plunker 中看到的那样,它会在您单击时丢失 select (虽然调试日志显示正在创建的对象)。

您有一个类别对象数组,每个对象都有一个 name(字符串)和 id(数字)。这些将用于填充允许多个 selection:

的 select 元素
export class App {
  categories = [
    { id: 1, name: 'test1'},
    { id: 2, name: 'test2'}
  ];
}
<select multiple size="2">
  <option repeat.for="category of categories">${category.name}</option>
</select>

交易对象由 name(字符串)和 categorizations 组成。分类对象如下所示:{ category_id: 1 }

export class App {
  categories = [
    { id: 1, name: 'test1'},
    { id: 2, name: 'test2'}];

  deal = {
    name: 'new deal',
    categorizations: [],
  }
}

我们想将 select 元素的值绑定到交易对象的分类,这是一个对象数组。这意味着 select 元素的每个选项都需要有一个对象 "value"。 HTMLOptionElement 的值属性只接受字符串。我们分配给它的任何东西都将被强制转换为一个字符串。我们可以将分类对象存储在可以处理任何类型的特殊 model 属性中。有关这方面的更多信息,请参阅 aurelia docs.

<select multiple size="2">
  <option repeat.for="category of categories" model.bind="{ category_id: category.id }">${category.name}</option>
</select>

最后,我们需要将 select 元素的值绑定到交易对象的分类:

<select value.bind="deal.categorizations" multiple size="2">
  <option repeat.for="category of categories" model.bind="{ category_id: category.id }">${category.name}</option>
</select>

总而言之,视图和视图模型如下所示:

export class App {
  categories = [
    { id: 1, name: 'test1'},
    { id: 2, name: 'test2'}];

  deal = {
    name: 'new deal',
    categorizations: [],
  }

  createDeal() {
    alert(JSON.stringify(this.deal, null, 2));
  }
}
<template>
  <form submit.delegate="createDeal()">
    <label>Name</label>
    <input type="text" placeholder="Ex. Buy One Get One Free" value.bind="deal.name">

    <label>Categories</label>
    <select value.bind="deal.categorizations" multiple size="2">
      <option repeat.for="category of categories" model.bind="{ category_id: category.id }">${category.name}</option>
    </select>

    <button type="submit">Save</button>
  </form>
</template>

这是一个有效的 plunker:http://plnkr.co/edit/KO3iFBostdThrHUA0QHY?p=preview

在 Aurelia Gitter 频道 whayes 的帮助下解决了这个问题。所以我希望在 fromView 方法中有一个数组,但我在 ValueConverter 中还需要一个 toView 方法。

export class CategoryToIDValueConverter {
    toView(cats){
      if (cats){
        var ids = [];
        cats.forEach(function(categorization){
          ids.push(categorization.category_id);
        });
        return ids;
      } else { return null; }
    }
    fromView(id) {
        if(id){
          var categorizations = [];
          id.forEach(function(cat_id){
            categorizations.push({category_id: cat_id})
          });
          return categorizations;
        } else { return null; }
    }
}

我也试过,但我最初认为我需要将转换器添加到表单的 select 行和选项行,如下所示:

<select class="form-control" value.bind="new_deal.categorizations | categoryToID" multiple size="2">
            <option repeat.for="category of categories" value.bind="category.id">${category.name}</option>
          </select>

但这实际上是不正确的。我只需要将 categoryToID ValueConverter 应用到 select 行,一切都按预期工作。

工作 Plunker 展示了在您单击保存之前,蛮力方法如何不更改模型,而 ValueConverter 会在您更改 selection 时更改它。

决赛app.js

import {LogManager} from 'aurelia-framework';
let logger = LogManager.getLogger('testItems');

export class App {
  constructor(){
    this.categories = [{id: 1, name: 'test1'}, {id: 2, name: 'test2'}];
    this.new_deal = {
        name:       'new deal',
        categorizations: [],
    };
    setInterval(() => this.debug = JSON.stringify(this.new_deal, null, 2), 100);
  }

  create(){
      var categorizations = this.new_deal.categorizations;
      this.new_deal.categorizations = categorizations.map(function (e) {return {category_id: e}});
      alert(JSON.stringify(this.new_deal, null, 2));
  }

  create2(){
    alert(JSON.stringify(this.new_deal, null, 2));
  }
}

export class CategoryToIDValueConverter {
    toView(cats){
      if (cats){
        var ids = [];
        cats.forEach(function(categorization){
          ids.push(categorization.category_id);
        });
        return ids;
      } else { return null; }
    }
    fromView(id) {
        if(id){
          var categorizations = [];
          id.forEach(function(cat_id){
            categorizations.push({category_id: cat_id})
          });
          return categorizations;
        } else { return null; }
    }
}

决赛app.html

<template>
  <h1>Testing ValueConverter</h1>
   <h3 >New Brute Force Deal</h3>
     <form role="form">
       <label>Name</label>
          <input type="text" placeholder="Ex. Buy One Get One Free" value.bind="new_deal.name">
       <label>Categories</label>
          <select value.bind="new_deal.categorizations" multiple size="2">
              <option repeat.for="category of categories" value.bind="category.id">${category.name}</option>
          </select>
       </form>
       <button type="submit" click.delegate="create()">Save</button>

  <h3>New ValueConverter Deal</h3>
    <form role="form">
      <label>Name</label>
          <input type="text" placeholder="Ex. Buy One Get One Free" value.bind="new_deal.name">
      <label>Categories</label>
          <select class="form-control" value.bind="new_deal.categorizations | categoryToID" multiple size="2">
            <option repeat.for="category of categories" value.bind="category.id">${category.name}</option>
          </select>
    </form>
    <button type="submit" click.delegate="create2()">Save</button>

  <!-- debug -->
  <pre><code>${debug}</code></pre>
</template>