summaryrefslogtreecommitdiff
path: root/framework/Web/UI/ActiveControls/TActivePageAdapter.php
blob: 90c869126182a075762fa1ab803e201ecf33816a (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
<?php
/**
 * TActivePageAdapter, TCallbackErrorHandler and TInvalidCallbackException class file.
 *
 * @author Wei Zhuo <weizhuo[at]gamil[dot]com>
 * @author Gabor Berczi <gabor.berczi@devworx.hu> (lazyload additions & progressive rendering)
 * @link http://www.pradosoft.com/
 * @copyright Copyright &copy; 2005-2014 PradoSoft
 * @license http://www.pradosoft.com/license/
 * @package Prado\Web\UI\ActiveControls
 */

namespace Prado\Web\UI\ActiveControls;

/**
 * Load callback response adapter class.
 */
use Prado\Prado;
use Prado\Web\Javascripts\TJavaScript;
use Prado\Web\UI\TControl;
use Prado\Web\UI\TControlAdapter;
use Prado\Web\UI\TPage;


/**
 * TActivePageAdapter class.
 *
 * Callback request handler.
 *
 * @author Wei Zhuo <weizhuo[at]gamil[dot]com>
 * @author Gabor Berczi <gabor.berczi@devworx.hu> (lazyload additions & progressive rendering)
 * @package Prado\Web\UI\ActiveControls
 * @since 3.1
 */
class TActivePageAdapter extends TControlAdapter
{
	/**
	 * Callback response data header name.
	 */
	const CALLBACK_DATA_HEADER = 'X-PRADO-DATA';
	/**
	 * Callback response client-side action header name.
	 */
	const CALLBACK_ACTION_HEADER = 'X-PRADO-ACTIONS';
	/**
	 * Callback error header name.
	 */
	const CALLBACK_ERROR_HEADER = 'X-PRADO-ERROR';
	/**
	 * Callback page state header name.
	 */
	const CALLBACK_PAGESTATE_HEADER = 'X-PRADO-PAGESTATE';
	/**
	 * Script list header name.
	 */
	const CALLBACK_SCRIPTLIST_HEADER = 'X-PRADO-SCRIPTLIST';
	/**
	 * Stylesheet list header name.
	 */
	const CALLBACK_STYLESHEETLIST_HEADER = 'X-PRADO-STYLESHEETLIST';
	/**
	 * Stylesheet header name.
	 */
	const CALLBACK_STYLESHEET_HEADER = 'X-PRADO-STYLESHEET';
	/**
	 * Hidden field list header name.
	 */
	const CALLBACK_HIDDENFIELDLIST_HEADER = 'X-PRADO-HIDDENFIELDLIST';

	/**
	 * Callback redirect url header name.
	 */
	const CALLBACK_REDIRECT = 'X-PRADO-REDIRECT';

	/**
	 * @var ICallbackEventHandler callback event handler.
	 */
	private $_callbackEventTarget;
	/**
	 * @var mixed callback event parameter.
	 */
	private $_callbackEventParameter;
	/**
	 * @var TCallbackClientScript callback client script handler
	 */
	private $_callbackClient;

	private $_controlsToRender=array();

	/**
	 * Constructor, trap errors and exception to let the callback response
	 * handle them.
	 */
	public function __construct(TPage $control)
	{
		parent::__construct($control);

		//TODO: can this be done later?
		$response = $this->getApplication()->getResponse();
		$response->setAdapter(new TCallbackResponseAdapter($response));

		$this->trapCallbackErrorsExceptions();
	}

	/**
	 * Process the callback request.
	 * @param THtmlWriter html content writer.
	 */
	public function processCallbackEvent($writer)
	{
		Prado::trace("ActivePage raiseCallbackEvent()",'System.Web.UI.ActiveControls.TActivePageAdapter');
		$this->raiseCallbackEvent();
	}

	/**
	 * Register a control for defered render() call.
	 * @param TControl control for defered rendering
	 * @param THtmlWriter the renderer
	 */
	public function registerControlToRender($control,$writer)
	{
		$id = $control->getUniqueID();
		if(!isset($this->_controlsToRender[$id]))
			$this->_controlsToRender[$id] = array($control,$writer);
	}

	/**
	 * Trap errors and exceptions to be handled by TCallbackErrorHandler.
	 */
	protected function trapCallbackErrorsExceptions()
	{
		$this->getApplication()->setErrorHandler(new TCallbackErrorHandler);
	}

	/**
	 * Render the callback response.
	 * @param THtmlWriter html content writer.
	 */
	public function renderCallbackResponse($writer)
	{
		Prado::trace("ActivePage renderCallbackResponse()",'System.Web.UI.ActiveControls.TActivePageAdapter');
		if(($url = $this->getResponse()->getAdapter()->getRedirectedUrl())===null)
			$this->renderResponse($writer);
		else
			$this->redirect($url);
	}

	/**
	 * Redirect url on the client-side using javascript.
	 * @param string new url to load.
	 */
	protected function redirect($url)
	{
		Prado::trace("ActivePage redirect()",'System.Web.UI.ActiveControls.TActivePageAdapter');
		$this->appendContentPart($this->getResponse(), self::CALLBACK_REDIRECT, $url);
	}

	/**
	 * Renders the callback response by adding additional callback data and
	 * javascript actions in the header and page state if required.
	 * @param THtmlWriter html content writer.
	 */
	protected function renderResponse($writer)
	{
		Prado::trace("ActivePage renderResponse()",'System.Web.UI.ActiveControls.TActivePageAdapter');
		//renders all the defered render() calls.
		foreach($this->_controlsToRender as $rid => $forRender)
			$forRender[0]->render($forRender[1]);

		$response = $this->getResponse();

		//send response data in header
		if($response->getHasAdapter())
		{
			$responseData = $response->getAdapter()->getResponseData();
			if($responseData!==null)
			{
				$data = TJavaScript::jsonEncode($responseData);

				$this->appendContentPart($response, self::CALLBACK_DATA_HEADER, $data);
			}
		}

		//sends page state in header
		if(($handler = $this->getCallbackEventTarget()) !== null)
		{
			if($handler->getActiveControl()->getClientSide()->getEnablePageStateUpdate())
			{
				$pagestate = $this->getPage()->getClientState();
				$this->appendContentPart($response, self::CALLBACK_PAGESTATE_HEADER, $pagestate);
			}
		}

		//safari must receive at least 1 byte of data.
		$writer->write(" ");

		//output the end javascript
		if($this->getPage()->getClientScript()->hasEndScripts())
		{
			$writer = $response->createHtmlWriter();
			$this->getPage()->getClientScript()->renderEndScriptsCallback($writer);
			$this->getPage()->getCallbackClient()->evaluateScript($writer);
		}

		//output the actions
		$executeJavascript = $this->getCallbackClientHandler()->getClientFunctionsToExecute();
		$actions = TJavaScript::jsonEncode($executeJavascript);
		$this->appendContentPart($response, self::CALLBACK_ACTION_HEADER, $actions);


		$cs = $this->Page->getClientScript();

		// collect all stylesheet file references
		$stylesheets = $cs->getStyleSheetUrls();
		if (count($stylesheets)>0)
		$this->appendContentPart($response, self::CALLBACK_STYLESHEETLIST_HEADER, TJavaScript::jsonEncode($stylesheets));

		// collect all stylesheet snippets references
		$stylesheets = $cs->getStyleSheetCodes();
		if (count($stylesheets)>0)
		$this->appendContentPart($response, self::CALLBACK_STYLESHEET_HEADER, TJavaScript::jsonEncode($stylesheets));

		// collect all script file references
		$scripts = $cs->getScriptUrls();
		if (count($scripts)>0)
		$this->appendContentPart($response, self::CALLBACK_SCRIPTLIST_HEADER, TJavaScript::jsonEncode($scripts));

		// collect all hidden field references
		$fields = $cs->getHiddenFields();
		if (count($fields)>0)
		$this->appendContentPart($response, self::CALLBACK_HIDDENFIELDLIST_HEADER, TJavaScript::jsonEncode($fields));
	}

	/**
	 * Appends data or javascript code to the body content surrounded with delimiters
	 */
	private function appendContentPart($response, $delimiter, $data)
	{
		$content = $response->createHtmlWriter();
		$content->getWriter()->setBoundary($delimiter);
		$content->write($data);
	}

	/**
	 * Trys to find the callback event handler and raise its callback event.
	 * @throws TInvalidCallbackException if call back target is not found.
	 * @throws TInvalidCallbackException if the requested target does not
	 * implement ICallbackEventHandler.
	 */
	private function raiseCallbackEvent()
	{
		 if(($callbackHandler=$this->getCallbackEventTarget())!==null)
		 {
			if($callbackHandler instanceof ICallbackEventHandler)
			{
				$param = $this->getCallbackEventParameter();
				$result = new TCallbackEventParameter($this->getResponse(), $param);
				$callbackHandler->raiseCallbackEvent($result);
			}
			else
			{
				throw new TInvalidCallbackException(
					'callback_invalid_handler', $callbackHandler->getUniqueID());
			}
		 }
		 else
		 {
		 	$target = $this->getRequest()->itemAt(TPage::FIELD_CALLBACK_TARGET);
		 	throw new TInvalidCallbackException('callback_invalid_target', $target);
		 }
	}

	/**
	 * @return TControl the control responsible for the current callback event,
	 * null if nonexistent
	 */
	public function getCallbackEventTarget()
	{
		if($this->_callbackEventTarget===null)
		{
			$eventTarget=$this->getRequest()->itemAt(TPage::FIELD_CALLBACK_TARGET);
			if(!empty($eventTarget))
				$this->_callbackEventTarget=$this->getPage()->findControl($eventTarget);
		}
		return $this->_callbackEventTarget;
	}

	/**
	 * Registers a control to raise callback event in the current request.
	 * @param TControl control registered to raise callback event.
	 */
	public function setCallbackEventTarget(TControl $control)
	{
		$this->_callbackEventTarget=$control;
	}

	/**
	 * Gets callback parameter.
	 * @return string postback event parameter
	 */
	public function getCallbackEventParameter()
	{
		if($this->_callbackEventParameter===null)
		{
			$param = $this->getRequest()->itemAt(TPage::FIELD_CALLBACK_PARAMETER);
			$this->_callbackEventParameter=$param;
		}
		return $this->_callbackEventParameter;
	}

	/**
	 * @param mixed postback event parameter
	 */
	public function setCallbackEventParameter($value)
	{
		$this->_callbackEventParameter=$value;
	}

	/**
	 * Gets the callback client script handler. It handlers the javascript functions
	 * to be executed during the callback response.
	 * @return TCallbackClientScript callback client handler.
	 */
	public function getCallbackClientHandler()
	{
		if($this->_callbackClient===null)
			$this->_callbackClient = new TCallbackClientScript;
		return $this->_callbackClient;
	}
}