当前位置:   article > 正文

mfc 已经设置edit只能输入数字如何动态改变值_OWL教程:如何编写Todo待办事项列表应用程序...

mfc edit只能输入数字

说明:这是对Odoo官方Owl公开的资料的翻译。以后再添加我在Odoo的Owl源码中领悟到的技术说明

这篇文章的确很烧脑,但是也只能从这里先学习,以后可以回过头来再看看这篇文章,会觉得很清晰。

建议先学习typescript和Qweb的知识

对于本教程,我们将构建一个非常简单的待办事项列表应用程序。 该应用程序应满足以下要求:

  • 让用户创建和删除任务
  • 任务可以标记为已完成
  • 可以过滤任务以显示活动/已完成的任务

这个项目将是发现和学习一些重要的Owl概念的机会,例如组件,存储以及如何组织应用程序。

1.建立项目

对于本教程,我们将做一个非常简单的项目,其中包含静态文件,并且没有其他工具。 第一步是创建以下文件结构:

cfd716f1e2aec78db3e45cbeddb1efc3.png

该应用程序的入口点是文件index.html,该文件应具有以下内容:

      OWL Todo App

然后,可以暂时将app.css留空。 以后对我们的应用程序进行样式设置将很有用。 app.js是我们编写所有代码的地方。 现在,我们只需要输入以下代码:

(function () {  console.log("hello owl", owl.__info__.version);})();

请注意,我们将所有内容放入立即执行的函数中,以避免将任何内容泄漏到全局范围。

最后,owl.js应该是从Owl存储库下载的最新版本(如果愿意,可以使用owl.min.js)。

现在,该项目应该已经准备好了。 将index.html文件加载到浏览器中时,应显示一个空白页面,标题为Owl Todo App,并且应在控制台中记录一条消息,例如hello owl 1.0.0

2.添加第一个组件

Owl应用程序由组件组成(https://github.com/odoo/owl/blob/master/doc/reference/component.md),具有单个根组件。 让我们首先定义一个App组件。 通过以下代码替换app.js中函数的内容:

const { Component } = owl;const { xml } = owl.tags;const { whenReady } = owl.utils;// Owl Componentsclass App extends Component {  static template = xml`
todo app
`;}// Setup codefunction setup() { const app = new App(); app.mount(document.body);}whenReady(setup);

现在,在浏览器中重新加载页面应该显示一条消息。

代码非常简单,但是让我们更详细地解释最后一行。浏览器尝试尽快执行app.js中的javascript代码,并且当我们尝试安装App组件时,可能发生DOM尚未准备就绪的情况。为了避免这种情况,我们使用whenReady帮助程序将setup函数的执行延迟到DOM准备就绪为止。

注意1:在更大的项目中,我们将代码分成多个文件,组件位于子文件夹中,而主文件则将初始化应用程序。但是,这是一个很小的项目,我们希望使其尽可能简单。

注意2:本教程使用静态类字段语法。并非所有浏览器都支持此功能。大多数实际项目都会转换其代码,因此这不是问题,但是对于本教程而言,如果您需要在每个浏览器上都能使用的代码,则需要将每个static关键字转换为对该类的分配:

class App extends Component {}App.template = xml`
todo app
`;

注意3:使用xml helper(https://github.com/odoo/owl/blob/master/doc/reference/tags.md#xml-tag)编写内联模板是不错的选择,但是没有突出显示语法,这使得格式错误的xml非常容易。 一些编辑器在这种情况下支持语法突出显示。 例如,VS Code具有附加Comment tagged template,如果已安装,它将正确显示标签模板:

  static template = xml /* xml */`
todo app
`;

注4:大型应用程序可能希望能够翻译模板。 使用内联模板会使它稍微困难一些,因为我们需要其他工具来从代码中提取xml,并将其替换为转换后的值。

3.显示任务列表

现在已经完成了基础工作,是时候开始考虑任务了。 为了完成我们需要的工作,我们将使用以下键将任务作为对象数组来跟踪:

  • id:一个数字。 拥有一种唯一标识任务的方法非常有用。 由于标题是用户创建/编辑的内容,因此无法保证其唯一性。 因此,我们将为每个任务生成一个唯一的ID号。
  • title:一个字符串,用来说明任务的含义。
  • isCompleted:一个布尔值,用于跟踪任务的状态

现在,我们决定了状态的内部格式,让我们向App组件添加一些演示数据和模板:

d2b4ff65504bfc9041a4936039cd323e.png

该模板包含一个t-foreach循环以迭代任务。 因为组件是渲染上下文,所以它可以从组件中找到tasks列表。 请注意,我们将每个任务的id用作t-key,这很常见。 有两个CSS类:task-listtask,我们将在下一节中使用它们。

最后,请注意t-att-checked属性的使用:通过t-att为属性添加前缀可以使其具有动态性。 Owl将评估表达式并将其设置为属性的值。

4.布局:一些基本的CSS

到目前为止,我们的任务列表看起来很糟糕。 让我们将以下内容添加到app.css中:

.task-list {  width: 300px;  margin: 50px auto;  background: aliceblue;  padding: 10px;}.task {  font-size: 18px;  color: #111111;}

这个更好。 现在,让我们添加一个额外的功能:完成的任务的样式应略有不同,以使它们变得不那么重要。 为此,我们将在每个任务上添加一个动态CSS类:

    
.task.done {  opacity: 0.7;}

注意,这里我们还使用了动态属性。

5.提取任务作为子组件

现在很明显,应该有一个Task组件来封装任务的外观和行为。

这个Task组件将显示一个任务,但是不能拥有该任务的状态:一条数据应该只有一个所有者。 否则会带来麻烦。 因此,Task组件将获得其数据作为prop。 这意味着数据仍归App组件所有,但可以由Task组件使用(无需修改)。

由于我们在移动代码,因此是重构代码的好机会:

e9ca77201c321b1900fb2e82d054679a.png
a03e7c5cc77a07661fc7851e6d27d662.png

这里发生了很多事情:

首先,我们现在在文件顶部定义了一个子组件Task,每当我们定义子组件时,都需要将其添加到其父组件的静态components(https://github.com/odoo/owl/blob/master/doc/reference/component.md#static-properties)键中,以便Owl可以获取对其的引用,模板已从组件中提取出来,以便更轻松地将“视图/模板”代码与“脚本/行为”代码区分开来,Task组件有一个props键:这仅用于验证目的。它说应该给每个task恰好一个道具,称为Task。如果不是这种情况,Owl将抛出错误(https://github.com/odoo/owl/blob/master/doc/reference/props_validation.md)。这在重构组件时非常有用最后,要激活道具验证,我们需要将Owl的mode(https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode)设置为dev。这是在setup函数中完成的。请注意,在实际生产环境中使用应用程序时,应将其删除,因为由于额外的检查和验证,dev模式会稍微慢一些。

6.添加任务(第1部分)

我们仍然使用硬编码任务列表。现在是时候让用户自己添加任务了。第一步是将输入添加到App组件。但是此输入将不在任务列表之内,因此我们需要调整App模板,js和CSS:

df2a5c9ee0298674cb2bcb1a700e86b6.png
.todo-app {  width: 300px;  margin: 50px auto;  background: aliceblue;  padding: 10px;}.todo-app > input {  display: block;  margin: auto;}.task-list {  margin-top: 8px;}

现在,我们有了一个有效的输入,只要用户添加了任务,该输入就会登录到控制台。 请注意,当您加载页面时,输入未聚焦。 但是添加任务是任务列表的核心功能,因此让我们通过集中输入使其尽可能快。

由于App是组件,因此它具有可实现的mounted lifecycle method(https://github.com/odoo/owl/blob/master/doc/reference/component.md#lifecycle)。 我们还需要通过使用带有useRef钩子的t-ref指令来获得对输入的引用:

// on top of file:const { useRef } = owl.hooks;
// in AppinputRef = useRef("add-input");mounted() {    this.inputRef.el.focus();}

inputRef被定义为类字段,因此等效于在构造函数中对其进行定义。 它仅指示Owl使用相应的t-ref关键字保留对任何内容的引用。 然后,我们实现mounted的生命周期方法,现在我们有了一个活动引用,可以用来集中输入。

7.添加任务(第2部分)

在上一节中,我们完成了所有工作,除了实现了实际创建任务的代码! 所以,让我们现在开始。

我们需要一种生成唯一id号的方法。 为此,我们只需在App中添加一个nextId号。 同时,让我们在App中删除演示任务:

nextId = 1;tasks = [];

现在,可以实现addTask方法:

addTask(ev) {    // 13 is keycode for ENTER    if (ev.keyCode === 13) {        const title = ev.target.value.trim();        ev.target.value = "";        if (title) {            const newTask = {                id: this.nextId++,                title: title,                isCompleted: false,            };            this.tasks.push(newTask);        }    }}

这几乎可以用,但是如果您对其进行测试,则会发现当用户按下Enter键时,不会显示任何新任务。 但是,如果添加debuggerconsole.log语句,您将看到代码实际上按预期运行。 问题在于,Owl无法知道它是否需要重新呈现用户界面。 我们可以通过使用useState挂钩使tasks具有反应性来解决此问题:

// on top of the fileconst { useRef, useState } = owl.hooks;// replace the task definition in App with the following:tasks = useState([]);

现在可以正常工作了!

8.切换任务

如果您尝试将任务标记为已完成,则可能已经注意到文本的不透明度没有改变。 这是因为没有代码可以修改isCompleted标志。

现在,这是一个有趣的情况:任务由Task组件显示,但它不是其状态的所有者,因此无法修改它。 相反,我们希望传达将任务切换到App组件的请求。 由于AppTask的父级,因此我们可以在Task中触发 trigger (https://github.com/odoo/owl/blob/master/doc/reference/event_handling.md)事件并在App中监听。

Task中,将input更改为:

并添加toggleTask方法:

toggleTask() {    this.trigger('toggle-task', {id: this.props.task.id});}

现在,我们需要在App模板中监听该事件:

并实现toggleTask代码:

toggleTask(ev) {    const task = this.tasks.find(t => t.id === ev.detail.id);    task.isCompleted = !task.isCompleted;}

9.删除任务

现在让我们添加执行删除任务的可能性。 为此,我们首先需要在每个任务上添加一个废纸icon图标,然后像上一节中一样继续进行。

首先,让我们更新Task模板,css和js:

.task {  font-size: 18px;  color: #111111;  display: grid;  grid-template-columns: 30px auto 30px;}.task > input {  margin: auto;}.delete {  opacity: 0;  cursor: pointer;  text-align: center;}.task:hover .delete {  opacity: 1;}
deleteTask() {    this.trigger('delete-task', {id: this.props.task.id});}

现在,我们需要监听App中的delete-task事件:

deleteTask(ev) {    const index = this.tasks.findIndex(t => t.id === ev.detail.id);    this.tasks.splice(index, 1);}

10.使用存储

看一下代码,很明显,我们现在有了处理分散在多个地方的任务的代码。 而且,它混合了UI代码和业务逻辑代码。 Owl有一种与用户界面分开管理状态的方法:Store(https://github.com/odoo/owl/blob/master/doc/reference/store.md)。

让我们在应用程序中使用它。 (对于我们的应用程序而言)这是一个相当大的重构,因为它涉及从组件中提取所有与任务相关的代码。 这是app.js文件的新内容:

eef95aea088c0c2eab2abe12172caa78.png
93bc01407f5bd6b7ca2892842c15a080.png
067c6dbdecf00f3e83aa95b981384c76.png
ceec7e2670424cb3b00f102eea8278cb.png

11-在本地存储中保存任务

现在,我们的TodoApp可以很好地运行,除非用户关闭或刷新浏览器! 仅将应用程序的状态保存在内存中确实很不方便。 为了解决这个问题,我们将任务保存在本地存储中。 使用我们当前的代码库,这是一个简单的更改:仅需要更新设置代码。

01b6130c049acb5d275e80b8c36bd9ca.png

关键是要使用这样的事实,即商店是一个EventBus(https://github.com/odoo/owl/blob/master/doc/reference/event_bus.md),它在每次更新时都会触发一个update事件。

12.过滤任务

我们差不多完成了,我们可以添加/更新/删除任务。 唯一缺少的功能是可以根据任务的完成状态显示任务。 我们将需要跟踪App中过滤器的状态,然后根据其值过滤可见任务。

// on top of file, readd useState:const { useRef, useDispatch, useState, useStore } = owl.hooks;// in App:filter = useState({value: "all"})get displayedTasks() {    switch (this.filter.value) {        case "active": return this.tasks.filter(t => !t.isCompleted);        case "completed": return this.tasks.filter(t => t.isCompleted);        case "all": return this.tasks;    }}setFilter(filter) {    this.filter.value = filter;}

最后,我们需要显示可见的过滤器。 我们可以做到这一点,同时在主列表下方的小面板中显示任务数:

/ task(s)
.task-panel {  color: #0088ff;  margin-top: 8px;  font-size: 14px;  display: flex;}.task-panel .task-counter {  flex-grow: 1;}.task-panel span {  padding: 5px;  cursor: pointer;}.task-panel span.active {  font-weight: bold;}

请注意,这里我们使用对象语法动态设置了过滤器的类:每个键都是要设置为真的类。

13.最后的接触

我们的清单功能齐全。 我们仍然可以添加一些额外的细节来改善用户体验。

当用户鼠标悬停在任务上时,添加视觉反馈:
.task:hover {  background-color: #def0ff;}
使任务标题可单击,以切换其复选框:
敲打已完成任务的标题:
.task.done label {  text-decoration: line-through;}

最终代码

现在我们的申请已经完成。 它可以正常工作,UI代码与业务逻辑代码完全隔离,可以测试,所有代码都在150行以下(包括模板!)。

供参考,这是最终代码:

      OWL Todo App
1d606958b3ced40bfe5310588df12f1d.png
5d3b3a906320f120940580c36d1848eb.png
dbd00c8f8c06a844f620df38efd318b7.png
2006c684cb2b3e333c1b129770e1f3df.png
1a436e0dc764167ec80353bf0e4ecae6.png
.todo-app {  width: 300px;  margin: 50px auto;  background: aliceblue;  padding: 10px;}.todo-app > input {  display: block;  margin: auto;}.task-list {  margin-top: 8px;}.task {  font-size: 18px;  color: #111111;  display: grid;  grid-template-columns: 30px auto 30px;}.task:hover {  background-color: #def0ff;}.task > input {  margin: auto;}.delete {  opacity: 0;  cursor: pointer;  text-align: center;}.task:hover .delete {  opacity: 1;}.task.done {  opacity: 0.7;}.task.done label {  text-decoration: line-through;}.task-panel {  color: #0088ff;  margin-top: 8px;  font-size: 14px;  display: flex;}.task-panel .task-counter {  flex-grow: 1;}.task-panel span {  padding: 5px;  cursor: pointer;}.task-panel span.active {  font-weight: bold;}
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/82973
推荐阅读
相关标签
  

闽ICP备14008679号