DetailView.php 7.57 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
<?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 yii\base\Arrayable;
use yii\base\Formatter;
use yii\base\InvalidConfigException;
use yii\base\Model;
use yii\base\Widget;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use yii\helpers\Inflector;

/**
 * DetailView displays the detail of a single data [[model]].
 *
 * DetailView is best used for displaying a model in a regular format (e.g. each model attribute
24
 * is displayed as a row in a table.) The model can be either an instance of [[Model]]
Qiang Xue committed
25 26 27 28 29 30 31 32
 * or an associative array.
 *
 * DetailView uses the [[attributes]] property to determines which model attributes
 * should be displayed and how they should be formatted.
 *
 * A typical usage of DetailView is as follows:
 *
 * ~~~
33
 * echo DetailView::widget(array(
Qiang Xue committed
34
 *     'model' => $model,
Qiang Xue committed
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
 *     'attributes' => array(
 *         'title',             // title attribute (in plain text)
 *         'description:html',  // description attribute in HTML
 *         array(               // the owner name of the model
 *             'label' => 'Owner',
 *             'value' => $model->owner->name,
 *         ),
 *     ),
 * ));
 * ~~~
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
class DetailView extends Widget
{
	/**
	 * @var array|object the data model whose details are to be displayed. This can be either a [[Model]] instance
	 * or an associative array.
	 */
	public $model;
	/**
	 * @var array a list of attributes to be displayed in the detail view. Each array element
	 * represents the specification for displaying one particular attribute.
	 *
	 * An attribute can be specified as a string in the format of "Name" or "Name:Type", where "Name" refers to
	 * the attribute name, and "Type" represents the type of the attribute. The "Type" is passed to the [[Formatter::format()]]
	 * method to format an attribute value into a displayable text. Please refer to [[Formatter]] for the supported types.
	 *
	 * An attribute can also be specified in terms of an array with the following elements:
	 *
	 * - name: the attribute name. This is required if either "label" or "value" is not specified.
	 * - label: the label associated with the attribute. If this is not specified, it will be generated from the attribute name.
	 * - value: the value to be displayed. If this is not specified, it will be retrieved from [[model]] using the attribute name
	 *   by calling [[ArrayHelper::getValue()]]. Note that this value will be formatted into a displayable text
	 *   according to the "type" option.
	 * - type: the type of the value that determines how the value would be formatted into a displayable text.
	 *   Please refer to [[Formatter]] for supported types.
	 * - visible: whether the attribute is visible. If set to `false`, the attribute will be displayed.
	 */
	public $attributes;
	/**
Qiang Xue committed
77
	 * @var string|callback the template used to render a single attribute. If a string, the token `{label}`
Qiang Xue committed
78
	 * and `{value}` will be replaced with the label and the value of the corresponding attribute.
Qiang Xue committed
79
	 * If a callback (e.g. an anonymous function), the signature must be as follows:
Qiang Xue committed
80 81 82 83 84 85 86 87 88 89 90 91 92
	 *
	 * ~~~
	 * function ($attribute, $index, $widget)
	 * ~~~
	 *
	 * where `$attribute` refer to the specification of the attribute being rendered, `$index` is the zero-based
	 * index of the attribute in the [[attributes]] array, and `$widget` refers to this widget instance.
	 */
	public $template = "<tr><th>{label}</th><td>{value}</td></tr>";
	/**
	 * @var array the HTML attributes for the container tag of this widget. The "tag" option specifies
	 * what container tag should be used. It defaults to "table" if not set.
	 */
93
	public $options = array('class' => 'table table-striped table-bordered');
Qiang Xue committed
94 95 96 97 98 99 100 101 102 103 104 105 106 107
	/**
	 * @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;

	/**
	 * Initializes the detail view.
	 * This method will initialize required property values.
	 */
	public function init()
	{
		if ($this->model === null) {
108
			throw new InvalidConfigException('Please specify the "model" property.');
Qiang Xue committed
109 110 111 112 113
		}
		if ($this->formatter == null) {
			$this->formatter = Yii::$app->getFormatter();
		} elseif (is_array($this->formatter)) {
			$this->formatter = Yii::createObject($this->formatter);
Qiang Xue committed
114 115
		}
		if (!$this->formatter instanceof Formatter) {
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
			throw new InvalidConfigException('The "formatter" property must be either a Format object or a configuration array.');
		}
		$this->normalizeAttributes();
	}

	/**
	 * Renders the detail view.
	 * This is the main entry of the whole detail view rendering.
	 */
	public function run()
	{
		$rows = array();
		$i = 0;
		foreach ($this->attributes as $attribute) {
			$rows[] = $this->renderAttribute($attribute, $i++);
		}

		$tag = ArrayHelper::remove($this->options, 'tag', 'table');
		echo Html::tag($tag, implode("\n", $rows), $this->options);
	}

	/**
	 * Renders a single attribute.
	 * @param array $attribute the specification of the attribute to be rendered.
	 * @param integer $index the zero-based index of the attribute in the [[attributes]] array
	 * @return string the rendering result
	 */
	protected function renderAttribute($attribute, $index)
	{
		if (is_string($this->template)) {
			return strtr($this->template, array(
				'{label}' => $attribute['label'],
				'{value}' => $this->formatter->format($attribute['value'], $attribute['type']),
			));
		} else {
			return call_user_func($this->template, $attribute, $index, $this);
		}
	}

	/**
	 * Normalizes the attribute specifications.
	 * @throws InvalidConfigException
	 */
	protected function normalizeAttributes()
	{
		if ($this->attributes === null) {
			if ($this->model instanceof Model) {
				$this->attributes = $this->model->attributes();
			} elseif (is_object($this->model)) {
				$this->attributes = $this->model instanceof Arrayable ? $this->model->toArray() : array_keys(get_object_vars($this->model));
			} elseif (is_array($this->model)) {
				$this->attributes = array_keys($this->model);
			} else {
169
				throw new InvalidConfigException('The "model" property must be either an array or an object.');
Qiang Xue committed
170 171 172 173 174 175 176
			}
			sort($this->attributes);
		}

		foreach ($this->attributes as $i => $attribute) {
			if (is_string($attribute)) {
				if (!preg_match('/^(\w+)(\s*:\s*(\w+))?$/', $attribute, $matches)) {
Qiang Xue committed
177
					throw new InvalidConfigException('The attribute must be specified in the format of "Name" or "Name:Type"');
Qiang Xue committed
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
				}
				$attribute = array(
					'name' => $matches[1],
					'type' => isset($matches[3]) ? $matches[3] : 'text',
				);
			}

			if (!is_array($attribute)) {
				throw new InvalidConfigException('The attribute configuration must be an array.');
			}

			if (!isset($attribute['type'])) {
				$attribute['type'] = 'text';
			}
			if (isset($attribute['name'])) {
				$name = $attribute['name'];
				if (!isset($attribute['label'])) {
					$attribute['label'] = $this->model instanceof Model ? $this->model->getAttributeLabel($name) : Inflector::camel2words($name, true);
				}
				if (!array_key_exists('value', $attribute)) {
					$attribute['value'] = ArrayHelper::getValue($this->model, $name);
				}
			} elseif (!isset($attribute['label']) || !array_key_exists('value', $attribute)) {
				throw new InvalidConfigException('The attribute configuration requires the "name" element to determine the value and display label.');
			}

			$this->attributes[$i] = $attribute;
		}
	}
}