<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>D&#039;Arcy Norman dot net &#187; views</title>
	<atom:link href="http://www.darcynorman.net/tag/views/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.darcynorman.net</link>
	<description>just a lowly edtech geek, mumble mumble university of calgary</description>
	<lastBuildDate>Mon, 15 Mar 2010 15:11:27 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Creating a custom compound field for CCK</title>
		<link>http://www.darcynorman.net/2008/05/02/creating-a-custom-compound-field-for-cck/</link>
		<comments>http://www.darcynorman.net/2008/05/02/creating-a-custom-compound-field-for-cck/#comments</comments>
		<pubDate>Fri, 02 May 2008 19:17:23 +0000</pubDate>
		<dc:creator>dnorman</dc:creator>
				<category><![CDATA[work]]></category>
		<category><![CDATA[cck]]></category>
		<category><![CDATA[drupal]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[views]]></category>

		<guid isPermaLink="false">http://www.darcynorman.net/?p=1924</guid>
		<description><![CDATA[I&#8217;m working on a project that partially involves the development of a website in Drupal to act as a directory of people who have graduated from a given University. Seems easy. I went into the project thinking it would be a trivial application of Taxonomies, or maybe some generic CCK fields.
Nope. Turns out the problem [...]]]></description>
			<content:encoded><![CDATA[<p></p><p>I&#8217;m working on a project that partially involves the development of a website in Drupal to act as a directory of people who have graduated from a given University. Seems easy. I went into the project thinking it would be a trivial application of Taxonomies, or maybe some generic CCK fields.</p>
<p>Nope. Turns out the problem is much more difficult and complex than I initially thought.</p>
<p>Taxonomies won&#8217;t work, because of the need to tie a number of values together, namely the year the degree was awarded (say, &#8220;1992&#8243;), the type of degree (say, &#8220;BSc&#8221;), the specialization of the degree (say, &#8220;Zoology&#8221;), and the granting institution (say, &#8220;University of Calgary&#8221;).</p>
<p>That could be an easy thing to solve with CCK &#8211; just add four text fields. Done.</p>
<p>BUT &#8211; people can earn more than one degree. Of different types, in different years, from different institutions.</p>
<p>Taxonomies fail. Generic CCK fields fail.</p>
<p>What I came up with is a new CCK field type, cryptically named &#8220;University Degrees&#8221;, that defines the four values that describe a degree. This solves the problem quite tidily, and supports multiple values, predefined valid sets of values, and can integrate with Views to be used as filters and sorting fields.</p>
<p>In building this module, I leaned heavily on a couple of web pages (<a href="http://drupal.org/node/106716">CCK Sample</a> and <a href="http://www.lullabot.com/articles/an_introduction_to_the_content_construction_kit">What is the Content Construction Kit?</a>) that describe how parts of the module should work, and provided some sample code. In the spirit of contributing back what I learned, I&#8217;m going to document the module to help others needing to do similar things.<br />
<span id="more-1924"></span></p>
<p>To start with, I just created a new folder to hold the module, and called it &#8220;<code>universitydegrees</code>&#8220;. Into this folder, I copied the <code>.install</code> and <code>.info</code> files from another module and customized them to suit my needs (the universitydegrees.install file doesn&#8217;t actually do anything, and the universitydegrees.info file is just for listing the module in the Drupal admin page). If you want to follow along (or use the files as a starting point to make something else) the files for my lame alpha version of the module are <a href='http://www.darcynorman.net/wp-content/uploads/2008/05/universitydegrees_20080402.zip'>available here</a>.</p>
<p><a href='http://www.darcynorman.net/wp-content/uploads/2008/05/universitydegres_files.png'><img src="http://www.darcynorman.net/wp-content/uploads/2008/05/universitydegres_files-600x234.png" alt="" title="universitydegrees files" width="600" height="234" class="alignnone size-medium wp-image-1925" /></a></p>
<p>The file where all the fun stuff happens is universitydegrees.module</p>
<p>The first method, universitydegrees_field_info(), defines how the field shows up in the CCK admin interface.</p>
<pre><code>function universitydegrees_field_info() {
  return array(
    'universitydegrees' => array('label' => 'University Degree'),
  );
}</code></pre>
<p>This method works with the <code>universitydegrees_widget_info()</code> method, and will list any available widgets under the &#8220;University Degree&#8221; field type. In this case, I just added a simple widget description, like this:</p>
<pre><code>function universitydegrees_widget_info() {
  return array(
    'universitydegrees_text' => array(
      'label' => 'University degree earned',
      'field types' => array('universitydegrees'),
    ),
  );
}</code></pre>
<p>So, a new field called &#8220;University Degree&#8221; will be available, like this:</p>
<p><a href='http://www.darcynorman.net/wp-content/uploads/2008/05/universitydegrees_cck_field_widget_menu.png'><img src="http://www.darcynorman.net/wp-content/uploads/2008/05/universitydegrees_cck_field_widget_menu.png" alt="" title="universitydegrees_cck_field_widget_menu" width="500" height="392" class="alignnone size-full wp-image-1927" /></a></p>
<p>Before actually adding the field to any content type, we need to define what data it&#8217;s going to store. This happens in the <code>universitydegrees_field_settings()</code> method:</p>
<pre><code>function universitydegrees_field_settings($op, $field) {
  switch ($op) {

    case 'save':
      return array('year', 'degreetype', 'programme', 'institution');

    case 'database columns':
      $columns = array(
        'year' => array('type' => 'int', 'not null' => TRUE, 'default' => '2008', 'sortable' => TRUE),
        'degreetype' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => "''", 'sortable' => TRUE),
        'programme' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => "''", 'sortable' => TRUE),
        'institution' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => "''", 'sortable' => TRUE),
      );
      return $columns;

    case 'filters':
      return array(
        'default' => array(
          'list' => '_universitydegrees_filter_handler',
          'list-type' => 'list',
          'operator' => 'views_handler_operator_or',
          'value-type' => 'array',
          'extra' => array('field' => $field),
        ),
      );
  }
}</code></pre>
<p>The <code>'save'</code> portion defines what values will be saved. The <code>'database columns'</code> portion defines the MySQL code to generate the fields in the database to store each of the values. In this case,<code> 'year'</code> will be an <code>int</code> and the remaining values will be <code>varchar(255)</code> text strings.</p>
<p>Now, to define how the widget that is used to edit the values should behave. This is the <code>universitydegrees_widget()</code> method.</p>
<pre><code>function universitydegrees_widget($op, &#038;$node, $field, &#038;$items) {
    $options_year = _universitydegrees_values_year();
	$options_degreetype = _universitydegrees_values_degreetype();
	$options_institution = _universitydegrees_values_institution();

   switch ($op) {

      case 'form':
        $form = array();
        $form[$field['field_name']] = array('#tree' => TRUE);

        if ($field['multiple']) {
          $form[$field['field_name']]['#type'] = 'fieldset';
          $form[$field['field_name']]['#description'] = t('Degrees Earned');
          $delta = 0;
          foreach (range($delta, $delta + 2) as $delta) {
			$item = $items[$delta];
			$form[$field['field_name']][$delta]['#type'] = 'fieldset';
            $form[$field['field_name']][$delta]['year'] = array(
              '#type' => 'select',
              '#title' => t('Year'),
			  '#default_value' => array_search($items[$delta]['year'], $options_year),
			  '#options' => $options_year,
            );
            $form[$field['field_name']][$delta]['degreetype'] = array(
              '#type' => 'select',
              '#title' => t('Degree Type'),
			  '#default_value' => array_search($items[$delta]['degreetype'], $options_degreetype),
			  '#options' => $options_degreetype,
              '#required' => ($delta == 0) ? $field['required'] : FALSE,
            );
            $form[$field['field_name']][$delta]['programme'] = array(
              '#type' => 'textfield',
              '#title' => t('Degree, major or concentration'),
			  '#default_value' => $items[$delta]['programme'],
              '#required' => ($delta == 0) ? $field['required'] : FALSE,
            );
            $form[$field['field_name']][$delta]['institution'] = array(
              '#type' => 'select',
              '#title' => t('School'),
			  '#default_value' => array_search($items[$delta]['institution'], $options_institution),
			  '#options' => $options_institution,
              '#required' => ($delta == 0) ? $field['required'] : FALSE,
            );
          }
        }
        else {
			$form[$field['field_name']][0]['#type'] = 'fieldset';
            $form[$field['field_name']][0]['year'] = array(
              '#type' => 'select',
              '#title' => t('Year'),
			  '#default_value' => array_search($items[0]['year'], $options_year),
			  '#options' => $options_year,
			  '#required' => $field['required'],
            );
            $form[$field['field_name']][$delta]['degreetype'] = array(
              '#type' => 'select',
              '#title' => t('Degree Type'),
			  '#default_value' => array_search($items[0]['degreetype'], $options_degreetype),
			  '#options' => $options_degreetype,
              '#required' => $field['required'],
            );
            $form[$field['field_name']][0]['programme'] = array(
              '#type' => 'select',
              '#title' => t('Degree, major or concentration'),
			  '#default_value' => $items[0]['programme'],
              '#required' => $field['required'],
            );
            $form[$field['field_name']][0]['institution'] = array(
              '#type' => 'select',
              '#title' => t('School'),
			  '#default_value' => array_search($items[0]['institution'], $options_institution),
			  '#options' => $options_institution,
              '#required' => $field['required'],
            );
        }
        return $form;

      case 'process form values':

        foreach ($items as $delta => $item) {

			// don't store empty stuff.
			if (empty($items[$delta]['year']) &#038;&#038; $delta > 0 ) {
				unset($items[$delta]);
			} else {
				// do an array lookup to store the actual value of the selection, not just the number of its index position.
				$items[$delta]['year'] = $options_year[$items[$delta]['year']];
				$items[$delta]['degreetype'] = $options_degreetype[$items[$delta]['degreetype']];
				$items[$delta]['institution'] = $options_institution[$items[$delta]['institution']];
			}
        }
  }
}</code></pre>
<p>The <code>$options_year</code>, <code>$options_degreetype</code>, and <code>$options_institution</code> variables are just calling methods that return arrays of values. I did this initially to separate the options from the various places they are used so I could change where the data comes from more easily (it&#8217;d be really cool to tie these into taxonomies or something more user-editable&#8230;)</p>
<p>The <code>'form'</code> portion generates the portion of the form that is used to edit the content associated with an instance of the field. It has two sub-portions, one for handling multiple values, and one for single values. I&#8217;m really not sure why that isn&#8217;t collapsed into a single chunk that can grok both single and multiple values, but I was following a recipe and left it that way. For now&#8230; The only portion I really cared about was the multivalue stuff anyway, because that&#8217;s how I&#8217;ll be using the content type.</p>
<p>The code for that portion defines a fieldset, called &#8220;Degrees Earned&#8221; and starts pumping out chunks of forms to present editors for each value. It creates a nested fieldset for each value, and presents a Select field for &#8220;Year&#8221;, &#8220;Degree Type&#8221; and &#8220;Institution&#8221; &#8211; and a textfield for &#8220;Degree, major or concentration&#8221;.</p>
<p>One thing that I didn&#8217;t like in the out-of-the-box widget behaviour was that it stored the index of the value, rather than the actual value itself, in the database. While that worked, it wasn&#8217;t ideal &#8211; if I changed the list of options, the index values would be invalid. So I modified the code to lookup the actual value selected and store that for the select widgets. (the <code>#default_value</code> portions of the code present the current value, if any, while editing).</p>
<p>The <code>'process form values' </code>portion does any processing of the values prior to saving in the database. This is where I convert the stored values from plain, dumb, indexes to a more meaningful text string containing the actual selection.</p>
<p>Here&#8217;s what the form looks like, with the widgets in place:</p>
<p><a href='http://www.darcynorman.net/wp-content/uploads/2008/05/universitydegrees_node_editing_form.png'><img src="http://www.darcynorman.net/wp-content/uploads/2008/05/universitydegrees_node_editing_form.png" alt="" title="universitydegrees_node_editing_form" width="493" height="359" class="alignnone size-full wp-image-1928" /></a></p>
<p>Now that we have a field defined, have provided a way to save values in the database, and described how the form widgets should behave, we need to display values on the node page. I took a shortcut and only defined a single hard-coded way to present the values. I&#8217;ll eventually work in a way to customize the display using a list of formatters. In the meantime, the <code>universitydegrees_field_formatter()</code> method handles the conversion from raw data into displayable text.</p>
<pre><code>function universitydegrees_field_formatter($field, $item, $formatter, $node) {
	$text = '';

	$text = $item['degreetype'] . ' ' . $item['programme'] . ' (' . $item['institution'] . ', ' . $item['year'] . ')';
	return $text;
}</code></pre>
<p>With the formatter in place, a node with this CCK field type will look like this:</p>
<p><a href='http://www.darcynorman.net/wp-content/uploads/2008/05/universitydegrees_node_display.png'><img src="http://www.darcynorman.net/wp-content/uploads/2008/05/universitydegrees_node_display.png" alt="" title="universitydegrees_node_display" width="373" height="124" class="alignnone size-medium wp-image-1929" /></a></p>
<p>And that&#8217;s it. Now, I can add a University Degree to any CCK content type, and be able to define all four values that describe a degree awarded to an individual. This pattern could easily be generalized for other compound data types, as well.</p>
<p>Next, I need to expose full Views functionality, so each value can be used for filtering and sorting Views. And, I need to provide a more flexible way to display the values, using the formatters. And, I need to abstract the lists of Years, Degrees and Institutions so that they are editable by users without having to modify the source code for the module&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.darcynorman.net/2008/05/02/creating-a-custom-compound-field-for-cck/feed/</wfw:commentRss>
		<slash:comments>47</slash:comments>
		</item>
	</channel>
</rss>
