1: <?php
2: /**
3: * This file contains the QListControl class.
4: *
5: * @package Controls
6: */
7:
8: /**
9: * Abstract object which is extended by anything which involves lists of selectable items.
10: * This object is the foundation for the ListBox, CheckBoxList, RadioButtonList
11: * and TreeNav. Subclasses can be used as objects to specify one-to-many and many-to-many relationships.
12: *
13: * @property-read integer $ItemCount the current count of ListItems in the control.
14: * @property integer $SelectedIndex is the index number of the control that is selected. "-1" means that nothing is selected. If multiple items are selected, it will return the lowest index number of all ListItems that are currently selected. Set functionality: selects that specific ListItem and will unselect all other currently selected ListItems.
15: * @property string $SelectedName simply returns ListControl::SelectedItem->Name, or null if nothing is selected.
16: * @property-read QListItem $SelectedItem (readonly!) returns the ListItem object, itself, that is selected (or the ListItem with the lowest index number of a ListItems that are currently selected if multiple items are selected). It will return null if nothing is selected.
17: * @property-read array $SelectedItems returns an array of selected ListItems (if any).
18: * @property mixed $SelectedValue simply returns ListControl::SelectedItem->Value, or null if nothing is selected.
19: * @property array $SelectedNames returns an array of all selected names
20: * @property array $SelectedValues returns an array of all selected values
21: * @property string $ItemStyle {@link QListItemStyle}
22: * @see QListItemStyle
23: * @package Controls
24: */
25: abstract class QListControl extends QControl {
26:
27: use QListItemManager;
28:
29: /** @var null|QListItemStyle The common style for all elements in the list */
30: protected $objItemStyle = null;
31:
32: //////////
33: // Methods
34: //////////
35:
36: public function AddItem($mixListItemOrName, $strValue = null, $blnSelected = null, $strItemGroup = null, $mixOverrideParameters = null) {
37: if (gettype($mixListItemOrName) == QType::Object) {
38: $objListItem = QType::Cast($mixListItemOrName, "QListItem");
39: }
40: elseif ($mixOverrideParameters) {
41: // The OverrideParameters can only be included if they are not null, because OverrideAttributes in QBaseClass can't except a NULL Value
42: $objListItem = new QListItem($mixListItemOrName, $strValue, $blnSelected, $strItemGroup, $mixOverrideParameters);
43: }
44: else {
45: $objListItem = new QListItem($mixListItemOrName, $strValue, $blnSelected, $strItemGroup);
46: }
47:
48: $this->AddListItem ($objListItem);
49: }
50:
51: /**
52: * Adds an array of items, or an array of key=>value pairs. Convenient for adding a list from a type table.
53: * When passing key=>val pairs, mixSelectedValues can be an array, or just a single value to compare against to indicate what is selected.
54: *
55: * @param array $mixItemArray Array of QListItems or key=>val pairs.
56: * @param mixed $mixSelectedValues Array of selected values, or value of one selection
57: * @param string $strItemGroup allows you to apply grouping (<optgroup> tag)
58: * @param string $mixOverrideParameters OverrideParameters for ListItemStyle
59: *
60: * @throws Exception|QInvalidCastException
61: */
62: public function AddItems(array $mixItemArray, $mixSelectedValues = null, $strItemGroup = null, $mixOverrideParameters = null) {
63: try {
64: $mixItemArray = QType::Cast($mixItemArray, QType::ArrayType);
65: } catch (QInvalidCastException $objExc) {
66: $objExc->IncrementOffset();
67: throw $objExc;
68: }
69:
70: foreach ($mixItemArray as $val => $item) {
71: if ($val === '') {
72: $val = null; // these are equivalent when specified as a key of an array
73: }
74: if ($mixSelectedValues && is_array($mixSelectedValues)) {
75: $blnSelected = in_array($val, $mixSelectedValues);
76: } else {
77: $blnSelected = ($val === $mixSelectedValues); // differentiate between null and 0 values
78: }
79: $this->AddItem($item, $val, $blnSelected, $strItemGroup, $mixOverrideParameters);
80: };
81: $this->Reindex();
82: $this->MarkAsModified();
83: }
84:
85: /**
86: * Return the id. Used by QListItemManager trait.
87: * @return string
88: */
89: public function GetId() {
90: return $this->strControlId;
91: }
92:
93: /**
94: * Recursively unselects all the items and subitems in the list.
95: *
96: * @param bool $blnRefresh True if we need to reflect the change in the html page. False if we are recording
97: * what the user has already done.
98: */
99: public function UnselectAllItems($blnRefresh = true) {
100: $intCount = $this->GetItemCount();
101: for ($intIndex = 0; $intIndex < $intCount; $intIndex++) {
102: $objItem = $this->GetItem($intIndex);
103: $objItem->Selected = false;
104: }
105: if ($blnRefresh && $this->blnOnPage) {
106: $this->RefreshSelection();
107: }
108: }
109:
110:
111: /**
112: * Selects the given items by Id, and unselects items that are not in the list.
113: * @param string[] $strIdArray
114: * @param bool $blnRefresh
115: */
116: public function SetSelectedItemsById(array $strIdArray, $blnRefresh = true) {
117: $intCount = $this->GetItemCount();
118: for ($intIndex = 0; $intIndex < $intCount; $intIndex++) {
119: $objItem = $this->GetItem($intIndex);
120: $strId = $objItem->GetId();
121: $objItem->Selected = in_array($strId, $strIdArray);
122: }
123: if ($blnRefresh && $this->blnOnPage) {
124: $this->RefreshSelection();
125: }
126: }
127:
128: /**
129: * Set the selected item by index. This can only set top level items. Lower level items are untouched.
130: * @param integer[] $intIndexArray
131: * @param bool $blnRefresh
132: */
133: public function SetSelectedItemsByIndex(array $intIndexArray, $blnRefresh = true) {
134: $intCount = $this->GetItemCount();
135: for ($intIndex = 0; $intIndex < $intCount; $intIndex++) {
136: $objItem = $this->GetItem($intIndex);
137: $objItem->Selected = in_array($intIndex, $intIndexArray);
138: }
139: if ($blnRefresh && $this->blnOnPage) {
140: $this->RefreshSelection();
141: }
142: }
143:
144: /**
145: * Set the selected items by value. We equate nulls and empty strings, but must be careful not to equate
146: * those with a zero.
147: *
148: * @param array $mixValueArray
149: * @param bool $blnRefresh
150: */
151: public function SetSelectedItemsByValue(array $mixValueArray, $blnRefresh = true) {
152: $intCount = $this->GetItemCount();
153:
154: for ($intIndex = 0; $intIndex < $intCount; $intIndex++) {
155: $objItem = $this->GetItem($intIndex);
156: $mixCurVal = $objItem->Value;
157: $blnSelected = false;
158: foreach ($mixValueArray as $mixValue) {
159: if (!$mixValue) {
160: if ($mixValue === null || $mixValue === '') {
161: if ($mixCurVal === null || $mixCurVal === '') {
162: $blnSelected = true;
163: }
164: } elseif (!$mixCurVal && !($mixCurVal === null || $mixCurVal === '')) {
165: $blnSelected = true;
166: }
167: }
168: elseif ($mixCurVal == $mixValue) {
169: $blnSelected = true;
170: }
171: }
172: $objItem->Selected = $blnSelected;
173: }
174: if ($blnRefresh && $this->blnOnPage) {
175: $this->RefreshSelection();
176: }
177: }
178:
179: /**
180: * Set the selected items by name.
181: * @param string[] $strNameArray
182: * @param bool $blnRefresh
183: */
184: public function SetSelectedItemsByName(array $strNameArray, $blnRefresh = true) {
185: $intCount = $this->GetItemCount();
186: for ($intIndex = 0; $intIndex < $intCount; $intIndex++) {
187: $objItem = $this->GetItem($intIndex);
188: $strName = $objItem->Name;
189: $objItem->Selected = in_array($strName, $strNameArray);
190: }
191: if ($blnRefresh && $this->blnOnPage) {
192: $this->RefreshSelection();
193: }
194: }
195:
196:
197: /**
198: * This method is called when a selection is changed. It should execute the code to refresh the selected state
199: * of the items in the control.
200: *
201: * The default just redraws the control. Redrawing a large list control can take a lot of time, so subclasses should
202: * implement a way of just setting the selection through javasacript.
203: */
204: protected function RefreshSelection() {
205: $this->MarkAsModified();
206: }
207:
208: /**
209: * Return the first item selected.
210: *
211: * @return null|QListItem
212: * @throws Exception
213: * @throws QIndexOutOfRangeException
214: * @throws QInvalidCastException
215: */
216: public function GetFirstSelectedItem() {
217: $intCount = $this->GetItemCount();
218: for ($intIndex = 0; $intIndex < $intCount; $intIndex++) {
219: $objItem = $this->GetItem($intIndex);
220: if ($objItem->Selected) {
221: return $objItem;
222: }
223: }
224: return null;
225: }
226:
227: /**
228: * Return all the selected items.
229: *
230: * @return QListItem[]
231: * @throws Exception
232: * @throws QIndexOutOfRangeException
233: * @throws QInvalidCastException
234: */
235: public function GetSelectedItems() {
236: $aResult = array();
237: $intCount = $this->GetItemCount();
238: for ($intIndex = 0; $intIndex < $intCount; $intIndex++) {
239: $objItem = $this->GetItem($intIndex);
240: if ($objItem->Selected) {
241: $aResult[] = $objItem;
242: }
243: }
244: return $aResult;
245: }
246:
247: /**
248: * Returns the current state of the control to be able to restore it later.
249: */
250: public function GetState(){
251: return array('SelectedValues'=>$this->SelectedValues);
252: }
253:
254: /**
255: * Restore the state of the control.
256: */
257: public function PutState($state) {
258: if (!empty($state['SelectedValues'])) {
259: $this->SelectedValues = $state['SelectedValues'];
260: }
261: }
262:
263: /////////////////////////
264: // Public Properties: GET
265: /////////////////////////
266: /**
267: * PHP __get magic method implementation
268: * @param string $strName Property Name
269: *
270: * @return mixed
271: * @throws Exception|QCallerException
272: */
273: public function __get($strName) {
274: switch ($strName) {
275: case "ItemCount":
276: return $this->GetItemCount();
277:
278: case "SelectedIndex":
279: for ($intIndex = 0; $intIndex < $this->GetItemCount(); $intIndex++) {
280: if ($this->GetItem($intIndex)->Selected)
281: return $intIndex;
282: }
283: return -1;
284:
285: case "SelectedIndexes":
286: $indexes = [];
287: for ($intIndex = 0; $intIndex < $this->GetItemCount(); $intIndex++) {
288: if ($this->GetItem($intIndex)->Selected) {
289: $indexes[] = $intIndex;
290: }
291: }
292: return $indexes;
293:
294: case "SelectedName": // assumes first selected item is the selection
295: if ($objItem = $this->GetFirstSelectedItem()) {
296: return $objItem->Name;
297: }
298: return null;
299:
300: case "SelectedValue":
301: case "Value":
302: if ($objItem = $this->GetFirstSelectedItem()) {
303: return $objItem->Value;
304: }
305: return null;
306:
307: case "SelectedItem":
308: if ($objItem = $this->GetFirstSelectedItem()) {
309: return $objItem;
310: }
311: elseif ($this->GetItemCount()) {
312: return $this->GetItem (0);
313: }
314: return null;
315: case "SelectedItems":
316: return $this->GetSelectedItems();
317:
318: case "SelectedNames":
319: $objItems = $this->GetSelectedItems();
320: $strNamesArray = array();
321: foreach ($objItems as $objItem) {
322: $strNamesArray[] = $objItem->Name;
323: }
324: return $strNamesArray;
325:
326: case "SelectedValues":
327: $objItems = $this->GetSelectedItems();
328: $values = array();
329: foreach ($objItems as $objItem) {
330: $values[] = $objItem->Value;
331: }
332: return $values;
333:
334: case "ItemStyle":
335: return $this->objItemStyle;
336:
337: default:
338: try {
339: return parent::__get($strName);
340: } catch (QCallerException $objExc) {
341: $objExc->IncrementOffset();
342: throw $objExc;
343: }
344: }
345: }
346:
347: /////////////////////////
348: // Public Properties: SET
349: /////////////////////////
350: /**
351: * PHP __set magic method implementation
352: *
353: * @param string $strName Property Name
354: * @param string $mixValue Propety Value
355: *
356: * @return mixed|void
357: * @throws QIndexOutOfRangeException|Exception|QCallerException|QInvalidCastException
358: */
359: public function __set($strName, $mixValue) {
360: switch ($strName) {
361: case "SelectedIndex":
362: try {
363: $mixValue = QType::Cast($mixValue, QType::Integer);
364: } catch (QInvalidCastException $objExc) {
365: $objExc->IncrementOffset();
366: throw $objExc;
367: }
368:
369: $itemCount = $this->GetItemCount();
370: if (($mixValue < -1) || // special case to unselect all
371: ($mixValue > ($itemCount - 1)))
372: throw new QIndexOutOfRangeException($mixValue, "SelectedIndex");
373:
374: $this->SetSelectedItemsByIndex(array($mixValue));
375: return $mixValue;
376:
377: case "SelectedName":
378: $this->SetSelectedItemsByName(array($mixValue));
379: return $mixValue;
380:
381: case "SelectedValue":
382: case "Value": // most common situation
383: $this->SetSelectedItemsByValue(array($mixValue));
384: return $mixValue;
385:
386: case "SelectedNames":
387: try {
388: $mixValue = QType::Cast($mixValue, QType::ArrayType);
389: } catch (QInvalidCastException $objExc) {
390: $objExc->IncrementOffset();
391: throw $objExc;
392: }
393: $this->SetSelectedItemsByName($mixValue);
394: return $mixValue;
395:
396: case "SelectedValues":
397: try {
398: $mixValues = QType::Cast($mixValue, QType::ArrayType);
399: } catch (QInvalidCastException $objExc) {
400: $objExc->IncrementOffset();
401: throw $objExc;
402: }
403: $this->SetSelectedItemsByValue($mixValue);
404: return $mixValues;
405:
406: case "ItemStyle":
407: try {
408: $this->blnModified = true;
409: $this->objItemStyle = QType::Cast($mixValue, "QListItemStyle");
410: } catch (QInvalidCastException $objExc) {
411: $objExc->IncrementOffset();
412: throw $objExc;
413: }
414: break;
415:
416:
417: default:
418: try {
419: parent::__set($strName, $mixValue);
420: break;
421: } catch (QCallerException $objExc) {
422: $objExc->IncrementOffset();
423: throw $objExc;
424: }
425: return null;
426: }
427: }
428:
429: /**
430: * Returns a description of the options available to modify by the designer for the code generator.
431: *
432: * @return QModelConnectorParam[]
433: */
434: public static function GetModelConnectorParams() {
435: return array_merge(parent::GetModelConnectorParams(), array(
436: new QModelConnectorParam (QModelConnectorParam::GeneralCategory, 'NoAutoLoad', 'Prevent automatically populating a list type control. Set this if you are doing more complex list loading.', QType::Boolean)
437: ));
438: }
439: }
440: