diff --git a/includes/common.inc b/includes/common.inc
index 581e115..dd4a2fd 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -6459,7 +6459,7 @@ function drupal_common_theme() {
'variables' => array('type' => MARK_NEW),
),
'item_list' => array(
- 'variables' => array('items' => array(), 'title' => NULL, 'type' => 'ul', 'attributes' => array()),
+ 'variables' => array('items' => array(), 'title' => '', 'type' => 'ul', 'attributes' => array()),
),
'more_help_link' => array(
'variables' => array('url' => NULL),
diff --git a/includes/theme.inc b/includes/theme.inc
index 1f991ea..b85a35c 100644
--- a/includes/theme.inc
+++ b/includes/theme.inc
@@ -1793,12 +1793,14 @@ function theme_mark($variables) {
*
* @param $variables
* An associative array containing:
- * - items: An array of items to be displayed in the list. If an item is a
- * string, then it is used as is. If an item is an array, then the "data"
- * element of the array is used as the contents of the list item. If an item
- * is an array with a "children" element, those children are displayed in a
- * nested list. All other elements are treated as attributes of the list
- * item element.
+ * - items: A list of items to render. String values are rendered as is. Each
+ * item can also be an associative array containing:
+ * - data: The string content of the list item.
+ * - children: A list of nested child items to render that behave
+ * identically to 'items', but any non-numeric string keys are treated as
+ * HTML attributes for the child list that wraps 'children'.
+ * Any other key/value pairs are used as HTML attributes for the list item
+ * in 'data'.
* - title: The title of the list.
* - type: The type of list to return (e.g. "ul", "ol").
* - attributes: The attributes applied to the list element.
@@ -1807,51 +1809,69 @@ function theme_item_list($variables) {
$items = $variables['items'];
$title = $variables['title'];
$type = $variables['type'];
- $attributes = $variables['attributes'];
+ $list_attributes = $variables['attributes'];
- $output = '
';
- if (isset($title)) {
- $output .= '
' . $title . '
';
- }
+ $output = '';
+ if ($items) {
+ $output .= '<' . $type . drupal_attributes($list_attributes) . '>';
- if (!empty($items)) {
- $output .= "<$type" . drupal_attributes($attributes) . '>';
$num_items = count($items);
- foreach ($items as $i => $item) {
+ $i = 0;
+ foreach ($items as $key => $item) {
+ $i++;
$attributes = array();
- $children = array();
- $data = '';
+
if (is_array($item)) {
- foreach ($item as $key => $value) {
- if ($key == 'data') {
- $data = $value;
- }
- elseif ($key == 'children') {
- $children = $value;
- }
- else {
- $attributes[$key] = $value;
+ $value = '';
+ if (isset($item['data'])) {
+ $value .= $item['data'];
+ }
+ $attributes = array_diff_key($item, array('data' => 0, 'children' => 0));
+
+ // Append nested child list, if any.
+ if (isset($item['children'])) {
+ // HTML attributes for the outer list are defined in the 'attributes'
+ // theme variable, but not inherited by children. For nested lists,
+ // all non-numeric keys in 'children' are used as list attributes.
+ $child_list_attributes = array();
+ foreach ($item['children'] as $child_key => $child_item) {
+ if (is_string($child_key)) {
+ $child_list_attributes[$child_key] = $child_item;
+ unset($item['children'][$child_key]);
+ }
}
+ $value .= theme('item_list', array(
+ 'items' => $item['children'],
+ 'type' => $type,
+ 'attributes' => $child_list_attributes,
+ ));
}
}
else {
- $data = $item;
- }
- if (count($children) > 0) {
- // Render nested list.
- $data .= theme_item_list(array('items' => $children, 'title' => NULL, 'type' => $type, 'attributes' => $attributes));
+ $value = $item;
}
- if ($i == 0) {
+
+ $attributes['class'][] = ($i % 2 ? 'odd' : 'even');
+ if ($i == 1) {
$attributes['class'][] = 'first';
}
- if ($i == $num_items - 1) {
+ if ($i == $num_items) {
$attributes['class'][] = 'last';
}
- $output .= '' . $data . "\n";
+
+ $output .= '' . $value . '';
}
$output .= "$type>";
}
- $output .= '';
+
+ // Only output the list container and title, if there are any list items.
+ if ($output !== '') {
+ if ($title !== '') {
+ $title = '' . $title . '
';
+ }
+ $output = '' . $title . $output . '
';
+ }
+
return $output;
}
diff --git a/modules/simpletest/tests/theme.test b/modules/simpletest/tests/theme.test
index 53557e3..d064359 100644
--- a/modules/simpletest/tests/theme.test
+++ b/modules/simpletest/tests/theme.test
@@ -164,30 +164,101 @@ class ThemeTableUnitTest extends DrupalWebTestCase {
}
/**
- * Unit tests for theme_item_list().
+ * Tests for common theme functions.
*/
-class ThemeItemListUnitTest extends DrupalWebTestCase {
+class ThemeFunctionsTestCase extends DrupalWebTestCase {
+ protected $profile = 'testing';
+
public static function getInfo() {
return array(
- 'name' => 'Theme item list',
- 'description' => 'Test the theme_item_list() function.',
+ 'name' => 'Theme functions',
+ 'description' => 'Tests common theme functions.',
'group' => 'Theme',
);
}
/**
- * Test nested list rendering.
+ * Tests theme_item_list().
*/
- function testNestedList() {
- $items = array('a', array('data' => 'b', 'children' => array('c', 'd')), 'e');
- $expected = '';
- $output = theme('item_list', array('items' => $items));
- $this->assertIdentical($expected, $output, 'Nested list is rendered correctly.');
+ function testItemList() {
+ // Verify that empty variables produce no output.
+ $variables = array();
+ $expected = '';
+ $this->assertThemeOutput('item_list', $variables, $expected, 'Empty %callback generates no output.');
+
+ $variables = array();
+ $variables['title'] = 'Some title';
+ $expected = '';
+ $this->assertThemeOutput('item_list', $variables, $expected, 'Empty %callback with title generates no output.');
+
+ // Verify nested item lists.
+ $variables = array();
+ $variables['title'] = 'Some title';
+ $variables['attributes'] = array(
+ 'id' => 'parentlist',
+ );
+ $variables['items'] = array(
+ 'a',
+ array(
+ 'data' => 'b',
+ 'children' => array(
+ 'c',
+ // Nested children may use additional attributes.
+ array(
+ 'data' => 'd',
+ 'class' => array('dee'),
+ ),
+ // Any string key is treated as child list attribute.
+ 'id' => 'childlist',
+ ),
+ // Any other keys are treated as item attributes.
+ 'id' => 'bee',
+ ),
+ array(
+ 'data' => 'e',
+ 'id' => 'E',
+ ),
+ );
+ $inner = '';
+ $inner .= '- c
';
+ $inner .= '- d
';
+ $inner .= '
';
+
+ $expected = '';
+ $expected .= '
Some title
';
+ $expected .= '
';
+ $expected .= '- a
';
+ $expected .= '- b' . $inner . '
';
+ $expected .= '- e
';
+ $expected .= '
';
+
+ $this->assertThemeOutput('item_list', $variables, $expected);
+ }
+
+ /**
+ * Asserts themed output.
+ *
+ * @param $callback
+ * The name of the theme function to invoke; e.g. 'links' for theme_links().
+ * @param $variables
+ * An array of variables to pass to the theme function.
+ * @param $expected
+ * The expected themed output string.
+ * @param $message
+ * (optional) An assertion message.
+ */
+ protected function assertThemeOutput($callback, array $variables = array(), $expected, $message = '') {
+ $output = theme($callback, $variables);
+ $this->verbose('Variables:' . '' . check_plain(var_export($variables, TRUE)) . '
'
+ . '
' . 'Result:' . '' . check_plain(var_export($output, TRUE)) . '
'
+ . '
' . 'Expected:' . '' . check_plain(var_export($expected, TRUE)) . '
'
+ . '
' . $output
+ );
+ if (!$message) {
+ $message = '%callback rendered correctly.';
+ }
+ $message = t($message, array('%callback' => 'theme_' . $callback . '()'));
+ $this->assertIdentical($output, $expected, $message);
}
}