GridView.php 10 KB
Newer Older
Qiang Xue committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\widgets;

use Yii;
use Closure;
use yii\base\Formatter;
use yii\base\InvalidConfigException;
use yii\base\Widget;
use yii\db\ActiveRecord;
use yii\helpers\Html;
use yii\widgets\grid\DataColumn;

/**
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
class GridView extends ListViewBase
{
	const FILTER_POS_HEADER = 'header';
	const FILTER_POS_FOOTER = 'footer';
	const FILTER_POS_BODY = 'body';

29
	public $dataColumnClass = 'yii\widgets\grid\DataColumn';
Qiang Xue committed
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
	public $caption;
	public $captionOptions = array();
	public $tableOptions = array('class' => 'table table-striped table-bordered');
	public $headerRowOptions = array();
	public $footerRowOptions = array();
	public $beforeRow;
	public $afterRow;
	public $showHeader = true;
	public $showFooter = false;
	/**
	 * @var array|Closure
	 */
	public $rowOptions = array();
	/**
	 * @var array|Formatter the formatter used to format model attribute values into displayable texts.
	 * This can be either an instance of [[Formatter]] or an configuration array for creating the [[Formatter]]
	 * instance. If this property is not set, the "formatter" application component will be used.
	 */
	public $formatter;
	/**
	 * @var array grid column configuration. Each array element represents the configuration
	 * for one particular grid column which can be either a string or an array.
	 *
	 * When a column is specified as a string, it should be in the format of "name:type:header",
	 * where "type" and "header" are optional. A {@link CDataColumn} instance will be created in this case,
	 * whose {@link CDataColumn::name}, {@link CDataColumn::type} and {@link CDataColumn::header}
	 * properties will be initialized accordingly.
	 *
	 * When a column is specified as an array, it will be used to create a grid column instance, where
	 * the 'class' element specifies the column class name (defaults to {@link CDataColumn} if absent).
	 * Currently, these official column classes are provided: {@link CDataColumn},
	 * {@link CLinkColumn}, {@link CButtonColumn} and {@link CCheckBoxColumn}.
	 */
	public $columns = array();
	/**
	 * @var string the layout that determines how different sections of the list view should be organized.
	 * The following tokens will be replaced with the corresponding section contents:
	 *
	 * - `{summary}`: the summary section. See [[renderSummary()]].
	 * - `{items}`: the list items. See [[renderItems()]].
	 * - `{sorter}`: the sorter. See [[renderSorter()]].
	 * - `{pager}`: the pager. See [[renderPager()]].
	 */
	public $layout = "{summary}\n{pager}{items}\n{pager}";
	public $emptyCell = '&nbsp;';
	/**
	 * @var \yii\base\Model the model instance that keeps the user-entered filter data. When this property is set,
	 * the grid view will enable column-based filtering. Each data column by default will display a text field
	 * at the top that users can fill in to filter the data.
	 * Note that in order to show an input field for filtering, a column must have its {@link CDataColumn::name}
	 * property set or have {@link CDataColumn::filter} as the HTML code for the input field.
	 * When this property is not set (null) the filtering is disabled.
	 */
	public $filterModel;
	/**
	 * @var string whether the filters should be displayed in the grid view. Valid values include:
	 * <ul>
	 *    <li>header: the filters will be displayed on top of each column's header cell.</li>
	 *    <li>body: the filters will be displayed right below each column's header cell.</li>
	 *    <li>footer: the filters will be displayed below each column's footer cell.</li>
	 * </ul>
	 */
	public $filterPosition = 'body';
	public $filterOptions = array('class' => 'filters');

	/**
	 * Initializes the grid view.
	 * This method will initialize required property values and instantiate {@link columns} objects.
	 */
	public function init()
	{
		parent::init();
		if ($this->formatter == null) {
			$this->formatter = Yii::$app->getFormatter();
		} elseif (is_array($this->formatter)) {
			$this->formatter = Yii::createObject($this->formatter);
		}
		if (!$this->formatter instanceof Formatter) {
			throw new InvalidConfigException('The "formatter" property must be either a Format object or a configuration array.');
		}

		$this->initColumns();
	}

	/**
115
	 * Renders the data models for the grid view.
Qiang Xue committed
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
	 */
	public function renderItems()
	{
		$content = array_filter(array(
			$this->renderCaption(),
			$this->renderColumnGroup(),
			$this->showHeader ? $this->renderTableHeader() : false,
			$this->showFooter ? $this->renderTableFooter() : false,
			$this->renderTableBody(),
		));
		return Html::tag('table', implode("\n", $content), $this->tableOptions);
	}

	public function renderCaption()
	{
		if (!empty($this->caption)) {
			return Html::tag('caption', $this->caption, $this->captionOptions);
		} else {
			return false;
		}
	}

	public function renderColumnGroup()
	{
		$requireColumnGroup = false;
		foreach ($this->columns as $column) {
			/** @var \yii\widgets\grid\Column $column */
			if (!empty($column->options)) {
				$requireColumnGroup = true;
				break;
			}
		}
		if ($requireColumnGroup) {
			$cols = array();
			foreach ($this->columns as $column) {
				$cols[] = Html::tag('col', '', $column->options);
			}
			return Html::tag('colgroup', implode("\n", $cols));
		} else {
			return false;
		}
	}

	/**
	 * Renders the table header.
	 * @return string the rendering result
	 */
	public function renderTableHeader()
	{
		$cells = array();
		foreach ($this->columns as $column) {
			/** @var \yii\widgets\grid\Column $column */
			$cells[] = $column->renderHeaderCell();
		}
		$content = implode('', $cells);
		if ($this->filterPosition == self::FILTER_POS_HEADER) {
			$content = $this->renderFilters() . $content;
		} elseif ($this->filterPosition == self::FILTER_POS_BODY) {
			$content .= $this->renderFilters();
		}
		return "<thead>\n" . Html::tag('tr', $content, $this->headerRowOptions) . "\n</thead>";
	}

	/**
	 * Renders the table footer.
	 * @return string the rendering result
	 */
	public function renderTableFooter()
	{
		$cells = array();
		foreach ($this->columns as $column) {
			/** @var \yii\widgets\grid\Column $column */
			$cells[] = $column->renderFooterCell();
		}
		$content = implode('', $cells);
		if ($this->filterPosition == self::FILTER_POS_FOOTER) {
			$content .= $this->renderFilters();
		}
		return "<tfoot>\n" . Html::tag('tr', $content, $this->footerRowOptions) . "\n</tfoot>";
	}

	/**
	 * Renders the filter.
	 */
	public function renderFilters()
	{
		if ($this->filterModel !== null) {
			$cells = array();
			foreach ($this->columns as $column) {
				/** @var \yii\widgets\grid\Column $column */
				$cells[] = $column->renderFilterCell();
			}
			return Html::tag('tr', implode('', $cells), $this->filterOptions);
		} else {
			return '';
		}
	}

	/**
	 * Renders the table body.
	 * @return string the rendering result
	 */
	public function renderTableBody()
	{
220
		$models = array_values($this->dataProvider->getModels());
Qiang Xue committed
221 222
		$keys = $this->dataProvider->getKeys();
		$rows = array();
223
		foreach ($models as $index => $model) {
Qiang Xue committed
224 225
			$key = $keys[$index];
			if ($this->beforeRow !== null) {
226
				$row = call_user_func($this->beforeRow, $model, $key, $index);
Qiang Xue committed
227 228 229 230 231
				if (!empty($row)) {
					$rows[] = $row;
				}
			}

232
			$rows[] = $this->renderTableRow($model, $key, $index);
Qiang Xue committed
233 234

			if ($this->afterRow !== null) {
235
				$row = call_user_func($this->afterRow, $model, $key, $index);
Qiang Xue committed
236 237 238 239 240 241 242 243 244
				if (!empty($row)) {
					$rows[] = $row;
				}
			}
		}
		return "<tbody>\n" . implode("\n", $rows) . "\n</tbody>";
	}

	/**
245 246 247 248
	 * Renders a table row with the given data model and key.
	 * @param mixed $model the data model to be rendered
	 * @param mixed $key the key associated with the data model
	 * @param integer $index the zero-based index of the data model among the model array returned by [[dataProvider]].
Qiang Xue committed
249 250
	 * @return string the rendering result
	 */
251
	public function renderTableRow($model, $key, $index)
Qiang Xue committed
252 253 254 255
	{
		$cells = array();
		/** @var \yii\widgets\grid\Column $column */
		foreach ($this->columns as $column) {
256
			$cells[] = $column->renderDataCell($model, $index);
Qiang Xue committed
257 258
		}
		if ($this->rowOptions instanceof Closure) {
259
			$options = call_user_func($this->rowOptions, $model, $key, $index);
Qiang Xue committed
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
		} else {
			$options = $this->rowOptions;
		}
		$options['data-key'] = $key;
		return Html::tag('tr', implode('', $cells), $options);
	}

	/**
	 * Creates column objects and initializes them.
	 */
	protected function initColumns()
	{
		if (empty($this->columns)) {
			$this->guessColumns();
		}
		$id = $this->getId();
		foreach ($this->columns as $i => $column) {
			if (is_string($column)) {
				$column = $this->createDataColumn($column);
			} else {
				$column = Yii::createObject(array_merge(array(
281
					'class' => $this->dataColumnClass,
Qiang Xue committed
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
					'grid' => $this,
				), $column));
			}
			if (!$column->visible) {
				unset($this->columns[$i]);
				continue;
			}
			if ($column->id === null) {
				$column->id = $id . '_c' . $i;
			}
			$this->columns[$i] = $column;
		}
	}

	/**
	 * Creates a {@link CDataColumn} based on a shortcut column specification string.
	 * @param string $text the column specification string
	 * @return DataColumn the column instance
	 * @throws InvalidConfigException if the column specification is invalid
	 */
	protected function createDataColumn($text)
	{
304 305
		if (!preg_match('/^([\w\.]+)(:(\w*))?(:(.*))?$/', $text, $matches)) {
			throw new InvalidConfigException('The column must be specified in the format of "Attribute", "Attribute:Format" or "Attribute:Format:Header');
Qiang Xue committed
306 307
		}
		return Yii::createObject(array(
308
			'class' => $this->dataColumnClass,
Qiang Xue committed
309 310
			'grid' => $this,
			'attribute' => $matches[1],
311 312
			'format' => isset($matches[3]) ? $matches[3] : 'text',
			'header' => isset($matches[5]) ? $matches[5] : null,
Qiang Xue committed
313 314 315 316 317
		));
	}

	protected function guessColumns()
	{
318 319 320 321
		$models = $this->dataProvider->getModels();
		$model = reset($models);
		if (is_array($model) || is_object($model)) {
			foreach ($model as $name => $value) {
Qiang Xue committed
322 323 324 325 326 327 328
				$this->columns[] = $name;
			}
		} else {
			throw new InvalidConfigException('Unable to generate columns from data.');
		}
	}
}