#258185 (by Moonshine): Provide option to retain sticky headers on tables.
[project/views.git] / theme / theme.inc
1 <?php
2 // $Id$
3
4 /**
5 * @file theme.inc
6 *
7 * An array of preprocessors to fill variables for templates and helper
8 * functions to make theming easier.
9 */
10
11 /**
12 * Provide a full array of possible themes to try for a given hook.
13 *
14 * @param $hook
15 * The hook to use. This is the base theme/template name.
16 * @param $view
17 * The view being rendered.
18 * @param $display
19 * The display being rendered, if applicable.
20 */
21 function _views_theme_functions($hook, $view, $display = NULL) {
22 $themes = array();
23
24 if ($display) {
25 $themes[] = $hook . '__' . $view->name . '__' . $display->id;
26 $themes[] = $hook . '__' . $display->id;
27 if ($display->id != $display->display_plugin) {
28 $themes[] = $hook . '__' . $view->name . '__' . $display->display_plugin;
29 $themes[] = $hook . '__' . $display->display_plugin;
30 }
31 }
32 $themes[] = $hook . '__' . $view->name;
33 $themes[] = $hook;
34
35 return $themes;
36 }
37
38 /**
39 * Preprocess the primary theme implementation for a view.
40 */
41 function template_preprocess_views_view(&$vars) {
42 $view = $vars['view'];
43
44 $vars['rows'] = !empty($view->result) ? $view->style_handler->render($view->result) : '';
45
46 $vars['css_name'] = views_css_safe($view->name);
47 $vars['name'] = $view->name;
48 $vars['display_id'] = $view->current_display;
49
50 if (!$vars['rows']) {
51 $vars['empty'] = $view->display_handler->render_empty();
52 if (!$view->display_handler->get_option('header_empty')) {
53 $vars['header'] = '';
54 }
55 if (!$view->display_handler->get_option('footer_empty')) {
56 $vars['footer'] = '';
57 }
58 }
59 else {
60 $vars['empty'] = '';
61 $header = TRUE;
62 }
63
64 $vars['exposed'] = !empty($view->exposed_widgets) ? $view->exposed_widgets : '';
65 if (!isset($vars['header'])) {
66 $vars['header'] = $view->display_handler->render_header();
67 }
68 if (!isset($vars['footer'])) {
69 $vars['footer'] = $view->display_handler->render_footer();
70 }
71 $vars['more'] = $view->display_handler->render_more_link();
72 $vars['feed_icon'] = !empty($view->feed_icon) ? $view->feed_icon : '';
73
74 $vars['attachment_before'] = !empty($view->attachment_before) ? $view->attachment_before : '';
75 $vars['attachment_after'] = !empty($view->attachment_after) ? $view->attachment_after : '';
76
77 $vars['pager'] = '';
78
79 $exposed_input = isset($view->exposed_data_raw) ? $view->exposed_data_raw : NULL;
80 if (!empty($view->pager['use_pager'])) {
81 $pager_type = ($view->pager['use_pager'] === 'mini' ? 'views_mini_pager' : 'pager');
82 $vars['pager'] = theme($pager_type, $exposed_input, $view->pager['items_per_page'], $view->pager['element']);
83 }
84
85 // if administrator, add some links. These used to be tabs, but this is better.
86 if (user_access('administer views')) {
87 $vars['admin_links_raw'] = array(
88 array(
89 'title' => t('Edit'),
90 'alt' => t("Edit this view"),
91 'href' => "admin/build/views/edit/$view->name",
92 'fragment' => 'views-tab-' . $view->current_display,
93 'query' => drupal_get_destination(),
94 ),
95 array(
96 'title' => t('Export'),
97 'alt' => t("Export this view"),
98 'href' => "admin/build/views/export/$view->name",
99 ),
100 array(
101 'title' => t('Clone'),
102 'alt' => t("Create a copy of this view"),
103 'href' => "admin/build/views/clone/$view->name",
104 ),
105 );
106 $vars['admin_links'] = theme('links', $vars['admin_links_raw']);
107 views_add_css('views');
108 }
109 else {
110 $vars['admin_links'] = '';
111 $vars['admin_links_raw'] = array();
112 }
113
114 // If using AJAX, send identifying data about this view.
115 if ($view->use_ajax) {
116 $settings = array(
117 'views' => array(
118 'ajax_path' => url('views/ajax'),
119 'ajaxViews' => array(
120 array(
121 'view_name' => $view->name,
122 'view_display_id' => $view->current_display,
123 'view_args' => implode('/', $view->args),
124 'view_path' => $_GET['q'],
125 ),
126 ),
127 ),
128 );
129
130 drupal_add_js($settings, 'setting');
131 views_add_js('ajax_view');
132 }
133 }
134
135 /**
136 * Preprocess theme function to print a single record from a row, with fields
137 */
138 function template_preprocess_views_view_fields(&$vars) {
139 $view = $vars['view'];
140
141 // Loop through the fields for this view.
142 $inline = FALSE;
143 foreach ($view->field as $id => $field) {
144 if (!empty($field['handler']) && is_object($field['handler'])) {
145 $object = new stdClass();
146
147 $object->content = $field['handler']->theme($vars['row']);
148 if (isset($field['handler']->field_alias) && isset($vars['row']->{$field['handler']->field_alias})) {
149 $object->raw = $vars['row']->{$field['handler']->field_alias};
150 }
151 else {
152 $object->raw = NULL; // make sure it exists to reduce NOTICE
153 }
154 $object->inline = !empty($vars['options']['inline'][$id]);
155 $object->inline_html = $object->inline ? 'span' : 'div';
156 if (!empty($vars['options']['separator']) && $inline && $object->inline && $object->content) {
157 $object->separator = filter_xss_admin($vars['options']['separator']);
158 }
159
160 $inline = $object->inline;
161
162 $object->handler = $field['handler'];
163 $object->class = views_css_safe($id);
164 $object->label = check_plain($field['handler']->label());
165 $vars['fields'][$id] = $object;
166 }
167 }
168
169 }
170
171 /**
172 * Display a single views field.
173 *
174 * Interesting bits of info:
175 * $field->field_alias says what the raw value in $row will be. Reach it like
176 * this: @code { $row->{$field->field_alias} @endcode
177 */
178 function theme_views_view_field($view, $field, $row) {
179 return $field->render($row);
180 }
181
182 /**
183 * Preprocess theme function to print a single record from a row, with fields
184 */
185 function template_preprocess_views_view_summary(&$vars) {
186 $view = $vars['view'];
187 $argument = $view->argument[$view->build_info['summary_level']]['handler'];
188
189 $url_options = array();
190
191 if (!empty($view->exposed_raw_input)) {
192 $url_options['query'] = $view->exposed_raw_input;
193 }
194 foreach ($vars['rows'] as $id => $row) {
195 $vars['rows'][$id]->link = $argument->summary_name($row);
196 $args = $view->args;
197 $args[$argument->position] = $argument->summary_argument($row);
198
199 $vars['rows'][$id]->url = url($view->get_url($args), $url_options);
200 $vars['rows'][$id]->count = intval($row->{$argument->count_alias});
201 }
202 }
203
204 /**
205 * Display a view as a table style.
206 */
207 function template_preprocess_views_view_table(&$vars) {
208 $view = $vars['view'];
209 $result = $view->result;
210 $options = $view->style_handler->options;
211 $handler = $view->style_handler;
212
213 $fields = $view->field;
214 $columns = $handler->sanitize_columns($options['columns'], $fields);
215
216 $active = !empty($handler->active) ? $handler->active : '';
217 $order = !empty($handler->order) ? $handler->order : 'asc';
218
219 $query = tablesort_get_querystring();
220 if ($query) {
221 $query = '&' . $query;
222 }
223
224 foreach ($columns as $field => $column) {
225 // render the header labels
226 if ($field == $column) {
227 $label = check_plain(!empty($fields[$field]['handler']) ? $fields[$field]['handler']->label() : '');
228 if (empty($options['info'][$field]['sortable'])) {
229 $vars['header'][$field] = $label;
230 }
231 else {
232 // @todo -- make this a setting
233 $initial = 'asc';
234
235 if ($active == $field && $order == 'asc') {
236 $initial = 'desc';
237 }
238
239 $image = theme('tablesort_indicator', $initial);
240 $title = t('sort by @s', array('@s' => $label));
241 $link_options = array(
242 'html' => true,
243 'attributes' => array('title' => $title),
244 'query' => 'order=' . urlencode($field) . '&sort=' . $initial . $query,
245 );
246 $vars['header'][$field] = l($label . $image, $_GET['q'], $link_options);
247 }
248 }
249
250 // Create a second variable so we can easily find what fields we have and what the
251 // CSS classes should be.
252 $vars['fields'][$field] = views_css_safe($field);
253 if ($active == $field) {
254 $vars['fields'][$field] .= ' active';
255 }
256
257 // Render each field into its appropriate column.
258 foreach ($result as $num => $row) {
259 if (!empty($fields[$field]['handler']) && is_object($fields[$field]['handler'])) {
260 $handler = &$fields[$field]['handler'];
261 $field_output = $handler->theme($row);
262
263 // Don't bother with separators and stuff if the field does not show up.
264 if (!isset($field_output) && isset($vars['rows'][$num][$column])) {
265 continue;
266 }
267
268 // Place the field into the column, along with an optional separator.
269 if (isset($vars['rows'][$num][$column])) {
270 if (!empty($options['info'][$column]['separator'])) {
271 $vars['rows'][$num][$column] .= filter_xss_admin($options['info'][$column]['separator']);
272 }
273 }
274 else {
275 $vars['rows'][$num][$column] = '';
276 }
277
278 $vars['rows'][$num][$column] .= $field_output;
279 }
280 }
281 }
282
283 $vars['class'] = 'views-table';
284 if (!empty($options['sticky'])) {
285 drupal_add_js('misc/tableheader.js');
286 $vars['class'] .= " sticky-enabled";
287 }
288 }
289
290 /**
291 * Display a view as a grid style.
292 */
293 function template_preprocess_views_view_grid(&$vars) {
294 $view = $vars['view'];
295 $result = $view->result;
296 $options = $view->style_handler->options;
297 $handler = $view->style_handler;
298
299 $columns = $options['columns'];
300
301 $rows = array();
302
303 if ($options['alignment'] == 'horizontal') {
304 $row = array();
305 foreach ($vars['rows'] as $count => $item) {
306 $row[] = $item;
307 if (($count + 1) % $columns == 0) {
308 $rows[] = $row;
309 $row = array();
310 }
311 }
312 if ($row) {
313 $rows[] = $row;
314 }
315 }
316 else {
317 $num_rows = floor(count($vars['rows']) / $columns);
318 // The remainders are the 'odd' columns that are slightly longer.
319 $remainders = count($vars['rows']) % $columns;
320 $row = 0;
321 $col = 0;
322 foreach ($vars['rows'] as $count => $item) {
323 $rows[$row][$col] = $item;
324 $row++;
325
326 if (!$remainders && $row == $num_rows) {
327 $row = 0;
328 $col++;
329 }
330 else if ($remainders && $row == $num_rows + 1) {
331 $row = 0;
332 $col++;
333 $remainders--;
334 }
335 }
336 }
337 $vars['rows'] = $rows;
338 }
339
340 /**
341 * Preprocess an RSS feed
342 */
343 function template_preprocess_views_view_rss(&$vars) {
344 global $base_url;
345 global $language;
346
347 $view = &$vars['view'];
348 $options = &$vars['options'];
349 $items = &$vars['rows'];
350
351 $style = &$view->style_handler;
352
353 if (!empty($options['mission_description'])) {
354 $description = variable_get('site_mission', '');
355 }
356 else {
357 $description = $options['description'];
358 }
359
360 // Figure out which display which has a path we're using for this feed. If there isn't
361 // one, use the global $base_url
362 $link_display = $view->display_handler->get_link_display();
363
364
365 // Compare the link to the default home page; if it's the default home page, just use $base_url.
366 if (empty($vars['link'])) {
367 $vars['link'] = $base_url;
368 }
369
370 $vars['namespaces'] = drupal_attributes($style->namespaces);
371 $vars['channel'] = format_rss_channel($view->get_title(), $vars['link'], $description, $items, $language->language);
372
373 drupal_set_header('Content-Type: application/rss+xml; charset=utf-8');
374 }
375
376 /**
377 * Default theme function for all filter forms.
378 */
379 function template_preprocess_views_exposed_form(&$vars) {
380 views_add_css('views');
381 $form = &$vars['form'];
382
383 // Put all single checkboxes together in the last spot.
384 $checkboxes = '';
385
386 if (!empty($form['q'])) {
387 $vars['q'] = drupal_render($form['q']);
388 }
389
390 $vars['widgets'] = array();
391 foreach ($form['#info'] as $id => $info) {
392 // Set aside checkboxes.
393 if (isset($form[$info['value']]['#type']) && $form[$info['value']]['#type'] == 'checkbox') {
394 $checkboxes .= drupal_render($form[$info['value']]);
395 continue;
396 }
397 $widget = new stdClass;
398 // set up defaults so that there's always something there.
399 $widget->label = $widget->operator = $widget->widget = NULL;
400
401 if (!empty($info['label'])) {
402 $widget->label = $info['label'];
403 }
404 if (!empty($info['operator'])) {
405 $widget->operator = drupal_render($form[$info['operator']]);
406 }
407 $widget->widget = drupal_render($form[$info['value']]);
408 $vars['widgets'][$id] = $widget;
409 }
410
411 // Wrap up all the checkboxes we set aside into a widget.
412 if ($checkboxes) {
413 $widget = new stdClass;
414 // set up defaults so that there's always something there.
415 $widget->label = $widget->operator = $widget->widget = NULL;
416 $widget->widget = $checkboxes;
417 $vars['widgets']['checkboxes'] = $widget;
418 }
419
420 // Don't render these:
421 unset($form['form_id']);
422 unset($form['form_build_id']);
423 unset($form['form_token']);
424
425 // This includes the submit button.
426 $vars['button'] = drupal_render($form);
427 }
428
429 function theme_views_mini_pager($tags = array(), $limit = 10, $element = 0, $parameters = array(), $quantity = 9) {
430 global $pager_page_array, $pager_total;
431
432 // Calculate various markers within this pager piece:
433 // Middle is used to "center" pages around the current page.
434 $pager_middle = ceil($quantity / 2);
435 // current is the page we are currently paged to
436 $pager_current = $pager_page_array[$element] + 1;
437 // max is the maximum page number
438 $pager_max = $pager_total[$element];
439 // End of marker calculations.
440
441
442 $li_previous = theme('pager_previous', (isset($tags[1]) ? $tags[1] : t('‹‹')), $limit, $element, 1, $parameters);
443 $li_next = theme('pager_next', (isset($tags[3]) ? $tags[3] : t('››')), $limit, $element, 1, $parameters);
444
445 if ($pager_total[$element] > 1) {
446 $items[] = array(
447 'class' => 'pager-previous',
448 'data' => $li_previous,
449 );
450
451 $items[] = array(
452 'class' => 'pager-current',
453 'data' => t('@current of @max', array('@current' => $pager_current, '@max' => $pager_max)),
454 );
455
456 $items[] = array(
457 'class' => 'pager-next',
458 'data' => $li_next,
459 );
460 return theme('item_list', $items, NULL, 'ul', array('class' => 'pager'));
461 }
462 }
463
464 /**
465 * @defgroup views_templates Views' template files
466 * @{
467 * All views templates can be overridden with a variety of names, using
468 * the view, the display ID of the view, the display type of the view,
469 * or some combination thereof.
470 *
471 * For each view, there will be a minimum of two templates used. The first
472 * is used for all views: views-view.tpl.php.
473 *
474 * The second template is determined by the style selected for the view. Note
475 * that certain aspects of the view can also change which style is used; for
476 * example, arguments which provide a summary view might change the style to
477 * one of the special summary styles.
478 *
479 * The default style for all views is views-view-unformatted.tpl.php
480 *
481 * Many styles will then farm out the actual display of each row to a row
482 * style; the default row style is views-view-fields.tpl.php.
483 *
484 * Here is an example of all the templates that will be tried in the following
485 * case:
486 *
487 * View, named foobar. Style: unformatted. Row style: Fields. Display: Page.
488 *
489 * - views-view--page--foobar.tpl.php
490 * - views-view--page.tpl.php
491 * - views-view--foobar.tpl.php
492 * - views-view.tpl.php
493 *
494 * - views-view-unformatted--page--foobar.tpl.php
495 * - views-view-unformatted--page.tpl.php
496 * - views-view-unformatted--foobar.tpl.php
497 * - views-view-unformatted.tpl.php
498 *
499 * - views-view-fields--page--foobar.tpl.php
500 * - views-view-fields--page.tpl.php
501 * - views-view-fields--foobar.tpl.php
502 * - views-view-fields.tpl.php
503 *
504 * Important! When adding a new template to your theme, be sure to flush the
505 * theme registry cache! Simply visit admin/build/themes.
506 *
507 * @see _views_theme_functions
508 * @}
509 */