我正在浏览Aurelia tutorials,在完成ContactManager教程之后,我认为我通过将Contact Manager站点修改为任务列表站点来获得一些练习。我已将联系人列表组件和联系人详细信息组件更改为使用任务而不是联系人。基本上,当我单击任务列表中的任务时,路由器会打开任务详细信息组件;与Contact Manager教程中的工作方式非常相似。
在任务详细信息组件中,我在<p>
元素中有一些文本,它使用字符串插值绑定到某些模型值。
<p>Date: | ${task.startDate} | ${task.dueDate}</p>
当我第一次点击任务列表中的任务时,任务详细信息视图会在<router-view>
元素中打开,并且插入的字符串会被正确呈现,例如Dates: | 12/25/2017 | 1/25/2018
如果我单击任务列表中的其他任务,则任务详细信息视图中的所有字段都会正确更改,但<p>
元素中的文本除外。它变为Dates: | |
要显示<p>
元素中的文本,我必须通过让路由器放入另一个视图然后再次选择另一个任务来清除选择。这将重新渲染视图,并再次显示插值。
为什么我必须重新渲染任务详细信息视图,以便将viewmodel中的值绑定到视图?
我将发布下面的代码和标记,但我也将一些示例代码推送到public Github repo以防万一。我尝试将它放入Gist.Run,但由于我使用的是Typescript,这证明是有问题的。
任务list.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>
任务list.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);
}
}
任务detail.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>
任务detail.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');
}
}
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>
元素中的插值字符串被绑定?通过切换到新视图,所有其他元素似乎都可以正确绑定。我知道可能有些事情我不明白这一切是如何起作用的,而且我正朝着正确的方向努力。
答案 0 :(得分:1)
绑定没有问题。它是input type="date"
混乱的事情。您可以通过执行以下操作来验证这一点:
在task-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);
});
}
在task-detail.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);
}
然后第一次打开任务:
现在导航到另一个任务:
您可以看到(在控制台警告消息中)日期现在处于无效格式,因此控件拒绝此值。这种拒绝传播到绑定中,因为它是双向的。这会导致删除任务对象中的值。
但是,如果您将输入类型更改为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,没有标准方法可以更改input type="date"
使用的格式,因此您应该使用标准yyyy-MM-dd
格式。如果您在task-web-api.ts中更改了虚拟数据,那么事情也开始与input type="date"
一起使用。
同时,确实很奇怪,在第一次绑定之后不会发生这种情况但后来会发生这种情况。但是,我不相信Aurelia导致这个问题,它可能是浏览器。不过,它可能值得创建一个问题。