前言:
此时朋友们对“css文字两边留空”大体比较关切,朋友们都需要了解一些“css文字两边留空”的相关内容。那么小编在网络上网罗了一些关于“css文字两边留空””的相关内容,希望同学们能喜欢,看官们一起来学习一下吧!说明:这是对Odoo官方Owl公开的资料的翻译。以后再添加我在Odoo的Owl源码中领悟到的技术说明
这篇文章的确很烧脑,但是也只能从这里先学习,以后可以回过头来再看看这篇文章,会觉得很清晰。
建议先学习typescript和Qweb的知识
对于本教程,我们将构建一个非常简单的待办事项列表应用程序。 该应用程序应满足以下要求:
让用户创建和删除任务任务可以标记为已完成可以过滤任务以显示活动/已完成的任务
这个项目将是发现和学习一些重要的Owl概念的机会,例如组件,存储以及如何组织应用程序。
1.建立项目
对于本教程,我们将做一个非常简单的项目,其中包含静态文件,并且没有其他工具。 第一步是创建以下文件结构:
该应用程序的入口点是文件index.html,该文件应具有以下内容:
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <title>OWL Todo App</title> <link rel="stylesheet" href="app.css" /> <script src="owl.js"></script> <script src="app.js"></script> </head> <body></body></html>
然后,可以暂时将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应用程序由组件组成(),具有单个根组件。 让我们首先定义一个App组件。 通过以下代码替换app.js中函数的内容:
const { Component } = owl;const { xml } = owl.tags;const { whenReady } = owl.utils;// Owl Componentsclass App extends Component { static template = xml`<div>todo app</div>`;}// 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`<div>todo app</div>`;
注意3:使用xml helper()编写内联模板是不错的选择,但是没有突出显示语法,这使得格式错误的xml非常容易。 一些编辑器在这种情况下支持语法突出显示。 例如,VS Code具有附加Comment tagged template,如果已安装,它将正确显示标签模板:
static template = xml /* xml */`<div>todo app</div>`;
注4:大型应用程序可能希望能够翻译模板。 使用内联模板会使它稍微困难一些,因为我们需要其他工具来从代码中提取xml,并将其替换为转换后的值。
3.显示任务列表
现在已经完成了基础工作,是时候开始考虑任务了。 为了完成我们需要的工作,我们将使用以下键将任务作为对象数组来跟踪:
id:一个数字。 拥有一种唯一标识任务的方法非常有用。 由于标题是用户创建/编辑的内容,因此无法保证其唯一性。 因此,我们将为每个任务生成一个唯一的ID号。title:一个字符串,用来说明任务的含义。isCompleted:一个布尔值,用于跟踪任务的状态
现在,我们决定了状态的内部格式,让我们向App组件添加一些演示数据和模板:
该模板包含一个t-foreach循环以迭代任务。 因为组件是渲染上下文,所以它可以从组件中找到tasks列表。 请注意,我们将每个任务的id用作t-key,这很常见。 有两个CSS类:task-list和task,我们将在下一节中使用它们。
最后,请注意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类:
<div class="task" t-att-class="task.isCompleted ? 'done' : ''">
.task.done { opacity: 0.7;}
注意,这里我们还使用了动态属性。
5.提取任务作为子组件
现在很明显,应该有一个Task组件来封装任务的外观和行为。
这个Task组件将显示一个任务,但是不能拥有该任务的状态:一条数据应该只有一个所有者。 否则会带来麻烦。 因此,Task组件将获得其数据作为prop。 这意味着数据仍归App组件所有,但可以由Task组件使用(无需修改)。
由于我们在移动代码,因此是重构代码的好机会:
这里发生了很多事情:
首先,我们现在在文件顶部定义了一个子组件Task,每当我们定义子组件时,都需要将其添加到其父组件的静态components()键中,以便Owl可以获取对其的引用,模板已从组件中提取出来,以便更轻松地将“视图/模板”代码与“脚本/行为”代码区分开来,Task组件有一个props键:这仅用于验证目的。它说应该给每个task恰好一个道具,称为Task。如果不是这种情况,Owl将抛出错误()。这在重构组件时非常有用最后,要激活道具验证,我们需要将Owl的mode()设置为dev。这是在setup函数中完成的。请注意,在实际生产环境中使用应用程序时,应将其删除,因为由于额外的检查和验证,dev模式会稍微慢一些。6.添加任务(第1部分)
我们仍然使用硬编码任务列表。现在是时候让用户自己添加任务了。第一步是将输入添加到App组件。但是此输入将不在任务列表之内,因此我们需要调整App模板,js和CSS:
<div class="todo-app"> <input placeholder="Enter a new task" t-on-keyup="addTask"/> <div class="task-list"> <t t-foreach="tasks" t-as="task" t-key="task.id"> <Task task="task"/> </t> </div></div>
.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()。 我们还需要通过使用带有useRef钩子的t-ref指令来获得对输入的引用:
<input placeholder="Enter a new task" t-on-keyup="addTask" t-ref="add-input"/>
// 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键时,不会显示任何新任务。 但是,如果添加debugger或console.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组件的请求。 由于App是Task的父级,因此我们可以在Task中触发 trigger ()事件并在App中监听。
在Task中,将input更改为:
<input type="checkbox" t-att-checked="props.task.isCompleted" t-on-click="toggleTask"/>
并添加toggleTask方法:
toggleTask() { this.trigger('toggle-task', {id: this.props.task.id});}
现在,我们需要在App模板中监听该事件:
<div class="task-list" t-on-toggle-task="toggleTask">
并实现toggleTask代码:
toggleTask(ev) { const task = this.tasks.find(t => t.id === ev.detail.id); task.isCompleted = !task.isCompleted;}9.删除任务
现在让我们添加执行删除任务的可能性。 为此,我们首先需要在每个任务上添加一个废纸icon图标,然后像上一节中一样继续进行。
首先,让我们更新Task模板,css和js:
<div class="task" t-att-class="props.task.isCompleted ? 'done' : ''"> <input type="checkbox" t-att-checked="props.task.isCompleted" t-on-click="toggleTask"/> <span><t t-esc="props.task.title"/></span> <span class="delete" t-on-click="deleteTask"></span></div>
.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事件:
<div class="task-list" t-on-toggle-task="toggleTask" t-on-delete-task="deleteTask">
deleteTask(ev) { const index = this.tasks.findIndex(t => t.id === ev.detail.id); this.tasks.splice(index, 1);}10.使用存储
看一下代码,很明显,我们现在有了处理分散在多个地方的任务的代码。 而且,它混合了UI代码和业务逻辑代码。 Owl有一种与用户界面分开管理状态的方法:Store()。
让我们在应用程序中使用它。 (对于我们的应用程序而言)这是一个相当大的重构,因为它涉及从组件中提取所有与任务相关的代码。 这是app.js文件的新内容:
11-在本地存储中保存任务
现在,我们的TodoApp可以很好地运行,除非用户关闭或刷新浏览器! 仅将应用程序的状态保存在内存中确实很不方便。 为了解决这个问题,我们将任务保存在本地存储中。 使用我们当前的代码库,这是一个简单的更改:仅需要更新设置代码。
关键是要使用这样的事实,即商店是一个EventBus(),它在每次更新时都会触发一个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;}
最后,我们需要显示可见的过滤器。 我们可以做到这一点,同时在主列表下方的小面板中显示任务数:
<div class="todo-app"> <input placeholder="Enter a new task" t-on-keyup="addTask" t-ref="add-input"/> <div class="task-list"> <t t-foreach="displayedTasks" t-as="task" t-key="task.id"> <Task task="task"/> </t> </div> <div class="task-panel" t-if="tasks.length"> <div class="task-counter"> <t t-esc="displayedTasks.length"/> <t t-if="displayedTasks.length lt tasks.length"> / <t t-esc="tasks.length"/> </t> task(s) </div> <div> <span t-foreach="['all', 'active', 'completed']" t-as="f" t-key="f" t-att-class="{active: filter.value===f}" t-on-click="setFilter(f)" t-esc="f"/> </div> </div></div>
.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;}使任务标题可单击,以切换其复选框:
<input type="checkbox" t-att-checked="props.task.isCompleted" t-att-id="props.task.id" t-on-click="dispatch('toggleTask', props.task.id)"/><label t-att-for="props.task.id"><t t-esc="props.task.title"/></label>敲打已完成任务的标题:
.task.done label { text-decoration: line-through;}最终代码
现在我们的申请已经完成。 它可以正常工作,UI代码与业务逻辑代码完全隔离,可以测试,所有代码都在150行以下(包括模板!)。
供参考,这是最终代码:
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <title>OWL Todo App</title> <link rel="stylesheet" href="app.css" /> <script src="owl.js"></script> <script src="app.js"></script> </head> <body></body></html>
.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;}
标签: #css文字两边留空 #css文字两边留空怎么办