快速行动。 编写TodoMVC。 第一部分

显然,关于dap的第一篇文章并没有成为我写作的成功:关于它的绝大多数评论都归结为“ niasilil”和“ niasilil,但我谴责”。 唯一具有建设性的顶级评论的奖项是OldVitus ,它以TodoMVC为例来说明演示dap的建议,以便进行比较。 我将在本文中做什么。

TodoMVC (如果没有人知道的话)就是这样一个标准的UI调用世界,它使您可以使用不同的框架比较相同问题(有条件的“待办事项列表”)的解决方案。 该任务非常简单(其在dap上的解决方案 “在一个屏幕中断”)非常具有说明性。 因此,在她的示例中,我将尝试展示如何使用dap来实现Web前端的典型任务。

我没有搜索和研究问题的正式描述,而是决定简单地反转其中一个示例。 本文的后端对我们来说并不有趣,因此我们不会自己编写,而是使用网站www.todobackend.com上的其中之一 ,然后从此处获取示例客户端和标准CSS文件

要使用dap,您无​​需下载和安装任何程序。 无需npm install ,仅此npm install 。 不需要创建具有特定目录结构,清单和IT成功其他属性的任何项目。 足够的文本编辑器和浏览器。 要调试XHR请求,您可能还需要一个Web服务器-一个简单的服务器,例如Chrome 扩展程序。 我们的整个前端将包含一个.html文件(当然,这是指dap引擎脚本和标准TodoMVC CSS文件)

因此,从头开始。

1.创建一个.html文件


 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Todo -- dap sample</title> <link rel="stylesheet" href="https://www.todobackend.com/client/css/vendor/todomvc-common.css"/> <script src="https://dap.js.org/0.4.js"></script> </head> <body> <script> //   dap </script> </body> </html> 

通常的html准备工作,其中包括网站www.todobackend.com友好提供的CSS文件和dap.js.org网站友好提供的dap引擎。

2.复制原始示例的DOM结构


要使用标准CSS文件而不进行更改,我们将坚持与原始示例相同的DOM结构。 在Chrome浏览器中将其打开,按Ctr + Shift + I,选择“元素”标签,然后查看应用程序本身是否位于元素section id="todo-app">



通过逐个打开此子树,我们将其结构重写为.html文件。 现在,我们只是以一种快速的方式进行草图绘制,而不是编写代码,因此我们仅在“单引号”及其子代括号中写下元素的签名。 如果没有孩子,我们画空括号。 我们监控指数和方括号的余额。

 //   dap '#todoapp'( '#header'( 'H1'() 'INPUT#new-todo placeholder="What needs to be done?" autofocus'() ) '#main'( '#toggle-all type=checkbox'() 'UL#todo-list'( 'LI'( 'INPUT.toggle type=checkbox'() 'LABEL'() 'BUTTON.destroy'() ) ) ) '#footer'( '#todo-count'() 'UL#filters'( 'LI'() ) '#clear-completed'() ) ) 

注意:重复元素(例如,这里是LI元素),即使原始元素中有多个元素,我们也会对其写入一次。 显然,这些是来自同一模式的数组。

我认为,用手写HTML和CSS的任何人都可以理解签名格式,因此,现在我不再详细介绍它。 我只能说标签是用大写字母写的,没有标签就等于有DIV标签。 #元素(带有id)的丰富之处是由于所包含的CSS文件的细节所致,它主要使用id选择器。

3.请记住,dap程序是Javascript


为了避免代码中不必要的括号,dap引擎将几种方法直接注入String.prototype (我知道在标准对象中实现您的方法是ayahay,但简而言之,我们已经通过了),它将签名字符串转换为dap模板。 一种这样的方法是.d(rule, ...children) 。 它采用的第一个参数是生成规则( d-rule ),其余参数是任意数量的子代。

基于此新知识,我们添加代码,以便代替每个左括号,而使用序列.d("" ,并且在每个左引号之前,除了第一个引号之外,还带有逗号。生活技巧:您可以使用自动替换。

 '#todoapp'.d("" ,'#header'.d("" ,'H1'.d("") ,'INPUT#new-todo placeholder="What needs to be done?" autofocus'.d("") ) ,'#main'.d("" ,'#toggle-all type=checkbox'.d("") ,'UL#todo-list'.d("" ,'LI'.d("" ,'INPUT.toggle type=checkbox'.d("") ,'LABEL'.d("") ,'BUTTON.destroy'.d("") ) ) ) ,'#footer'.d("" ,'#todo-count'.d("") ,'UL#filters'.d("" ,'LI'.d("") ) ,'#clear-completed'.d("") ) ) 

瞧! 我们得到了对.d方法的调用树,可以将其转换为dap模板。 空字符串""是将来d规则的种子,而子元素是用逗号分隔的参数。 正式地,这是一个有效的dap程序,尽管还不能完全满足我们的需求。 但是它已经可以启动了! 为此,请在右方括号后添加.RENDER()方法。 顾名思义,此方法将呈现结果模板。

因此,在此阶段,我们有一个包含以下内容的.html文件:

 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Todo -- dap sample</title> <link rel="stylesheet" href="https://www.todobackend.com/client/css/vendor/todomvc-common.css"/> <script src="https://dap.js.org/0.4.js"></script> </head> <body> <script> '#todoapp'.d("" ,'#header'.d("" ,'H1'.d("") ,'INPUT#new-todo placeholder="What needs to be done?" autofocus'.d("") ) ,'#main'.d("" ,'#toggle-all type=checkbox'.d("") ,'UL#todo-list'.d("" ,'LI'.d("" ,'INPUT.toggle type=checkbox'.d("") ,'LABEL'.d("") ,'BUTTON.destroy'.d("") ) ) ) ,'#footer'.d("" ,'#todo-count'.d("") ,'UL#filters'.d("" ,'LI'.d("") ) ,'#clear-completed'.d("") ) ) .RENDER() //   dap   </script> </body> </html> 

您可以在浏览器中将其打开,以确保生成DOM元素,应用CSS样式,仅将其填充为该模板即可。

4.获取数据


我们转到原始页面 ,在工具中打开“网络”选项卡,打开XHR过滤器,然后查看数据的来源和格式。





好吧好吧 待办事项列表直接取自todo-backend-express.herokuapp.com作为对象的json数组。 太好了

为了接收数据,dap有一个内置的converter :query ,可将URL异步“转换”为从URL接收的数据。 URL本身,我们不会直接在规则中编写,而用常量todos表示; 那么整个数据挖掘设计将如下所示:

 todos:query 

并在字典中将todos常量本身写入-在.RENDER()之前的.DICT部分:

 '#todoapp'.d("" ... ) .DICT({ todos : "https://todo-backend-express.herokuapp.com/" }) .RENDER() 

收到todos数组后,我们从中构建一个待办事项列表:对于每种情况,我们从.title字段中获取名称,并将其写入LABEL元素,从.completed字段中.completed获取“ completeness”的符号,并写入至checked元素INPUT.togglechecked属性。 这样做是这样的:

  ,'UL#todo-list'.d("*@ todos:query" //  *       ,'LI'.d("" ,'INPUT.toggle type=checkbox'.d("#.checked=.completed") // #  " " ,'LABEL'.d("! .title") //  !      ,'BUTTON.destroy'.d("") ) ) 

我们在浏览器中更新此页面,如果您从文件系统启动它,则什么也不会发生。 问题是现代浏览器不允许本地文档中的跨域XHR请求。



现在该使用任何本地Web服务器通过http观看我们的页面了。 好吧,或者如果您还没有准备好用自己的双手编写dap,请使用我的链接查看页面的顺序版本(别忘了查看源代码-在Chrome中,这是使用Ctrl + U完成的)

因此,我们转到位于http://的页面,看到数据即将到来,列表正在构建中。 太好了! 您已经掌握了*和运算符! ,converter :query ,常量和访问当前数组元素的字段。 再次查看结果代码。 您仍然觉得它不可读吗?

5.添加状态


也许您已经尝试单击待办事项列表中的复选标记。 复选框本身会更改颜色,但是与原始复选框不同, LI父元素不会更改其样式(“已完成的工作”应变为灰色并划掉,但这不会发生)-事物不会更改其状态 。 但是这些元素还没有任何状态,因此无法更改它。 现在我们将修复它。

LI元素添加“完成”状态。 为此,请在其d规则中定义$completed 状态变量 。 对于可以更改此状态的INPUT.toggle ,我们将分配一个适当的反应规则( ui-rule ),该规则将根据其自己的checked标志(“ daw已打开”)来设置$completed变量。 根据$completed状态$completed LI元素将启用或禁用CSS类“已完成”。

  ,'UL#todo-list'.d("*@ todos:query" ,'LI'.d("$completed=.completed"//  ,    .completed ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") //       .ui("$completed=#.checked") //    $completed ,'LABEL'.d("! .title") ,'BUTTON.destroy'.d("") ) .a("!? $completed") //     $completed,    css- completed ) 

CSS类的这种操作是很常见的事情,因此在dap中有一个特殊的运算符可用于它们!
请注意,我们在a规则中执行此操作(从单词累计)。 为什么不在D规则中? 这两种规则之间的区别在于,更新时,d规则会完全重建元素的内容,并再次删除旧的和新的内容,而a规则不会触及元素的现有内容,而是将结果``附加''到现有内容中。 更改LI元素的单个属性不需要重组其其余内容,因此在a规则中执行此操作更为合理。

我们看一下结果 。 效果已经更好:点击复选框更改相应待办事项的状态,并根据此状态更改元素的视觉样式。 但是仍然存在一个问题:如果最初存在已完成任务的列表,则它们不会是灰色的,因为默认情况下,在生成元素时不执行a规则。 为了即使在生成过程中也要执行它,我们将运算符a!添加到元素LI的d规则中

  ,'LI'.d("$completed=.completed; a!" //      $completed    a- 

我们看 。 好吧 以$completed的状态$completed 。 在初次启动时和随后的手动切换过程中,已完成的案例均会正确设置样式。

6.编辑案例名称


回到原来 。 通过双击案例名称,将激活编辑模式,在该模式下可以更改此名称。 在此以完全隐藏“视图”视图模式模板(带有DAW,标题和删除按钮)的方式实现,并显示INPUT class="edit"元素。 我们将做一些不同的操作-我们将只隐藏LABEL元素,因为其他两个元素不会干扰我们的编辑。 只需将view类添加到LABEL元素。

对于“编辑”状态,在LI元素中定义$editing变量。 最初,它(状态)被重置,通过dblclickLABEL元素上打开,并在INPUT.edit元素失去焦点时关闭。 所以我们写:

  ,'LI'.d("$completed=.completed $editing=; a!" //       ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$completed=#.checked") ,'LABEL.view' .d("? $editing:!; ! .title") //  $editing ,     .e("dblclick","$editing=`yes") //  dblclick  $editing ,'INPUT.edit' .d("? $editing; !! .title@value") //  $editing  .ui(".title=#.value") //  .title   change (ui     INPUT) .e("blur","$editing=") //  $editing   blur ,'BUTTON.destroy'.d("") ).a("!? $completed $editing") //   $completed  $editing  css-  'LI' 

现在我们可以编辑案例的名称。

7.将数据发送到服务器


好的,我们已经可以在浏览器中进行编辑了,但是这些更改也必须转移到服务器上。 我们看一下原始代码是怎么做的:



更改将使用具有特定URL的PATCH方法(以http://todo-backend-express.herokuapp.com/28185的形式)发送到服务器,这显然在每种情况下都是唯一的。 服务器在列表中的每种情况的.url字段中指示此URL。 也就是说,我们更新服务器上大小写所需要做的就是将PATCH请求发送到.url字段中指定的地址,更改后的数据以JSON格式发送:

  ,'INPUT.edit' .d("? $editing; !! .title@value") .ui(".title=#.value; (@method`PATCH .url (@Content-type`application/json)@headers (.title):json.encode@body):query") .e("blur","$editing=") 

在这里,我们使用相同的converter :query ,但是版本更详细。 如果将:query应用于简单字符串,则将该字符串视为URL,并执行GET请求。 如果:query收到一个复杂对象,在这种情况下,它将把它当作对请求的详细描述,包含字段.method.url.headers.body ,并根据它们执行请求。 在这里,在更新.title立即向服务器发送带有此更新的.title的PATCH请求。

但是有细微差别。 我们从服务器获取.url字段,它看起来像这样: http://todo-backend-express.herokuapp.com/28185 : http://todo-backend-express.herokuapp.com/28185 ,也就是说,如果我们的客户端也通过http://打开,则http://协议是硬编码的那一切都很好 但是,如果客户端通过https://打开,则会出现问题:出于安全原因,浏览器会阻止来自https来源的http-traffic。

解决方法很简单:如果您从.url删除协议,则请求将通过页面协议。 因此,让我们开始吧:编写适当的转换器dehttp ,然后将.url通过它。 .FUNC部分中.FUNC了自定义转换器(和其他功能):

  .ui(".title=#.value; (@method`PATCH .url:dehttp (@Content-type`application/json)@headers (.title):json.encode@body):query") ... .FUNC({ convert:{ //  -         dehhtp: url=>url.replace(/^https?\:/,'')//    URL } }) 

将标头对象放在字典中也可以在其他查​​询中使用:

  .ui(".title=#.value; (@method`PATCH .url:dehttp headers (.title):json.encode@body):query") ... .DICT({ todos : "//todo-backend-express.herokuapp.com/", headers: {"Content-type":"application/json"} }) 

好吧,对于完全风水,我们将使用转换器的另一个有用属性:query query-根据Content-type:application/json标头在json中自动编码请求主体。 结果,规则将如下所示:

  .ui(".title=#.value; (@method`PATCH .url:dehttp headers (.title)):query") 

所以, 。 好的,案例名称现在不仅在浏览器中更改,而且在服务器上也更改。 但是! 不仅案件的名称可以更改,而且完成状态也可以更改。 因此,它也需要发送到服务器。
您可以将相同的PATCH请求添加到INPUT.toggle元素,只需发送(.completed)而不是(.completed)

  ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$completed=#.checked; (@method`PATCH .url:dehttp headers (.completed:?)):query") 

您可以将此PATCH请求放在“括号之外”:

  ,'LI'.d("$completed=.completed $editing= $patch=; a!" // $patch - ""   ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$patch=($completed=#.checked)") //   $patch  completed ,'LABEL.view' .d("? $editing:!; ! .title") .e("dblclick","$editing=`yes") ,'INPUT.edit' .d("? $editing; !! .title@value") .ui("$patch=(.title=#.value)") //   $patch  title .e("blur","$editing=") ,'BUTTON.destroy'.d("") ) .a("!? $completed $editing") //  $patch  ,   ,   .u("? $patch; (@method`PATCH .url:dehttp headers $patch@):query $patch=") 

就是这个 反应规则属于“上规则”组,从“下至上”执行-从后代到父级,再到根本身(必要时可以中断此顺序)。 这有点像DOM中的弹出事件。 因此,可以将几个后代共有的一些反应片段分配给一个共同的祖先。

具体来说,在我们的案例中,从这种委托中获得的收益并不是特别明显,但是,如果存在更多可编辑的字段,那么将这一庞大的请求(当然是基于dap标准)放在一条通用规则中,将大大有助于保持代码的简单性和可读性。 所以我推荐它。

我们来看 :现在,名称更改和状态更改都发送到了服务器。

在下一篇文章中,如果您有兴趣,请考虑添加,删除和过滤案例。 同时,您可以在dap.js.org/docs上查看最终结果和dap代码的其他示例。

Source: https://habr.com/ru/post/zh-CN480912/


All Articles