代码之家  ›  专栏  ›  技术社区  ›  greyseal96

Aurelia-帮助理解我看到的一些绑定行为

  •  1
  • greyseal96  · 技术社区  · 6 年前

    我正在通过 Aurelia tutorials 而且,在完成ContactManager教程后,我想我应该通过将ContactManager站点修改为任务列表站点来进行一些练习。我已将联系人列表组件和联系人详细信息组件更改为使用任务而不是联系人。基本上,当我单击任务列表中的任务时,路由器会打开一个任务详细信息组件;与“联系人管理器”教程中的工作方式非常相似。

    问题

    在任务详细信息组件中,我在 <p> 元素,该元素使用字符串插值绑定到某些模型值。 <p>Date: | ${task.startDate} | ${task.dueDate}</p> 当我第一次单击任务列表中的任务时,任务详细信息视图将在 <router-view> 元素和插值字符串正确渲染,例如。 Dates: | 12/25/2017 | 1/25/2018 如果单击任务列表中的另一个任务,“任务详细信息”视图中的所有字段都会正确更改,但 <p> 要素它变成 Dates: | | 获取 <p> 元素,我必须清除选择,让路由器放入另一个视图,然后再次选择另一个任务。这将重新渲染视图,并再次显示插值。

    为什么必须重新渲染任务详细信息视图才能将viewmodel中的值绑定到视图?

    我将在下面发布代码和标记,但我还将一些示例代码推送到 public Github repo 以防有帮助。我试着把它写成要点。运行,但由于我使用的是Typescript,这被证明是有问题的。

    任务列表组件

    任务列表。html:

    <template>
        <table class="table">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Title</th>
                    <th>Priority</th>
                </tr>
            </thead>
            <tbody>
                <tr repeat.for="task of tasks" class="testClass ${task.id === $parent.SelectedTask.id ? 'active' : ''}" click.delegate="selectTask(task)">
                    <td>${task.id}</td>
                    <td>${task.title}</td>
                    <td>${task.priority}</td>
                </tr>
            </tbody>
        </table>
    </template>
    

    任务列表。ts:

    import {autoinject} from 'aurelia-framework';
    import {TaskWebAPI} from './task-web-api';
    import {Task} from './task-web-api';
    import {Router} from 'aurelia-router';
    
    //@inject(TaskWebAPI)
    @autoinject
    export class TaskList {
        tasks;
        SelectedTask: Task;
    
        constructor(private api: TaskWebAPI, private router: Router) {
            this.SelectedTask = new Task();
            this.SelectedTask.id = 0;
        }
    
        created() {
            this.api.getTaskList().then(tasks => this.tasks = tasks);
        }
    
        selectTask(task) {
            this.router.navigateToRoute("tasks", { id: task.id });
            this.SelectedTask = new Task();
            Object.assign(this.SelectedTask, task);
        }
    }
    

    TaskDetail组件

    任务详细信息。html:

    <template>
        <form class="form-horizontal">
            <div class="form-group">
                <label for="taskTitle">Title</label>
                <input type="text" placeholder="Task Title" id="taskTitle" class="form-control" value.bind="task.title" />
            </div>
            <div class="form-group">
                <label for="taskPriority" class="control-label col-sm-2">Priority</label>
                <div class="col-sm-3">
                    <input type="number" placeholder="Task Priority" id="taskPriority" class="form-control" value.bind="task.priority" />
                </div>
                <label for="taskStatus" class="control-label col-sm-2">Status</label>
                <div class="col-sm-4">
                    <select class="form-control" value.bind="task.status">
                        <option repeat.for="status of taskStatuses">${status}</option>
                    </select>
                </div>
            </div>
            <div class="form-group">
                <label for="taskPctComplete" class="control-label col-sm-2">Percent Complete</label>
                <div class="col-sm-3">
                    <input type="number" placeholder="Task % Complete" id="taskPctComplete" class="form-control" value.bind="task.percentComplete" />
                </div>
            </div>
            <div class="form-group">
                <label for="taskStartDate" class="control-label col-sm-2">Start Date</label>
                <div class="col-sm-4">
                    <input type="date" placeholder="Start Date" id="taskStartDate" class="form-control" value.bind="task.startDate" />
                </div>
                <label for="taskDueDate" class="control-label col-sm-2">Due Date</label>
                <div class="col-sm-4">
                    <input type="date" placeholder="Due Date" id="taskDueDate" class="form-control" value.bind="task.dueDate" />
                </div>
            </div>
            <div class="form-group">
                <label for="taskDescription" class="control-label col-sm-2">Description</label>
                <div class="col-sm-10">
                    <textarea class="form-control" rows="4" id="taskDescription" value.bind="task.description"></textarea>
                    <!--This is the area place where the string interpolation is doing something that I don't understand. -->
                    <p>
                        Dates:    | ${task.startDate} | ${task.dueDate}
                    </p>
                </div>
            </div>
            <button type="button" class="btn btn-default" click.trigger="cancelClick()">Cancel</button>
        </form>
    </template>
    

    任务详细信息。ts

    import {autoinject} from 'aurelia-framework';
    import {TaskWebAPI} from './task-web-api';
    import {Router} from 'aurelia-router';
    
    @autoinject
    export class TaskDetail {
        routeConfig;
        task;
        taskStatuses;
    
        constructor(private api: TaskWebAPI, private router: Router) { }
    
        activate(params, routeConfig) {
            this.routeConfig = routeConfig;
    
            return this.api.getTaskDetails(params.id).then(task => {
                this.task = task;
                this.routeConfig.navModel.setTitle(this.task.title);
            }).then(() => this.api.getTaskStatuses())
            .then((statuses) => this.taskStatuses = statuses);
            //this.api.getTaskStatuses();
        }
    
        cancelClick() {
            this.router.navigateToRoute('noselection');
        }
    }
    

    任务Web Api

    let latency = 200;
    let id = 0;
    
    function getId(){
      return ++id;
    }
    
    export class Task {
      id: number;
      title: string;
      priority: number;
      status: string;
      percentComplete: number;
      description: string;
      startDate: Date;
      dueDate: Date;
    }
    
    let taskStatuses = ['Not Started', 'In Progress', 'Deferred', 'Completed'];
    
    let tasks = [
      {
        id:getId(),
        title:'TestTask1',
        priority:'1',
        status:'In Progress',
        percentComplete:'22',
        description:'This is the first test task.',
        startDate:'12/25/2017',
        dueDate:'1/25/2018'
      },
      {
        id:getId(),
        title:'TestTask2',
        priority:'1',
        status:'In Progress',
        percentComplete:'45',
        description:'This is the second test task.',
        startDate:'1/25/2017',
        dueDate:'11/25/2017'
      },
      {
        id:getId(),
        title:'TestTask3',
        priority:'2',
        status:'In Progress',
        percentComplete:'89',
        description:'This is the third test task.',
        startDate:'4/25/2017',
        dueDate:'9/25/2018'
      },
      {
        id:getId(),
        title:'TestTask4',
        priority:'2',
        status:'In Progress',
        percentComplete:'10',
        description:'This is the fourth test task.',
        startDate:'5/25/2017',
        dueDate:'7/16/2017'
      },
      {
        id:getId(),
        title:'TestTask5',
        priority:'3',
        status:'Not Started',
        percentComplete:'0',
        description:'This is the fifth test task.',
        startDate:'',
        dueDate:''
      }
    ];
    
    export class TaskWebAPI {
      isRequesting = false;
    
      getTaskList(){
        this.isRequesting = true;
        return new Promise(resolve => {
          setTimeout(() => {
            let results = tasks.map(x =>  { return {
              id:x.id,
              title:x.title,
              priority:x.priority,
              status:x.status,
              percentComplete:x.percentComplete,
              description:x.description,
              startDate:x.startDate,
              dueDate:x.dueDate
            }});
            resolve(results);
            this.isRequesting = false;
          }, latency);
        });
      }
    
      getTaskStatuses() {
        this.isRequesting = true;
        return new Promise(resolve => {
          setTimeout(() => {
            let results = taskStatuses;
            resolve(results);
            this.isRequesting = false;
          }, latency);
        });
      }
    
      getTaskDetails(id){
        this.isRequesting = true;
        return new Promise(resolve => {
          setTimeout(() => {
            let found = tasks.filter(x => x.id == id)[0];
            resolve(JSON.parse(JSON.stringify(found)));
            this.isRequesting = false;
          }, latency);
        });
      }
    
      saveTask(task){
        this.isRequesting = true;
        return new Promise(resolve => {
          setTimeout(() => {
            let instance = JSON.parse(JSON.stringify(task));
            let found = tasks.filter(x => x.id == task.id)[0];
    
            if(found){
              let index = tasks.indexOf(found);
              tasks[index] = instance;
            }else{
              instance.id = getId();
              tasks.push(instance);
            }
    
            this.isRequesting = false;
            resolve(instance);
          }, latency);
        });
      }
    }
    

    这可能是一个愚蠢的问题,但为什么每次都必须清除任务详细信息视图,以便在 <p> 要绑定的元素?所有其他元素似乎只需切换到新视图即可正确绑定。我知道这一切的运作方式可能有一些我不明白的地方,我正在寻找一个正确的方向。

    1 回复  |  直到 6 年前
        1
  •  1
  •   Balázs    6 年前

    您的绑定没有问题。这是 input type="date" 那会把事情搞砸的。您可以通过执行以下操作来验证这一点:

    在任务web api中。ts,更改 getTaskDetails 以下内容:

    getTaskDetails(id) {
      this.isRequesting = true;
      return new Promise(resolve => {
        setTimeout(() => {
          let found = tasks.filter(x => x.id == id)[0];
          let res = JSON.parse(JSON.stringify(found));
    
          // Note these
          console.log('Found: ', found);
          console.log('Result: ', res);
    
          resolve(res);
          this.isRequesting = false;
        }, latency);
      });
    }
    

    以及任务细节。ts,更改 activate 对此:

    activate(params, routeConfig) {
        this.routeConfig = routeConfig;
        return this.api.getTaskDetails(params.id).then(task => {
            // Note this:
            console.log('Task: ', task);
    
            this.task = <iTask>task;
            this.routeConfig.navModel.setTitle(this.task.title);
        }).then(() => this.api.getTaskStatuses())
            .then((statuses) => this.taskStatuses = statuses);
    }
    

    然后第一次打开任务:

    enter image description here

    现在导航到另一个任务:

    enter image description here

    您可以看到(在控制台警告消息中)日期现在的格式无效,因此控件拒绝此值。这种拒绝会传播到绑定中,因为它是双向的。这会导致任务对象中的值被擦除。

    但是,如果将输入类型更改为 text 而不是 date ,它将工作:

    <div class="form-group">
      <label for="taskStartDate" class="control-label col-sm-2">Start Date</label>
      <div class="col-sm-4">
        <input type="text" placeholder="Start Date" id="taskStartDate" class="form-control" value.bind="task.startDate" />
      </div>
      <label for="taskDueDate" class="control-label col-sm-2">Due Date</label>
      <div class="col-sm-4">
        <input type="text" placeholder="Due Date" id="taskDueDate" class="form-control" value.bind="task.dueDate" />
      </div>
    </div>
    

    总之,导致这种行为的不是绑定。相反,发生这种情况的原因是格式无效,因此该值被拒绝。自根据 this answer ,没有标准的方法来更改 输入类型=“日期” 使用时,应使用标准 yyyy-MM-dd 总体安排如果在任务web api中更改虚拟数据。ts,事情开始好转了 输入类型=“日期”

    同时,令人奇怪的是,这种情况在第一次绑定之后并没有发生,但在以后会发生。然而,我不认为是Aurelia导致了这个问题,可能是浏览器造成的。尽管如此,还是有必要就此提出一个问题。