summaryrefslogtreecommitdiff
path: root/framework/Web/UI/JuiControls/TJuiAutoComplete.php
blob: adad1fd87dca1fe091d82bb91ffbdef37e28d63d (plain)
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
77
78
79
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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
<?php
/**
 * TJuiAutoComplete class file.
 *
 * @author Wei Zhuo <weizhuo[at]gamil[dot]com>
 * @link http://www.pradosoft.com/
 * @copyright Copyright &copy; 2005-2014 PradoSoft
 * @license http://www.pradosoft.com/license/
 * @package Prado\Web\UI\JuiControls
 */

namespace Prado\Web\UI\JuiControls;

/**
 * Load active text box.
 */
use Prado\IO\TTextWriter;
use Prado\Prado;
use Prado\TPropertyValue;
use Prado\Web\UI\ActiveControls\TActiveTextBox;
use Prado\Web\UI\INamingContainer;
use Prado\Web\UI\TTemplate;

/**
 * TJuiAutoComplete class.
 *
 * TJuiAutoComplete is a textbox that provides a list of suggestion on
 * the current partial word typed in the textbox. The suggestions are
 * requested using callbacks, and raises the {@link onSuggestion OnSuggestion}
 * event. The events of the TActiveText (from which TJuiAutoComplete is extended from)
 * and {@link onSuggestion OnSuggestion} are mutually exculsive. That is,
 * if {@link onTextChange OnTextChange} and/or {@link onCallback OnCallback}
 * events are raise, then {@link onSuggestion OnSuggestion} will not be raise, and
 * vice versa.
 *
 * The list of suggestions should be set in the {@link onSuggestion OnSuggestion}
 * event handler. The partial word to match the suggestion is in the
 * {@link TCallbackEventParameter::getCallbackParameter TCallbackEventParameter::CallbackParameter}
 * property. The datasource of the TJuiAutoComplete must be set using {@link setDataSource}
 * method. This sets the datasource for the suggestions repeater, available through
 * the {@link getSuggestions Suggestions} property. Header, footer templates and
 * other properties of the repeater can be access via the {@link getSuggestions Suggestions}
 * property and its sub-properties.
 *
 * The {@link setTextCssClass TextCssClass} property if set is used to find
 * the element within the Suggestions.ItemTemplate and Suggestions.AlternatingItemTemplate
 * that contains the actual text for the suggestion selected. That is,
 * only text inside elements with CSS class name equal to {@link setTextCssClass TextCssClass}
 * will be used as suggestions.
 *
 * To return the list of suggestions back to the browser, supply a non-empty data source
 * and call databind. For example,
 * <code>
 * function autocomplete_suggestion($sender, $param)
 * {
 *   $token = $param->getToken(); //the partial word to match
 *   $sender->setDataSource($this->getSuggestionsFor($token)); //set suggestions
 *   $sender->dataBind();
 * }
 * </code>
 *
 * The suggestion will be rendered when the {@link dataBind()} method is called
 * <strong>during a callback request</strong>.
 *
 * When an suggestion is selected, that is, when the use has clicked, pressed
 * the "Enter" key, or pressed the "Tab" key, the {@link onSuggestionSelected OnSuggestionSelected}
 * event is raised. The
 * {@link TCallbackEventParameter::getCallbackParameter TCallbackEventParameter::CallbackParameter}
 * property contains the index of the selected suggestion.
 *
 * TJuiAutoComplete allows multiple suggestions within one textbox with each
 * word or phrase separated by any characters specified in the
 * {@link setSeparator Separator} property. The {@link setFrequency Frequency}
 * and {@link setMinChars MinChars} properties sets the delay and minimum number
 * of characters typed, respectively, before requesting for sugggestions.
 *
 * Use {@link onTextChange OnTextChange} and/or {@link onCallback OnCallback} events
 * to handle post backs due to {@link setAutoPostBack AutoPostBack}.
 *
 * In the {@link getSuggestions Suggestions} TRepater item template, all HTML text elements
 * are considered as text for the suggestion. Text within HTML elements with CSS class name
 * "informal" are ignored as text for suggestions.
 *
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
 * @package Prado\Web\UI\JuiControls
 * @since 3.1
 */
class TJuiAutoComplete extends TActiveTextBox implements INamingContainer, IJuiOptions
{
	/**
	 * @var ITemplate template for repeater items
	 */
	private $_repeater=null;
	/**
	 * @var TPanel result panel holding the suggestion items.
	 */
	private $_resultPanel=null;

	/**
	 * Creates a new callback control, sets the adapter to
	 * TActiveControlAdapter. If you override this class, be sure to set the
	 * adapter appropriately by, for example, by calling this constructor.
	 */
	public function __construct()
	{
		parent::__construct();
		$this->setAdapter(new TJuiControlAdapter($this));
	}

	/**
	 * Object containing defined javascript options
	 * @return TJuiControlOptions
	 */
	public function getOptions()
	{
		static $options;
		if($options===null)
			$options=new TJuiControlOptions($this);
		return $options;
	}

	/**
	 * Array containing valid javascript options
	 * @return array()
	 */
	public function getValidOptions()
	{
		return array('appendTo', 'autoFocus', 'delay', 'disabled', 'minLength', 'position', 'source');
	}

	/**
	 * Array containing valid javascript events
	 * @return array()
	 */
	public function getValidEvents()
	{
		return array();
	}

	/**
	 * @param string Css class name of the element to use for suggestion.
	 */
	public function setTextCssClass($value)
	{
		$this->setViewState('TextCssClass', $value);
	}

	/**
	 * @return string Css class name of the element to use for suggestion.
	 */
	public function getTextCssClass()
	{
		return $this->getViewState('TextCssClass');
	}


	/**
	 * @return string word or token separators (delimiters).
	 */
	public function getSeparator()
	{
		return $this->getViewState('separator', '');
	}

	/**
	 * @param string word or token separators (delimiters).
	 */
	public function setSeparator($value)
	{
		$this->setViewState('separator', TPropertyValue::ensureString($value), '');
	}

	/**
	 * @return float maximum delay (in seconds) before requesting a suggestion.
	 */
	public function getFrequency()
	{
		return $this->getViewState('frequency', 0.4);
	}

	/**
	 * @param float maximum delay (in seconds) before requesting a suggestion.
	 * Default is 0.4.
	 */
	public function setFrequency($value)
	{
		$this->setViewState('frequency', TPropertyValue::ensureFloat($value), 0.4);
	}

	/**
	 * @return integer minimum number of characters before requesting a suggestion.
	 */
	public function getMinChars()
	{
		return $this->getViewState('minChars','');
	}

	/**
	 * @param integer minimum number of characters before requesting a suggestion.
	 * Default is 1
	 */
	public function setMinChars($value)
	{
		$this->setViewState('minChars', TPropertyValue::ensureInteger($value), 1);
	}

	/**
	 * Raises the callback event. This method is overrides the parent implementation.
	 * If {@link setAutoPostBack AutoPostBack} is enabled it will raise
	 * {@link onTextChanged OnTextChanged} event event and then the
	 * {@link onCallback OnCallback} event. The {@link onSuggest OnSuggest} event is
	 * raise if the request is to find sugggestions, the {@link onTextChanged OnTextChanged}
	 * and {@link onCallback OnCallback} events are <b>NOT</b> raised.
	 * This method is mainly used by framework and control developers.
	 * @param TCallbackEventParameter the event parameter
	 */
 	public function raiseCallbackEvent($param)
	{
		$token = $param->getCallbackParameter();
		if(is_array($token) && count($token) == 2)
		{
			if($token[1] === '__TJuiAutoComplete_onSuggest__')
			{
				$parameter = new TJuiAutoCompleteEventParameter($this->getResponse(), $token[0]);
				$this->onSuggest($parameter);
			}
			else if($token[1] === '__TJuiAutoComplete_onSuggestionSelected__')
			{
				$parameter = new TJuiAutoCompleteEventParameter($this->getResponse(), null, $token[0]);
				$this->onSuggestionSelected($parameter);
			}
		}
		else if($this->getAutoPostBack())
			parent::raiseCallbackEvent($param);
	}

	/**
	 * This method is invoked when an autocomplete suggestion is requested.
	 * The method raises 'OnSuggest' event. If you override this
	 * method, be sure to call the parent implementation so that the event
	 * handler can be invoked.
	 * @param TCallbackEventParameter event parameter to be passed to the event handlers
	 */
	public function onSuggest($param)
	{
		$this->raiseEvent('OnSuggest', $this, $param);
	}

	/**
	 * This method is invoked when an autocomplete suggestion is selected.
	 * The method raises 'OnSuggestionSelected' event. If you override this
	 * method, be sure to call the parent implementation so that the event
	 * handler can be invoked.
	 * @param TCallbackEventParameter event parameter to be passed to the event handlers
	 */
	public function onSuggestionSelected($param)
	{
		$this->raiseEvent('OnSuggestionSelected', $this, $param);
	}

	/**
	 * @param array data source for suggestions.
	 */
	public function setDataSource($data)
	{
		$this->getSuggestions()->setDataSource($data);
	}

	/**
	 * Overrides parent implementation. Callback {@link renderSuggestions()} when
	 * page's IsCallback property is true.
	 */
	public function dataBind()
	{
		parent::dataBind();
		if($this->getPage()->getIsCallback())
			$this->renderSuggestions($this->getResponse()->createHtmlWriter());
	}

	/**
	 * @return TPanel suggestion results panel.
	 */
	public function getResultPanel()
	{
		if($this->_resultPanel===null)
			$this->_resultPanel = $this->createResultPanel();
		return $this->_resultPanel;
	}

	/**
	 * @return TPanel new instance of result panel. Default uses TPanel.
	 */
	protected function createResultPanel()
	{
		$panel = Prado::createComponent('System.Web.UI.WebControls.TPanel');
		$this->getControls()->add($panel);
		$panel->setID('result');
		return $panel;
	}

	/**
	 * @return TRepeater suggestion list repeater
	 */
	public function getSuggestions()
	{
		if($this->_repeater===null)
			$this->_repeater = $this->createRepeater();
		return $this->_repeater;
	}

	/**
	 * @return TRepeater new instance of TRepater to render the list of suggestions.
	 */
	protected function createRepeater()
	{
		$repeater = Prado::createComponent('System.Web.UI.WebControls.TRepeater');
		$repeater->setItemTemplate(new TTemplate('<%# $this->Data %>',null));
		$this->getControls()->add($repeater);
		return $repeater;
	}

	/**
	 * Renders the end tag and registers javascript effects library.
	 */
	public function renderEndTag($writer)
	{
		parent::renderEndTag($writer);
		$this->renderResultPanel($writer);
	}

	/**
	 * Renders the result panel.
	 * @param THtmlWriter the renderer.
	 */
	protected function renderResultPanel($writer)
	{
		$this->getResultPanel()->render($writer);
	}

	/**
	 * Renders the suggestions during a callback respones.
	 * @param THtmlWriter the renderer.
	 */
	public function renderCallback($writer)
	{
		$this->renderSuggestions($writer);
	}

	/**
	 * Renders the suggestions repeater.
	 * @param THtmlWriter the renderer.
	 */
	public function renderSuggestions($writer)
	{
		if($this->getActiveControl()->canUpdateClientSide())
		{
			$data=array();
			$items=$this->getSuggestions()->getItems();
			$writer = new TTextWriter;
			for($i=0; $i<$items->Count; $i++)
			{
				$items->itemAt($i)->render($writer);
				$item=$writer->flush();
				$data[]=array( 'id' => $i, 'label' => $item);
			}

			$this->getResponse()->getAdapter()->setResponseData($data);
		}
	}

	/**
	 * @return array list of callback options.
	 */
	protected function getPostBackOptions()
	{
		$options = $this->getOptions()->toArray();

		if(strlen($separator = $this->getSeparator()))
			$options['Separators'] = $separator;

		if($this->getAutoPostBack())
		{
			$options = array_merge($options,parent::getPostBackOptions());
			$options['AutoPostBack'] = true;
		}
		if(strlen($textCssClass = $this->getTextCssClass()))
			$options['textCssClass'] = $textCssClass;
		$options['minLength'] = $this->getMinChars();
		$options['delay'] = $this->getFrequency()/1000.0;
		$options['appendTo'] = '#'.$this->getResultPanel()->getClientID();
		$options['ID'] = $this->getClientID();
		$options['EventTarget'] = $this->getUniqueID();
		$options['CausesValidation'] = $this->getCausesValidation();
		$options['ValidationGroup'] = $this->getValidationGroup();
		return $options;
	}

	/**
	 * Override parent implementation, no javascript is rendered here instead
	 * the javascript required for active control is registered in {@link addAttributesToRender}.
	 */
	protected function renderClientControlScript($writer)
	{
	}

	/**
	 * @return string corresponding javascript class name for this TActiveButton.
	 */
	protected function getClientClassName()
	{
		return 'Prado.WebUI.TJuiAutoComplete';
	}
}