DetailView.php 7.56 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 29 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
<?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
 * is displayed as a row in a table.) The model can be either an instance of [[Model]] or
 * 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:
 *
 * ~~~
 * \yii\widgets\DetailView::widget(array(
 *     'data' => $model,
 *     '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 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 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
	 *
	 * ~~~
	 * 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.
	 */
	public $options = array('class' => 'table table-striped');
	/**
	 * @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) {
			throw new InvalidConfigException('Please specify the "data" property.');
		}
		if ($this->formatter == null) {
			$this->formatter = Yii::$app->getFormatter();
		} elseif (is_array($this->formatter)) {
			$this->formatter = Yii::createObject($this->formatter);
		} elseif (!$this->formatter instanceof Formatter) {
			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 {
				throw new InvalidConfigException('The "data" property must be either an array or an object.');
			}
			sort($this->attributes);
		}

		foreach ($this->attributes as $i => $attribute) {
			if (is_string($attribute)) {
				if (!preg_match('/^(\w+)(\s*:\s*(\w+))?$/', $attribute, $matches)) {
					throw new InvalidConfigException('The attribute must be in the format of "Name" or "Name:Type"');
				}
				$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;
		}
	}
}