1: <?php
2: /**
3: * Autocomplete Base File
4: *
5: * The QAutocompleteBase class defined here provides an interface between the generated
6: * QAutocompleteGen class, and QCubed. This file is part of the core and will be overwritten
7: * when you update QCubed. To override, make your changes to the QAutocomplete.class.php file instead.
8: *
9: */
10:
11:
12: /**
13: * @deprecated since Qcubed 2.1.1. Please use QListItem
14: * List items that can be sent to an autocomplete in non-ajax mode. Put them in an array and send to ->Source.
15: */
16: class QAutocompleteListItem extends QListItem {
17: /**
18: * @deprecated since Qcubed 2.1.1. Please use QListItem
19: * @param $strName
20: * @param $strValue
21: * @param bool $blnSelected
22: * @param null $strItemGroup
23: * @param null $strOverrideParameters
24: */
25: public function __construct($strName, $strValue, $blnSelected = false, $strItemGroup = null, $strOverrideParameters = null) {
26: parent::__construct($strName, $strValue, $blnSelected, $strItemGroup, $strOverrideParameters);
27: trigger_error("QAutocompleteListItem has been deprecated. Please use QListItem", E_USER_NOTICE);
28: }
29:
30: /**
31: * @deprecated since Qcubed 2.1.1. Please use QListItem
32: * @return string
33: */
34: public function toJsObject() {
35: trigger_error("QAutocompleteListItem has been deprecated. Please use QListItem", E_USER_NOTICE);
36: return JavaScriptHelper::toJsObject(array("value" => $this->Name, "id" => $this->Value));
37: }
38: }
39:
40: /**
41: * Special event to handle source ajax callbacks
42: */
43: class QAutocomplete_SourceEvent extends QEvent {
44: /** Event Name */
45: const EventName = 'QAutocomplete_Source';
46: const JsReturnParam = 'ui'; // ends up being the request.term value
47: }
48:
49:
50: /**
51: * Implements the JQuery UI Autocomplete widget
52: *
53: * The Autocomplete is JQuery UIs version of a field with an attached drop down menu. As you type in
54: * the field, the menu appears, and the items in the menu are filtered by what the user types. This class allows
55: * you to use an array of QListItems, or an array of database objects as the source. You can also pass this array
56: * statically in the Source parameter at creation time, or dynamically via Ajax by using SetDataBinder, and then
57: * in your data binder function, setting the DataSource parameter.
58: *
59: * @property string $SelectedId the id of the selected item. When QAutocompleteListItem objects are used for the DataSource, this corresponds to the Value of the item
60: * @property boolean $MustMatch if true, non matching values are not accepted by the input
61: * @property string $MultipleValueDelimiter if set, the Autocomplete will keep appending the new selections to the previous term, delimited by this string.
62: * This is useful when making QAutocomplete handle multiple values (see http://jqueryui.com/demos/autocomplete/#multiple ).
63: * @property boolean $DisplayHtml if set, the Autocomplete will treat the 'label' portion of each data item as Html.
64: * @property-write array $Source an array of strings, QListItem's, or data objects. To be used at creation time. {@inheritdoc }
65: * @property-write array $DataSource an array of strings, QListItem's, or data objects
66: * @link http://jqueryui.com/autocomplete/
67: * @access private
68: * @package Controls\Base
69: */
70: class QAutocompleteBase extends QAutocompleteGen
71: {
72: /** @var string */
73: protected $strSelectedId = null;
74: /** @var boolean */
75: protected $blnUseAjax = false;
76:
77: /* Moved to QAutoComplete2 plugin */
78: //protected $blnMustMatch = false;
79: //protected $strMultipleValueDelimiter = null;
80: //protected $blnDisplayHtml = false;
81:
82: public function __construct($objParentObject, $strControlId = null) {
83: parent::__construct($objParentObject, $strControlId);
84:
85: $this->AddJavascriptFile(__JS_ASSETS__ . '/qcubed.autocomplete.js');
86: }
87:
88: /**
89: * When this filter is passed to QAutocomplete::UseFilter, only the items in the source list that contain the typed term will be shown in the drop-down
90: * This is the default filter used by the jQuery autocomplete. Useful when resetting from a previousely set filter.
91: * @see QAutocomplete::UseFilter
92: */
93: const FILTER_CONTAINS ='return $j.ui.autocomplete.escapeRegex(term);'; // this is the default filter
94: /**
95: * When this filter is passed to QAutocomplete::UseFilter, only the items in the source list that begin with the typed term will be shown in the drop-down
96: * @see QAutocomplete::UseFilter
97: */
98: const FILTER_STARTS_WITH ='return ("^" + $j.ui.autocomplete.escapeRegex(term));';
99:
100: /**
101: * Set a filter to use when using a simple array as a source (in non-ajax mode). Note that ALL non-ajax autocompletes on the page
102: * will use the new filter.
103: *
104: * @static
105: * @throws QCallerException
106: * @param string|QJsClosure $filter represents a closure that will be used as the global filter function for jQuery autocomplete.
107: * The closure should take two arguments - array and term. array is the list of all available choices, term is what the user typed in the input box.
108: * It should return an array of suggestions to show in the drop-down.
109: * <b>Example:</b> <code>QAutocomplete::UseFilter(QAutocomplete::FILTER_STARTS_WITH)</code>
110: * @return void
111: *
112: * @see QAutocomplete::FILTER_CONTAINS
113: * @see QAutocomplete::FILTER_STARTS_WITH
114: */
115: static public function UseFilter($filter) {
116: if (is_string($filter)) {
117: $filter = new QJsClosure ($filter, ['term']);
118: }
119: else if (!($filter instanceof QJsClosure)) {
120: throw new QCallerException("filter must be either a string or an instance of QJsClosure");
121: }
122: QApplication::ExecuteJsFunction('qcubed.acUseFilter', $filter);
123: }
124:
125:
126: /**
127: * Set the data binder for ajax filtering
128: *
129: * Call this at creation time to set the data binder of the item list you will display. The data binder
130: * will be an AjaxAction function, and so will receive the following parameters:
131: * - FormId
132: * - ControlId
133: * - Parameter
134: * The Parameter in particular will be the term that you should use for filtering. There are situations
135: * where the term will not be the same as the contents of the field.
136: *
137: * @param string $strMethodName Name of the method which has to be bound
138: * @param QForm|QControl $objParentControl The parent control on which the action is to be bound
139: */
140: public function SetDataBinder($strMethodName, $objParentControl = null) {
141: if ($objParentControl) {
142: $objAction = new QAjaxControlAction($objParentControl, $strMethodName, 'default', null, 'ui');
143: } else {
144: $objAction = new QAjaxAction($strMethodName, 'default', null, 'ui');
145: }
146: $this->AddAction(new QAutocomplete_SourceEvent(), $objAction);
147:
148: $this->mixSource = new QJsVarName('qcubed.acSourceFunction');
149:
150: $this->blnUseAjax = true;
151: $this->blnModified = true;
152: }
153:
154: // These functions are used to keep track of the selected value, and to implement
155: // optional autocomplete functionality.
156: /**
157: * Gets the Javascript part of the control which is sent to the client side upon the completion of Render
158: * @return string The JS string
159: */
160: public function GetEndScript() {
161: $strJS = parent::GetEndScript();
162: QApplication::ExecuteJsFunction('qc.autocomplete', $this->GetJqControlId(), QJsPriority::High);
163:
164: return $strJS;
165: }
166:
167:
168: // Response to an ajax request for data
169: protected function prepareAjaxList($dataSource) {
170: if (!$dataSource) {
171: $dataSource = array();
172: }
173: QApplication::ExecuteJsFunction('qc.acSetData', $this->getJqControlId(), $dataSource, QJsPriority::Exclusive);
174: }
175:
176: /**
177: *
178: */
179: public function SetEmpty() {
180: $this->Text = '';
181: $this->SelectedId = null;
182: }
183:
184: /**
185: * Control subclasses should return their state data that they will use to restore later.
186: * @return mixed
187: */
188: protected function GetState() {
189: $state = parent::GetState();
190: $state['selectedId']=$this->SelectedId;
191: return $state;
192: }
193:
194: /**
195: * Restore the state of the control. The control will have already been
196: * created and initialized. Subclasses should verify that the restored state is still valid for the data
197: * available.
198: * @param mixed $state
199: */
200: protected function PutState($state) {
201: parent::PutState($state);
202: if (isset($state['selectedId'])) {
203: $this->SelectedId = $state['selectedId'];
204: }
205: }
206:
207:
208: /**
209: * PHP __set Magic method
210: * @param string $strName Property Name
211: * @param string $mixValue Property Value
212: *
213: * @throws Exception|QInvalidCastException
214: */
215: public function __set($strName, $mixValue) {
216: switch ($strName) {
217: case 'DataSource':
218: // Assign data to a DataSource from within the data binder function only.
219: // Data should be array items that at a minimum contain a 'value' and an 'id'
220: // They can also contain a 'label', which will be displayed in the popup menu only
221: if ($this->blnUseAjax) {
222: $this->prepareAjaxList($mixValue);
223: } else {
224: $this->Source = $mixValue;
225: }
226: break;
227:
228: case "SelectedValue": // mirror list control
229: case "Value":
230: case 'SelectedId':
231: // Set this at creation time to initialize the selected id.
232: // This is also set by the javascript above to keep track of subsequent selections made by the user.
233: try {
234: if ($mixValue == 'null') {
235: $this->strSelectedId = null;
236: } else {
237: $this->strSelectedId = QType::Cast($mixValue, QType::String);
238: }
239: } catch (QInvalidCastException $objExc) {
240: $objExc->IncrementOffset();
241: throw $objExc;
242: }
243: break;
244:
245: case 'Source':
246: try {
247: if (is_array ($mixValue) && count($mixValue) > 0 && $mixValue[0] instanceof QListItem) {
248: // figure out what item is selected
249: foreach ($mixValue as $objItem) {
250: if ($objItem->Selected) {
251: $this->strSelectedId = $objItem->Value;
252: $this->Text = $objItem->Name;
253: }
254: }
255: }
256: parent::__set($strName, $mixValue);
257: } catch (QInvalidCastException $objExc) {
258: $objExc->IncrementOffset();
259: throw $objExc;
260: }
261: break;
262:
263: default:
264: try {
265: parent::__set($strName, $mixValue);
266: } catch (QInvalidCastException $objExc) {
267: $objExc->IncrementOffset();
268: throw $objExc;
269: }
270: break;
271: }
272:
273: }
274:
275: /**
276: * PHP __get magic method implementation
277: * @param string $strName Name of the property
278: *
279: * @return mixed
280: * @throws Exception|QCallerException
281: */
282: public function __get($strName) {
283: switch ($strName) {
284: case "SelectedValue": // mirror list control
285: case "Value": // most common situation
286: case 'SelectedId': return $this->strSelectedId;
287:
288: default:
289: try {
290: return parent::__get($strName);
291: } catch (QCallerException $objExc) {
292: $objExc->IncrementOffset();
293: throw $objExc;
294: }
295: }
296: }
297:
298:
299: }