1: <?php
2: /**
3: * CListView class file.
4: *
5: * @author Qiang Xue <qiang.xue@gmail.com>
6: * @link http://www.yiiframework.com/
7: * @copyright 2008-2013 Yii Software LLC
8: * @license http://www.yiiframework.com/license/
9: */
10:
11: Yii::import('zii.widgets.CBaseListView');
12:
13: /**
14: * CListView displays a list of data items in terms of a list.
15: *
16: * Unlike {@link CGridView} which displays the data items in a table, CListView allows one to use
17: * a view template to render each data item. As a result, CListView could generate more flexible
18: * rendering result.
19: *
20: * CListView supports both sorting and pagination of the data items. The sorting
21: * and pagination can be done in AJAX mode or normal page request. A benefit of using CListView is that
22: * when the user browser disables JavaScript, the sorting and pagination automatically degenerate
23: * to normal page requests and are still functioning as expected.
24: *
25: * CListView should be used together with a {@link IDataProvider data provider}, preferably a
26: * {@link CActiveDataProvider}.
27: *
28: * The minimal code needed to use CListView is as follows:
29: *
30: * <pre>
31: * $dataProvider=new CActiveDataProvider('Post');
32: *
33: * $this->widget('zii.widgets.CListView', array(
34: * 'dataProvider'=>$dataProvider,
35: * 'itemView'=>'_post', // refers to the partial view named '_post'
36: * 'sortableAttributes'=>array(
37: * 'title',
38: * 'create_time'=>'Post Time',
39: * ),
40: * ));
41: * </pre>
42: *
43: * The above code first creates a data provider for the <code>Post</code> ActiveRecord class.
44: * It then uses CListView to display every data item as returned by the data provider.
45: * The display is done via the partial view named '_post'. This partial view will be rendered
46: * once for every data item. In the view, one can access the current data item via variable <code>$data</code>.
47: * For more details, see {@link itemView}.
48: *
49: * In order to support sorting, one has to specify the {@link sortableAttributes} property.
50: * By doing so, a list of hyperlinks that can sort the data will be displayed.
51: *
52: * @author Qiang Xue <qiang.xue@gmail.com>
53: * @package zii.widgets
54: * @since 1.1
55: */
56: class CListView extends CBaseListView
57: {
58: /**
59: * @var string the view used for rendering each data item.
60: * This property value will be passed as the first parameter to either {@link CController::renderPartial}
61: * or {@link CWidget::render} to render each data item.
62: * In the corresponding view template, the following variables can be used in addition to those declared in {@link viewData}:
63: * <ul>
64: * <li><code>$this</code>: refers to the owner of this list view widget. For example, if the widget is in the view of a controller,
65: * then <code>$this</code> refers to the controller.</li>
66: * <li><code>$data</code>: refers to the data item currently being rendered.</li>
67: * <li><code>$index</code>: refers to the zero-based index of the data item currently being rendered.</li>
68: * <li><code>$widget</code>: refers to this list view widget instance.</li>
69: * </ul>
70: */
71: public $itemView;
72: /**
73: * @var string the HTML code to be displayed between any two consecutive items.
74: * @since 1.1.7
75: */
76: public $separator;
77: /**
78: * @var array additional data to be passed to {@link itemView} when rendering each data item.
79: * This array will be extracted into local PHP variables that can be accessed in the {@link itemView}.
80: */
81: public $viewData=array();
82: /**
83: * @var array list of sortable attribute names. In order for an attribute to be sortable, it must also
84: * appear as a sortable attribute in the {@link IDataProvider::sort} property of {@link dataProvider}.
85: * @see enableSorting
86: */
87: public $sortableAttributes;
88: /**
89: * @var string the template to be used to control the layout of various components in the list view.
90: * These tokens are recognized: {summary}, {sorter}, {items} and {pager}. They will be replaced with the
91: * summary text, the sort links, the data item list, and the pager.
92: */
93: public $template="{summary}\n{sorter}\n{items}\n{pager}";
94: /**
95: * @var string the CSS class name that will be assigned to the widget container element
96: * when the widget is updating its content via AJAX. Defaults to 'list-view-loading'.
97: * @since 1.1.1
98: */
99: public $loadingCssClass='list-view-loading';
100: /**
101: * @var string the CSS class name for the sorter container. Defaults to 'sorter'.
102: */
103: public $sorterCssClass='sorter';
104: /**
105: * @var string the text shown before sort links. Defaults to 'Sort by: '.
106: */
107: public $sorterHeader;
108: /**
109: * @var string the text shown after sort links. Defaults to empty.
110: */
111: public $sorterFooter='';
112: /**
113: * @var mixed the ID of the container whose content may be updated with an AJAX response.
114: * Defaults to null, meaning the container for this list view instance.
115: * If it is set false, it means sorting and pagination will be performed in normal page requests
116: * instead of AJAX requests. If the sorting and pagination should trigger the update of multiple
117: * containers' content in AJAX fashion, these container IDs may be listed here (separated with comma).
118: */
119: public $ajaxUpdate;
120: /**
121: * @var string the jQuery selector of the HTML elements that may trigger AJAX updates when they are clicked.
122: * If not set, the pagination links and the sorting links will trigger AJAX updates.
123: * @since 1.1.7
124: */
125: public $updateSelector;
126: /**
127: * @var string a javascript function that will be invoked if an AJAX update error occurs.
128: *
129: * The function signature is <code>function(xhr, textStatus, errorThrown, errorMessage)</code>
130: * <ul>
131: * <li><code>xhr</code> is the XMLHttpRequest object.</li>
132: * <li><code>textStatus</code> is a string describing the type of error that occurred.
133: * Possible values (besides null) are "timeout", "error", "notmodified" and "parsererror"</li>
134: * <li><code>errorThrown</code> is an optional exception object, if one occurred.</li>
135: * <li><code>errorMessage</code> is the CGridView default error message derived from xhr and errorThrown.
136: * Usefull if you just want to display this error differently. CGridView by default displays this error with an javascript.alert()</li>
137: * </ul>
138: * Note: This handler is not called for JSONP requests, because they do not use an XMLHttpRequest.
139: *
140: * Example (add in a call to CGridView):
141: * <pre>
142: * ...
143: * 'ajaxUpdateError'=>'function(xhr,ts,et,err,id){ $("#"+id).text(err); }',
144: * ...
145: * </pre>
146: * @since 1.1.13
147: */
148: public $ajaxUpdateError;
149: /**
150: * @var string the name of the GET variable that indicates the request is an AJAX request triggered
151: * by this widget. Defaults to 'ajax'. This is effective only when {@link ajaxUpdate} is not false.
152: */
153: public $ajaxVar='ajax';
154: /**
155: * @var mixed the URL for the AJAX requests should be sent to. {@link CHtml::normalizeUrl()} will be
156: * called on this property. If not set, the current page URL will be used for AJAX requests.
157: * @since 1.1.8
158: */
159: public $ajaxUrl;
160: /**
161: * @var string the type ('GET' or 'POST') of the AJAX requests. If not set, 'GET' will be used.
162: * You can set this to 'POST' if you are filtering by many fields at once and have a problem with GET query string length.
163: * Note that in POST mode direct links and {@link enableHistory} feature may not work correctly!
164: * @since 1.1.14
165: */
166: public $ajaxType;
167: /**
168: * @var string a javascript function that will be invoked before an AJAX update occurs.
169: * The function signature is <code>function(id)</code> where 'id' refers to the ID of the list view.
170: */
171: public $beforeAjaxUpdate;
172: /**
173: * @var string a javascript function that will be invoked after a successful AJAX response is received.
174: * The function signature is <code>function(id, data)</code> where 'id' refers to the ID of the list view
175: * 'data' the received ajax response data.
176: */
177: public $afterAjaxUpdate;
178: /**
179: * @var string the base script URL for all list view resources (e.g. javascript, CSS file, images).
180: * Defaults to null, meaning using the integrated list view resources (which are published as assets).
181: */
182: public $baseScriptUrl;
183: /**
184: * @var string the URL of the CSS file used by this list view. Defaults to null, meaning using the integrated
185: * CSS file. If this is set false, you are responsible to explicitly include the necessary CSS file in your page.
186: */
187: public $cssFile;
188: /**
189: * @var string the HTML tag name for the container of all data item display. Defaults to 'div'.
190: * @since 1.1.4
191: */
192: public $itemsTagName='div';
193:
194: /**
195: * @var boolean whether to leverage the {@link https://developer.mozilla.org/en/DOM/window.history DOM history object}. Set this property to true
196: * to persist state of list across page revisits. Note, there are two limitations for this feature:
197: * - this feature is only compatible with browsers that support HTML5.
198: * - expect unexpected functionality (e.g. multiple ajax calls) if there is more than one grid/list on a single page with enableHistory turned on.
199: * @since 1.1.11
200: */
201: public $enableHistory=false;
202:
203: /**
204: * Initializes the list view.
205: * This method will initialize required property values and instantiate {@link columns} objects.
206: */
207: public function init()
208: {
209: if($this->itemView===null)
210: throw new CException(Yii::t('zii','The property "itemView" cannot be empty.'));
211: parent::init();
212:
213: if(!isset($this->htmlOptions['class']))
214: $this->htmlOptions['class']='list-view';
215:
216: if($this->baseScriptUrl===null)
217: $this->baseScriptUrl=Yii::app()->getAssetManager()->publish(Yii::getPathOfAlias('zii.widgets.assets')).'/listview';
218:
219: if($this->cssFile!==false)
220: {
221: if($this->cssFile===null)
222: $this->cssFile=$this->baseScriptUrl.'/styles.css';
223: Yii::app()->getClientScript()->registerCssFile($this->cssFile);
224: }
225: }
226:
227: /**
228: * Registers necessary client scripts.
229: */
230: public function registerClientScript()
231: {
232: $id=$this->getId();
233:
234: if($this->ajaxUpdate===false)
235: $ajaxUpdate=array();
236: else
237: $ajaxUpdate=array_unique(preg_split('/\s*,\s*/',$this->ajaxUpdate.','.$id,-1,PREG_SPLIT_NO_EMPTY));
238: $options=array(
239: 'ajaxUpdate'=>$ajaxUpdate,
240: 'ajaxVar'=>$this->ajaxVar,
241: 'pagerClass'=>$this->pagerCssClass,
242: 'loadingClass'=>$this->loadingCssClass,
243: 'sorterClass'=>$this->sorterCssClass,
244: 'enableHistory'=>$this->enableHistory
245: );
246: if($this->ajaxUrl!==null)
247: $options['url']=CHtml::normalizeUrl($this->ajaxUrl);
248: if($this->ajaxType!==null)
249: $options['ajaxType']=strtoupper($this->ajaxType);
250: if($this->updateSelector!==null)
251: $options['updateSelector']=$this->updateSelector;
252: foreach(array('beforeAjaxUpdate', 'afterAjaxUpdate', 'ajaxUpdateError') as $event)
253: {
254: if($this->$event!==null)
255: {
256: if($this->$event instanceof CJavaScriptExpression)
257: $options[$event]=$this->$event;
258: else
259: $options[$event]=new CJavaScriptExpression($this->$event);
260: }
261: }
262:
263: $options=CJavaScript::encode($options);
264: $cs=Yii::app()->getClientScript();
265: $cs->registerCoreScript('jquery');
266: $cs->registerCoreScript('bbq');
267: if($this->enableHistory)
268: $cs->registerCoreScript('history');
269: $cs->registerScriptFile($this->baseScriptUrl.'/jquery.yiilistview.js',CClientScript::POS_END);
270: $cs->registerScript(__CLASS__.'#'.$id,"jQuery('#$id').yiiListView($options);");
271: }
272:
273: /**
274: * Renders the data item list.
275: */
276: public function renderItems()
277: {
278: echo CHtml::openTag($this->itemsTagName,array('class'=>$this->itemsCssClass))."\n";
279: $data=$this->dataProvider->getData();
280: if(($n=count($data))>0)
281: {
282: $owner=$this->getOwner();
283: $viewFile=$owner->getViewFile($this->itemView);
284: $j=0;
285: foreach($data as $i=>$item)
286: {
287: $data=$this->viewData;
288: $data['index']=$i;
289: $data['data']=$item;
290: $data['widget']=$this;
291: $owner->renderFile($viewFile,$data);
292: if($j++ < $n-1)
293: echo $this->separator;
294: }
295: }
296: else
297: $this->renderEmptyText();
298: echo CHtml::closeTag($this->itemsTagName);
299: }
300:
301: /**
302: * Renders the sorter.
303: */
304: public function renderSorter()
305: {
306: if($this->dataProvider->getItemCount()<=0 || !$this->enableSorting || empty($this->sortableAttributes))
307: return;
308: echo CHtml::openTag('div',array('class'=>$this->sorterCssClass))."\n";
309: echo $this->sorterHeader===null ? Yii::t('zii','Sort by: ') : $this->sorterHeader;
310: echo "<ul>\n";
311: $sort=$this->dataProvider->getSort();
312: foreach($this->sortableAttributes as $name=>$label)
313: {
314: echo "<li>";
315: if(is_integer($name))
316: echo $sort->link($label);
317: else
318: echo $sort->link($name,$label);
319: echo "</li>\n";
320: }
321: echo "</ul>";
322: echo $this->sorterFooter;
323: echo CHtml::closeTag('div');
324: }
325: }
326: