summaryrefslogtreecommitdiff
path: root/lib/prado/framework/Web/UI/JuiControls/TJuiAutoComplete.php
blob: 612d89deaa0b54644c812fdd2489dba6e8a64ae6 (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
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
<?php
/**
 * TJuiAutoComplete class file.
 *
 * @author Wei Zhuo <weizhuo[at]gamil[dot]com>
 * @link https://github.com/pradosoft/prado
 * @copyright Copyright &copy; 2005-2016 The PRADO Group
 * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
 * @package System.Web.UI.ActiveControls
 */

/**
 * Load active text box.
 */
Prado::using('System.Web.UI.ActiveControls.TActiveTextBox');
Prado::using('System.Web.UI.ActiveControls.TCallbackEventParameter');
Prado::using('System.Web.UI.JuiControls.TJuiControlAdapter');
/**
 * 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 System.Web.UI.ActiveControls
 * @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));
	}

	/**
	 * @return string the name of the jQueryUI widget method
	 */
	public function getWidget()
	{
		return 'autocomplete';
	}

	/**
	 * @return string the clientid of the jQueryUI widget element
	 */
	public function getWidgetID()
	{
		return $this->getClientID();
	}

	/**
	 * Object containing defined javascript options
	 * @return TJuiControlOptions
	 */
	public function getOptions()
	{
		if (($options=$this->getViewState('JuiOptions'))===null)
		{
		  $options=new TJuiControlOptions($this);
		  $this->setViewState('JuiOptions', $options);
		}
		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(true))
		{
			$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';
	}
}

/**
 * TAutCompleteEventParameter contains the {@link getToken Token} requested by
 * the user for a partial match of the suggestions.
 *
 * The {@link getSelectedIndex SelectedIndex} is a zero-based index of the
 * suggestion selected by the user, -1 if not suggestion is selected.
 *
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
 * @package System.Web.UI.ActiveControls
 * @since 3.1
 */
class TJuiAutoCompleteEventParameter extends TCallbackEventParameter
{
	private $_selectedIndex=-1;

	/**
	 * Creates a new TCallbackEventParameter.
	 */
	public function __construct($response, $parameter, $index=-1)
	{
		parent::__construct($response, $parameter);
		$this->_selectedIndex=$index;
	}

	/**
	 * @return int selected suggestion zero-based index, -1 if not selected.
	 */
	public function getSelectedIndex()
	{
		return $this->_selectedIndex;
	}

	/**
	 * @return string token for matching a list of suggestions.
	 */
	public function getToken()
	{
		return $this->getCallbackParameter();
	}
}

/**
 * TJuiAutoCompleteTemplate class.
 *
 * TJuiAutoCompleteTemplate is the default template for TJuiAutoCompleteTemplate
 * item template.
 *
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
 * @package System.Web.UI.ActiveControls
 * @since 3.1
 */
class TJuiAutoCompleteTemplate extends TComponent implements ITemplate
{
	private $_template;

	public function __construct($template)
	{
		$this->_template = $template;
	}
	/**
	 * Instantiates the template.
	 * It creates a {@link TDataList} control.
	 * @param TControl parent to hold the content within the template
	 */
	public function instantiateIn($parent)
	{
		$parent->getControls()->add($this->_template);
	}
}