实体形式的剖析

这是用 tracker 立方体。我们将用一组实体填充数据库,并查看自动实体表单所做的工作。

填充数据库

我们应该先设置一点上下文:一个包含两个未发布版本的项目,以及一个链接到项目和第一个版本的通知单。

>>> p = rql('INSERT Project P: P name "cubicweb"')
>>> for num in ('0.1.0', '0.2.0'):
...  rql('INSERT Version V: V num "%s", V version_of P WHERE P eid %%(p)s' % num, {'p': p[0][0]})
...
<resultset 'INSERT Version V: V num "0.1.0", V version_of P WHERE P eid %(p)s' (1 rows): [765L] (('Version',))>
<resultset 'INSERT Version V: V num "0.2.0", V version_of P WHERE P eid %(p)s' (1 rows): [766L] (('Version',))>
>>> t = rql('INSERT Ticket T: T title "let us write more doc", T done_in V, '
            'T concerns P WHERE V num "0.1.0"', P eid %(p)s', {'p': p[0][0]})
>>> commit()

现在让我们看看版本表单为我们构建了什么。

>>> cnx.use_web_compatible_requests('http://fakeurl.com')
>>> req = cnx.request()
>>> form = req.vreg['forms'].select('edition', req, rset=rql('Ticket T'))
>>> html = form.render()

注解

为了与Web端应用程序对象进行交互播放,我们必须通过调用 use_web_compatible_requests() 在连接上。

这将创建一个自动实体窗体。这个 .render() 调用生成一个HTML(Unicode)字符串。HTML输出如下所示(省略了内部字段集)。

查看HTML输出

表格信封

<div class="iformTitle"><span>main informations</span></div>
<div class="formBody">
 <form action="http://crater:9999/validateform" method="post" enctype="application/x-www-form-urlencoded"
       id="entityForm" onsubmit="return freezeFormButtons(&#39;entityForm&#39;);"
       class="entityForm" target="eformframe">
   <div id="progress">validating...</div>
   <fieldset>
     <input name="__form_id" type="hidden" value="edition" />
     <input name="__errorurl" type="hidden" value="http://perdu.com#entityForm" />
     <input name="__domid" type="hidden" value="entityForm" />
     <input name="__type:763" type="hidden" value="Ticket" />
     <input name="eid" type="hidden" value="763" />
     <input name="__maineid" type="hidden" value="763" />
     <input name="_cw_edited_fields:763" type="hidden"
            value="concerns-subject,done_in-subject,priority-subject,type-subject,title-subject,description-subject,__type,_cw_generic_field" />
     ...
   </fieldset>
   <iframe width="0px" height="0px" name="eformframe" id="eformframe" src="javascript: void(0);"></iframe>
  </form>
</div>

主字段集包含一组包含各种元数据的隐藏字段,这些元数据将由 edit controller 正确处理它。

这个 freezeFormButtons(...) 在上定义的javascript回调 onlick 表单元素的事件可防止意外地在一行中多次单击。

这个 action 窗体的映射到 validateform 控制器(位于 cubicweb.web.views.basecontrollers

验证循环的完整说明见 表单验证过程 .

属性部分

我们可以看看表单的一些内部节点。有些字段被省略,因为它们对于我们来说是多余的。

<fieldset class="default">
  <table class="attributeForm">
    <tr class="title_subject_row">
      <th class="labelCol"><label class="required" for="title-subject:763">title</label></th>
      <td>
        <input id="title-subject:763" maxlength="128" name="title-subject:763" size="45"
               type="text" value="let us write more doc" />
      </td>
    </tr>
    ... (description field omitted) ...
    <tr class="priority_subject_row">
      <th class="labelCol"><label class="required" for="priority-subject:763">priority</label></th>
      <td>
        <select id="priority-subject:763" name="priority-subject:763" size="1">
          <option value="important">important</option>
          <option selected="selected" value="normal">normal</option>
          <option value="minor">minor</option>
        </select>
        <div class="helper">importance</div>
      </td>
    </tr>
    ... (type field omitted) ...
    <tr class="concerns_subject_row">
      <th class="labelCol"><label class="required" for="concerns-subject:763">concerns</label></th>
      <td>
        <select id="concerns-subject:763" name="concerns-subject:763" size="1">
          <option selected="selected" value="760">Foo</option>
        </select>
      </td>
    </tr>
    <tr class="done_in_subject_row">
      <th class="labelCol"><label for="done_in-subject:763">done in</label></th>
      <td>
        <select id="done_in-subject:763" name="done_in-subject:763" size="1">
          <option value="__cubicweb_internal_field__"></option>
          <option selected="selected" value="761">Foo 0.1.0</option>
          <option value="762">Foo 0.2.0</option>
        </select>
        <div class="helper">version in which this ticket will be / has been  done</div>
      </td>
    </tr>
  </table>
</fieldset>

请注意,整个表单布局是由表单呈现器计算的。生成表结构的是渲染器。否则,字段HTML结构由其关联的小部件发出。

当它被称为 attributes 窗体的节,它实际上包含属性和 强制性关系 .对于每个领域,我们观察到:

  • 具有特定类的专用行,例如 title_subject_row (表单呈现器的责任)

  • HTML小部件(输入,选择,…)具有:

    • 基于 rtype-role:eid 模式

    • 从同一模式生成的名称

    • 可能的值或预选选项

关系科

<fieldset class="This ticket :">
  <legend>This ticket :</legend>
  <table class="attributeForm">
    <tr class="_cw_generic_field_None_row">
      <td colspan="2">
        <table id="relatedEntities">
          <tr><th>&#160;</th><td>&#160;</td></tr>
          <tr id="relationSelectorRow_763" class="separator">
            <th class="labelCol">
              <select id="relationSelector_763"
                      onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,763);">
                <option value="">select a relation</option>
                <option value="appeared_in_subject">appeared in</option>
                <option value="custom_workflow_subject">custom workflow</option>
                <option value="depends_on_object">dependency of</option>
                <option value="depends_on_subject">depends on</option>
                <option value="identical_to_subject">identical to</option>
                <option value="see_also_subject">see also</option>
              </select>
            </th>
            <td id="unrelatedDivs_763"></td>
          </tr>
        </table>
      </td>
    </tr>
  </table>
</fieldset>

可选关系分组到下拉组合框中。选择一个项目会触发一个javascript函数,该函数将:

  • 在ID的分区中显示已关联的实体 relatedentities 使用两个colown布局,并执行一个允许删除单个关系的操作(本例中没有任何关系)

  • 在ID的DIV中提供关系选择器 relationSelector_EID 允许用户设置关系并在最后一个DIV上触发动态操作

  • 填充ID的分区 unrelatedDivs_EID 使用动态计算的选择小部件,可以直接选择不相关(但可关联)的实体或切换到 search mode 属于 CubicWeb 它允许使用位于左侧列框中的专用操作对实体进行完全浏览和选择。

按钮区

最后是按钮区。

<table width="100%">
  <tbody>
    <tr>
      <td align="center">
        <button class="validateButton" type="submit" value="validate">
          <img alt="OK_ICON" src="http://myapp/datafd8b5d92771209ede1018a8d5da46a37/ok.png" />
          validate
        </button>
      </td>
      <td style="align: right; width: 50%;">
        <button class="validateButton"
                onclick="postForm(&#39;__action_apply&#39;, &#39;button_apply&#39;, &#39;entityForm&#39;)"
                type="button" value="apply">
          <img alt="APPLY_ICON" src="http://myapp/datafd8b5d92771209ede1018a8d5da46a37/plus.png" />
          apply
        </button>
        <button class="validateButton"
                onclick="postForm(&#39;__action_cancel&#39;, &#39;button_cancel&#39;, &#39;entityForm&#39;)"
                type="button" value="cancel">
          <img alt="CANCEL_ICON" src="http://myapp/datafd8b5d92771209ede1018a8d5da46a37/cancel.png" />
          cancel
        </button>
      </td>
    </tr>
  </tbody>
</table>

这里最著名的文物是 postForm(...) 在这些按钮上单击事件时定义的调用。此函数基本上提交表单。

表单验证过程

验证循环

提交表单时,将调用form.action。基本上, validateform 控制器被调用,其输出在指定的 target ,一个看不见的 <iframe> 在表格的末尾。

因此,不替换主页面,只替换iframe内容。这个 validateform 控制器只输出一个很小的javascript片段,然后立即执行。

<iframe width="0px" height="0px" name="eformframe" id="eformframe" src="javascript: void(0);">
  <script type="text/javascript">
    window.parent.handleFormValidationResponse('entityForm', null, null,
                                               [false, [2164, {"name-subject": "required field"}], null],
                                               null);
  </script>
</iframe>

这个 window.parent 部分确保在正确的上下文(即表单元素)上调用javascript函数。我们将描述其参数:

  • 首先是表单ID (entityForm

  • 然后是成功和失败案例的两个可选回调

  • 包含以下内容的数组:

    • 一个布尔值,指示状态(成功或失败),然后在出错时:

      • 数组结构为 [eid, {{'rtype-role': 'error msg'}}, ...]

    • 成功时:

      • 表示下一个要跳转到的对象的URL(字符串)

考虑到上面描述的数组结构,很容易操作DOM在适当的位置显示错误。

解释

这种麦加主义似乎有些过于复杂,但我们必须面对两个现实:

  • 在(严格的)XHTML世界中,没有iframe(因此,firefox允许动态包含)。

  • 没有(或不是所有)浏览器支持通过Ajax处理文件输入字段。