1: <?php
2: /**
3: * QControlBase is the base class of all QControls and shares their common properties.
4: *
5: * Not every control will utilize every single one of these properties.
6: *
7: * All Controls must implement the following abstract functions:
8: * <ul>
9: * <li>{@link QControlBase::GetControlHtml()}</li>
10: * <li>{@link QControlBase::ParsePostData()}</li>
11: * <li>{@link QControlBase::Validate()}</li>
12: * </ul>
13: *
14: * A QControl conceptually is an object in an html form that manages data or that can be controlled via PHP.
15: * In the early days of the internet, this was simply an html input or select tag that was submitted via a POST.
16: * As the internet has evolved, so has QControl, but its basic idea is the same. Its an object on the screen that
17: * you would like to either control from PHP, or receive information from. The parts of a QControl that are
18: * sent to the browser are:
19: * - The base tag and its contents, as returned by GetControlHtml(). This would be an Input tag, or a Button, or
20: * even just a div. Many Javascript widget libraries will take a div and add to it to create a control. The tag
21: * will include an id in all cases. If you do not assign one, a unique id will be created automatically.
22: * - An optional Name, often sent to the browser in a Label tag.
23: * - Optional instructions
24: * - Optional validation error text
25: * - Optional Javascript attached to the control as part of its inherint functionality, or to control settable options
26: * that are handled by a jQuery wrapper function of some kind.
27: * - Optional Javascript attached to the control through the AddActions mechanism.
28: *
29: * You control how these parts are rendered by implementing Render* methods in your own QControl class. Some basic
30: * ones are included in this class for you to start with.
31: *
32: * Depending on the control, and the implementation, the control might need or want to be rendered with a wrapper tag,
33: * which is controlled by the blnUseWrapper member. For example, if you want to have a form object with a name,
34: * instructions and error text, a wrapper might be needed to make sure all these parts redraw when something changes in
35: * the control. Bootstrap's formObjectGroup is an example of a control that would have all these parts.
36: * Also, if you know that a javascript widget library is going to wrap your html in additional html,
37: * you should include a wrapper here so the additional html is included inside your wrapper, and thus the entire
38: * control will get redrawn on a refresh (jQueryUI's Dialog is an example of this kind of widget.)
39: *
40: * QControls are part of a tree type hierarchy, whose parent can either be a QForm, or another QControl.
41: *
42: * The QControl system is designed to manage the process of redrawing a control automatically when something about
43: * the control changes. You can force a redraw by using the Refresh command from outside of a control, or by setting
44: * the blnModified member variable from a subclass. You can also use the QWatcher mechanism to automatically redraw
45: * when something in the database changes.
46: *
47: * QControls are the base objects for actions to be attached to events. When attaching actions to multiple objects
48: * of the same type, considering attaching the event to a parent object and using event delegation for your action,
49: * as it can be more efficient in certain cases.
50: *
51: * QControls can trigger validation and are part of the validation system. QControls that are not Enabled or not
52: * Visible will not go through the form's Validation routine.
53: *
54: * Controls can be made visible using either the Visible or Display PHP parameters. Both are booleans.
55: * - Setting Visible to false completely removes the control from the DOM, leaving either just its
56: * wrapper or a an invisible span stub in its place. When the control is made visible again, it is entirely
57: * redrawn.
58: * - Setting Display to false leaves the control in the DOM, but simply sets its display property to 'none' in CSS.
59: * Show and hide are much faster.
60: *
61: * @package Controls
62: *
63: * @property-read boolean $ActionsMustTerminate Prevent the default action from happenning upon an event trigger. See documentation for "protected $blnActionsMustTerminate" below.
64: * @property-read boolean $ScriptsOnly Whether the control only generates javascripts and not html.
65: * @property mixed $ActionParameter This property allows you to pass your own parameters to the handlers for actions applied to this control.
66: * this can be a string or an object of type QJsClosure. If you pass in a QJsClosure it is possible to return javascript objects/arrays
67: * when using an ajax or server action.
68: * @property mixed $CausesValidation flag says whether or not the form should run through its validation routine if this control has an action defined and is acted upon
69: * @property-read string $ControlId returns the id of this control
70: * @property-read QForm $Form returns the parent form object
71: * @property-read array $FormAttributes
72: * @property string $HtmlAfter HTML that is shown after the control {@link QControl::RenderWithName}
73: * @property string $HtmlBefore HTML that is shown before the control {@link QControl::RenderWithName}
74: * @property string $Instructions instructions that is shown next to the control's name label {@link QControl::RenderWithName}
75: * @property-read string $JavaScripts
76: * @property-read boolean $Modified indicates if the control has been changed. Used to tell Qcubed to rerender the control or not (Ajax calls).
77: * @property boolean $Moveable
78: * @property boolean $Resizable
79: * @property string $Name sets the Name of the Control (see {@link QControl::RenderWithName})
80: * @property-read boolean $OnPage is true if the control is connected to the form
81: * @property-read QForm|QControl $ParentControl returns the parent control
82: * @property-read boolean $Rendered
83: * @property-read boolean $Rendering
84: * @property-read string $RenderMethod carries the name of the function, which were initially used for rendering
85: * @property string $PreferredRenderMethod carries the name of the function, which were initially used for rendering
86: * @property boolean $Required specifies whether or not this is required (will cause a validation error if the form is trying to be validated and this control is left blank)
87: * @property-read string $StyleSheets
88: * @property string $ValidationError is the string that contains the validation error (if applicable) or will be blank if (1) the form did not undergo its validation routine or (2) this control had no error
89: * @property boolean $Visible specifies whether or not the control should be rendered in the page. This is in contrast to Display, which will just hide the control via CSS styling.
90: * @property string $Warning is warning text that will be shown next to the control's name label {@link QControl::RenderWithName}
91: * @property boolean $UseWrapper defaults to true
92: * @property QQNode $LinkedNode A database node that this control is directly editing
93: * @property-read boolean $WrapperModified
94: * @property string $WrapperCssClass
95: * @property boolean $WrapLabel For checkboxes, radio buttons, and similar controls, whether to wrap the label around
96: * the control, or place the label next to the control. Two legal styles of label creation that different css and JS frameworks expect.
97: * @property-write boolean $SaveState set to true to have the control remember its state between visits to the form that the control is on.
98: * @property boolean $Minimize True to force the entire control and child controls to draw minimized. This is helpful when drawing inline-block items to prevent spaces from appearing between them.
99: * @property boolean $AutoRender true to have the control be automatically rendered without an explicit "Render..." call. This is used by QDialogs,
100: * and other similar controls that are controlled via javascript, and generally start out hidden on the page. These controls
101: * are appended to the form after all other controls.
102: */
103: abstract class QControlBase extends QHtmlAttributeManager {
104:
105: /*
106: * Constannts
107: */
108: const CommentStart = 'Begin';
109: const CommentEnd = 'End';
110:
111: /*
112: * Static Members
113: */
114:
115: protected $objWrapperStyler = null;
116:
117: /**
118: * Protected members
119: */
120:
121: /** @var mixed Controls how this control will effect the validation system */
122: protected $mixCausesValidation = false;
123: /** @var bool Is it mandatory for the control to receive data on a POST back for the control to be called valid? */
124: protected $blnRequired = false;
125: /** @var int Tab-index */
126: protected $strValidationError = null;
127: /** @var bool Should the control be visible or not (it normally effects whether Render method will be called or not) */
128: protected $blnVisible = true;
129: /** @var bool should the control be displayed? */
130: protected $blnDisplay = true;
131: /** @var string Preferred method to be used for rendering e.g. Render, RenderWithName, RenderWithError */
132: protected $strPreferredRenderMethod = 'Render';
133:
134: /** @var string HTML to rendered before the actual control */
135: protected $strHtmlBefore = null;
136: /** @var string HTML to rendered after the actual control */
137: protected $strHtmlAfter = null;
138: /** @var string the Instructions for the control (useful for controls used in data entry) */
139: protected $strInstructions = null;
140: /** @var string Same as validation error message but is supposed to contain custom messages */
141: protected $strWarning = null;
142:
143: /** @var QDraggable|null When initialized, it implements the jQuery UI Draggable capabilities on to this control.*/
144: protected $objDraggable = null;
145: /** @var QResizable|null When initialized, it implements the jQuery UI Resizable capabilities on to this control.*/
146: protected $objResizable = null;
147: /** @var QDroppable|null When initialized, it implements the jQuery UI Droppable capabilities on to this control.*/
148: protected $objDroppable = null;
149:
150: // MISC
151: /**
152: * @var null|string The control ID of this control. Used to represent the control internally
153: * and used for the HTML 'id' attribute on the control.
154: */
155: protected $strControlId;
156: /** @var QForm Redundant copy of the global $_FORM variable. */
157: protected $objForm = null;
158: /** @var QControl Immediate parent of this control */
159: protected $objParentControl = null;
160: /** @var QControl[] Controls which have this control as their parent */
161: protected $objChildControlArray = array();
162: /** @var string|null Name of the control - used as a lable for the control when RenderWithName is used to render */
163: protected $strName = null;
164: /** @var bool Has the control already been rendered? */
165: protected $blnRendered = false;
166: /** @var bool Is the control mid-way the process of rendering? */
167: protected $blnRendering = false;
168: /** @var bool Is the control available on page? Useful when 're-rendering' a control that has children */
169: protected $blnOnPage = false;
170: /** @var bool Has the control been modified? Used mostly in Ajax or Server callbacks */
171: protected $blnModified = false;
172: /** @var bool Has the control's wrapper been modified? Used in Ajax or Server callbacks */
173: protected $blnWrapperModified = false;
174: /** @var string Render method to be used */
175: protected $strRenderMethod;
176: /** @var string|null Custom HTML attributes for the control */
177: protected $strCustomAttributeArray = null;
178: /** @var string|null Custom CSS style attributes for the control */
179: protected $strCustomStyleArray = null;
180: /** @var array Array containing the list of actions set on the control (for different events) */
181: protected $objActionArray = array();
182: /** @var string|QJsClosure|null The action parameter (typically small amount of data) for the Ajax or Server Callback */
183: protected $mixActionParameter = null;
184: /** @var string|null CSS class for the control's wrapper */
185: //protected $strWrapperCssClass = null; -- See objWrapperStyler now
186: /** @var bool Should the wrapper be used when rendering? */
187: protected $blnUseWrapper = true;
188: /** @var string One time scripts associated with the control. */
189: protected $strAttributeScripts = null;
190: /** @var string The INITIAL class for the object. Only subclasses should set this before calling the parent constructor. */
191: protected $strCssClass = null;
192: /** @var bool Force this control, and all subcontrols to draw minimized. This is important when using inline-block styles, as not doing so will cause spaces between the objects. */
193: protected $blnMinimize = false;
194:
195: // SETTINGS
196: /** @var string List of JavaScript files to be attached with the control when rendering */
197: protected $strJavaScripts = null;
198: /** @var string List of CSS files to be attaches with the control when rendering */
199: protected $strStyleSheets = null;
200: /** @var string Form attributes for the control */
201: protected $strFormAttributes = null;
202: /**
203: * @var bool Should the default action be stopped from the being triggerred when an even occurrs?
204: *
205: * e.g.:
206: *
207: * 1. When a link is clicked which has an action associated with it - the browser will try to
208: * navigate to the link.
209: * 2. When someone presses enter on a textbox - the form will try to submit.
210: *
211: * This variable stops the default behavior (navigation to link / form submission) when set to true.
212: * Modification of this variable is to be done by using 'ActionMustTerminate' property exposed as a property
213: */
214: protected $blnActionsMustTerminate = false;
215: /** @var bool True if this control only generates javascripts and not html. */
216: protected $blnScriptsOnly = false;
217: /** @var bool Is this control a block type element? This determines whether the control will be wrapped in
218: * a div or a span if blnUseWrapper is true. For example, if */
219: protected $blnIsBlockElement = false;
220: /** @var QWatcher Stores information about watched tables. */
221: protected $objWatcher = null;
222: /** @var QQNode Used by designer to associate a db node with this control */
223: protected $objLinkedNode;
224: /**
225: * @var bool | null For controls that also produce built-in labels (QCheckBox, QCheckBoxList, etc.)
226: * True to wrap the checkbox with the label (the Bootstrap way). False to put the label next to the
227: * checkbox (the jQueryUI way).
228: */
229: protected $blnWrapLabel = false;
230:
231: /** @var bool true to remember the state of this control to restore if the user comes back to it. */
232: protected $blnSaveState = false;
233:
234: /** @var bool true to have the control be automatically rendered without an explicit "Render..." call. This is used by QDialogs,
235: * and other similar controls that are controlled via javascript, and generally start out hidden on the page. These controls
236: * are appended to the form after all other controls.
237: */
238: protected $blnAutoRender = false;
239:
240: //////////
241: // Methods
242: //////////
243: /**
244: * Creates a QControlBase object
245: * This constructor will generally not be used to create a QControlBase object. Instead it is used by the
246: * classes which extend the class. Only the parent object parameter is required. If the option strControlId
247: * parameter is not used, QCubed will generate the id.
248: *
249: * @param QControl|QForm|QControlBase $objParentObject
250: * @param string $strControlId
251: * optional id of this Control. In html, this will be set as the value of the id attribute. The id can only
252: * contain alphanumeric characters. If this parameter is not passed, QCubed will generate the id.
253: *
254: * @throws Exception|QCallerException
255: */
256: public function __construct($objParentObject, $strControlId = null) {
257: if ($objParentObject instanceof QForm)
258: $this->objForm = $objParentObject;
259: else if ($objParentObject instanceof QControl) {
260: $this->objParentControl = $objParentObject;
261: $this->objForm = $objParentObject->Form;
262: } else
263: throw new QCallerException('ParentObject must be either a QForm or QControl object');
264:
265: if (strlen($strControlId) == 0)
266: $this->strControlId = $this->objForm->GenerateControlId();
267: else {
268: // Verify ControlId is only AlphaNumeric Characters
269: if (ctype_alnum($strControlId))
270: $this->strControlId = $strControlId;
271: else
272: throw new QCallerException('ControlIds must be only alphanumeric characters: ' . $strControlId);
273: }
274:
275: /* If the subclass sets this, we pass it off to the attribute manager. Mostly for backwards compatibility,
276: * but is a conventient way to set the initial class.
277: */
278: if ($this->strCssClass) {
279: $this->AddCssClass($this->strCssClass);
280: }
281:
282: try {
283: $this->objForm->AddControl($this);
284: if ($this->objParentControl)
285: $this->objParentControl->AddChildControl($this);
286: } catch (QCallerException $objExc) {
287: $objExc->IncrementOffset();
288: throw $objExc;
289: }
290: }
291:
292: /**
293: * ParsePostData parses the value of this control from FormState
294: *
295: * This abstract method must be implemented by all controls.
296: *
297: * When utilizing formgen, the programmer should never access form variables directly (e.g.
298: * via the $_FORM array). It can be assumed that at *ANY* given time, a control's
299: * values/properties will be "up to date" with whatever the webuser has entered in.
300: *
301: * When a Form is Created via Form::Create(string), the form will go through to check and
302: * see if it is a first-run of a form, or if it is a post-back. If it is a postback, it
303: * will go through its own private array of controls and call ParsePostData on EVERY control
304: * it has. Each control is responsible for "knowing" how to parse the $_POST data to update
305: * its own values/properties based on what was returned to via the postback.
306: */
307: abstract public function ParsePostData();
308:
309: /**
310: * Checks if this controls contains a valid value.
311: *
312: * This abstract method defines how a control should validate itself based on the value/
313: * properties it has. It should also include the handling of ensuring the "Required"
314: * requirements are obeyed if this control's "Required" flag is set to true.
315: *
316: * For Controls that can't realistically be "validated" (e.g. labels, datagrids, etc.),
317: * those controls should simply have Validate() return true.
318: *
319: * @return boolean
320: */
321: abstract public function Validate();
322:
323: /**
324: * Object persistance support.
325: */
326:
327: /**
328: * Save the state of the control to restore it later, so that if the user comes back to this page, the control
329: * will be in the showing the same thing. Subclasses should put minimally important information into the state that
330: * is needed to restore the state later.
331: *
332: * This implementation puts the state into the session. Override to provide a different method if so desired.
333: *
334: * Should normally be called only by FormBase code. See GetState and PutState for the control side implementation.
335: */
336: public function _WriteState() {
337: global $_FORM;
338:
339: assert ($_FORM !== null);
340: if (defined ('__SESSION_SAVED_STATE__') && $this->blnSaveState) {
341: $formName = get_class($_FORM); // must use global $_FORM here instead of $this->objForm, since serialization will have nulled the objForm.
342: $_SESSION[__SESSION_SAVED_STATE__][$formName][$this->ControlId] = $this->GetState();
343: }
344: }
345:
346: /**
347: * Restore the state of the control.
348: */
349: public function _ReadState() {
350: if (defined ('__SESSION_SAVED_STATE__') && $this->blnSaveState) {
351: $formName = get_class($this->objForm);
352: if (isset ($_SESSION[__SESSION_SAVED_STATE__][$formName][$this->ControlId])) {
353: $state = $_SESSION[__SESSION_SAVED_STATE__][$formName][$this->ControlId];
354: $this->PutState($state);
355: }
356: }
357: }
358:
359: /**
360: * Control subclasses should return their state data that they will use to restore later.
361: * @return mixed
362: */
363: protected function GetState() {
364: return null;
365: }
366:
367: /**
368: * Restore the state of the control. The control will have already been
369: * created and initialized. Subclasses should verify that the restored state is still valid for the data
370: * available.
371: * @param mixed $state
372: */
373: protected function PutState($state) {}
374:
375: /**
376: * Completely forget the saved state for this control.
377: */
378: public function ForgetState() {
379: if (defined ('__SESSION_SAVED_STATE__')) {
380: $formName = get_class($this->objForm);
381: unset($_SESSION[__SESSION_SAVED_STATE__][$formName][$this->ControlId]);
382: }
383: }
384:
385:
386: /**
387: * A utility function to convert a template file name into a full path.
388: *
389: * @param $strTemplate
390: */
391: public function GetTemplatePath($strTemplate) {
392: // If no path is specified, or a relative path, use the path of the child control's file as the starting point.
393: if (strpos($strTemplate, DIRECTORY_SEPARATOR) !== 0) {
394: $strOriginalPath = $strTemplate;
395:
396: // Try the control's subclass location
397: $reflector = new ReflectionClass(get_class($this));
398: $strDir = dirname($reflector->getFileName());
399: $strTemplate = $strDir . DIRECTORY_SEPARATOR . $strTemplate;
400:
401: if (!file_exists($strTemplate)) {
402: // Try the control's parent
403: if ($this->objParentControl) {
404: $reflector = new ReflectionClass(get_class($this->objParentControl));
405: $strDir = dirname($reflector->getFileName());
406: $strTemplate = $strDir . DIRECTORY_SEPARATOR . $strTemplate;
407: }
408: }
409:
410: if (!file_exists($strTemplate)) {
411: // Try the form's location
412: $reflector = new ReflectionClass(get_class($this->objForm));
413: $strDir = dirname($reflector->getFileName());
414: $strTemplate = $strDir . DIRECTORY_SEPARATOR . $strTemplate;
415:
416: if (!file_exists($strTemplate)) {
417: $strTemplate = $strOriginalPath; // not found, but return original name
418: }
419: }
420: }
421: return $strTemplate;
422: }
423:
424:
425: /**
426: * This function evaluates a template and is used by a variety of controls. It is similar to the function found in the
427: * QForm, but recreated here so that the "$this" in the template will be the control, instead of the form,
428: * and the protected members of the control are available to draw directly.
429: * @param string $strTemplate Path to the HTML template file
430: *
431: * @return string The evaluated HTML string
432: */
433: public function EvaluateTemplate($strTemplate) {
434: global $_ITEM; // used by data repeater
435: global $_CONTROL;
436: global $_FORM;
437:
438: if ($strTemplate) {
439: QApplication::$ProcessOutput = false;
440: // Store the Output Buffer locally
441: $strAlreadyRendered = ob_get_contents();
442: if ($strAlreadyRendered) {
443: ob_clean();
444: }
445:
446: // Evaluate the new template
447: ob_start('__QForm_EvaluateTemplate_ObHandler');
448:
449: $strTemplate = $this->GetTemplatePath($strTemplate);
450: require($strTemplate);
451: $strTemplateEvaluated = ob_get_contents();
452: ob_end_clean();
453:
454: // Restore the output buffer and return evaluated template
455: if ($strAlreadyRendered) {
456: print($strAlreadyRendered);
457: }
458: QApplication::$ProcessOutput = true;
459:
460: return $strTemplateEvaluated;
461: }
462:
463: return null;
464: }
465:
466: /**
467: * This function passes control of action parameter processing to the control that caused the action, so that
468: * the control can further process the action parameters. It also saves additional information in the returned
469: * parameter array. This is useful for widgets that need to pass more information to the action than just a
470: * simple string, and allows actions to get more information as well. This also allows widgets to modify
471: * the action parameter, while preserving the original action parameter so that the action can see both.
472: *
473: * @param QControl $objSourceControl
474: * @param QAction $objAction
475: * @param $mixParameter
476: * @return mixed
477: */
478: public static function _ProcessActionParams(QControl $objSourceControl, QAction $objAction, $mixParameter) {
479: $mixParameters['param'] = null;
480: $mixParameters = $objSourceControl->ProcessActionParameters($objAction, $mixParameter);
481: return $mixParameters;
482: }
483:
484: /**
485: * Breaks down the action parameter if needed to more useful information. Subclasses should override, call
486: * the parent, and then modify the "param" item in the returned array if needed. This also provides additional
487: * information to the action about the triggering control.
488: *
489: * @param $mixParameter
490: * @return mixed
491: */
492: protected function ProcessActionParameters(QAction $objAction, $mixParameter) {
493: $params['param'] = $mixParameter; // this value can be modified by subclass if needed
494: $params['originalParam'] = $mixParameter;
495: $params['action'] = $objAction;
496: $params['controlId'] = $this->strControlId;
497: return $params;
498: }
499:
500: /**
501: * Used by the QForm engine to call the method in the control, allowing the method to be a protected method.
502: *
503: * @param QControl $objDestControl
504: * @param string $strMethodName
505: * @param $strSourceControlId
506: * @param mixed $mixParameter Parameters coming from javascript
507: */
508: public static function _CallActionMethod(QControl $objDestControl, $strMethodName, $strFormId, $params) {
509: $objDestControl->$strMethodName($strFormId, $params['controlId'], $params['param'], $params);
510: }
511:
512: /**
513: * Prepare the control for serialization. All pointers to forms and form objects should be
514: * converted to something that can be restored using Wakeup().
515: *
516: * The main problem we are resolving is that the PHP serialization process will convert an internal reference
517: * to the object being serialized into a copy of the object. After deserialization, you would have the form,
518: * and then somewhere inside the form, a separate copy of the form. This is a long-standing bug in PHP.
519: */
520: public function Sleep() {
521: $this->objForm = null;
522: }
523:
524: /**
525: * The object has just been unserialized, so fix up pointers to embedded forms.
526: * @param QForm $objForm
527: */
528: public function Wakeup(QForm $objForm) {
529: $this->objForm = $objForm;
530: }
531:
532: /**
533: * A helper function to fix up a 'callable', a formObj, or any other object that we would like to represent
534: * in the serialized stream differently than the default. If a QControl, make sure this isn't the only
535: * instance of the control in the stream, or have some other way to serialize the control.
536: *
537: * @param QForm|QControl|array|Callable $obj
538: * @return mixed
539: */
540: public static function SleepHelper($obj) {
541: if ($obj instanceof QForm) {
542: // assume its THE form
543: return '**QF;';
544: }
545: elseif ($obj instanceof QControl) {
546: return '**QC;' . $obj->strControlId;
547: }
548: elseif (is_array ($obj)) {
549: $ret = array();
550: foreach ($obj as $key=>$val) {
551: $ret[$key] = self::SleepHelper($val);
552: }
553: return $ret;
554: }
555: return $obj;
556: }
557:
558: /**
559: * A helper function to restore something possibly serialized with SleepHelper.
560: *
561: * @param QForm|QFormBase $objForm
562: * @param array|string $obj
563: *
564: * @return mixed
565: */
566: public static function WakeupHelper($objForm, $obj) {
567: if (is_array ($obj)) {
568: $ret = array();
569: foreach ($obj as $key=>$val) {
570: $ret[$key] = self::WakeupHelper($objForm, $val);
571: }
572: return $ret;
573: } elseif (is_string ($obj)) {
574: if (substr($obj, 0, 5) == '**QF;') {
575: return $objForm;
576: }
577: elseif (substr($obj, 0, 5) == '**QC;') {
578: return $objForm->GetControl(substr($obj, 5));
579: }
580: }
581: return $obj;
582: }
583:
584: /**
585: * Adds a control as a child of this control.
586: *
587: * @param QControl|QControlBase $objControl the control to add
588: */
589: public function AddChildControl(QControl $objControl) {
590: $this->blnModified = true;
591: $this->objChildControlArray[$objControl->ControlId] = $objControl;
592: $objControl->objParentControl = $this;
593: }
594:
595: /**
596: * Returns all child controls as an array
597: *
598: * @param boolean $blnUseNumericIndexes
599: * @return QControl[]
600: */
601: public function GetChildControls($blnUseNumericIndexes = true) {
602: if ($blnUseNumericIndexes) {
603: $objToReturn = array();
604: foreach ($this->objChildControlArray as $objChildControl)
605: array_push($objToReturn, $objChildControl);
606: return $objToReturn;
607: } else
608: return $this->objChildControlArray;
609: }
610:
611: /**
612: * Returns the child control with the given id
613: * @param string $strControlId
614: * @return QControl
615: */
616: public function GetChildControl($strControlId) {
617: if (isset($this->objChildControlArray[$strControlId])) {
618: return $this->objChildControlArray[$strControlId];
619: }
620: else {
621: return null;
622: }
623: }
624:
625: /**
626: * Removes all child controls
627: * @param boolean $blnRemoveFromForm
628: */
629: public function RemoveChildControls($blnRemoveFromForm) {
630: foreach ($this->objChildControlArray as $objChildControl) {
631: $this->RemoveChildControl($objChildControl->ControlId, $blnRemoveFromForm);
632: }
633: }
634:
635: /**
636: * Removes the child control with the given id
637: * @param string $strControlId
638: * @param boolean $blnRemoveFromForm should the control be removed from the form, too?
639: */
640: public function RemoveChildControl($strControlId, $blnRemoveFromForm) {
641: $this->blnModified = true;
642: if (isset($this->objChildControlArray[$strControlId])) {
643: $objChildControl = $this->objChildControlArray[$strControlId];
644: $objChildControl->objParentControl = null;
645: unset($this->objChildControlArray[$strControlId]);
646:
647: if ($blnRemoveFromForm)
648: $this->objForm->RemoveControl($objChildControl->ControlId);
649: }
650: }
651:
652: /**
653: * Adds an action to the control
654: *
655: * @param QEvent $objEvent
656: * @param QAction $objAction
657: *
658: * @throws QCallerException
659: */
660: public function AddAction($objEvent, $objAction) {
661: if (!($objEvent instanceof QEvent)) {
662: throw new QCallerException('First parameter of AddAction is expecting an object of type QEvent');
663: }
664:
665: if (!($objAction instanceof QAction)) {
666: throw new QCallerException('Second parameter of AddAction is expecting an object of type QAction');
667: }
668:
669: // Modified
670: $this->blnModified = true;
671:
672: // Store the Event object in the Action object
673: if ($objAction->Event) {
674: //this Action is in use -> clone it
675: $objAction = clone($objAction);
676: }
677: $objAction->Event = $objEvent;
678:
679: // Pull out the Event Name
680: $strEventName = $objEvent->EventName;
681:
682: if (!array_key_exists($strEventName, $this->objActionArray))
683: $this->objActionArray[$strEventName] = array();
684: array_push($this->objActionArray[$strEventName], $objAction);
685: }
686:
687: /**
688: * Adds an array of actions to the control
689: *
690: * @param QEvent $objEvent
691: * @param array $objActionArray
692: *
693: * @throws QCallerException
694: */
695: public function AddActionArray($objEvent, $objActionArray) {
696: if (!($objEvent instanceof QEvent)) {
697: throw new QCallerException('First parameter of AddAction is expecting on object of type QEvent');
698: }
699:
700: foreach ($objActionArray as $objAction) {
701: $objAction = clone($objAction);
702: $this->AddAction($objEvent, $objAction);
703: }
704: }
705:
706: /**
707: * Removes all events for a given event name.
708: *
709: * Be sure and use a QFooEvent::EventName constant here
710: * (QClickEvent::EventName, for example).
711: *
712: * @param string $strEventName
713: */
714: public function RemoveAllActions($strEventName = null) {
715: // Modified
716: $this->blnModified = true;
717:
718: if ($strEventName) {
719: $this->objActionArray[$strEventName] = array();
720: }
721: else {
722: $this->objActionArray = array();
723: }
724: }
725:
726: /**
727: * Returns all actions that are connected with specific events
728: *
729: * @param string $strEventType the type of the event. Be sure and use a
730: * QFooEvent::EventName here. (QClickEvent::EventName, for example)
731: * @param string $strActionType if given only actions of this type will be
732: * returned
733: *
734: * @return QAction[]
735: */
736: public function GetAllActions($strEventType, $strActionType = null) {
737: $objArrayToReturn = array();
738: if ($this->objActionArray) foreach ($this->objActionArray as $objActionArray) {
739: foreach ($objActionArray as $objAction)
740: if (get_class($objAction->Event) == $strEventType) {
741: if ((!$strActionType) ||
742: ($objAction instanceof $strActionType))
743: array_push($objArrayToReturn, $objAction);
744: }
745: }
746:
747: return $objArrayToReturn;
748: }
749:
750: /**
751: * Sets one custom attribute
752: *
753: * Custom Attributes refers to the html name-value pairs that can be rendered within the control that are not
754: * covered by an explicit method. For example, on a textbox, you can render any number of additional name-value
755: * pairs, to assign additional javascript actions, additional formatting, etc.
756: * <code>
757: * <?php
758: * $txtTextbox = new Textbox("txtTextbox");
759: * $txtTextbox->SetCustomAttribute("onfocus", "alert('You are about to edit this field');");
760: * $txtTextbox->SetCustomAttribute("nowrap", "nowrap");
761: * $txtTextbox->SetCustomAttribute("blah", "foo");
762: * ?>
763: * </code>
764: * Will render:
765: * <code>
766: * <input type="text" ...... onfocus="alert('You are about to edit this field');" nowrap="nowrap" blah="foo" />
767: * </code>
768: *
769: * @param string $strName
770: * @param string $strValue
771: * @deprecated Use SetHtmlAttribute instead
772: */
773: public function SetCustomAttribute($strName, $strValue) {
774: $this->SetHtmlAttribute($strName, $strValue);
775: }
776:
777: /**
778: * Returns the value of a custom attribute
779: *
780: * @param string $strName
781: *
782: * @throws QCallerException
783: * @return string
784: * @deprected Use GetHtmlAttribute instead
785: */
786: public function GetCustomAttribute($strName) {
787: return $this->GetHtmlAttribute($strName);
788: }
789:
790: /**
791: * Removes the given custom attribute
792: *
793: * @param string $strName
794: *
795: * @throws QCallerException
796: * @deprecated Use RemoveHtmlAttribute instead
797: */
798: public function RemoveCustomAttribute($strName) {
799: $this->RemoveHtmlAttribute($strName);
800: }
801:
802: /**
803: * Adds a custom style property/value to the html style attribute
804: *
805: * Sets a custom css property. For example:
806: * <code>
807: * <?php
808: * $txtTextbox = new Textbox("txtTextbox");
809: * $txtTextbox->SetCustomStyle("white-space", "nowrap");
810: * $txtTextbox->SetCustomStyle("margin", "10px");
811: * ?>
812: * </code>
813: * Will render:
814: * <code>
815: * <input type="text" ...... style="white-space:nowrap;margin:10px" />
816: * </code>
817: *
818: * @param string $strName
819: * @param string $strValue
820: * @deprecated Use SetCssStyle instead
821: */
822: public function SetCustomStyle($strName, $strValue) {
823: $this->SetCssStyle($strName, $strValue);
824: }
825:
826: /**
827: * Returns the value of the given custom style
828: *
829: * @param string $strName
830: *
831: * @throws QCallerException
832: * @return string
833: */
834: public function GetCustomStyle($strName) {
835: return $this->GetCssStyle($strName);
836: }
837:
838: /**
839: * Deletes the given custom style
840: *
841: * @param string $strName
842: *
843: * @throws QCallerException
844: * @deprecated use RemoveCssStyle instead
845: */
846: public function RemoveCustomStyle($strName) {
847: $this->RemoveCssStyle($strName);
848: }
849:
850: /**
851: * Add javascript file to be included in the form.
852: * The include mechanism will take care of duplicates, and also change the given URL in the following ways:
853: * - If the file name begins with 'http', it will use it directly as a URL
854: * - If the file name begins with '/', the url will be relative to __DOCROOT__ . __VIRTUAL_DIRECTORY__
855: * - If the file name begins with anything else, the url will be relative to __JS_ASSETS__
856: *
857: * @param string $strJsFileName url, path, or file name to include
858: */
859: public function AddJavascriptFile($strJsFileName) {
860: if($this->strJavaScripts) {
861: $this->strJavaScripts .= ','.$strJsFileName;
862: } else {
863: $this->strJavaScripts = $strJsFileName;
864: }
865: }
866:
867: /**
868: * Add javascript file to be included from a plugin. Plugins should use this function instead of AddJavascriptFile.
869: * The include mechanism will take care of duplicates, and also change the given URL in the following ways:
870: * - If the file name begins with 'http', it will use it directly as a URL
871: * - If the file name begins with '/', the url will be relative to the __DOCROOT__ . __VIRTUAL_DIRECTORY__ directory.
872: * - If the file name begins with anything else, the url will be relative to __PLUGIN_ASSETS__/pluginName/js/
873: *
874: * @param string $strPluginName name of plugin
875: * @param string $strJsFileName url, path, or file name to include
876: */
877: public function AddPluginJavascriptFile($strPluginName, $strJsFileName) {
878: if (strpos($strJsFileName, "http") === 0) {
879: $this->AddJavascriptFile($strJsFileName); // plugin uses js from some other website
880: }
881: else {
882: if (strpos($strJsFileName, "/") === 0) {
883: // custom location for plugin javascript, relative to virtual directory
884: $this->AddJavascriptFile($strJsFileName);
885: } else {
886: // Use the default location, relative to plugin's own directory given.
887: $this->AddJavascriptFile(__PLUGIN_ASSETS__ . '/' . $strPluginName . "/js/" . $strJsFileName);
888: }
889: }
890: }
891:
892: /**
893: * Add style sheet file to be included in the form.
894: * The include mechanism will take care of duplicates, and also change the given URL in the following ways:
895: * - If the file name begins with 'http', it will use it directly as a URL
896: * - If the file name begins with '/', the url will be relative to the ___DOCROOT__ . __VIRTUAL_DIRECTORY__
897: * - If the file name begins with anything else, the url will be relative to __CSS_ASSETS__
898: *
899: * @param string $strCssFileName url, path, or file name to include
900: */
901: public function AddCssFile($strCssFileName) {
902: if($this->strStyleSheets) {
903: $this->strStyleSheets .= ','.$strCssFileName;
904: } else {
905: $this->strStyleSheets = $strCssFileName;
906: }
907: }
908:
909: /**
910: * Add style sheet file to be included from a plugin. Plugins should use this function instead of AddCssFile.
911: * The include mechanism will take care of duplicates, and also change the given URL in the following ways:
912: * - If the file name begins with 'http', it will use it directly as a URL
913: * - If the file name begins with '/', the url will be relative to the __PLUGIN_ASSETS__ directory.
914: * - If the file name begins with anything else, the url will be relative to __PLUGIN_ASSETS__/pluginName/css/
915: *
916: * @param string $strPluginName name of plugin
917: * @param string $strCssFileName url, path, or file name to include
918: */
919: public function AddPluginCssFile($strPluginName, $strCssFileName) {
920: if (strpos($strCssFileName, "http") === 0) {
921: $this->AddCssFile($strCssFileName); // plugin uses style sheet from some other website
922: }
923: else {
924: if (strpos($strCssFileName, "/") === 0) {
925: // custom location for plugin javascript, relative to virtual dir
926: $this->AddCssFile($strCssFileName);
927: } else {
928: // Use the default location
929: $this->AddCssFile(__PLUGIN_ASSETS__ . '/' . $strPluginName . "/css/" . $strCssFileName);
930: }
931: }
932: }
933:
934:
935: /**
936: * Returns all attributes in the correct HTML format
937: *
938: * This is utilized by Render methods to display various name-value HTML attributes for the
939: * control.
940: *
941: * ControlBase's implementation contains the very-basic set of HTML attributes... it is expected
942: * that most subclasses will extend this method's functionality to add Control-specific HTML
943: * attributes (e.g. textbox will likely add the maxlength html attribute, etc.)
944: *
945: * @return string
946: * @deprecated Use renderHtmlAttributes instead
947: */
948: public function GetAttributes() {
949: return $this->RenderHtmlAttributes() . ' ';
950: }
951:
952: /**
953: * Returns the custom attributes HTML formatted
954: *
955: * All attributes will be returned as concatened the string of the form
956: * key1="value1" key2="value2"
957: * Note: if the the value is === false, then the key will be randered as is, without any value
958: *
959: * @return string
960: * @deprecated Unused
961: */
962: public function GetCustomAttributes() {
963: return $this->RenderHtmlAttributes();
964: }
965:
966: /**
967: * Returns the html for the attributes for the base control of the QControl.
968: * Allows the given arrays to override the attributes and styles before
969: * rendering. This inserts the control id into the rendering of the tag.
970: * @param null|string $attributeOverrides
971: * @param null|string $styleOverrides
972: * @return string
973: */
974: public function RenderHtmlAttributes($attributeOverrides = null, $styleOverrides = null) {
975: $attributes['id'] = $this->strControlId;
976: if ($attributeOverrides) {
977: $attributes = array_merge($attributes, $attributeOverrides);
978: }
979: return parent::RenderHtmlAttributes($attributes, $styleOverrides);
980: }
981:
982: /**
983: * Returns all action attributes for this QControl
984: *
985: * @return string
986: */
987: public function RenderActionScripts() {
988: $strToReturn = '';
989: foreach ($this->objActionArray as $strEventName => $objActions) {
990: $strToReturn .= $this->GetJavaScriptForEvent($strEventName);
991: }
992: return $strToReturn;
993: }
994:
995: /**
996: * Get the JavaScript for a given Element
997: * @param string $strEventName
998: *
999: * @return null|string
1000: */
1001:
1002: public function GetJavaScriptForEvent($strEventName) {
1003: return QAction::RenderActions($this, $strEventName, $this->objActionArray[$strEventName]);
1004: }
1005:
1006: /**
1007: * Returns all style-attributes
1008: *
1009: * Similar to GetAttributes, but specifically for CSS name/value pairs that will render
1010: * within a control's HTML "style" attribute
1011: *
1012: * <code>
1013: * <?php
1014: * $txtTextbox = new Textbox("txtTextbox");
1015: * $txtTextbox->SetCustomStyle("white-space", "nowrap");
1016: * $txtTextbox->SetCustomStyle("margin", "10px");
1017: * $txtTextBox->Height = 20;
1018: * $txtTextBox->GetStyleAttributes();
1019: * ?>
1020: * will return:
1021: * white-space:nowrap;margin:10px;height:20px;
1022: *
1023: * @return string
1024: * @deprected Use
1025: */
1026: public function GetStyleAttributes() {
1027: return $this->RenderCssStyles();
1028: }
1029:
1030: /**
1031: * Returns the styler for the wrapper tag.
1032: * @return null|QTagStyler
1033: */
1034: public function GetWrapperStyler() {
1035: if (!$this->objWrapperStyler) {
1036: $this->objWrapperStyler = new QTagStyler();
1037: }
1038: return $this->objWrapperStyler;
1039: }
1040:
1041: /**
1042: * Adds the given class to the wrapper tag.
1043: * @param $strClass
1044: */
1045: public function AddWrapperCssClass($strClass) {
1046: if ($this->GetWrapperStyler()->AddCssClass($strClass)) {
1047: $this->MarkAsWrapperModified();
1048: }
1049: /**
1050: * TODO: This can likely be done just in javascript without a complete refresh of the control.
1051: *
1052: * if ($this->blnRendered && $this->blnOnScreen) {
1053: * Change using javascript
1054: * }
1055: */
1056: }
1057:
1058: /**
1059: * Removes the given class from the wrapper tag.
1060: * @param $strClass
1061: */
1062: public function RemoveWrapperCssClass($strClass) {
1063: if ($this->GetWrapperStyler()->RemoveCssClass($strClass)) {
1064: $this->MarkAsWrapperModified();
1065: }
1066:
1067: // TODO: do this in javascript
1068: // QApplication::ExecuteControlCommand($this->WrapperId, 'removeClass', $this->strValidationState);
1069:
1070: }
1071:
1072: /**
1073: * Returns all wrapper-style-attributes
1074: * Similar to GetStyleAttributes, but specifically for CSS name/value pairs that will render
1075: * within a "wrapper's" HTML "style" attribute
1076: *
1077: * @param bool $blnIsBlockElement
1078: * @deprecated
1079: *
1080: * @return string
1081: */
1082: protected function GetWrapperStyleAttributes($blnIsBlockElement = false) {
1083: return $this->GetWrapperStyler()->RenderCssStyles();
1084: }
1085:
1086:
1087: /**
1088: * Overrides the default CSS renderer in order to deal with a special situation:
1089: * Since there is the possibility of a wrapper, we have to delegate certain CSS properties to the wrapper so
1090: * that the whole control gets those properties. Those are mostly positioning properties. In this override,
1091: * we detect when we do NOT have a wrapper, and therefore have to copy the positioning properties from the
1092: * wrapper styler down to the control itself.
1093: *
1094: * @param null $styleOverrides
1095: * @return string
1096: */
1097: public function RenderCssStyles($styleOverrides = null) {
1098: $styles = $this->styles;
1099: if ($styleOverrides) {
1100: $styles = array_merge($this->styles, $styleOverrides);
1101: }
1102:
1103: if (!$this->blnUseWrapper) {
1104: // add wrapper styles if no wrapper. control must stand on its own.
1105: // This next line sucks just the given attributes out of the wrapper styler
1106: $aWStyles = array_intersect_key ($this->getWrapperStyler()->styles, ['position'=>1, 'top'=>1, 'left'=>1]);
1107: $styles = array_merge($styles, $aWStyles);
1108: if (!$this->blnDisplay) {
1109: $styles['display'] ='none';
1110: }
1111: }
1112: return QHtml::RenderStyles($styles);
1113: }
1114:
1115: /**
1116: * Returns an array of the wrapper attributes to be used for drawing, including CSS styles. Makes sure the control is hidden if display is off.
1117: * @param array $attributeOverrides
1118: * @return string
1119: */
1120: protected function GetWrapperAttributes($attributeOverrides = null) {
1121: $styleOverrides = null;
1122: if (!$this->blnDisplay) {
1123: $styleOverrides = ['display'=>'none'];
1124: }
1125: $attributes = $this->GetWrapperStyler()->GetHtmlAttributes($attributeOverrides, $styleOverrides);
1126:
1127: return $attributes;
1128: }
1129:
1130: /**
1131: * Renders the given output with the current wrapper.
1132: *
1133: * @param $strOutput
1134: * @param $blnForceAsBlockElement
1135: *
1136: * @return string
1137: */
1138: protected function RenderWrappedOutput($strOutput, $blnForceAsBlockElement = false) {
1139: $strTag = ($this->blnIsBlockElement || $blnForceAsBlockElement) ? 'div' : 'span';
1140: $overrides = ['id'=>$this->strControlId . '_ctl'];
1141: $attributes = $this->GetWrapperAttributes($overrides);
1142:
1143: return QHtml::RenderTag($strTag, $attributes, $strOutput);
1144: }
1145:
1146: /**
1147: * RenderHelper should be called from all "Render" functions FIRST in order to check for and
1148: * perform attribute overrides (if any).
1149: * All render methods should take in an optional first boolean parameter blnDisplayOutput
1150: * (default to true), and then any number of attribute overrides.
1151: * Any "Render" method (e.g. Render, RenderWithName, RenderWithError) should call the
1152: * RenderHelper FIRST in order to:
1153: * <ul>
1154: * <li>Check for and perform attribute overrides</li>
1155: * <li>Check to see if this control is "Visible". If it is Visible=false, then
1156: * the renderhelper will cause the method to immediately return</li>
1157: * </ul>
1158: * Proper usage within the first line of any Render() method is:
1159: * <code>$this->RenderHelper(func_get_args(), __FUNCTION__);</code>
1160: * See {@link QControl::RenderWithName()} as example.
1161: *
1162: * @param mixed $mixParameterArray the parameters given to the render call
1163: * @param string $strRenderMethod the method which has been used to render the
1164: * control. This is important for ajax rerendering
1165: *
1166: * @throws QCallerException
1167: * @throws Exception|QCallerException
1168: * @see QControlBase::RenderOutput()
1169: */
1170: protected function RenderHelper($mixParameterArray, $strRenderMethod) {
1171: // Make sure the form is already "RenderBegun"
1172: if ((!$this->objForm) || ($this->objForm->FormStatus != QForm::FormStatusRenderBegun)) {
1173: if (!$this->objForm)
1174: $objExc = new QCallerException('Control\'s form does not exist. It could be that you are attempting to render after RenderEnd() has been called on the form.');
1175: else if ($this->objForm->FormStatus == QForm::FormStatusRenderEnded)
1176: $objExc = new QCallerException('Control cannot be rendered after RenderEnd() has been called on the form.');
1177: else
1178: $objExc = new QCallerException('Control cannot be rendered until RenderBegin() has been called on the form.');
1179:
1180: // Incremement because we are two-deep below the call stack
1181: // (e.g. the Render function call, and then this RenderHelper call)
1182: $objExc->IncrementOffset();
1183: throw $objExc;
1184: }
1185:
1186: // Make sure this hasn't yet been rendered
1187: if (($this->blnRendered) || ($this->blnRendering)) {
1188: $objExc = new QCallerException('This control has already been rendered: ' . $this->strControlId);
1189:
1190: // Incremement because we are two-deep below the call stack
1191: // (e.g. the Render function call, and then this RenderHelper call)
1192: $objExc->IncrementOffset();
1193: throw $objExc;
1194: }
1195:
1196: // Let's remember *which* render method was used to render this control
1197: $this->strRenderMethod = $strRenderMethod;
1198:
1199: // Remove non-overrides from the parameter array
1200: while (!empty($mixParameterArray) && gettype(reset($mixParameterArray)) != QType::String && gettype(reset($mixParameterArray)) != QType::ArrayType) {
1201: array_shift($mixParameterArray);
1202: }
1203:
1204: // Apply any overrides (if applicable)
1205: if (!empty($mixParameterArray)) {
1206: // Override
1207: try {
1208: $this->OverrideAttributes($mixParameterArray);
1209: } catch (QCallerException $objExc) {
1210: // Incremement Twice because we are two-deep below the call stack
1211: // (e.g. the Render function call, and then this RenderHelper call)
1212: $objExc->IncrementOffset();
1213: $objExc->IncrementOffset();
1214: throw $objExc;
1215: }
1216: }
1217:
1218: // Because we may be re-rendering a parent control, we need to make sure all "children" controls are marked as NOT being on the page.
1219: foreach ($this->GetChildControls() as $objChildControl)
1220: $objChildControl->blnOnPage = false;
1221:
1222: // Finally, let's specify that we have begun rendering this control
1223: $this->blnRendering = true;
1224: }
1225:
1226: /**
1227: * The current use of this function is unknown at the moment.
1228: */
1229: protected function GetNonWrappedHtml() {}
1230:
1231: /**
1232: * Sets focus to this control
1233: * TODO: Turn this into a specific command to avoid the javascript eval that happens on the other end.
1234: */
1235: public function Focus() {
1236: QApplication::ExecuteControlCommand($this->strControlId, 'focus');
1237: }
1238:
1239: /**
1240: * Same as "Focus": Sets focus to this control
1241: */
1242: public function SetFocus() {
1243: $this->Focus();
1244: }
1245:
1246: /**
1247: * Let this control blink
1248: *
1249: * @param string $strFromColor start color
1250: * @param string $strToColor blink color
1251: * TODO: Turn this into a specific command to avoid the javascript eval that happens on the other end.
1252: */
1253: public function Blink($strFromColor = '#ffff66', $strToColor = '#ffffff') {
1254: QApplication::ExecuteJavaScript(sprintf('qc.getW("%s").blink("%s", "%s");', $this->strControlId, $strFromColor, $strToColor));
1255: }
1256:
1257: /**
1258: * Returns and fires the JavaScript that is associated with this control. The html for the control will have already
1259: * been rendered, so refer to the html object with "\$j(#{$this->ControlId})". You should do the following:
1260: * - Return any script that attaches a JavaScript widget to the the html control.
1261: * - Use functions like ExecuteControlCommand to fire commands to execute AFTER all controls have been attached.
1262: *
1263: * @return string
1264: */
1265: public function GetEndScript() {
1266:
1267: $strToReturn = '';
1268:
1269: if ($this->objResizable)
1270: $strToReturn .= $this->objResizable->GetEndScript();
1271:
1272: if ($this->objDraggable)
1273: $strToReturn .= $this->objDraggable->GetEndScript();
1274:
1275: if ($this->objDroppable)
1276: $strToReturn .= $this->objDroppable->GetEndScript();
1277:
1278: $strToReturn .= $this->RenderActionScripts();
1279:
1280: $this->strAttributeScripts = null; // erase the attribute scripts, because the entire control is being drawn, so we don't need them anymore.
1281:
1282: return $strToReturn;
1283: }
1284:
1285: /**
1286: * Return one-time scripts associated with the control. Called by the form during an ajax draw only if the
1287: * entire control was not rendered.
1288: *
1289: * Instead of actually rendering, we add them to the application event queue.
1290: */
1291: public function RenderAttributeScripts()
1292: {
1293: if ($this->strAttributeScripts) {
1294: foreach ($this->strAttributeScripts as $scriptArgs) {
1295: array_unshift($scriptArgs, $this->getJqControlId());
1296: call_user_func_array('QApplication::ExecuteControlCommand', $scriptArgs);
1297: }
1298: }
1299: $this->strAttributeScripts = null;
1300: }
1301:
1302: /**
1303: * Executes a java script associated with the control. These scripts are specifically for the purpose of
1304: * changing some attribute of the control that would also be taken care of during a refresh of the entire
1305: * control. The script will only be executed in ajax if the entire control is not redrawn.
1306: *
1307: * Note that these will execute after most of the other commands execute, so do not count on the order
1308: * in which they will execute relative to other commands.
1309: *
1310: * @param string $strMethod The name of the javascript function to call on this control.
1311: * @param string $args One or more arguments to send to the method that will cause the control to change
1312: */
1313: public function AddAttributeScript ($strMethod, $args /*, ... */)
1314: {
1315: $args = func_get_args();
1316: $this->strAttributeScripts[] = $args;
1317: }
1318:
1319: /**
1320: * For any HTML code that needs to be rendered at the END of the QForm when this control is
1321: * INITIALLY rendered.
1322: *
1323: */
1324: public function GetEndHtml() {}
1325:
1326: /**
1327: * Refreshes the control
1328: *
1329: * If not yet rendered during this ajax event, will set the Modified variable to true. This will
1330: * have the effect of forcing a refresh of this control when it is supposed to be rendered.
1331: * Otherwise, this will do nothing
1332: */
1333: public function Refresh() {
1334: if ((!$this->blnRendered) &&
1335: (!$this->blnRendering))
1336: $this->MarkAsModified();
1337: }
1338:
1339: /**
1340: * RenderOutput should be the last call in your custom RenderMethod. It is responsible for the following:
1341: * - Creating the wrapper if you are using a wrapper, or
1342: * - Possibly creating a dummy control if not using a wrapper and the control is hidden.
1343: * - Generating the control's output in one of 3 ways:
1344: * - Generate straight html if drawing the control as part of a complete page refresh
1345: * - Generate straight html if in an ajax call, but a parent is getting redrawn, which requires this
1346: * whole control to get drawn
1347: * - If in an ajax call and we are the top level control getting drawn, then generate special code that
1348: * out javascript will read and put into the control's spot on the page. Requires coordination with
1349: * the code in qcubed.js.
1350: *
1351: * @param string $strOutput
1352: * Your html-code which should be printed out
1353: * @param boolean $blnDisplayOutput
1354: * should it be printed, or just be returned?
1355: *
1356: * @return string
1357: */
1358: protected function RenderOutput($strOutput, $blnDisplayOutput, $blnForceAsBlockElement = false) {
1359: if ($blnForceAsBlockElement) {
1360: $this->blnIsBlockElement = true; // must be remembered for ajax drawing
1361: }
1362:
1363: if ($this->blnUseWrapper) {
1364: if (!$this->blnVisible) $strOutput = '';
1365: } else if (!$this->blnVisible) {
1366: /* No wrapper is used and the control is not visible. We must enter a span with the control id and
1367: * display:none in order to be able change blnVisible to true in an Ajax call later and redraw the control.
1368: */
1369: $strOutput = sprintf('<span id="%s" style="display:none;"></span>', $this->strControlId);
1370: }
1371:
1372: switch ($this->objForm->CallType) {
1373: case QCallType::Ajax:
1374: if ($this->objParentControl) {
1375: if ($this->objParentControl->Rendered || $this->objParentControl->Rendering) {
1376: // If we have a ParentControl and the ParentControl has NOT been rendered, then output
1377: // as standard HTML
1378: if ($this->blnUseWrapper) {
1379: $strOutput = $this->RenderWrappedOutput($strOutput, $blnForceAsBlockElement) . $this->GetNonWrappedHtml();
1380: } else {
1381: $strOutput = $strOutput . $this->GetNonWrappedHtml();
1382: }
1383: } else {
1384: // Do nothing. RenderAjax will handle it.
1385: }
1386: } else {
1387: // if this is an injected top-level control, then we need to render the whole thing
1388: if (!$this->blnOnPage) {
1389: if ($this->blnUseWrapper) {
1390: $strOutput = $this->RenderWrappedOutput($strOutput, $blnForceAsBlockElement) . $this->GetNonWrappedHtml();
1391: } else {
1392: $strOutput = $strOutput . $this->GetNonWrappedHtml();
1393: }
1394: }
1395: }
1396: break;
1397:
1398: default:
1399: if ($this->blnUseWrapper) {
1400: $strOutput = $this->RenderWrappedOutput($strOutput) . $this->GetNonWrappedHtml();
1401: } else {
1402: $strOutput = $strOutput . $this->GetNonWrappedHtml();
1403: }
1404:
1405: $strOutput = $this->RenderComment(self::CommentStart) . _indent($strOutput) . $this->RenderComment(self::CommentEnd);
1406: break;
1407: }
1408:
1409: // Update watcher
1410: if ($this->objWatcher) {
1411: $this->objWatcher->MakeCurrent();
1412: }
1413:
1414: $this->blnRendering = false;
1415: $this->blnRendered = true;
1416: $this->blnOnPage = true;
1417:
1418: // Output or Return
1419: if ($blnDisplayOutput) {
1420: print($strOutput);
1421: } else {
1422: return $strOutput;
1423: }
1424: }
1425:
1426: /**
1427: * This method will render the control, itself, and will return the rendered HTML as a string
1428: *
1429: * As an abstract method, any class extending QControlBase must implement it. This ensures that
1430: * each control has its own specific html.
1431: *
1432: * When outputting html, you should call GetHtmlAttributes to get the attributes for the main control.
1433: *
1434: * If you are outputting a complex control, and need to include ids in subcontrols, your ids should be of the form:
1435: * $parentControl->ControlId . '_' . $strSubcontrolId.
1436: * The underscore indicates that actions and post data should be routed first to the parent control, and the parent
1437: * control will handle the rest.
1438: *
1439: * @return string
1440: */
1441: abstract protected function GetControlHtml();
1442:
1443: /**
1444: * This render method is the most basic render-method available.
1445: * It will perform attribute overiding (if any) and will either display the rendered
1446: * HTML (if blnDisplayOutput is true, which it is by default), or it will return the
1447: * rendered HTML as a string.
1448: *
1449: * @param boolean $blnDisplayOutput render the control or return as string
1450: *
1451: * @throws Exception|QCallerException
1452: * @return string
1453: */
1454: public function Render($blnDisplayOutput = true /* ... */) {
1455: $blnMinimized = QApplication::$Minimize;
1456: if ($this->blnMinimize) {
1457: QApplication::$Minimize = true;
1458: }
1459: $this->RenderHelper(func_get_args(), __FUNCTION__);
1460:
1461: try {
1462: if ($this->blnVisible) {
1463: $strOutput = sprintf('%s%s%s',
1464: $this->strHtmlBefore,
1465: $this->GetControlHtml(),
1466: $this->strHtmlAfter
1467: );
1468: } else {
1469: // Avoid going through the time to render the control if we are not going to display it.
1470: $strOutput = "";
1471: }
1472: } catch (QCallerException $objExc) {
1473: $objExc->IncrementOffset();
1474: throw $objExc;
1475: }
1476:
1477: // Call RenderOutput, returning its contents
1478: $strOut = $this->RenderOutput($strOutput, $blnDisplayOutput);
1479:
1480: QApplication::$Minimize = $blnMinimized;
1481:
1482: return $strOut;
1483: }
1484:
1485: /**
1486: * RenderAjax will be called during an Ajax rendering of the controls. Every control gets called. Each control
1487: * is responsible for rendering itself. Some objects automatically render their child objects, and some don't,
1488: * so we detect whether the parent is being rendered, and assume the parent is taking care of rendering for
1489: * us if so.
1490: *
1491: * Override if you want more control over ajax drawing, like it you detect parts of your control that have changed
1492: * and then want to draw only those parts. This will get called on every control on every ajax draw request.
1493: * It is up to you to test the blnRendered flag of the control to know whether the control was already rendered
1494: * by a parent control before drawing here.
1495: *
1496: * @return array[] array of control arrays to be interpreted by the response function in qcubed.js
1497: */
1498: public function RenderAjax() {
1499: // Only render if this control has been modified at all
1500: $controls = [];
1501: if ($this->IsModified()) {
1502: // Render if (1) object has no parent or (2) parent was not rendered nor currently being rendered
1503: if ((!$this->objParentControl) || ((!$this->objParentControl->Rendered) && (!$this->objParentControl->Rendering))) {
1504: $strRenderMethod = $this->strRenderMethod;
1505: if (!$strRenderMethod && $this->AutoRender) {
1506: // This is an auto-injected control (a dialog for instance) that is not on the page, so go ahead and render it
1507: $strRenderMethod = $this->strPreferredRenderMethod;
1508: }
1509: if ($strRenderMethod) {
1510: $strOutput = $this->$strRenderMethod(false);
1511: $controls[] = [QAjaxResponse::Id=>$this->strControlId, QAjaxResponse::Html=>$strOutput];
1512: }
1513: }
1514: }
1515:
1516: if ($this->blnWrapperModified && ($this->blnVisible) && ($this->blnUseWrapper)) {
1517: // Top level ajax response will usually just draw the innerText of the wrapper
1518: // If something changed in the wrapper attributes, we need to tell the jQuery response to handle that too.
1519: // In particular, if the wrapper was hidden, and is now displayed, we need to make sure that the control
1520: // becomes visible before other scripts execute, or those other scripts will not see the control.
1521: $wrapperAttributes = $this->GetWrapperAttributes();
1522: if (!isset($wrapperAttributes['style'])) {
1523: $wrapperAttributes['style'] = ''; // must specifically turn off styles if none were drawn, in case the previous state had a style and it had changed
1524: }
1525: $controls[] = [QAjaxResponse::Id=>$this->strControlId . '_ctl', QAjaxResponse::Attributes=>$wrapperAttributes];
1526: }
1527: return $controls;
1528: }
1529:
1530: /**
1531: * Returns true if the control should be redrawn.
1532: * @return boolean
1533: */
1534: public function IsModified() {
1535: return ($this->blnModified ||
1536: ($this->objWatcher && !$this->objWatcher->IsCurrent()));
1537: }
1538:
1539: /**
1540: * Renders all Children
1541: * @param boolean $blnDisplayOutput display output (echo out) or just return as string
1542: * @return string
1543: */
1544: protected function RenderChildren($blnDisplayOutput = true) {
1545: $strToReturn = "";
1546:
1547: foreach ($this->GetChildControls() as $objControl) {
1548: if (!$objControl->Rendered) {
1549: $renderMethod = $objControl->strPreferredRenderMethod;
1550: if ($renderMethod) {
1551: $strToReturn .= $objControl->$renderMethod($blnDisplayOutput);
1552: }
1553: }
1554: }
1555:
1556: if ($blnDisplayOutput) {
1557: print($strToReturn);
1558: return null;
1559: } else
1560: return $strToReturn;
1561: }
1562:
1563: /**
1564: * This render method will render the control with additional output of
1565: * any validation errors, that might occur
1566: *
1567: * @param boolean $blnDisplayOutput display output (echo out) or just return as string
1568: *
1569: * @throws Exception|QCallerException
1570: * @return string
1571: */
1572: public function RenderWithError($blnDisplayOutput = true) {
1573: // Call RenderHelper
1574: $this->RenderHelper(func_get_args(), __FUNCTION__);
1575:
1576: /**
1577: * If we are not using a wrapper, then we are going to tag related elements so that qcubed.js
1578: * can remove them when we redraw. Otherwise, they will be repeatedly added instead of replaced.
1579: */
1580: $strDataRel = '';
1581: if (!$this->blnUseWrapper) {
1582: $strDataRel = sprintf('data-qrel="#%s" ', $this->strControlId);
1583: }
1584:
1585: try {
1586: $strOutput = $this->GetControlHtml();
1587:
1588: if ($this->strValidationError)
1589: $strOutput .= sprintf('<br %s/><span %sclass="error">%s</span>', $strDataRel, $strDataRel, QHtml::RenderString($this->strValidationError));
1590: else if ($this->strWarning)
1591: $strOutput .= sprintf('<br %s/><span %sclass="warning">%s</span>', $strDataRel, $strDataRel, QHtml::RenderString($this->strWarning));
1592: } catch (QCallerException $objExc) {
1593: $objExc->IncrementOffset();
1594: throw $objExc;
1595: }
1596:
1597: // Call RenderOutput, Returning its Contents
1598: return $this->RenderOutput($strOutput, $blnDisplayOutput, false);
1599: }
1600:
1601:
1602: /**
1603: * Renders the control with an attached name
1604: *
1605: * This will call {@link QControlBase::GetControlHtml()} for the bulk of the work, but will add layout html as well. It will include
1606: * the rendering of the Controls' name label, any errors or warnings, instructions, and html before/after (if specified).
1607: * As this is the parent class of all controls, this method defines how ALL controls will render when rendered with a name.
1608: * If you need certain controls to display differently, override this function in that control's class.
1609: *
1610: * @param boolean $blnDisplayOutput true to send to display buffer, false to just return then html
1611: * @throws QCallerException
1612: * @return string HTML of rendered Control
1613: */
1614: public function RenderWithName($blnDisplayOutput = true) {
1615: ////////////////////
1616: // Call RenderHelper
1617: $this->RenderHelper(func_get_args(), __FUNCTION__);
1618: ////////////////////
1619:
1620: $aWrapperAttributes = array();
1621: if (!$this->blnUseWrapper) {
1622: //there is no wrapper --> add the special attribute data-qrel to the name control
1623: $aWrapperAttributes['data-qrel'] = $this->strControlId;
1624: if (!$this->blnDisplay) {
1625: $aWrapperAttributes['style'] = 'display: none';
1626: }
1627: }
1628:
1629: // Custom Render Functionality Here
1630:
1631: // Because this example RenderWithName will render a block-based element (e.g. a DIV), let's ensure
1632: // that IsBlockElement is set to true
1633: $this->blnIsBlockElement = true;
1634:
1635: // Render the Left side
1636: $strLabelClass = "form-name";
1637: if ($this->blnRequired){
1638: $strLabelClass .= ' required';
1639: }
1640: if (!$this->Enabled){
1641: $strLabelClass .= ' disabled';
1642: }
1643:
1644: if ($this->strInstructions){
1645: $strInstructions = '<br/>' .
1646: QHtml::RenderTag('span', ['class'=>"instructions"], QHtml::RenderString($this->strInstructions));
1647: }else{
1648: $strInstructions = '';
1649: }
1650: $strLabel = QHtml::RenderTag('label', null, QHtml::RenderString($this->strName));
1651: $strToReturn = QHtml::RenderTag('div', ['class'=>$strLabelClass], $strLabel . $strInstructions);
1652:
1653: // Render the Right side
1654: $strMessage = '';
1655: if ($this->strValidationError){
1656: $strMessage = sprintf('<span class="error">%s</span>', QHtml::RenderString($this->strValidationError));
1657: }else if ($this->strWarning){
1658: $strMessage = sprintf('<span class="warning">%s</span>', QHtml::RenderString($this->strWarning));
1659: }
1660:
1661: try {
1662: $strToReturn .= sprintf('<div class="form-field">%s%s%s%s</div>',
1663: $this->strHtmlBefore,
1664: $this->GetControlHtml(),
1665: $this->strHtmlAfter,
1666: $strMessage);
1667: } catch (QCallerException $objExc) {
1668: $objExc->IncrementOffset();
1669: throw $objExc;
1670: }
1671:
1672: // render control dressing, which is essentially a wrapper. Not sure why we are not just rendering a wrapper here!
1673: $strToReturn = QHtml::RenderTag('div', $aWrapperAttributes, $strToReturn);
1674:
1675: ////////////////////////////////////////////
1676: // Call RenderOutput, Returning its Contents
1677: return $this->RenderOutput($strToReturn, $blnDisplayOutput, false);
1678: ////////////////////////////////////////////
1679: }
1680:
1681: /**
1682: * Format a comment block if we are not in MINIMIZE mode.
1683: *
1684: * @param string $strType Either QControl::CommentStart or QControl::CommentEnd
1685: * @return string
1686: */
1687: public function RenderComment($strType) {
1688: return QHtml::Comment( $strType . ' ' . get_class($this) . ' ' . $this->strName . ' id:' . $this->strControlId);
1689: }
1690:
1691:
1692: /**
1693: * Helper method to render the control using some other class/method.
1694: *
1695: * Useful for plugins that want to override the render behavior for the controls
1696: * without modifying the control code.
1697: * @param $classname
1698: * @param $methodname
1699: * @param array $args
1700: * @return mixed
1701: */
1702: public function RenderExtensionRenderer($classname, $methodname, $args=array()){
1703: $RenderExtensionInstance = new $classname;
1704: return $RenderExtensionInstance->{$methodname}($args);
1705: }
1706:
1707: /**
1708: * Validate self + child controls. Controls must mark themselves modified, or somehow redraw themselves
1709: * if by failing the validation, they change their visual look in some way (like by adding warning text, turning
1710: * red, etc.)
1711: *
1712: * @return bool
1713: */
1714: public function ValidateControlAndChildren() {
1715: // Initially Assume Validation is True
1716: $blnToReturn = true;
1717:
1718: // Check the Control Itself
1719: if (!$this->Validate()) {
1720: $blnToReturn = false;
1721: }
1722:
1723: // Recursive call on Child Controls
1724: foreach ($this->GetChildControls() as $objChildControl) {
1725: // Only Enabled and Visible and Rendered controls should be validated
1726: if (($objChildControl->Visible) && ($objChildControl->Enabled) && ($objChildControl->RenderMethod) && ($objChildControl->OnPage)) {
1727: if (!$objChildControl->ValidateControlAndChildren()) {
1728: $blnToReturn = false;
1729: }
1730: }
1731: }
1732:
1733: return $blnToReturn;
1734: }
1735:
1736:
1737:
1738: // The following three methods are only intended to be called by code within the Form class.
1739: // It must be declared as public so that a form object can have access to them, but it really should never be
1740: // called by user code.
1741: /**
1742: * Reset the control flags to default
1743: */
1744: public function ResetFlags() {
1745: $this->blnRendered = false;
1746: $this->blnModified = false;
1747: $this->blnWrapperModified = false;
1748: }
1749:
1750: /**
1751: * Reset the On-Page status to default (false)
1752: */
1753: public function ResetOnPageStatus() {
1754: $this->blnOnPage = false;
1755: }
1756:
1757: /**
1758: * Marks this control as modified
1759: */
1760: public function MarkAsModified() {
1761: $this->blnModified = true;
1762: /*
1763: TODO: Implement and test the code below to reduce the amount of redrawing. In particular, the current
1764: implementation will cause invisible and display:none controls to be redrawn whenever something changes,
1765: even though its not needed.
1766:
1767: if ($this->blnVisible &&
1768: $this->blnDisplay) {
1769: $this->blnModified = true;
1770: } */
1771: }
1772:
1773: /**
1774: * Marks the wrapper of this control as modified
1775: */
1776: public function MarkAsWrapperModified() {
1777: $this->blnWrapperModified = true;
1778: $this->blnModified = true;
1779: }
1780:
1781: /**
1782: * Marks this control as Rendered
1783: */
1784: public function MarkAsRendered() {
1785: $this->blnRendered = true;
1786: }
1787:
1788: /**
1789: * Sets the Form of this QControl
1790: * @param QForm|QFormBase $objForm
1791: */
1792: public function SetForm($objForm) {
1793: $this->objForm = $objForm;
1794: }
1795:
1796: /**
1797: * Sets the parent control for this control
1798: * @param QControl|QControlBase $objControl The control which has to be set as this control's parent
1799: */
1800: public function SetParentControl($objControl) {
1801: // Mark this object as modified
1802: $this->MarkAsModified();
1803:
1804: // Mark the old parent (if applicable) as modified
1805: if ($this->objParentControl) {
1806: $this->objParentControl->RemoveChildControl($this->ControlId, false);
1807: }
1808:
1809: // Mark the new parent (if applicable) as modified
1810: if ($objControl) {
1811: $objControl->AddChildControl($this);
1812: }
1813: }
1814:
1815: /**
1816: * Resets the validation state to default
1817: */
1818: public function ValidationReset() {
1819: if (($this->strValidationError) || ($this->strWarning)) {
1820: $this->blnModified = true;
1821: }
1822: $this->strValidationError = null;
1823: $this->strWarning = null;
1824: }
1825:
1826: /**
1827: * Runs var_export on this QControl
1828: * @param bool $blnReturn Does the result of var_export have to be returned?
1829: *
1830: * @return mixed
1831: */
1832: public function VarExport($blnReturn = true) {
1833: if ($this->objForm)
1834: $this->objForm = $this->objForm->FormId;
1835: if ($this->objParentControl)
1836: $this->objParentControl = $this->objParentControl->ControlId;
1837:
1838: // In order to make the control exportable, we can't have circular references or things that are not exportable.
1839: // We use the sleep helper as an aid to deep exporting the object.
1840:
1841: $vars = get_object_vars($this);
1842: foreach ($vars as $key=>$val) {
1843: $this->$key = self::SleepHelper($val);
1844: }
1845:
1846: if ($blnReturn) {
1847: return var_export($this, true);
1848: }
1849: }
1850:
1851: /**
1852: * Used by jQuery UI wrapper controls to find the element on which to apply the jQuery function
1853: *
1854: * NOTE: Some controls that use jQuery will get wrapped with extra divs by the jQuery library.
1855: * If such a control then gets replaced by Ajax, the jQuery effects will be deleted. To solve this,
1856: * the corresponding QCubed control should set UseWrapper to true, attach the jQuery effect to
1857: * the wrapper, and override this function to return the id of the wrapper. See QDialogBase.class.php for
1858: * an example.
1859: *
1860: * @return string the DOM element id on which to apply the jQuery UI function
1861: */
1862: public function GetJqControlId() {
1863: return $this->ControlId;
1864: }
1865:
1866: /**
1867: * Watch a particular node in the database. Call this to trigger a redraw of the control
1868: * whenever the database table that this node points to is changed.
1869: *
1870: * @param QQNode $objNode
1871: */
1872: public function Watch (QQNode $objNode)
1873: {
1874: if (!$this->objWatcher) {
1875: if (defined('WATCHER_CLASS')) {
1876: $class = WATCHER_CLASS;
1877: $this->objWatcher = new $class(); // only create a watcher object when needed, since it is stored in the form state
1878: } else {
1879: $this->objWatcher = new QWatcher(); // only create a watcher object when needed, since it is stored in the form state
1880: }
1881: }
1882: $this->objWatcher->Watch ($objNode);
1883: }
1884:
1885: /**
1886: * Make this control current as of the latest changes so that it will not refresh on the next draw.
1887: */
1888: public function MakeCurrent() {
1889: if ($this->objWatcher) {
1890: $this->objWatcher->MakeCurrent();
1891: }
1892: }
1893:
1894: /**
1895: * Returns true if the given control is anywhere in the parent hierarchy of this control.
1896: *
1897: * @param $objControl
1898: * @return bool
1899: */
1900: public function IsDescendantOf ($objControl) {
1901: $objParent = $this->objParentControl;
1902: while ($objParent) {
1903: if ($objParent == $objControl) {
1904: return true;
1905: }
1906: $objParent = $objParent->objParentControl;
1907: }
1908: return false;
1909: }
1910:
1911: /**
1912: * Searches the control and it's hierarchy to see if a method by given name exists.
1913: * This method searches only in the current control and its parents and so on.
1914: * It will not search for the method in any siblings at any stage in the process.
1915: *
1916: * @param string $strMethodName Name of the method
1917: * @param bool $blnIncludeCurrentControl Include this control as well?
1918: *
1919: * @return null|QControl The control found in the hierarchy to have the method
1920: * Or null if no control was found in the hierarchy with the given name
1921: */
1922: public function GetControlFromHierarchyByMethodName($strMethodName, $blnIncludeCurrentControl = true) {
1923: if ($blnIncludeCurrentControl == true) {
1924: $ctlDelegatorControl = $this;
1925: } else {
1926: if (!$this->ParentControl) {
1927: // ParentControl is null. This means the parent is a QForm.
1928: $ctlDelegatorControl = $this->Form;
1929: } else {
1930: $ctlDelegatorControl = $this->ParentControl;
1931: }
1932: }
1933:
1934: do {
1935: if (method_exists($ctlDelegatorControl, $strMethodName)) {
1936: return $ctlDelegatorControl;
1937: } else {
1938: if (!$ctlDelegatorControl->ParentControl) {
1939: // ParentControl is null. This means the parent is a QForm.
1940: $ctlDelegatorControl = $ctlDelegatorControl->Form;
1941: } else {
1942: $ctlDelegatorControl = $ctlDelegatorControl->ParentControl;
1943: }
1944: }
1945: } while (!($ctlDelegatorControl instanceof QForm));
1946:
1947: // If we are here, we could not find the method in the hierarchy/lineage of this control.
1948: return null;
1949: }
1950:
1951: /**
1952: * Returns the form associated with the control. Used by the QDataBinder trait.
1953: * @return QForm
1954: */
1955: public function GetForm() {
1956: return $this->objForm;
1957: }
1958:
1959: /////////////////////////
1960: // Public Properties: GET
1961: /////////////////////////
1962: /**
1963: * PHP __get magic method implementation
1964: * @param string $strName Property Name
1965: *
1966: * @return mixed
1967: * @throws Exception|QCallerException
1968: */
1969: public function __get($strName) {
1970: switch ($strName) {
1971: case "Display": return $this->blnDisplay;
1972: case "CausesValidation": return $this->mixCausesValidation;
1973: case "Required": return $this->blnRequired;
1974: case "ValidationError": return $this->strValidationError;
1975: case "Visible": return $this->blnVisible;
1976: case "PreferredRenderMethod": return $this->strPreferredRenderMethod;
1977:
1978: // LAYOUT
1979: case "HtmlBefore": return $this->strHtmlBefore;
1980: case "HtmlAfter": return $this->strHtmlAfter;
1981: case "Instructions": return $this->strInstructions;
1982: case "Warning": return $this->strWarning;
1983: case "Minimize": return $this->blnMinimize;
1984:
1985: case "Moveable": return $this->objDraggable && !$this->objDraggable->Disabled;
1986: case "Resizable": return $this->objResizable && !$this->objResizable->Disabled;
1987: case "Droppable": return $this->objDroppable && !$this->objDroppable->Disabled;
1988: case "DragObj": return $this->objDraggable;
1989: case "ResizeObj": return $this->objResizable;
1990: case "DropObj": return $this->objDroppable;
1991:
1992: // MISC
1993: case "ControlId": return $this->strControlId;
1994: case "WrapperId": return $this->strControlId . '_ctl';
1995: case "Form": return $this->objForm;
1996: case "ParentControl": return $this->objParentControl;
1997:
1998: case "Name": return $this->strName;
1999: case "Rendered": return $this->blnRendered;
2000: case "Rendering": return $this->blnRendering;
2001: case "OnPage": return $this->blnOnPage;
2002: case "RenderMethod": return $this->strRenderMethod;
2003: case "WrapperModified": return $this->blnWrapperModified;
2004: case "ActionParameter": return $this->mixActionParameter;
2005: case "ActionsMustTerminate": return $this->blnActionsMustTerminate;
2006: case "ScriptsOnly": return $this->blnScriptsOnly;
2007: case "WrapperCssClass": return $this->GetWrapperStyler()->CssClass;
2008: case "UseWrapper": return $this->blnUseWrapper;
2009:
2010: // SETTINGS
2011: case "JavaScripts": return $this->strJavaScripts;
2012: case "StyleSheets": return $this->strStyleSheets;
2013: case "FormAttributes": return (array) $this->strFormAttributes;
2014:
2015: case "Modified": return $this->IsModified();
2016: case "LinkedNode": return $this->objLinkedNode;
2017: case "WrapperStyles": return $this->getWrapperStyler();
2018: case "WrapLabel": return $this->blnWrapLabel;
2019: case "AutoRender": return $this->blnAutoRender;
2020:
2021: default:
2022: try {
2023: return parent::__get($strName);
2024: } catch (QCallerException $objExc) {
2025: $objExc->IncrementOffset();
2026: throw $objExc;
2027: }
2028: }
2029: }
2030:
2031: /////////////////////////
2032: // Public Properties: SET
2033: /////////////////////////
2034: /**
2035: * PHP __set magic method implementation
2036: * @param string $strName Property Name
2037: * @param string $mixValue Property Value
2038: *
2039: * @return mixed|void
2040: * @throws QCallerException
2041: * @throws Exception|QCallerException
2042: * @throws Exception|QInvalidCastException
2043: */
2044: public function __set($strName, $mixValue) {
2045: switch ($strName) {
2046: // Shunt position settings to the wrapper. Actual drawing will get resolved at draw time.
2047: case "Position":
2048: case "Top":
2049: case "Left":
2050: try {
2051: $this->getWrapperStyler()->__set($strName, $mixValue);
2052: $this->markAsWrapperModified();
2053: break;
2054: } catch (QInvalidCastException $objExc) {
2055: $objExc->IncrementOffset();
2056: throw $objExc;
2057: }
2058: case "Display": // boolean to determine whether to display or not
2059: try {
2060: $mixValue = QType::Cast($mixValue, QType::Boolean);
2061: $this->markAsWrapperModified();
2062: $this->blnDisplay = $mixValue;
2063: break;
2064: } catch (QInvalidCastException $objExc) {
2065: $objExc->IncrementOffset();
2066: throw $objExc;
2067: }
2068:
2069: case "CausesValidation":
2070: try {
2071: $this->mixCausesValidation = $mixValue;
2072: // This would not need to cause a redraw
2073: break;
2074: } catch (QInvalidCastException $objExc) {
2075: $objExc->IncrementOffset();
2076: throw $objExc;
2077: }
2078: case "Required":
2079: try {
2080: $this->blnRequired = QType::Cast($mixValue, QType::Boolean);
2081: break;
2082: } catch (QInvalidCastException $objExc) {
2083: $objExc->IncrementOffset();
2084: throw $objExc;
2085: }
2086: case "Visible":
2087: try {
2088: if ($this->blnVisible !== ($mixValue = QType::Cast($mixValue, QType::Boolean))) {
2089: $this->MarkAsModified();
2090: $this->blnVisible = $mixValue;
2091: }
2092: break;
2093: } catch (QInvalidCastException $objExc) {
2094: $objExc->IncrementOffset();
2095: throw $objExc;
2096: }
2097: case "PreferredRenderMethod":
2098: try {
2099: if ($this->strPreferredRenderMethod !== ($mixValue = QType::Cast($mixValue, QType::String))) {
2100: $this->MarkAsModified();
2101: $this->strPreferredRenderMethod = $mixValue;
2102: }
2103: break;
2104: } catch (QInvalidCastException $objExc) {
2105: $objExc->IncrementOffset();
2106: throw $objExc;
2107: }
2108:
2109: case "HtmlBefore":
2110: try {
2111: if ($this->strHtmlBefore !== ($mixValue = QType::Cast($mixValue, QType::String))) {
2112: $this->MarkAsModified();
2113: $this->strHtmlBefore = $mixValue;
2114: }
2115: break;
2116: } catch (QInvalidCastException $objExc) {
2117: $objExc->IncrementOffset();
2118: throw $objExc;
2119: }
2120: case "HtmlAfter":
2121: try {
2122: if ($this->strHtmlAfter !== ($mixValue = QType::Cast($mixValue, QType::String))) {
2123: $this->MarkAsModified();
2124: $this->strHtmlAfter = $mixValue;
2125: }
2126: break;
2127: } catch (QInvalidCastException $objExc) {
2128: $objExc->IncrementOffset();
2129: throw $objExc;
2130: }
2131: case "Instructions":
2132: try {
2133: if ($this->strInstructions !== ($mixValue = QType::Cast($mixValue, QType::String))) {
2134: $this->MarkAsModified();
2135: $this->strInstructions = $mixValue;
2136: }
2137: break;
2138: } catch (QInvalidCastException $objExc) {
2139: $objExc->IncrementOffset();
2140: throw $objExc;
2141: }
2142: case "Warning":
2143: try {
2144: $this->strWarning = QType::Cast($mixValue, QType::String);
2145: $this->MarkAsModified(); // always modify, since it will get reset on subsequent drawing
2146: break;
2147: } catch (QInvalidCastException $objExc) {
2148: $objExc->IncrementOffset();
2149: throw $objExc;
2150: }
2151:
2152: case "ValidationError":
2153: try {
2154: $this->strValidationError = QType::Cast($mixValue, QType::String);
2155: $this->MarkAsModified(); // always modify, since it will get reset on subsequent drawing
2156: break;
2157: } catch (QInvalidCastException $objExc) {
2158: $objExc->IncrementOffset();
2159: throw $objExc;
2160: }
2161:
2162: case "Minimize":
2163: try {
2164: $this->blnMinimize = QType::Cast($mixValue, QType::Boolean);
2165: $this->MarkAsModified();
2166: break;
2167: } catch (QInvalidCastException $objExc) {
2168: $objExc->IncrementOffset();
2169: throw $objExc;
2170: }
2171:
2172: case "Moveable":
2173: try {
2174: $this->MarkAsWrapperModified();
2175: if (QType::Cast($mixValue, QType::Boolean)) {
2176: if (!$this->objDraggable) {
2177: $this->objDraggable = new QDraggable($this, $this->ControlId . 'draggable');
2178: } else {
2179: $this->objDraggable->Disabled = false;
2180: }
2181: }
2182: else {
2183: if ($this->objDraggable) {
2184: $this->objDraggable->Disabled = true;
2185: }
2186: }
2187: break;
2188: } catch (QInvalidCastException $objExc) {
2189: $objExc->IncrementOffset();
2190: throw $objExc;
2191: }
2192:
2193: case "Resizable":
2194: try {
2195: $this->MarkAsWrapperModified();
2196: if (QType::Cast($mixValue, QType::Boolean)) {
2197: if (!$this->objResizable) {
2198: $this->objResizable = new QResizable($this);
2199: } else {
2200: $this->objResizable->Disabled = false;
2201: }
2202: }
2203: else {
2204: if ($this->objResizable) {
2205: $this->objResizable->Disabled = true;
2206: }
2207: }
2208: break;
2209: } catch (QInvalidCastException $objExc) {
2210: $objExc->IncrementOffset();
2211: throw $objExc;
2212: }
2213:
2214: case "Droppable":
2215: try {
2216: $this->MarkAsWrapperModified();
2217: if (QType::Cast($mixValue, QType::Boolean)) {
2218: if (!$this->objDroppable) {
2219: $this->objDroppable = new QDroppable($this);
2220: } else {
2221: $this->objDroppable->Disabled = false;
2222: }
2223: }
2224: else {
2225: if ($this->objDroppable) {
2226: $this->objDroppable->Disabled = true;
2227: }
2228: }
2229: break;
2230: } catch (QInvalidCastException $objExc) {
2231: $objExc->IncrementOffset();
2232: throw $objExc;
2233: }
2234:
2235: // MISC
2236: case "Name":
2237: try {
2238: if ($this->strName !== ($mixValue = QType::Cast($mixValue, QType::String))) {
2239: $this->MarkAsModified();
2240: $this->strName = $mixValue;
2241: }
2242: break;
2243: } catch (QInvalidCastException $objExc) {
2244: $objExc->IncrementOffset();
2245: throw $objExc;
2246: }
2247: case "ActionParameter":
2248: try {
2249: $this->mixActionParameter = ($mixValue instanceof QJsClosure) ? $mixValue : QType::Cast($mixValue, QType::String);
2250: $this->MarkAsModified();
2251: break;
2252: } catch (QInvalidCastException $objExc) {
2253: $objExc->IncrementOffset();
2254: throw $objExc;
2255: }
2256:
2257: case "WrapperCssClass":
2258: try {
2259: $strWrapperCssClass = QType::Cast($mixValue, QType::String);
2260: if ($this->GetWrapperStyler()->SetCssClass($strWrapperCssClass)) {
2261: $this->MarkAsWrapperModified();
2262: }
2263: break;
2264: } catch (QInvalidCastException $objExc) {
2265: $objExc->IncrementOffset();
2266: throw $objExc;
2267: }
2268: case "UseWrapper":
2269: try {
2270: if($this->blnUseWrapper != QType::Cast($mixValue, QType::Boolean)) {
2271: $this->blnUseWrapper = !$this->blnUseWrapper;
2272: //need to render the parent again (including its children)
2273: if ($this->ParentControl) {
2274: $this->ParentControl->MarkAsModified();
2275: }
2276: }
2277: break;
2278: } catch (QInvalidCastException $objExc) {
2279: $objExc->IncrementOffset();
2280: throw $objExc;
2281: }
2282: case "WrapLabel":
2283: try {
2284: if($this->blnWrapLabel != QType::Cast($mixValue, QType::Boolean)) {
2285: $this->blnWrapLabel = !$this->blnWrapLabel;
2286: //need to render the parent again (including its children)
2287: $this->MarkAsModified();
2288: }
2289: break;
2290: } catch (QInvalidCastException $objExc) {
2291: $objExc->IncrementOffset();
2292: throw $objExc;
2293: }
2294:
2295: case "SaveState":
2296: try {
2297: $this->blnSaveState = QType::Cast($mixValue, QType::Boolean);
2298: $this->_ReadState(); // during form creation, if we are setting this value, it means we want the state restored at form creation too, so handle both here.
2299: } catch (QInvalidCastException $objExc) {
2300: $objExc->IncrementOffset();
2301: throw $objExc;
2302: }
2303: break;
2304:
2305: case "AutoRender":
2306: try {
2307: $this->blnAutoRender = QType::Cast($mixValue, QType::Boolean);
2308: } catch (QInvalidCastException $objExc) {
2309: $objExc->IncrementOffset();
2310: throw $objExc;
2311: }
2312: break;
2313:
2314:
2315: // CODEGEN
2316: case "LinkedNode":
2317: try {
2318: $this->objLinkedNode = QType::Cast($mixValue, 'QQNode');
2319: break;
2320: } catch (QInvalidCastException $objExc) {
2321: $objExc->IncrementOffset();
2322: throw $objExc;
2323: }
2324:
2325: default:
2326: try {
2327: parent::__set($strName, $mixValue);
2328: break;
2329: } catch (QCallerException $objExc) {
2330: $objExc->IncrementOffset();
2331: throw $objExc;
2332: }
2333: }
2334: }
2335:
2336:
2337: /**
2338: * Returns a description of the options available to modify by the designer for the code generator.
2339: *
2340: * @return QModelConnectorParam[]
2341: */
2342: public static function GetModelConnectorParams() {
2343: return array(
2344: new QModelConnectorParam ('QControl', 'CssClass', 'Css Class assigned to the control', QType::String),
2345: new QModelConnectorParam ('QControl', 'AccessKey', 'Access Key to focus control', QType::String),
2346: new QModelConnectorParam ('QControl', 'CausesValidation', 'How and what to validate. Can also be set to a control.', QModelConnectorParam::SelectionList,
2347: array(
2348: null=>'None',
2349: 'QCausesValidation::AllControls'=>'All Controls',
2350: 'QCausesValidation::SiblingsAndChildren'=>'Siblings And Children',
2351: 'QCausesValidation::SiblingsOnly'=>'Siblings Only'
2352: )
2353: ),
2354: new QModelConnectorParam ('QControl', 'Enabled', 'Will it start as enabled (default true)?', QType::Boolean),
2355: new QModelConnectorParam ('QControl', 'Required', 'Will it fail validation if nothing is entered (default depends on data definition, if NULL is allowed.)?', QType::Boolean),
2356: new QModelConnectorParam ('QControl', 'TabIndex', '', QType::Integer),
2357: new QModelConnectorParam ('QControl', 'ToolTip', '', QType::String),
2358: new QModelConnectorParam ('QControl', 'Visible', '', QType::Boolean),
2359: new QModelConnectorParam ('QControl', 'Height', 'Height in pixels. However, you can specify a different unit (e.g. 3.0 em).', QType::String),
2360: new QModelConnectorParam ('QControl', 'Width', 'Width in pixels. However, you can specify a different unit (e.g. 3.0 em).', QType::String),
2361: new QModelConnectorParam ('QControl', 'Instructions', 'Additional help for user.', QType::String),
2362: new QModelConnectorParam ('QControl', 'Moveable', '', QType::Boolean),
2363: new QModelConnectorParam ('QControl', 'Resizable', '', QType::Boolean),
2364: new QModelConnectorParam ('QControl', 'Droppable', '', QType::Boolean),
2365: new QModelConnectorParam ('QControl', 'UseWrapper', 'Control will be forced to be wrapped with a div', QType::Boolean),
2366: new QModelConnectorParam ('QControl', 'WrapperCssClass', '', QType::String),
2367: new QModelConnectorParam ('QControl', 'PreferredRenderMethod', '', QType::String)
2368: );
2369:
2370: }
2371:
2372:
2373: }