跳转到主要内容
ThomasHuang 提交于 10 March 2014

本文转载并翻译自https://www.isovera.com/blog/programmatically-creating-forms-views-drupal-7

翻译不准确的地方请见谅,可参照英文原文。

With Views 3, the Views' team introduced a clever technique for creating forms, using the Form API, with Views. Views Bulk Operations uses this method as does the D7 version of editablefields (those modules might be a good place to start if you aren't a developer and just landed here from the Googs). 在 Views 3中,,Views 团队使用了很灵巧的方式来构造表单——使用Form API和Views结合。 Views Bulk Operations 模块,Drupal7版本的editablefields,就是使用这种方法。(如果你不是一个开发人员而仅仅是从Google进来的,这些模块可能会对你的起步有帮助).

In this example, I'll discuss how to implement this technique in your own custom code. This example assumes you know how to create a module in Drupal and that you have programmatically created (or at leasted altered) forms using the Drupal Form API. You should have experience creating Views through the UI. Some experience playing with Views in code might be helpful but is probably not necessary. 在本例子中,我将会讨论如何用代码自定义实现功能。所以假设你知道如何编写Drupal模块并且使用Form API 编写(至少修改)过表单。你应该有使用Views的用户界面创建view的经验。有在代码中使用过Views的经验会有一定的帮助,但不是必要的。

Why would you want to do this?为什么做这功能

  1. you want to give users some ability to create custom forms using the Views UI;你需要更方便用户通过view的界面构造表单。
  2. you need a form that's a list of data;你需要一个表单列出数据。
  3. you want to use AJAX to smoothly reload a View based on user input (or elapsed time or some browser-side event) (I'll demonstrate this in a second part).你需要根据用户的输入(或者运行时间或着一些浏览器事件)使用AJAX流畅地载入view

In this example you'll: 在这个例子中,你将要:

  1. create a simple View, a list of node information;构造一个简单的view,一个node信息的列表。
  2. create a custom Views' field handler to render a textfield for editing the node titles;编写一个自定义字段处理类,输出(译者注:render,图形学中也译作"渲染",Drupal中的render是指从一段PHP数据结构转化成HTML,用输出更方便理解)一个文本字段供用户编辑node标题。
  3. create validation and submission handlers for our form.编写这个表单的校验和提交处理程序(译者注:处理程序(handler),它可以是函数,也可以是类)。

This is what you'll end up with: 这就是将要实现的功能:

First, create the View. I decided to create a table view of my nodes with some basic information: 首先,新建view,我决定新建表格样式的view列出node的一些基本信息。    Not much to say here. Just your typical, run-of-the-mill View. You'll replace the title field with our custom form field. 这里没什么可说的,就是平常的view。你即将要使用自定义的表单字段来更新node的标题字段。

Next, create a starting module with a .info and a .module file. I called mine module "view_form" (in real life, I'd probably choose a name less likely to collide with another module). 接下来,创建一个空的带有.info和.module文件的模块。我把这模块命名为“view_form”(现实中,我可能会选一个不那么容易和其他模块名产生冲突的名字)

This is the only code inside the .module file: 以下是.model文件的代码:

 

<?php
/**
* Implements hook_views_api().
*/
function view_form_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'view_form') . '/views',
  );
}
?>

This code implements "hook_views_api" and tells Views where it can find your "[module].views.inc" file, which will contain my Views' hook implementations. As is typical, the "view_form.views.inc" file is located in the "views" directory under the root of the module. 这段代码实现了“hook_views_api”这个钩子函数,并告诉Views那里可以找到[模块名].views.inc文件。这个[模块名].views.inc文件将会包含views的钩子函数实现。一般情况下,"view_form.views.inc"这个文件将会放在本模块的根目录的views文件夹里.

The view_form.views.inc contains just one Views' hook implementation: view_form.views.inc文件里,只有一个views的钩子函数实现.

<?php
/**
* Implements hook_views_data_alter().
*
* @param $data
*   Information about Views' tables and fields.
*/
function view_form_views_data_alter(&$data) {
  // Add the Title form field to 
  $data['node']['title_edit'] = array(
    'field' => array(
      'title' => t('Title form field'),
      'help' => t('Edit the node title'),
      'handler' => 'view_form_field_handler_title_edit',
    ),
  );
}
?>

Use "hook_views_data_alter" to add a new "field" to the "node" element provided by Views. The node element primarily consists of properties and fields defined by the Schema and Field APIs. But other modules can add "dummy" fields--fields that don't provide any data of their own but process and render values already defined by the Node and Field modules. 使用view提供的"hook_views_data_alter",对node元素添加一个“字段”。Node元素基本上包含了Schema和Field API中定义的属性和字段。但其他的模块可以添加“虚假的”字段,这些字段不需要存储数据,但会加工并输出Node和Field模块已经定义好的值。

The practical effect of "view_form_views_data_alter" is that--after clearing caches--there will be one new field to add to our node Views in the Views UI: "view_form_views_data_alter"钩子函数的实际影响就是,在清楚缓存之后,一个新的字段会被添加到View的UI界面里node字段列表。

  You could go ahead and add that field--but Views will complain because it can't find the code for the "view_form_field_handler_title_edit" field handler. 你可以照着步骤添加这个字段,但views会抱怨找不到"view_form_field_handler_title_edit"这个字段的处理程序。

So go ahead and create a "view_form_field_handler_title_edit.inc" file inside of the "views" sub-directory. The module directory structure is now complete and looks like this: 现在开始在“views”这个子文件夹里新建一个文件"view_form_field_handler_title_edit.inc"。完成后,模块目录结构现在看起来是这样的:

Next, edit the .info file and add the following the line. 接下来编辑.info文件,添加下面这行代码。

files[] = views/view_form_field_handler_title_edit.inc

This line tells Drupal where to find and auto-load our field handler whenever Views needs it. 这行代码告诉Drupal,当views需要时哪里可以自动加载我们的字段处理程序。

Now add this code inside "view_form_field_handler_title_edit.inc": 现在,把如下代码放入 "view_form_field_handler_title_edit.inc"文件中。

<?php
/**
   * @file
   *
   * A Views' field handler for editing a node title.
   *
   */
class view_form_field_handler_title_edit  extends views_handler_field {
  function construct() {
    parent::construct();
    $this->additional_fields['nid'] = 'nid';
    $this->additional_fields['title'] = 'title';
  }

  function query() {
    $this->ensure_my_table();
    $this->add_additional_fields();
  }
}
?>

This is the basic class definition for your field handler. It extends "views_handler_field" and adds constructor and query methods to make sure the node id and title values are loaded into the view. 这是你的字段处理程序基本的类的定义。它继承自"views_handler_field"类并添加了构造函数和查询方法,以确保node id和标题被加载进view里。

Now, add the following "render" method to the class...this is when a little magic happens.现在添加如下输出方法

<?php
/**
   * @file
   *
   * A Views' field handler for editing a node title.
   *
   */
class view_form_field_handler_title_edit  extends views_handler_field {
  ...
  function render($values) {
    // Render a Views form item placeholder.
    // This causes Views to wrap the View in a form.
    // Render a Views form item placeholder.
    return '<!--form-item-' . $this->options['id'] . '--' . $this->view->row_index . '-->';
  }
  ...
}
?>

The render method returns the html (or other markup) to display for the field. To output a Views' managed form field, you render an HTML comment in the form in this format: 输出方法返回的HTML(或其他标记)是用来显示字段的。为了输出views管理的表单字段,你需要按照如下格式输出HTML的注释: <!--form-item-FIELD_ID--ROW_INDEX-->  This syntax mirrors the form array structure you'll use to provide replacement form fields views. When Views' sees that your field handler renders a comment like above, it will initialize and wrap a form around the view then call your handlers "views_form" method 这段语法表示了表单的数组结构,你将会使用这个结构给views的表单字段提供可替换的字符。当views看到你的字段处理程序输出如上注释,它会初始化并用一个表单的内容包裹在这段注释外面,然后调用你的处理函数的 "views_form" 这个方法。

<?php
class view_form_field_handler_title_edit extends views_handler_field {
  ...
  /**
   * Add to and alter the form created by Views.
   */
  function views_form(&$form, &$form_state) {
    // Create a container for our replacements
    $form[$this->options['id']] = array(
      '#type' => 'container',
      '#tree' => TRUE,
    );
    // Iterate over the result and add our replacement fields to the form.
    foreach($this->view->result as $row_index => $row) {
      // Add a text field to the form.  This array convention
      // corresponds to the placeholder HTML comment syntax.
      $form[$this->options['id']][$row_index] = array(
        '#type' => 'textfield',
        '#default_value' => $row->{$this->aliases['title']},
        '#element_validate' => array('view_form_field_handler_title_edit_validate'),
        '#required' => TRUE,
      );
    }
  }
  ...
}
?>

The "views_form" method allows you to alter the form created by Views. Add your field replacements by creating a container element with an index that matches the field id ($this->options['id']). Then iterate over the view result and add a form field element to the container for every row in the view, using the row index as the element key in the container. Views matches this form array structure to the corresponding HTML comment to insert the field in the rendered View. In other words: "views_form"方法允许你去修改views创建的表单。添加你的字段,用一个带有索引并与字段ID ($this->options['id'])匹配的容器元素代替。然后,遍历view的结果并添加一个表单元素到容器中的每一行,使用行的索引当做容器的键名。Views匹配这个表单的数组结构到对应的HTML注释中,在输出好的view中插入字段。换句话说:

<!--form-item-FIELD_ID--ROW_INDEX-->  is replaced by:可以被替换为

<?php
$form[FIELD_ID][ROW_INDEX];
?>

The above "views_form" method adds a textfield to every row pre-filled with the node title. I've specified a "element_validate" handler to the field because Views doesn't wire up a form-wide validate handler. 上述的"views_form"方法添加了文本字段到每行中,预填了node标题的值。我对这个字段指定了"element_validate"处理程序,因为views不使用平常广泛使用的表单校验处理程序。

Create your element validation callback OUTSIDE the class but in the same .inc file so that it is available in the global context but is only loaded with the field handler: 新建你的元素校验处理程序,在类的外面但在.inc文件的里面。这样它才能再全局上下文中可以使用但只和字段处理程序一起加载。

<?php
class view_form_field_handler_title_edit extends views_handler_field {
  ...
}
/**
* Validation callback for the title element.
*
* @param $element
* @param $form_state
*/
function view_form_field_handler_title_edit_validate($element, &$form_state) {
  // Only allow titles where the first character is capitalized.
  if (!ctype_upper(substr($element['#value'], 0, 1))) {
    form_error($element, t('All titles must be capitalized.'));
  }
}
?>

This simple validator just makes sure that the node titles are always capitalized. 这个简单的校验就只是确保node标题首字母大写。

Finally, add a "views_form_submit" method to the field handler: 最后,添加一个"views_form_submit"方法到字段处理程序中。

<?php
class view_form_field_handler_title_edit extends views_handler_field {
  ...
  /**
   * Form submit method.
   */
  function views_form_submit($form, &$form_state) {
    // Determine which nodes we need to update.
    $updates = array();
    // Iterate over the view result.
    foreach($this->view->result as $row_index => $row) {
      // Grab the correspondingly submitted form value.
      $value = $form_state['values'][$this->options['id']][$row_index];
      // If the submitted value is different from the original value add it to the
      // array of nodes to update.
      if ($row->{$this->aliases['title']} != $value) {
        $updates[$row->{$this->aliases['nid']}] = $value;
      }
    }

    // Grab the nodes we need to update and update them.
    $nodes = node_load_multiple(array_keys($updates));
    foreach($nodes as $nid => $node) {
      $node->title = $updates[$nid];
      node_save($node);
    }

    drupal_set_message(t('Update @num node titles.', array('@num' => sizeof($updates))));
  }
  ...
}
?>

The "views_form_submit" method works much like any Form API submit callback. In the case above, you iterate over the view result to identify submitted changes, then iterate over the changes to update the corresponding nodes. "views_form_submit"方法和Form API 的提交回调函数工作原理类似。在上述例子中,你遍历了view的结果用来区分提交后的改动的内容,然后遍历改动的内容去更新相应的node。

A note about the use of "$this->aliases": this is one way that Views ensures that different field handlers don't collide. Think about it, the Views UI allows you to add any number of fields called "title", any other form field handler, or even another instance of this handler. To allow this, Views indexes the result objects with unique field aliases, so you should use the syntax "$row->{$this->aliases['title']}" when grabbing results to make sure you get the right "title". 关于使用"$this->aliases"一个值得注意的地方,这是一个确保不同的字段处理函数不会冲突的地方。设想一下,View的界面允许你添加任意数量叫“标题”的字段,还有任何其他的表单处理程序,甚至当前处理函数的其他实例。为了允许这么做,views使用唯一的字段别名给结果对象的编配了唯一索引。所以,当你获取结果时,应该使用语法如"$row->{$this->aliases['title']}" 去保证你获得正确的那个“标题”字段。

So that's it. You've now created a View form. There are plenty of places to you could take this. You could easily substitute multiple form elements by adding additional fields to your container. You could use the Form API "#ajax" element to handle form submissions without a page refresh. You could include a submit button on every row and only allow one row to be updated at a time. 就这样了,你现在已经创建了一个view的表单。还有很多地方你需要使用这个功能。你可以通过添加额外的字段到容器中简单的替换多个表单元素。你可以使用Form API 的'#ajax'元素去无刷新处理表单提交.你可以在任意行或只有一行引入一个提交按钮去同时更新.

The example code is available in my Drupal.org sandbox 例子的代码在Drupal.org的沙盒中. http://drupalcode.org/sandbox/krlucas/1872176.git/tree

 

标签
Drupal 版本

Hi 各位,

我在Field List中有个字段field_warehousecheckdate,但按这篇文章进行设置的时候报错:SQLSTATE[42S22]: Column not found: 1054 Unknown column 'node.field_warehousecheckdate' in 'field list',这个问题,是什么情况?哪儿没设置好呢?