1: <?php
2: /**
3: * Dialog Base Class
4: *
5: * The QDialogBase class defined here provides an interface between the generated
6: * QDialogGen 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 QDialog.class.php file instead.
8: *
9: */
10:
11: /**
12: * Special event to handle button clicks.
13: *
14: * Add an action to this event to get a button click.
15: * The action parameter will be the id of the button that was clicked.
16: * @usage $dlg->AddAction(new QDialog_ButtonEvent(), new QAjaxAction($this, 'ButtonClick'));
17: */
18: class QDialog_ButtonEvent extends QEvent {
19: /** Event Name */
20: const EventName = 'QDialog_Button';
21: const JsReturnParam = 'ui'; // ends up being the button id
22: }
23:
24:
25: /**
26: * Implements a JQuery UI Dialog
27: *
28: * A QDialog is a QPanel that pops up on the screen and implements an "in window" dialog.
29: *
30: * There are a couple of ways to use the dialog. The simplest is as follows:
31: *
32: * In your Form_Create():
33: * <code>
34: * $this->dlg = new QDialog($this);
35: * $this->dlg->AutoOpen = false;
36: * $this->dlg->Modal = true;
37: * $this->dlg->Text = 'Show this on the dialog.'
38: * $this->dlg->AddButton ('OK', 'ok');
39: * $this->dlg->AddAction (new QDialog_ButtonEvent(), new QHideDialog());
40: * </code>
41: *
42: * When you want to show the dialog:
43: * <code>
44: * $this->dlg->Open();
45: * </code>
46: *
47: * And, also remember to draw the dialog in your form template:
48: *
49: * <code>
50: * $this->dlg->Render();
51: * </code>
52: *
53: *
54: * Since QDialog is a descendant of QPanel, you can do anything you can to a normal QPanel,
55: * including add QControls and use a template. When you want to hide the dialog, call <code>Close()</code>
56: *
57: * @property boolean $HasCloseButton Disables (false) or enables (true) the close X in the upper right corner of the title. Can be set when initializing the dialog.
58: * Can be set when initializing the dialog. Also enables or disables the ability to close the box by pressing the ESC key.
59: * @property-read integer $ClickedButton Returns the id of the button most recently clicked. (read-only)
60: * @property-write string $DialogState Set whether this dialog is in an error or highlight (info) state. Choose on of QDialog::StateNone, QDialogState::StateError, QDialogState::StateHighlight (write-only)
61: *
62: * @link http://jqueryui.com/dialog/
63: * @package Controls\Base
64: */
65:
66: class QDialogBase extends QDialogGen
67: {
68: // enumerations
69:
70: /** Default dialog state */
71: const StateNone = '';
72: /** Display using the Themeroller error state */
73: const StateError = 'ui-state-error';
74: /** Display using the Themeroller highlight state */
75: const StateHighlight = 'ui-state-highlight';
76:
77: /** The control id to use for the reusable global alert dialog. */
78: const MessageDialogId = 'qAlertDialog';
79:
80: /** @var bool default to auto open being false, since this would be a rare need, and dialogs are auto-rendered. */
81: protected $blnAutoOpen = false;
82: /** @var string Id of last button clicked. */
83: protected $strClickedButtonId;
84: /** @var bool Should we draw a close button on the top? */
85: protected $blnHasCloseButton = true;
86: /** @var bool records whether dialog is open */
87: protected $blnIsOpen = false;
88: /** @var array whether a button causes validation */
89: protected $blnValidationArray = array();
90:
91: protected $blnUseWrapper = true;
92: /** @var string state of the dialog for special display */
93: protected $strDialogState;
94:
95: protected $blnAutoRender = true;
96:
97: public function __construct($objParentObject, $strControlId = null) {
98: parent::__construct($objParentObject, $strControlId);
99: $this->blnDisplay = false;
100: $this->mixCausesValidation = $this;
101: }
102:
103: /**
104: * Validate the child items if the dialog is visible and the clicked button requires validation.
105: * This piece of magic makes validation specific to the dialog if an action is coming from the dialog,
106: * and prevents the controls in the dialog from being validated if the action is coming from outside
107: * the dialog.
108: *
109: * @return bool
110: */
111: public function ValidateControlAndChildren() {
112: if ($this->blnIsOpen) { // don't validate a closed dialog
113: if (!empty($this->mixButtons)) { // using built-in dialog buttons
114: if (!empty ($this->blnValidationArray[$this->strClickedButtonId])) {
115: return parent::ValidateControlAndChildren();
116: }
117: } else { // using QButtons placed in the control
118: return parent::ValidateControlAndChildren();
119: }
120: }
121: return true;
122: }
123:
124: /**
125: * Returns the control id for purposes of jQuery UI.
126: * @return string
127: */
128: public function getJqControlId() {
129: if ($this->blnUseWrapper) {
130: return $this->ControlId . '_ctl';
131: }
132: return $this->ControlId;
133: }
134:
135: /**
136: * Overrides the parent to add code to cause the default button to be fired if an enter key is pressed
137: * on a control. This purposefully does not include textarea controls, which should get the enter key to
138: * insert a newline.
139: *
140: * @return string
141: */
142:
143: public function GetEndScript() {
144: $strJS = parent::GetEndScript();
145: $strControlId = $this->GetJqControlId();
146: QApplication::ExecuteJsFunction('qc.dialog', $strControlId, QJsPriority::High);
147:
148: return $strJS;
149: }
150:
151: /**
152: * Add additional javascript to the dialog creation to further format the dialog.
153: * This will set the class of the title bar to the strDialogState value and add an
154: * icon to implement a dialog state. Override and restyle for a different look.
155: * @return string
156: */
157: protected function StylingJs() {
158: $strJs = '';
159: if ($this->strDialogState) {
160: // Move the dialog class to the header of dialog to improve the appearance over the default.
161: // Also add an appropriate icon.
162: // Override this if you want your dialogs to look different.
163: switch ($this->strDialogState) {
164: case QDialog::StateError:
165: $strIcon = 'alert';
166: break;
167:
168: case QDialog::StateHighlight:
169: $strIcon = 'info';
170: break;
171: }
172: $strIconJs = sprintf('<span class="ui-icon ui-icon-%s" ></span>', $strIcon);
173:
174: $strJs .= sprintf (
175: '$j("#%s").prev().addClass("%s").prepend(\'%s\');
176: ',
177: $this->getJqControlId(), $this->strDialogState, $strIconJs);
178: }
179: return $strJs;
180: }
181:
182: /**
183: * Implements QCubed specific dialog functions. Makes sure dialog is put at the end of the form
184: * to fix an overlay problem with jQuery UI.
185: *
186: * @return string
187: */
188: protected function MakeJqOptions() {
189: $jqOptions = parent::MakeJqOptions();
190:
191: $controlId = $this->ControlId;
192: $strFormId = $this->Form->FormId;
193:
194: if (!$this->blnHasCloseButton) {
195: $strHideCloseButtonScript = '$j(this).prev().find(".ui-dialog-titlebar-close").hide();';
196: }
197: else {
198: $strHideCloseButtonScript = '';
199: }
200:
201: $jqOptions['open'] = new QJsClosure (
202: sprintf ('qcubed.recordControlModification("%s", "_IsOpen", true);
203: %s', $controlId, $strHideCloseButtonScript)
204: , ['event', 'ui']);
205: $jqOptions['close'] = new QJsClosure (sprintf (
206: 'qcubed.recordControlModification("%s", "_IsOpen", false);
207: ', $controlId), ['event', 'ui']);
208: $jqOptions['appendTo'] = "#{$strFormId}";
209:
210: // By doing the styling at creation time, we ensure that it gets done only once.
211: if ($strCreateJs = $this->StylingJs()) {
212: $jqOptions['create'] = new QJsClosure($strCreateJs);
213: }
214: return $jqOptions;
215: }
216:
217:
218: /**
219: * Adds a button to the dialog. Use this to add buttons BEFORE bringing up the dialog.
220: *
221: * @param string $strButtonName
222: * @param string $strButtonId Id associated with the button for detecting clicks. Note that this is not the id on the form.
223: * Different dialogs can have the same button id.
224: * To specify a control id for the button (for styling purposes for example), set the id in options.
225: * @param bool $blnCausesValidation If the button causes the dialog to be validated before the action is executed
226: * @param bool $blnIsPrimary Whether this button will be automatically clicked if user presses an enter key.
227: * @param string $strConfirmation If set, will confirm with the given string before the click is sent
228: * @param array $options Additional attributes to add to the button. Useful things to do are:
229: * array('class'=>'ui-button-left') to create a button on the left side.
230: * array('class'=>'ui-priority-primary') to style a button as important or primary.
231: */
232: public function AddButton ($strButtonName, $strButtonId = null, $blnCausesValidation = false, $blnIsPrimary = false, $strConfirmation = null, $options = null) {
233: if (!$this->mixButtons) {
234: $this->mixButtons = array();
235: }
236: $strJS = '';
237: if ($strConfirmation) {
238: $strJS .= sprintf ('if (confirm("%s"))', $strConfirmation);
239: }
240:
241: $controlId = $this->ControlId;
242:
243: if (!$strButtonId) {
244: $strButtonId = $strButtonName;
245: }
246:
247: // Brackets are for possible "confirm" above
248: $strJS .= sprintf('
249: {
250: qcubed.recordControlModification("%s", "_ClickedButton", "%s");
251: $j("#%s").trigger("QDialog_Button", $j(event.currentTarget).data("btnid"));
252: }
253: event.preventDefault();
254: ', $controlId, $strButtonId, $controlId);
255:
256: $btnOptions = array ('text'=>$strButtonName,
257: 'click'=>new QJsNoQuoteKey(new QJsClosure($strJS, array ('event'))),
258: 'data-btnid'=>$strButtonId);
259:
260: if ($options) {
261: $btnOptions = array_merge($options, $btnOptions);
262: }
263:
264: if ($blnIsPrimary) {
265: $btnOptions['type'] = 'submit';
266: }
267:
268: $this->mixButtons[] = $btnOptions;
269:
270: $this->blnValidationArray[$strButtonId] = $blnCausesValidation;
271:
272: $this->blnModified = true;
273: }
274:
275: /**
276: * Remove the given button from the dialog.
277: *
278: * @param $strButtonId
279: */
280: public function RemoveButton ($strButtonId) {
281: if (!empty($this->mixButtons)) {
282: $this->mixButtons = array_filter ($this->mixButtons, function ($a) use ($strButtonId) {return $a['id'] == $strButtonId;});
283: }
284:
285: unset ($this->blnValidationArray[$strButtonId]);
286:
287: $this->blnModified = true;
288: }
289:
290: /**
291: * Remove all the buttons from the dialog.
292: */
293: public function RemoveAllButtons() {
294: $this->mixButtons = array();
295: $this->blnValidationArray = array();
296: $this->blnModified = true;
297: }
298:
299: /**
300: * Show or hide the given button. Changes the display attribute, so the buttons will reflow.
301: *
302: * @param $strButtonId
303: * @param $blnVisible
304: */
305: public function ShowHideButton ($strButtonId, $blnVisible) {
306: if ($blnVisible) {
307: QApplication::ExecuteJavaScript(
308: sprintf ('$j("#%s").next().find("button[data-btnid=\'%s\']").show();',
309: $this->getJqControlId(), $strButtonId)
310: );
311: } else {
312: QApplication::ExecuteJavaScript(
313: sprintf ('$j("#%s").next().find("button[data-btnid=\'%s\']").hide();',
314: $this->getJqControlId(), $strButtonId)
315: );
316: }
317: }
318:
319: /**
320: * Applies CSS styles to a button that is already in the dialog.
321: *
322: * @param string $strButtonId Id of button to set the style on
323: * @param array $styles Array of key/value style specifications
324: */
325: public function SetButtonStyle ($strButtonId, $styles) {
326: QApplication::ExecuteJavaScript(
327: sprintf ('$j("#%s").next().find("button[data-btnid=\'%s\']").css(%s)', $this->getJqControlId(), $strButtonId, JavaScriptHelper::toJsObject($styles))
328: );
329: }
330:
331: /**
332: * Adds a close button that just closes the dialog without firing the QDialogButton event. You can
333: * trap this by adding an action to the QDialog_CloseEvent.
334: *
335: * @param $strButtonName
336: */
337: public function AddCloseButton ($strButtonName) {
338: // This is an alternate button format supported by jQuery UI.
339: $this->mixButtons[$strButtonName] = new QJsClosure('$j(this).dialog("close")');
340: }
341:
342: /**
343: * Create a message dialog. Automatically adds an OK button that closes the dialog. To detect the close,
344: * add an action on the QDialog_CloseEvent. To change the message, use the return value and set ->Text.
345: * To detect a button click, add a QDialog_ButtonEvent.
346: *
347: * If you specify no buttons, a close box in the corner will be created that will just close the dialog. If you
348: * specify just a string in $strButtons, or just one string in the button array, one button will be shown that will just close the message.
349: *
350: * If you specify more than one button, the first button will be the default button (the one pressed if the user presses the return key). In
351: * this case, you will need to detect the button by adding a QDialog_ButtonEvent. You will also be responsible for calling "Close()" on
352: * the dialog after detecting a button.
353: *
354: * @param QForm $objForm // The parent object, which should always be the form itself.
355: * @param string $strMessage // The message
356: * @param string|string[]|null $strButtons
357: * @param string|null $strControlId
358: * @return QDialog
359: */
360: public static function Alert($strMessage, $strButtons = null, $strControlId = null) {
361: global $_FORM;
362:
363: $objForm = $_FORM;
364: $dlg = new QDialog($objForm, $strControlId);
365: $dlg->Modal = true;
366: $dlg->Resizable = false;
367: $dlg->Text = $strMessage;
368: $dlg->AddAction (new QDialog_CloseEvent(), new QAjaxControlAction($dlg, 'alert_Close'));
369: if ($strButtons) {
370: $dlg->blnHasCloseButton = false;
371: if (is_string($strButtons)) {
372: $dlg->AddCloseButton($strButtons);
373: }
374: elseif (count($strButtons) == 1) {
375: $dlg->AddCloseButton($strButtons[0]);
376: }
377: else {
378: $strButton = array_shift($strButtons);
379: $dlg->AddButton($strButton, null, false, true); // primary button
380:
381: foreach ($strButtons as $strButton) {
382: $dlg->AddButton($strButton);
383: }
384: }
385: } else {
386: $dlg->blnHasCloseButton = true;
387: $dlg->Height = 100; // fix problem with jquery ui dialog making space for buttons that don't exist
388: }
389: $dlg->Open();
390: return $dlg;
391: }
392:
393: /**
394: * An alert is closing, so we remove the dialog from the dom.
395: *
396: * @param $strFormId
397: * @param $strControlId
398: * @param $strParameter
399: */
400: protected function alert_Close($strFormId, $strControlId, $strParameter) {
401: $this->Form->RemoveControl($this->ControlId);
402: QApplication::ExecuteControlCommand($this->getJqControlId(), 'remove');
403: }
404:
405: /**
406: * Show the dialog.
407: */
408: public function ShowDialogBox() {
409: $this->Visible = true;
410: $this->Display = true;
411: $this->Open();
412: }
413:
414: /**
415: * Hide the dialog
416: */
417: public function HideDialogBox() {
418: $this->Close();
419: }
420:
421: /**
422: * PHP magic method
423: *
424: * @param string $strName
425: * @param string $mixValue
426: *
427: * @throws Exception|QCallerException|QInvalidCastException
428: */
429: public function __set($strName, $mixValue) {
430: switch ($strName) {
431: case '_ClickedButton': // Internal only. Do not use. Used by JS above to keep track of clicked button.
432: try {
433: $this->strClickedButtonId = QType::Cast($mixValue, QType::String);
434: } catch (QInvalidCastException $objExc) {
435: $objExc->IncrementOffset();
436: throw $objExc;
437: }
438: break;
439:
440: case '_IsOpen': // Internal only, to detect when dialog has been opened or closed.
441: try {
442: $this->blnIsOpen = QType::Cast($mixValue, QType::Boolean);
443: $this->blnAutoOpen = $this->blnIsOpen; // in case it gets redrawn
444: } catch (QInvalidCastException $objExc) {
445: $objExc->IncrementOffset();
446: throw $objExc;
447: }
448: break;
449:
450: // set to false to remove the close x in upper right corner and disable the
451: // escape key as well
452: case 'HasCloseButton':
453: try {
454: $this->blnHasCloseButton = QType::Cast($mixValue, QType::Boolean);
455: $this->blnCloseOnEscape = $this->blnHasCloseButton;
456: $this->blnModified = true; // redraw
457: break;
458: } catch (QInvalidCastException $objExc) {
459: $objExc->IncrementOffset();
460: throw $objExc;
461: }
462:
463: case 'Height':
464: try {
465: if ($mixValue == 'auto') {
466: $this->mixHeight = 'auto';
467: if ($this->Rendered) {
468: $this->CallJqUiMethod("option", $strName, $mixValue);
469: }
470: } else {
471: parent::__set($strName, $mixValue);
472: }
473: break;
474: } catch (QInvalidCastException $objExc) {
475: $objExc->IncrementOffset();
476: throw $objExc;
477: }
478:
479: case 'Width':
480: try {
481: if ($mixValue == 'auto') {
482: $this->intWidth = 'auto';
483: if ($this->Rendered) {
484: $this->CallJqUiMethod("option", $strName, $mixValue);
485: }
486: } else {
487: parent::__set($strName, $mixValue);
488: }
489: break;
490: } catch (QInvalidCastException $objExc) {
491: $objExc->IncrementOffset();
492: throw $objExc;
493: }
494:
495: case 'DialogState':
496: try {
497: $this->strDialogState = QType::Cast($mixValue, QType::String);
498: break;
499: } catch (QInvalidCastException $objExc) {
500: $objExc->IncrementOffset();
501: throw $objExc;
502: }
503:
504: default:
505: try {
506: parent::__set($strName, $mixValue);
507: break;
508: } catch (QCallerException $objExc) {
509: $objExc->IncrementOffset();
510: throw $objExc;
511: }
512: }
513: }
514:
515: /**
516: * PHP magic method
517: *
518: * @param string $strName
519: *
520: * @return mixed
521: * @throws Exception|QCallerException
522: */
523: public function __get($strName) {
524: switch ($strName) {
525: case 'ClickedButton': return $this->strClickedButtonId;
526:
527: case 'HasCloseButton' : return $this->blnHasCloseButton;
528:
529: default:
530: try {
531: return parent::__get($strName);
532: } catch (QCallerException $objExc) {
533: $objExc->IncrementOffset();
534: throw $objExc;
535: }
536: }
537: }
538: }