From 55c4ac1bfe565f1ca7f537fdd8b7a201be28e581 Mon Sep 17 00:00:00 2001 From: xue <> Date: Thu, 10 Nov 2005 12:47:19 +0000 Subject: Initial import of prado framework --- framework/Web/UI/TClientScriptManager.php | 236 +++ framework/Web/UI/TControl.php | 1518 ++++++++++++++++++++ framework/Web/UI/TForm.php | 128 ++ .../Web/UI/THiddenFieldPageStatePersister.php | 59 + framework/Web/UI/THtmlTextWriter.php | 235 +++ framework/Web/UI/TPage.php | 617 ++++++++ framework/Web/UI/TPageStatePersister.php | 22 + framework/Web/UI/TPostBackOptions.php | 36 + framework/Web/UI/TTemplate.php | 494 +++++++ framework/Web/UI/TTemplateControl.php | 215 +++ framework/Web/UI/TTemplateManager.php | 66 + framework/Web/UI/TTheme.php | 64 + framework/Web/UI/WebControls/TButton.php | 291 ++++ framework/Web/UI/WebControls/TCheckBox.php | 399 +++++ framework/Web/UI/WebControls/TContent.php | 47 + .../Web/UI/WebControls/TContentPlaceHolder.php | 47 + framework/Web/UI/WebControls/TExpression.php | 61 + framework/Web/UI/WebControls/TFont.php | 276 ++++ framework/Web/UI/WebControls/THiddenField.php | 123 ++ framework/Web/UI/WebControls/THyperLink.php | 144 ++ framework/Web/UI/WebControls/TImage.php | 122 ++ framework/Web/UI/WebControls/TImageButton.php | 320 +++++ framework/Web/UI/WebControls/TLabel.php | 106 ++ framework/Web/UI/WebControls/TLiteral.php | 79 + framework/Web/UI/WebControls/TPanel.php | 162 +++ framework/Web/UI/WebControls/TPlaceHolder.php | 25 + framework/Web/UI/WebControls/TStatements.php | 61 + framework/Web/UI/WebControls/TStyle.php | 334 +++++ framework/Web/UI/WebControls/TTextBox.php | 444 ++++++ framework/Web/UI/WebControls/TWebControl.php | 368 +++++ 30 files changed, 7099 insertions(+) create mode 100644 framework/Web/UI/TClientScriptManager.php create mode 100644 framework/Web/UI/TControl.php create mode 100644 framework/Web/UI/TForm.php create mode 100644 framework/Web/UI/THiddenFieldPageStatePersister.php create mode 100644 framework/Web/UI/THtmlTextWriter.php create mode 100644 framework/Web/UI/TPage.php create mode 100644 framework/Web/UI/TPageStatePersister.php create mode 100644 framework/Web/UI/TPostBackOptions.php create mode 100644 framework/Web/UI/TTemplate.php create mode 100644 framework/Web/UI/TTemplateControl.php create mode 100644 framework/Web/UI/TTemplateManager.php create mode 100644 framework/Web/UI/TTheme.php create mode 100644 framework/Web/UI/WebControls/TButton.php create mode 100644 framework/Web/UI/WebControls/TCheckBox.php create mode 100644 framework/Web/UI/WebControls/TContent.php create mode 100644 framework/Web/UI/WebControls/TContentPlaceHolder.php create mode 100644 framework/Web/UI/WebControls/TExpression.php create mode 100644 framework/Web/UI/WebControls/TFont.php create mode 100644 framework/Web/UI/WebControls/THiddenField.php create mode 100644 framework/Web/UI/WebControls/THyperLink.php create mode 100644 framework/Web/UI/WebControls/TImage.php create mode 100644 framework/Web/UI/WebControls/TImageButton.php create mode 100644 framework/Web/UI/WebControls/TLabel.php create mode 100644 framework/Web/UI/WebControls/TLiteral.php create mode 100644 framework/Web/UI/WebControls/TPanel.php create mode 100644 framework/Web/UI/WebControls/TPlaceHolder.php create mode 100644 framework/Web/UI/WebControls/TStatements.php create mode 100644 framework/Web/UI/WebControls/TStyle.php create mode 100644 framework/Web/UI/WebControls/TTextBox.php create mode 100644 framework/Web/UI/WebControls/TWebControl.php (limited to 'framework/Web/UI') diff --git a/framework/Web/UI/TClientScriptManager.php b/framework/Web/UI/TClientScriptManager.php new file mode 100644 index 00000000..0f760251 --- /dev/null +++ b/framework/Web/UI/TClientScriptManager.php @@ -0,0 +1,236 @@ +_owner=$owner; + } + + final public function getPostBackEventReference($options) + { + if($options->RequiresJavaScriptProtocol) + $str='javascript:'; + else + $str=''; + if($options->AutoPostBack) + $str.="setTimeout('"; + if(!$options->PerformValidation && !$options->TrackFocus && $options->ClientSubmit && $options->ActionUrl==='') + { + $this->_owner->registerPostBackScript(); + $postback="__doPostBack('".$options->TargetControl->getUniqueID()."','".THttpUtility::quoteJavaScriptString($options->Argument)."')"; + if($options->AutoPostBack) + { + $str.=THttpUtility::quoteJavaScriptString($postback); + $str.="',0)"; + } + else + $str.=$postback; + return $str; + } + $str.='WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions("'; + $str.=$options->TargetControl->getUniqueID().'", '; + if(($arg=$options->Argument)==='') + $str.='"", '; + else + $str.='"'.THttpUtility::quoteJavaScriptString($arg).'", '; + $flag=false; + if($options->PerformValidation) + { + $flag=true; + $str.='true, '; + } + else + $str.='false, '; + if($options->ValidationGroup!=='') + { + $flag=true; + $str.='"'.$options->ValidationGroup.'", '; + } + else + $str.='"", '; + if($options->ActionUrl!=='') + { + $flag=true; + $this->_owner->setContainsCrossPagePost(true); + $str.='"'.THttpUtility::quoteJavaScriptString($options->ActionUrl).'", '; + } + else + $str.='"", '; + if($options->TrackFocus) + { + $this->_owner->registerFocusScript(); + $flag=true; + $str.='true, '; + } + else + $str.='false, '; + if($options->ClientSubmit) + { + $flag=true; + $this->_owner->registerPostBackScript(); + $str.='true))'; + } + else + $str.='false))'; + if($options->AutoPostBack) + $str.="', 0)"; + if($flag) + { + $this->_owner->registerWebFormsScript(); + return $str; + } + else + return ''; + } + + final public function isHiddenFieldRegistered($key) + { + return isset($this->_hiddenFields[$key]); + } + + final public function isClientScriptBlockRegistered($key) + { + return isset($this->_scriptBlocks[$key]); + } + + final public function isClientScriptIncludeRegistered($key) + { + return isset($this->_scriptIncludes[$key]); + } + + final public function isStartupScriptRegistered($key) + { + return isset($this->_startupScripts[$key]); + } + + final public function isOnSubmitStatementRegistered($key) + { + return isset($this->_onSubmitStatements[$key]); + } + + final public function registerArrayDeclaration($name,$value) + { + $this->_arrayDeclares[$name][]=$value; + } + + final public function registerClientScriptBlock($key,$script) + { + $this->_criptBlocks[$key]=$script; + } + + final public function registerClientScriptInclude($key,$url) + { + $this->_scriptIncludes[$key]=$url; + } + + // todo: register an asset + + final public function registerHiddenField($name,$value) + { + $this->_hiddenFields[$name]=$value; + } + + final public function registerOnSubmitStatement($key,$script) + { + $this->_onSubmitStatements[$key]=$script; + } + + final public function registerStartupScript($key,$script) + { + $this->_startupScripts[$key]=$script; + } + + final public function renderArrayDeclarations($writer) + { + if(count($this->_arrayDeclares)) + { + $str="\n\n"; + $writer->write($str); + } + } + + final public function renderClientScriptBlocks($writer) + { + $str=''; + foreach($this->_scriptBlocks as $script) + $str.=$script; + if($this->_owner->getClientOnSubmitEvent()!=='' && $this->_owner->getClientSupportsJavaScript()) + { + $str.="function WebForm_OnSubmit() {\n"; + foreach($this->_onSubmitStatements as $script) + $str.=$script; + $str.="\nreturn true;\n}"; + } + if($str!=='') + $writer->write("\n\n"); + } + + final public function renderClientStartupScripts($writer) + { + if(count($this->_startupScripts)) + { + $str="\n\n"; + $writer->write($str); + } + } + + final public function renderHiddenFields($writer) + { + $str=''; + foreach($this->_hiddenFields as $name=>$value) + { + $value=THttpUtility::htmlEncode($value); + $str.="\n"; + } + if($str!=='') + $writer->write($str); + $this->_hiddenFields=array(); + } + + /** + * @internal + */ + final public function getHasHiddenFields() + { + return count($this->_hiddenFields)>0; + } + + /** + * @internal + */ + final public function getHasSubmitStatements() + { + return count($this->_onSubmitStatements)>0; + } +} + +?> \ No newline at end of file diff --git a/framework/Web/UI/TControl.php b/framework/Web/UI/TControl.php new file mode 100644 index 00000000..0d7fb333 --- /dev/null +++ b/framework/Web/UI/TControl.php @@ -0,0 +1,1518 @@ + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Revision: $ $Date: $ + * @package System.Web.UI + */ + +/** + * TControl class + * + * TControl is the base class for all components on a page hierarchy. + * It implements the following features for UI-related functionalities: + * - databinding feature + * - naming container and containee relationship + * - parent and child relationship + * - viewstate and controlstate features + * - rendering scheme + * - control lifecycles + * + * A property can be data-bound with an expression. By calling {@link dataBind} + * expressions bound to properties will be evaluated and the results will be + * set to the corresponding properties. + * + * A naming container control implements INamingContainer and ensures that + * its containee controls can be differentiated by their ID property values. + * Naming container and containee realtionship specifies a protocol to uniquely + * identify an arbitrary control on a page hierarchy by an ID path (concatenation + * of all naming containers' IDs and the target control's ID). + * + * Parent and child relationship determines how the presentation of controls are + * enclosed within each other. A parent will determine where to place + * the presentation of its child controls. For example, a TPanel will enclose + * all its child controls' presentation within a div html tag. + * + * Viewstate and controlstate are two approaches to preserve state across + * page postback requests. ViewState is mainly related with UI specific state + * and can be disabled if not needed. ControlState represents crucial logic state + * and cannot be disabled. + * + * Each control on a page will undergo a series of lifecycles, including + * control construction, OnInit, OnLoad, OnPreRender, Render, and OnUnload. + * They work together with page lifecycles to process a page request. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web.UI + * @since 3.0 + */ +class TControl extends TComponent +{ + /** + * format of control ID + */ + const ID_FORMAT='/^[a-zA-Z_]\\w*$/'; + /** + * separator char between IDs in a UniqueID + */ + const ID_SEPARATOR='$'; + /** + * separator char between IDs in a ClientID + */ + const CLIENT_ID_SEPARATOR='_'; + /** + * prefix to an ID automatically generated + */ + const AUTOMATIC_ID_PREFIX='ctl'; + + /** + * the stage of lifecycles that the control is currently at + */ + const CS_CONSTRUCTED=0; + const CS_CHILD_INITIALIZED=1; + const CS_INITIALIZED=2; + const CS_STATE_LOADED=3; + const CS_LOADED=4; + const CS_PRERENDERED=5; + + /** + * State bits. + */ + const IS_ID_SET=0x01; + const IS_INVISIBLE=0x02; + const IS_DISABLE_VIEWSTATE=0x04; + const IS_SKIN_APPLIED=0x08; + const IS_STYLESHEET_APPLIED=0x10; + const IS_DISABLE_THEMING=0x20; + const IS_CHILD_CREATED=0x40; + const IS_CREATING_CHILD=0x80; + + /** + * Indexes for the rare fields. + * In order to save memory, rare fields will only be created if they are needed. + */ + const RF_CONTROLS=0; // cihld controls + const RF_CHILD_STATE=1; // child state field + const RF_NAMED_CONTROLS=2; // list of controls whose namingcontainer is this control + const RF_NAMED_CONTROLS_ID=3; // counter for automatic id + const RF_SKIN_ID=4; // skin ID + const RF_DATA_BINDINGS=5; // data bindings + const RF_EVENTS=6; // event handlers + const RF_CONTROLSTATE=7; // controlstate + const RF_NAMED_OBJECTS=8; // controls declared with ID on template + + /** + * @var string control ID + */ + private $_id=''; + /** + * @var string control unique ID + */ + private $_uid=''; + /** + * @var TControl parent of the control + */ + private $_parent=null; + /** + * @var TPage page that the control resides in + */ + private $_page=null; + /** + * @var TControl naming container of the control + */ + private $_namingContainer=null; + /** + * @var TTemplateControl control whose template contains the control + */ + private $_tplControl=null; + /** + * @var TMap viewstate data + */ + private $_viewState=array(); + /** + * @var integer the current stage of the control lifecycles + */ + private $_stage=0; + /** + * @var integer representation of the state bits + */ + private $_flags=0; + /** + * @var array a collection of rare control data + */ + private $_rf=array(); + + + /** + * Returns a property value by name or a control by ID. + * This overrides the parent implementation by allowing accessing + * a control via its ID using the following syntax, + * + * $menuBar=$this->menuBar; + * + * Note, the control must be configured in the template + * with explicit ID. If the name matches both a property and a control ID, + * the control ID will take the precedence. + * + * @param string the property name or control ID + * @return mixed the property value or the target control + * @throws TInvalidOperationException if the property is not defined. + * @see registerObject + */ + public function __get($name) + { + if(isset($this->_rf[self::RF_NAMED_OBJECTS][$name])) + return $this->_rf[self::RF_NAMED_OBJECTS][$name]; + else + return parent::__get($name); + } + + /** + * @return TControl the parent of this control + */ + public function getParent() + { + return $this->_parent; + } + + /** + * @return TControl the naming container of this control + */ + public function getNamingContainer() + { + if(!$this->_namingContainer && $this->_parent) + $this->_namingContainer=$this->_parent->getNamingContainer(); + return $this->_namingContainer; + } + + /** + * @return TPage the page that contains this control + */ + public function getPage() + { + if(!$this->_page && $this->_parent) + $this->_page=$this->_parent->getPage(); + return $this->_page; + } + + /** + * Sets the page for a control. + * Only framework developers should use this method. + * @param TPage the page that contains this control + */ + public function setPage($page) + { + $this->_page=$page; + } + + /** + * Sets the control whose template contains this control. + * Only framework developers should use this method. + * @param TTemplateControl the control whose template contains this control + */ + public function setTemplateControl($control) + { + $this->_tplControl=$control; + } + + /** + * @return TTemplateControl the control whose template contains this control + */ + public function getTemplateControl() + { + if(!$this->_tplControl && $this->_parent) + $this->_tplControl=$this->_parent->getTemplateControl(); + return $this->_tplControl; + } + + /** + * @return IApplication the application object that the current page is using + */ + public function getApplication() + { + return Prado::getApplication(); + } + + /** + * Returns the id of the control. + * Control ID can be either manually set or automatically generated. + * If $hideAutoID is true, automatically generated ID will be returned as an empty string. + * @param boolean whether to hide automatically generated ID + * @return string the ID of the control + */ + public function getID($hideAutoID=true) + { + if($hideAutoID) + return ($this->_flags & self::IS_ID_SET) ? $this->_id : ''; + else + return $this->_id; + } + + /** + * @param string the new control ID. The value must consist of word characters [a-zA-Z0-9_] only + * @throws TInvalidDataValueException if ID is in a bad format + */ + public function setID($id) + { + if(!preg_match(self::ID_FORMAT,$id)) + throw new TInvalidDataValueException('control_id_invalid',$id,get_class($this)); + $this->_id=$id; + $this->_flags |= self::IS_ID_SET; + $this->clearCachedUniqueID($this instanceof INamingContainer); + if($this->_namingContainer) + $this->_namingContainer->clearNameTable(); + } + + /** + * Returns a unique ID that identifies the control in the page hierarchy. + * A unique ID is the contenation of all naming container controls' IDs and the control ID. + * These IDs are separated by '$' character. + * Control users should not rely on the specific format of UniqueID, however. + * @return string a unique ID that identifies the control in the page hierarchy + */ + public function getUniqueID() + { + if($this->_uid==='') // need to build the UniqueID + { + if($namingContainer=$this->getNamingContainer()) + { + if($this->_page===$namingContainer) + return ($this->_uid=$this->_id); + else if(($prefix=$namingContainer->getUniqueID())==='') + return $this->_id; + else + return ($this->_uid=$prefix.self::ID_SEPARATOR.$this->_id); + } + else // no naming container + return $this->_id; + } + else + return $this->_uid; + } + + /** + * Returns the client ID of the control. + * The client ID can be used to uniquely identify + * the control in client-side scripts (such as JavaScript). + * Do not rely on the explicit format of the return ID. + * @return string the client ID of the control + */ + public function getClientID() + { + return strtr($this->getUniqueID(),self::ID_SEPARATOR,self::CLIENT_ID_SEPARATOR); + } + + /** + * @return string the skin ID of this control + */ + public function getSkinID() + { + return isset($this->_rf[self::RF_SKIN_ID])?$this->_rf[self::RF_SKIN_ID]:''; + } + + /** + * @param string the skin ID of this control + * @throws TInvalidOperationException if the SkinID is set in a stage later than PreInit, or if the skin is applied already. + */ + public function setSkinID($value) + { + if(($this->_flags & self::IS_SKIN_APPLIED) || $this->_stage>=self::CS_CHILD_INITIALIZED) + throw new TInvalidOperationException('control_skinid_unchangeable',get_class($this),$this->getUniqueID()); + else + $this->_rf[self::RF_SKIN_ID]=$value; + } + + /** + * @return boolean whether theming is enabled for this control. + * The theming is enabled if the control and all its parents have it enabled. + */ + public function getEnableTheming() + { + if($this->_flags & self::IS_DISABLE_THEMING) + return false; + else + return $this->_parent?$this->_parent->getEnableTheming():true; + } + + /** + * @param boolean whether to enable theming + * @throws TInvalidOperationException if this method is invoked after OnPreInit + */ + public function setEnableTheming($value) + { + if($this->_stage>=self::CS_CHILD_INITIALIZED) + throw new TInvalidOperationException('control_enabletheming_unchangeable',get_class($this),$this->getUniqueID()); + else if(TPropertyValue::ensureBoolean($value)) + $this->_flags &= ~self::IS_DISABLE_THEMING; + else + $this->_flags |= self::IS_DISABLE_THEMING; + } + + /** + * @return boolean whether the control has child controls + */ + public function getHasControls() + { + return isset($this->_rf[self::RF_CONTROLS]) && $this->_rf[self::RF_CONTROLS]->getCount()>0; + } + + /** + * @return TControlList the child control collection + */ + public function getControls() + { + if(!isset($this->_rf[self::RF_CONTROLS])) + $this->_rf[self::RF_CONTROLS]=new TControlList($this); + return $this->_rf[self::RF_CONTROLS]; + } + + /** + * @param boolean whether the control is visible + */ + public function setVisible($value) + { + if(TPropertyValue::ensureBoolean($value)) + $this->_flags &= ~self::IS_INVISIBLE; + else + $this->_flags |= self::IS_INVISIBLE; + } + + /** + * @return boolean whether the control is visible (default=true). + * A control is visible if all its parents and itself are visible. + */ + public function getVisible() + { + if($this->_flags & self::IS_INVISIBLE) + return false; + else + return $this->_parent?$this->_parent->getVisible():true; + } + + /** + * Returns a value indicating whether the control is enabled. + * A control is enabled if it allows client user interaction. + * If $checkParents is true, all parent controls will be checked, + * and unless they are all enabled, false will be returned. + * The property Enabled is mainly used for {@link TWebControl} + * derived controls. + * @param boolean whether the parents should also be checked enabled + * @return boolean whether the control is enabled. + */ + public function getEnabled($checkParents=false) + { + if($checkParents) + { + for($control=$this;$control;$control=$control->_parent) + if(!$control->getEnabled()) + return false; + return true; + } + else + return $this->getViewState('Enabled',true); + } + + /** + * @param boolean whether the control is to be enabled. + */ + public function setEnabled($value) + { + $this->setViewState('Enabled',TPropertyValue::ensureBoolean($value),true); + } + + /** + * @return boolean whether the control has custom attributes + */ + public function getHasAttributes() + { + if($attributes=$this->getViewState('Attributes',null)) + return $attributes->getCount()>0; + else + return false; + } + + /** + * Returns a value indicating whether this control type can take attributes in template. + * This method can be overriden. + * Only framework developers and control developers should use this method. + * @return boolean whether the control allows attributes in template (default=true) + */ + public function getAllowCustomAttributes() + { + return true; + } + + /** + * Returns the list of custom attributes. + * Custom attributes are name-value pairs that may be rendered + * as HTML tags' attributes. + * @return TMap the list of custom attributes + */ + public function getAttributes() + { + if($attributes=$this->getViewState('Attributes',null)) + return $attributes; + else + { + $attributes=new TMap; + $this->setViewState('Attributes',$attributes,null); + return $attributes; + } + } + + /** + * @return boolean whether viewstate is enabled + */ + public function getEnableViewState() + { + return !($this->_flags & self::IS_DISABLE_VIEWSTATE); + } + + /** + * @param boolean set whether to enable viewstate + */ + public function setEnableViewState($value) + { + if(TPropertyValue::ensureBoolean($value)) + $this->_flags &= ~self::IS_DISABLE_VIEWSTATE; + else + $this->_flags |= self::IS_DISABLE_VIEWSTATE; + } + + /** + * Returns a controlstate value. + * + * This function is mainly used in defining getter functions for control properties + * that must be kept in controlstate. + * @param string the name of the controlstate value to be returned + * @param mixed the default value. If $key is not found in controlstate, $defaultValue will be returned + * @return mixed the controlstate value corresponding to $key + */ + protected function getControlState($key,$defaultValue=null) + { + return isset($this->_rf[self::RF_CONTROLSTATE][$key])?$this->_rf[self::RF_CONTROLSTATE][$key]:$defaultValue; + } + + /** + * Sets a controlstate value. + * + * This function is very useful in defining setter functions for control properties + * that must be kept in controlstate. + * Make sure that the controlstate value must be serializable and unserializable. + * @param string the name of the controlstate value + * @param mixed the controlstate value to be set + * @param mixed default value. If $value===$defaultValue, the item will be cleared from controlstate + */ + protected function setControlState($key,$value,$defaultValue=null) + { + if($value===$defaultValue) + unset($this->_rf[self::RF_CONTROLSTATE][$key]); + else + $this->_rf[self::RF_CONTROLSTATE][$key]=$value; + } + + /** + * Returns a viewstate value. + * + * This function is very useful in defining getter functions for component properties + * that must be kept in viewstate. + * @param string the name of the viewstate value to be returned + * @param mixed the default value. If $key is not found in viewstate, $defaultValue will be returned + * @return mixed the viewstate value corresponding to $key + */ + protected function getViewState($key,$defaultValue=null) + { + return isset($this->_viewState[$key])?$this->_viewState[$key]:$defaultValue; + } + + /** + * Sets a viewstate value. + * + * This function is very useful in defining setter functions for control properties + * that must be kept in viewstate. + * Make sure that the viewstate value must be serializable and unserializable. + * @param string the name of the viewstate value + * @param mixed the viewstate value to be set + * @param mixed default value. If $value===$defaultValue, the item will be cleared from the viewstate. + */ + protected function setViewState($key,$value,$defaultValue=null) + { + if($value===$defaultValue) + unset($this->_viewState[$key]); + else + $this->_viewState[$key]=$value; + } + + /** + * Sets up the binding between a property (or property path) and an expression. + * The context of the expression is the control itself. + * @param string the property name, or property path + * @param string the expression + */ + public function bindProperty($name,$expression) + { + $this->_rf[self::RF_DATA_BINDINGS][$name]=$expression; + } + + /** + * Breaks the binding between a property (or property path) and an expression. + * @param string the property name (or property path) + */ + public function unbindProperty($name) + { + unset($this->_rf[self::RF_DATA_BINDINGS][$name]); + } + + /** + * Performs the databinding for this component. + * Databinding a property includes evaluating the binded expression + * and setting the property with the evaluation result. + * @param boolean whether to raise OnDataBinding event. + * @throws TInvalidOperationException if some bounded property is invalid + * @throws TExpressionInvalidException if some bounded expression is invalid + */ + public function dataBind($raiseOnDataBinding=true) + { + if(isset($this->_rf[self::RF_DATA_BINDINGS])) + { + foreach($this->_rf[self::RF_DATA_BINDINGS] as $property=>$expression) + $this->setPropertyByPath($property,$this->evaluateExpression($expression)); + if($raiseOnDataBinding) + $this->onDataBinding(null); + if(isset($this->_rf[self::RF_CONTROLS])) + { + foreach($this->_rf[self::RF_CONTROLS] as $control) + if($control instanceof TControl) + $control->dataBind($raiseOnDataBinding); + } + } + } + + /** + * @return boolean whether child controls have been created + */ + final protected function getChildControlsCreated() + { + return ($this->_flags & self::IS_CHILD_CREATED)!==0; + } + + /** + * Sets a value indicating whether child controls are created. + * If false, any existing child controls will be cleared up. + * @param boolean whether child controls are created + */ + final protected function setChildControlsCreated($value) + { + if($value) + $this->_flags |= self::IS_CHILD_CREATED; + else + { + if($this->hasControl() && ($this->_flags & self::IS_CHILD_CREATED)) + $this->getControls()->clear(); + $this->_flags &= ~self::IS_CHILD_CREATED; + } + } + + /** + * Ensures child controls are created. + * If child controls are not created yet, this method will invoke + * {@link createChildControl} to create them. + */ + public function ensureChildControls() + { + if(!($this->_flags & self::IS_CHILD_CREATED) && !($this->_flags & self::IS_CREATING_CHILD)) + { + try + { + $this->_flags |= self::IS_CREATING_CHILD; + $this->createChildControls(); + $this->_flags &= ~self::IS_CREATING_CHILD; + $this->_flags |= self::IS_CHILD_CREATED; + } + catch(Exception $e) + { + $this->_flags &= ~self::IS_CREATING_CHILD; + $this->_flags |= self::IS_CHILD_CREATED; + throw $e; + } + } + } + + /** + * Creates child controls. + * This method can be overriden for controls who want to have their controls. + * Do not call this method directly. Instead, call {@link ensureChildControls} + * to ensure child controls are created only once. + */ + protected function createChildControls() + { + } + + /** + * Finds a control by ID path within the current naming container. + * The current naming container is either the control itself + * if it implements {@link INamingContainer} or the control's naming container. + * The ID path is an ID sequence separated by {@link TControl::ID_SEPARATOR}. + * For example, 'Repeater1:Item1:Button1' looks for a control with ID 'Button1' + * whose naming container is 'Item1' whose naming container is 'Repeater1'. + * @param string ID of the control to be looked up + * @return TControl|null the control found, null if not found + * @throws TInvalidDataValueException if a control's ID is found not unique within its naming container. + */ + public function findControl($id) + { + $container=($this instanceof INamingContainer)?$this:$this->getNamingContainer(); + if(!$container || !$container->getHasControls()) + return null; + if(!isset($container->_rf[self::RF_NAMED_CONTROLS])) + { + $container->_rf[self::RF_NAMED_CONTROLS]=array(); + $container->fillNameTable($container,$container->_rf[self::RF_CONTROLS]); + } + if(($pos=strpos($id,self::ID_SEPARATOR))===false) + return isset($container->_rf[self::RF_NAMED_CONTROLS][$id])?$container->_rf[self::RF_NAMED_CONTROLS][$id]:null; + else + { + $cid=substr($id,0,$pos); + $sid=substr($id,$pos+1); + if(isset($container->_rf[self::RF_NAMED_CONTROLS][$cid])) + return $container->_rf[self::RF_NAMED_CONTROLS][$cid]->findControl($sid); + else + return null; + } + } + + /** + * Resets the control as a naming container. + * Only framework developers should use this method. + */ + public function clearNamingContainer() + { + unset($this->_rf[self::RF_NAMED_CONTROLS_ID]); + $this->clearNameTable(); + } + + /** + * Registers an object by a name. + * A registered object can be accessed like a public member variable. + * This method should only be used by framework and control developers. + * @param string name of the object + * @param object object to be declared + * @see __get + */ + public function registerObject($name,$object) + { + $this->_rf[self::RF_NAMED_OBJECTS][$name]=$object; + } + + /** + * This method is invoked after the control is instantiated by a template. + * When this method is invoked, the control should have a valid TemplateControl + * and has its properties initialized according to template configurations. + * The control, however, has not been added to the page hierarchy yet. + * The default implementation of this method will invoke + * the potential parent control's {@link addParsedObject} to add the control as a child. + * This method can be overriden. + * @param TControl potential parent of this control + * @see addParsedObject + */ + public function createdOnTemplate($parent) + { + $parent->addParsedObject($this); + } + + /** + * Processes an object that is created during parsing template. + * The object can be either a control or a static text string. + * By default, the object will be added into the child control collection. + * This method can be overriden to customize the handling of newly created objects in template. + * Only framework developers and control developers should use this method. + * @param string|TComponent text string or component parsed and instantiated in template + * @see createdOnTemplate + */ + public function addParsedObject($object) + { + $this->getControls()->add($object); + } + + /** + * Clears up the child state data. + * After a control loads its state, those state that do not belong to + * any existing child controls are stored as child state. + * This method will remove these state. + * Only frameworker developers and control developers should use this method. + */ + final protected function clearChildState() + { + unset($this->_rf[self::RF_CHILD_STATE]); + } + + /** + * @param TControl the potential ancestor control + * @return boolean if the control is a descendent (parent, parent of parent, etc.) + * of the specified control + */ + final protected function isDescendentOf($ancestor) + { + $control=$this; + while($control!==$ancestor && $control->_parent) + $control=$control->_parent; + return $control===$ancestor; + } + + /** + * Adds a control into the child collection of the control. + * Control lifecycles will be caught up during the addition. + * Only framework developers should use this method. + * @param TControl the new child control + */ + public function addedControl($control) + { + if($control->_parent) + $control->_parent->getControls()->remove($control); + $control->_parent=$this; + $control->_page=$this->getPage(); + $namingContainer=($this instanceof INamingContainer)?$this:$this->_namingContainer; + if($namingContainer) + { + $control->_namingContainer=$namingContainer; + if($control->_id==='') + $control->generateAutomaticID(); + else + $namingContainer->clearNameTable(); + } + + if($this->_stage>=self::CS_INITIALIZED) + { + $control->initRecursive($namingContainer); + if($this->_stage>=self::CS_STATE_LOADED) + { + if(isset($this->_rf[self::RF_CHILD_STATE])) + $state=$this->_rf[self::RF_CHILD_STATE]->remove($control->_id); + else + $state=null; + $control->loadStateRecursive($state,!($this->_flags & self::IS_DISABLE_VIEWSTATE)); + if($this->_stage>=self::CS_LOADED) + { + $control->loadRecursive(); + if($this->_stage>=self::CS_PRERENDERED) + $control->preRenderRecursive(); + } + } + } + } + + /** + * Removes a control from the child collection of the control. + * Only framework developers should use this method. + * @param TControl the child control removed + */ + public function removedControl($control) + { + if($this->_namingContainer) + $this->_namingContainer->clearNameTable(); + $control->unloadRecursive(); + $control->_parent=null; + $control->_page=null; + $control->_namingContainer=null; + $control->_tplControl=null; + if(!($control->_flags & self::IS_ID_SET)) + $control->_id=''; + $control->clearCachedUniqueID(true); + } + + /** + * Performs the Init step for the control and all its child controls. + * Only framework developers should use this method. + * @param TControl the naming container control + */ + protected function initRecursive($namingContainer) + { + $this->ensureChildControls(); + if($this->getHasControls()) + { + if($this instanceof INamingContainer) + $namingContainer=$this; + $page=$this->getPage(); + foreach($this->_rf[self::RF_CONTROLS] as $control) + { + if($control instanceof TControl) + { + $control->_namingContainer=$namingContainer; + $control->_page=$page; + if($control->_id==='' && $namingContainer) + $control->generateAutomaticID(); + $control->initRecursive($namingContainer); + } + } + } + if($this->_stage_stage=self::CS_CHILD_INITIALIZED; + if(($page=$this->getPage()) && $page->getContainsTheme() && $this->getEnableTheming() && !($this->_flags & self::IS_SKIN_APPLIED)) + { + $page->applyControlSkin($this); + $this->_flags |= self::IS_SKIN_APPLIED; + } + $this->onInit(null); + $this->_stage=self::CS_INITIALIZED; + } + } + + /** + * Performs the Load step for the control and all its child controls. + * Only framework developers should use this method. + */ + protected function loadRecursive() + { + if($this->_stageonLoad(null); + if($this->getHasControls()) + { + foreach($this->_rf[self::RF_CONTROLS] as $control) + if($control instanceof TControl) + $control->loadRecursive(); + } + if($this->_stage_stage=self::CS_LOADED; + } + + /** + * Performs the PreRender step for the control and all its child controls. + * Only framework developers should use this method. + */ + protected function preRenderRecursive() + { + if($this->getVisible()) + { + $this->_flags &= ~self::IS_INVISIBLE; + $this->onPreRender(null); + if($this->getHasControls()) + { + foreach($this->_rf[self::RF_CONTROLS] as $control) + if($control instanceof TControl) + $control->preRenderRecursive(); + } + } + else + { + $this->_flags |= self::IS_INVISIBLE; + } + $this->_stage=self::CS_PRERENDERED; + } + + /** + * Performs the Unload step for the control and all its child controls. + * Only framework developers should use this method. + */ + protected function unloadRecursive() + { + if(!($this->_flags & self::IS_ID_SET)) + $this->_id=''; + if($this->getHasControls()) + { + foreach($this->_rf[self::RF_CONTROLS] as $control) + if($control instanceof TControl) + $control->unloadRecursive(); + } + $this->onUnload(null); + } + + /** + * This method is invoked when the control enters 'Init' stage. + * The method raises 'Init' event. + * If you override this method, be sure to call the parent implementation + * so that the event handlers can be invoked. + * @param TEventParameter event parameter to be passed to the event handlers + */ + protected function onInit($param) + { + $this->raiseEvent('Init',$this,$param); + } + + /** + * This method is invoked when the control enters 'Load' stage. + * The method raises 'Load' event. + * If you override this method, be sure to call the parent implementation + * so that the event handlers can be invoked. + * @param TEventParameter event parameter to be passed to the event handlers + */ + protected function onLoad($param) + { + $this->raiseEvent('Load',$this,$param); + } + + /** + * Raises 'DataBinding' event. + * This method is invoked when {@link dataBind} is invoked. + * @param TEventParameter event parameter to be passed to the event handlers + */ + protected function onDataBinding($param) + { + $this->raiseEvent('DataBinding',$this,$param); + } + + + /** + * This method is invoked when the control enters 'Unload' stage. + * The method raises 'Unload' event. + * If you override this method, be sure to call the parent implementation + * so that the event handlers can be invoked. + * @param TEventParameter event parameter to be passed to the event handlers + */ + protected function onUnload($param) + { + $this->raiseEvent('Unload',$this,$param); + } + + /** + * This method is invoked when the control enters 'PreRender' stage. + * The method raises 'PreRender' event. + * If you override this method, be sure to call the parent implementation + * so that the event handlers can be invoked. + * @param TEventParameter event parameter to be passed to the event handlers + */ + protected function onPreRender($param) + { + $this->raiseEvent('PreRender',$this,$param); + } + + /** + * Invokes the parent's onBubbleEvent method. + * A control who wants to bubble an event must call this method in its onEvent method. + * @param TControl sender of the event + * @param TEventParameter event parameter + * @see onBubbleEvent + */ + protected function raiseBubbleEvent($sender,$param) + { + $control=$this; + while($control=$control->_parent) + { + if($control->onBubbleEvent($sender,$param)) + break; + } + } + + /** + * This method responds to a bubbled event. + * This method should be overriden to provide customized response to a bubbled event. + * Check the type of event parameter to determine what event is bubbled currently. + * @param TControl sender of the event + * @param TEventParameter event parameters + * @return boolean true if the event bubbling is handled and no more bubbling. + * @see raiseBubbleEvent + */ + protected function onBubbleEvent($sender,$param) + { + return false; + } + + /** + * Renders the control. + * Only when the control is visible will the control be rendered. + * @param THtmlTextWriter the writer used for the rendering purpose + */ + protected function renderControl($writer) + { + if(!($this->_flags & self::IS_INVISIBLE)) + $this->render($writer); + } + + /** + * Renders the control. + * This method is invoked by {@link renderControl} when the control is visible. + * You can override this method to provide customized rendering of the control. + * By default, the control simply renders all its child contents. + * @param THtmlTextWriter the writer used for the rendering purpose + */ + protected function render($writer) + { + $this->renderChildren($writer); + } + + /** + * Renders the children of the control. + * This method iterates through all child controls and static text strings + * and renders them in order. + * @param THtmlTextWriter the writer used for the rendering purpose + */ + protected function renderChildren($writer) + { + if($this->getHasControls()) + { + foreach($this->_rf[self::RF_CONTROLS] as $control) + { + if($control instanceof TControl) + $control->renderControl($writer); + else if(is_string($control)) + $writer->write($control); + } + } + } + + /** + * This method is invoked when control state is to be saved. + * You can override this method to do last step state saving. + * Parent implementation must be invoked. + * @param TEventParameter event parameter + */ + protected function onSaveState($param) + { + $this->setViewState('Visible',!($this->_flags & self::IS_INVISIBLE),true); + $this->raiseEvent('SaveState',$this,$param); + } + + /** + * This method is invoked right after the control has loaded its state. + * You can override this method to initialize data from the control state. + * Parent implementation must be invoked. + * @param TEventParameter + */ + protected function onLoadState($param) + { + $this->setVisible($this->getViewState('Visible',true)); + $this->raiseEvent('LoadState',$this,$param); + } + + /** + * Loads state (viewstate and controlstate) into a control and its children. + * @param TMap the collection of the state + * @param boolean whether the viewstate should be loaded + */ + final protected function loadStateRecursive(&$state,$needViewState=true) + { + // A null state means the stateful properties all take default values. + // So if the state is enabled, we have to assign the null value. + $needViewState=($needViewState && !($this->_flags & self::IS_DISABLE_VIEWSTATE)); + if(is_array($state)) + { + if(isset($state[1])) + { + $this->_rf[self::RF_CONTROLSTATE]=&$state[1]; + unset($state[1]); + } + else + unset($this->_rf[self::RF_CONTROLSTATE]); + if($needViewState) + { + if(isset($state[0])) + $this->_viewState=&$state[0]; + else + $this->_viewState=array(); + } + unset($state[0]); + if($this->getHasControls()) + { + foreach($this->_rf[self::RF_CONTROLS] as $control) + { + if($control instanceof TControl) + { + if(isset($state[$control->_id])) + { + $s=&$state[$control->_id]; + unset($state[$control->_id]); + } + else + $s=null; + $control->loadStateRecursive($s,$needViewState); + } + } + } + if(!empty($state)) + $this->_rf[self::RF_CHILD_STATE]=&$state; + } + else + { + unset($this->_rf[self::RF_CONTROLSTATE]); + if($needViewState) + $this->_viewState=array(); + if($this->getHasControls()) + { + foreach($this->_rf[self::RF_CONTROLS] as $control) + { + $s=null; + if($control instanceof TControl) + $control->loadStateRecursive($s,$needViewState); + } + } + } + $this->onLoadState(null); + $this->_stage=self::CS_STATE_LOADED; + } + + /** + * Saves the all control state (viewstate and controlstate) as a collection. + * @param boolean whether the viewstate should be saved + * @return TMap the collection of the control state (including its children's state). + */ + final protected function &saveStateRecursive($needViewState=true) + { + $this->onSaveState(null); + $needViewState=($needViewState && !($this->_flags & self::IS_DISABLE_VIEWSTATE)); + $state=array(); + if($this->getHasControls()) + { + foreach($this->_rf[self::RF_CONTROLS] as $control) + { + if($control instanceof TControl) + { + $cs=&$control->saveStateRecursive($needViewState); + if(!empty($cs)) + $state[$control->_id]=&$cs; + } + } + } + if($needViewState && !empty($this->_viewState)) + $state[0]=&$this->_viewState; + if(isset($this->_rf[self::RF_CONTROLSTATE]) && !empty($this->_rf[self::RF_CONTROLSTATE])) + $state[1]=&$this->_rf[self::RF_CONTROLSTATE]; + return $state; + } + + /** + * Applies a stylesheet skin to a control. + * @param TPage the page containing the control + * @throws TInvalidOperationException if the stylesheet skin is applied already + */ + public function applyStyleSheetSkin($page) + { + if($page && !($this->_flags & self::IS_STYLESHEET_APPLIED)) + { + $page->applyControlStyleSheet($this); + $this->_flags |= self::IS_STYLESHEET_APPLIED; + } + else if($this->_flags & self::IS_STYLESHEET_APPLIED) + throw new TInvalidOperationException('control_stylesheet_applied',get_class($this),$this->getUniqueID()); + } + + /** + * Clears the cached UniqueID. + * If $recursive=true, all children's cached UniqueID will be cleared as well. + * @param boolean whether the clearing is recursive. + */ + private function clearCachedUniqueID($recursive) + { + $this->_uid=''; + if($recursive && isset($this->_rf[self::RF_CONTROLS])) + { + foreach($this->_rf[self::RF_CONTROLS] as $control) + if($control instanceof TControl) + $control->clearCachedUniqueID($recursive); + } + } + + /** + * Generates an automatic ID for the control. + */ + private function generateAutomaticID() + { + $this->_flags &= ~self::IS_ID_SET; + if(!isset($this->_namingContainer->_rf[self::RF_NAMED_CONTROLS_ID])) + $this->_namingContainer->_rf[self::RF_NAMED_CONTROLS_ID]=0; + $id=$this->_namingContainer->_rf[self::RF_NAMED_CONTROLS_ID]++; + $this->_id=self::AUTOMATIC_ID_PREFIX . $id; + $this->_namingContainer->clearNameTable(); + } + + /** + * Clears the list of the controls whose IDs are managed by the specified naming container. + */ + private function clearNameTable() + { + unset($this->_rf[self::RF_NAMED_CONTROLS]); + } + + /** + * Updates the list of the controls whose IDs are managed by the specified naming container. + * @param TControl the naming container + * @param TControlList list of controls + * @throws TInvalidDataValueException if a control's ID is not unique within its naming container. + */ + private function fillNameTable($container,$controls) + { + foreach($controls as $control) + { + if($control instanceof TControl) + { + if($control->_id!=='') + { + if(isset($container->_rf[self::RF_NAMED_CONTROLS][$control->_id])) + throw new TInvalidDataValueException('control_id_not_unique',$control->_id,get_class($control)); + else + $container->_rf[self::RF_NAMED_CONTROLS][$control->_id]=$control; + } + if(!($control instanceof INamingContainer) && $control->getHasControls()) + $this->fillNameTable($container,$control->_rf[self::RF_CONTROLS]); + } + } + } +} + + +/** + * TControlList class + * + * TControlList implements a collection that enables + * controls to maintain a list of their child controls. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web.UI + * @since 3.0 + */ +class TControlList extends TList +{ + /** + * the control that owns this collection. + * @var TControl + */ + private $_o; + + /** + * Constructor. + * @param TControl the control that owns this collection. + */ + public function __construct(TControl $owner) + { + parent::__construct(); + $this->_o=$owner; + } + + /** + * @return TControl the control that owns this collection. + */ + protected function getOwner() + { + return $this->_o; + } + + /** + * Overrides the parent implementation with customized processing of the newly added item. + * @param mixed the newly added item + */ + protected function addedItem($item) + { + if($item instanceof TControl) + $this->_o->addedControl($item); + } + + /** + * Overrides the parent implementation with customized processing of the removed item. + * @param mixed the removed item + */ + protected function removedItem($item) + { + if($item instanceof TControl) + $this->_o->removedControl($item); + } + + /** + * Only string or instance of TControl can be added into collection. + * @param mixed the item to be added + */ + protected function canAddItem($item) + { + return is_string($item) || ($item instanceof TControl); + } + + /** + * Overrides the parent implementation by invoking {@link TControl::clearNamingContainer} + */ + public function clear() + { + parent::clear(); + if($this->_o instanceof INamingContainer) + $this->_o->clearNamingContainer(); + } +} + +/** + * INamingContainer interface. + * INamingContainer marks a control as a naming container. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web.UI + * @since 3.0 + */ +interface INamingContainer +{ +} + +/** + * IPostBackEventHandler interface + * + * If a control wants to respond to postback event, it must implement this interface. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web.UI + * @since 3.0 + */ +interface IPostBackEventHandler +{ + /** + * Raises postback event. + * The implementation of this function should raise appropriate event(s) (e.g. OnClick, OnCommand) + * indicating the component is responsible for the postback event. + * @param string the parameter associated with the postback event + */ + public function raisePostBackEvent($param); +} + + +/** + * IPostBackDataHandler interface + * + * If a control wants to load post data, it must implement this interface. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web.UI + * @since 3.0 + */ +interface IPostBackDataHandler +{ + /** + * Loads user input data. + * The implementation of this function can use $values[$key] to get the user input + * data that are meant for the particular control. + * @param string the key that can be used to retrieve data from the input data collection + * @param array the input data collection + * @return boolean whether the data of the control has been changed + */ + public function loadPostData($key,$values); + /** + * Raises postdata changed event. + * The implementation of this function should raise appropriate event(s) (e.g. OnTextChanged) + * indicating the control data is changed. + */ + public function raisePostDataChangedEvent(); +} + + +/** + * IValidator interface + * + * If a control wants to validate user input, it must implement this interface. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web.UI + * @since 3.0 + */ +interface IValidator +{ + /** + * Validates certain data. + * The implementation of this function should validate certain data + * (e.g. data entered into TTextBox control). + * @return boolean whether the data passes the validation + */ + public function validate(); + /** + * @return boolean whether the previous {@link validate()} is successful. + */ + public function getIsValid(); + /** + * @param boolean whether the validator validates successfully + */ + public function setIsValid($value); + /** + * @return string error message during last validate + */ + public function getErrorMessage(); + /** + * @param string error message for the validation + */ + public function setErrorMessage($value); +} + + +/** + * IValidatable interface + * + * If a control wants to be validated by a validator, it must implement this interface. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web.UI + * @since 3.0 + */ +interface IValidatable +{ + /** + * @return mixed the value of the property to be validated. + */ + public function getValidationPropertyValue(); +} + +/** + * TCommandEventParameter class + * + * TCommandEventParameter encapsulates the parameter data for OnCommand + * event of button controls. You can access the name of the command via + * Name property, and the parameter carried with the command via + * Parameter property. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web.UI + * @since 3.0 + */ +class TCommandEventParameter extends TEventParameter +{ + private $_name; + private $_param; + + /** + * Constructor. + * @param string name of the command + * @param string parameter of the command + */ + public function __construct($name='',$parameter='') + { + $this->_name=$name; + $this->_param=$parameter; + } + + /** + * @return string name of the command + */ + public function getName() + { + return $this->_name; + } + + /** + * @param string name of the command + */ + public function setName($value) + { + $this->_name=$value; + } + + /** + * @return string parameter of the command + */ + public function getParameter() + { + return $this->_param; + } + + /** + * @param string parameter of the command + */ + public function setParameter($value) + { + $this->_param=$value; + } +} + +?> \ No newline at end of file diff --git a/framework/Web/UI/TForm.php b/framework/Web/UI/TForm.php new file mode 100644 index 00000000..0edb976b --- /dev/null +++ b/framework/Web/UI/TForm.php @@ -0,0 +1,128 @@ +getPage()->setForm($this); + } + + protected function addAttributesToRender($writer) + { + $attributes=$this->getAttributes(); + $writer->addAttribute('name',$this->getName()); + $writer->addAttribute('method',$this->getMethod()); + $writer->addAttribute('action',$this->getApplication()->getRequest()->getRequestURI()); + $attributes->remove('name'); + $attributes->remove('method'); + $attributes->remove('action'); + + $page=$this->getPage(); + $onsubmit=$page->getClientOnSubmitEvent(); + if($onsubmit!=='') + { + if(($existing=$attributes->itemAt('onsubmit'))!=='') + { + $page->getClientScript()->registerOnSubmitStatement('TForm:OnSubmitScript',$existing); + $attributes->remove('onsubmit'); + } + if($page->getClientSupportsJavaScript()) + $writer->addAttribute('onsubmit',$onsubmit); + } + if($this->getDefaultButton()!=='') + {//todo + $control=$this->findControl($this->getDefaultButton()); + if(!$control) + $control=$page->findControl($this->getDefaultButton()); + if($control instanceof IButtonControl) + $page->getClientScript()->registerDefaultButtonScript($control,$writer,false); + else + throw new Exception('Only IButtonControl can be default button.'); + } + $writer->addAttribute('id',$this->getUniqueID()); + foreach($attributes as $name=>$value) + $writer->addAttribute($name,$value); + } + + /** + * @internal + */ + protected function render($writer) + { + $this->addAttributesToRender($writer); + $writer->renderBeginTag('form'); + $page=$this->getPage(); + $page->beginFormRender($writer); + $this->renderChildren($writer); + $page->endFormRender($writer); + $writer->renderEndTag(); + } + + public function getDefaultButton() + { + return $this->getViewState('DefaultButton',''); + } + + public function setDefaultButton($value) + { + $this->setViewState('DefaultButton',$value,''); + } + + public function getDefaultFocus() + { + return $this->getViewState('DefaultFocus',''); + } + + public function setDefaultFocus($value) + { + $this->setViewState('DefaultFocus',$value,''); + } + + public function getMethod() + { + return $this->getViewState('Method','post'); + } + + public function setMethod($value) + { + $this->setViewState('Method',$value,'post'); + } + + public function getEnctype() + { + return $this->getViewState('Enctype',''); + } + + public function setEnctype($value) + { + $this->setViewState('Enctype',$value,''); + } + + public function getSubmitDisabledControls() + { + return $this->getViewState('SubmitDisabledControls',false); + } + + public function setSubmitDisabledControls($value) + { + $this->setViewState('SubmitDisabledControls',TPropertyValue::ensureBoolean($value),false); + } + + public function getName() + { + return $this->getUniqueID(); + } + + public function getTarget() + { + return $this->getViewState('Target',''); + } + + public function setTarget($value) + { + $this->setViewState('Target',$value,''); + } +} + +?> \ No newline at end of file diff --git a/framework/Web/UI/THiddenFieldPageStatePersister.php b/framework/Web/UI/THiddenFieldPageStatePersister.php new file mode 100644 index 00000000..d2cb5226 --- /dev/null +++ b/framework/Web/UI/THiddenFieldPageStatePersister.php @@ -0,0 +1,59 @@ +_page=$page; + } + + public function save($state) + { + $data=Prado::serialize($state); + $hmac=$this->computeHMAC($data,$this->getKey()); + if(function_exists('gzuncompress') && function_exists('gzcompress')) + $data=gzcompress($hmac.$data); + else + $data=$hmac.$data; + $this->_page->saveStateField($data); + } + + public function load() + { + $str=$this->_page->loadStateField(); + if($str==='') + return null; + if(function_exists('gzuncompress') && function_exists('gzcompress')) + $data=gzuncompress($str); + else + $data=$str; + if($data!==false && strlen($data)>32) + { + $hmac=substr($data,0,32); + $state=substr($data,32); + if($hmac===$this->computeHMAC($state,$this->getKey())) + return Prado::unserialize($state); + } + throw new Exception('viewstate data is corrupted.'); + } + + private function getKey() + { + return 'abcdefe'; + } + + private function computeHMAC($data,$key) + { + if (strlen($key) > 64) + $key = pack('H32', md5($key)); + elseif (strlen($key) < 64) + $key = str_pad($key, 64, "\0"); + return md5((str_repeat("\x5c", 64) ^ substr($key, 0, 64)) . pack('H32', md5((str_repeat("\x36", 64) ^ substr($key, 0, 64)) . $data))); + } +} + +?> \ No newline at end of file diff --git a/framework/Web/UI/THtmlTextWriter.php b/framework/Web/UI/THtmlTextWriter.php new file mode 100644 index 00000000..4ea78383 --- /dev/null +++ b/framework/Web/UI/THtmlTextWriter.php @@ -0,0 +1,235 @@ +2, + 'a'=>0, + 'acronym'=>0, + 'address'=>2, + 'area'=>1, + 'b'=>0, + 'base'=>1, + 'basefont'=>1, + 'bdo'=>0, + 'bgsound'=>1, + 'big'=>0, + 'blockquote'=>2, + 'body'=>2, + 'br'=>2, + 'button'=>0, + 'caption'=>2, + 'center'=>2, + 'cite'=>0, + 'code'=>0, + 'col'=>1, + 'colgroup'=>2, + 'del'=>0, + 'dd'=>0, + 'dfn'=>0, + 'dir'=>2, + 'div'=>2, + 'dl'=>2, + 'dt'=>0, + 'em'=>0, + 'embed'=>1, + 'fieldset'=>2, + 'font'=>0, + 'form'=>2, + 'frame'=>1, + 'frameset'=>2, + 'h1'=>2, + 'h2'=>2, + 'h3'=>2, + 'h4'=>2, + 'h5'=>2, + 'h6'=>2, + 'head'=>2, + 'hr'=>1, + 'html'=>2, + 'i'=>0, + 'iframe'=>2, + 'img'=>1, + 'input'=>1, + 'ins'=>0, + 'isindex'=>1, + 'kbd'=>0, + 'label'=>0, + 'legend'=>2, + 'li'=>0, + 'link'=>1, + 'map'=>2, + 'marquee'=>2, + 'menu'=>2, + 'meta'=>1, + 'nobr'=>0, + 'noframes'=>2, + 'noscript'=>2, + 'object'=>2, + 'ol'=>2, + 'option'=>2, + 'p'=>0, + 'param'=>2, + 'pre'=>2, + 'ruby'=>2, + 'rt'=>2, + 'q'=>0, + 's'=>0, + 'samp'=>0, + 'script'=>2, + 'select'=>2, + 'small'=>2, + 'span'=>0, + 'strike'=>0, + 'strong'=>0, + 'style'=>2, + 'sub'=>0, + 'sup'=>0, + 'table'=>2, + 'tbody'=>2, + 'td'=>0, + 'textarea'=>0, + 'tfoot'=>2, + 'th'=>0, + 'thead'=>2, + 'title'=>2, + 'tr'=>2, + 'tt'=>0, + 'u'=>0, + 'ul'=>2, + 'var'=>0, + 'wbr'=>1, + 'xml'=>2 + ); + private static $_attrEncode=array( + 'abbr'=>true, + 'accesskey'=>true, + 'align'=>false, + 'alt'=>true, + 'autocomplete'=>false, + 'axis'=>true, + 'background'=>true, + 'bgcolor'=>false, + 'border'=>false, + 'bordercolor'=>false, + 'cellpadding'=>false, + 'cellspacing'=>false, + 'checked'=>false, + 'class'=>true, + 'cols'=>false, + 'colspan'=>false, + 'content'=>true, + 'coords'=>false, + 'dir'=>false, + 'disabled'=>false, + 'for'=>false, + 'headers'=>true, + 'height'=>false, + 'href'=>true, + 'id'=>false, + 'longdesc'=>true, + 'maxlength'=>false, + 'multiple'=>false, + 'name'=>false, + 'nowrap'=>false, + 'onclick'=>true, + 'onchange'=>true, + 'readonly'=>false, + 'rel'=>false, + 'rows'=>false, + 'rowspan'=>false, + 'rules'=>false, + 'scope'=>false, + 'selected'=>false, + 'shape'=>false, + 'size'=>false, + 'src'=>true, + 'style'=>false, + 'tabindex'=>false, + 'target'=>false, + 'title'=>true, + 'type'=>false, + 'usemap'=>false, + 'valign'=>false, + 'value'=>true, + 'vcard_name'=>false, + 'width'=>false, + 'wrap'=>false + ); + + private $_attributes=array(); + private $_openTags=array(); + private $_writer=null; + + public function __construct($writer) + { + $this->_writer=$writer; + } + + public function isValidFormAttribute($name) + { + return true; + } + + public function addAttribute($name,$value) + { + $this->_attributes[$name]=isset(self::$_attrEncode[$name])?THttpUtility::htmlEncode($value):$value; + } + + public function flush() + { + $this->_writer->flush(); + } + + public function write($str) + { + $this->_writer->write($str); + } + + public function writeLine($str='') + { + $this->_writer->write($str.self::CHAR_NEWLINE); + } + + public function writeAttribute($name,$value,$encode=false) + { + $this->_writer->write(' '.$name.='"'.($encode?THttpUtility::htmlEncode($value):$value).'"'); + } + + public function renderBeginTag($tagName) + { + $tagType=isset(self::$_tagTypes[$tagName])?self::$_tagTypes[$tagName]:self::TAG_OTHER; + $str='<'.$tagName; + foreach($this->_attributes as $name=>$value) + $str.=' '.$name.'="'.$value.'"'; + if($tagType===self::TAG_NONCLOSING) + { + $str.=' />'; + array_push($this->_openTags,''); + } + else + { + $str.='>'; + array_push($this->_openTags,$tagName); + } + $this->_writer->write($str); + $this->_attributes=array(); + } + + public function renderEndTag() + { + if(!empty($this->_openTags) && ($tagName=array_pop($this->_openTags))!=='') + $this->_writer->write(''); + } +} + +?> \ No newline at end of file diff --git a/framework/Web/UI/TPage.php b/framework/Web/UI/TPage.php new file mode 100644 index 00000000..bb8f2253 --- /dev/null +++ b/framework/Web/UI/TPage.php @@ -0,0 +1,617 @@ +_application=Prado::getApplication(); + $this->setPage($this); + if(is_array($initProperties)) + { + foreach($initProperties as $name=>$value) + $this->setPropertyByPath($name,$value); + } + parent::__construct(); + } + + /** + * Loads and parses the control template + * @return ITemplate the parsed template structure + */ + protected function loadTemplate() + { + if($this->_templateFile===null) + return parent::loadTemplate(); + else + { + $template=Prado::getApplication()->getService()->getTemplateManager()->loadTemplateByFileName(Prado::getPathOfNamespace($this->_templateFile,'.tpl')); + $this->setTemplate($template); + return $template; + } + } + + public function getTemplateFile() + { + return $this->_templateFile; + } + + public function setTemplateFile($value) + { + $this->_templateFile=$value; + } + + final public function setForm($form) + { + $this->_form=$form; + } + + final public function getForm() + { + return $this->_form; + } + + public function validate($validationGroup='') + { + $this->_validated=true; + if($validationGroup==='') + { + foreach($this->_validators as $validator) + $validator->validate(); + } + else + { + foreach($this->_validators as $validator) + if($validator->getValidationGroup()===$validationGroup) + $validator->validate(); + } + } + + public function RegisterEnabledControl($control) + { + $this->getEna.EnabledControls.Add(control); + } + + + + /** + * @internal + */ + public function registerPostBackScript() + { + if($this->getClientSupportsJavaScript() && !$this->_postBackScriptRendered) + { + if(!$this->_requirePostBackScript) + { + $this->getClientScript()->registerHiddenField('__EVENTTARGET',''); + $this->getClientScript()->registerHiddenField('__EVENTPARAM',''); + $this->_requirePostBackScript=true; + } + } + } + + public function registerWebFormsScript() + { + if($this->getClientSupportsJavaScript() && !$this->_webFormsScriptRendered) + { + $this->registerPostBackScript(); + $this->_requireWebFormsScript=true; + } + } + + + public function ensureRenderInForm($control) + { + if(!$this->_inFormRender) + throw new THttpException('control_not_in_form',$control->getUniqueID()); + } + + /** + * @internal + */ + final protected function addContentTemplate($name,$template) + { + if(!$this->_contentTemplateCollection) + $this->_contentTemplateCollection=new TMap; + if($this->_contentTemplateCollection->has($name)) + throw new Exception("Content '$name' duplicated."); + $this->_contentTemplateCollection->add($name,$template); + } + + /** + * @internal + */ + final public function applyControlSkin($control) + { + if($this->_theme) + $this->_theme->applySkin($control); + } + + /** + * @internal + */ + final public function applyControlStyleSheet($control) + { + if($this->_styleSheet) + { + $this->_styleSheet->applySkin($control); + return true; + } + else + return false; + } + + private function renderStateFields($writer) + { + $writer->write("\n_pageState."\" />\n"); + } + + private function renderPostBackScript($writer) + { + $id=$this->_form->getUniqueID(); + $str=<< + +\n +EOD; + $writer->write($str); + $this->_postBackScriptRendered=true; + } + + private function renderWebFormsScript($writer) + { + $writer->write("\n\n"); + $this->_webFormsScriptRendered=true; + } + + final public function getClientSupportsJavaScript() + { + // todo + return true; + } + + /** + * @internal + */ + final public function beginFormRender($writer) + { + if($this->_formRendered) + throw new THttpException('multiple_form_not_allowed'); + $this->_formRendered=true; + $this->_inFormRender=true; + + $this->getClientScript()->renderHiddenFields($writer); + //$this->renderStateFields($writer); + if($this->getClientSupportsJavaScript()) + { + /* + if($this->getMaintainScrollPositionOnPostBack() && !$this->_requireScrollScript) + { + $cs=$this->getClientScript(); + $cs->registerHiddenField('_SCROLLPOSITIONX',$this->_scrollPositionX); + $cs->registerHiddenField('_SCROLLPOSITIONY',$this->_scrollPositionY); + $cs->registerStartupScript(get_class($this),"PageScrollPositionScript", "\r\nvar WebForm_ScrollPositionSubmit = theForm.submit;\r\ntheForm.submit = WebForm_SaveScrollPositionSubmit;\r\n\r\nvar WebForm_ScrollPositionOnSubmit = theForm.onsubmit;\r\ntheForm.onsubmit = WebForm_SaveScrollPositionOnSubmit;\r\n\r\nvar WebForm_ScrollPositionLoad = window.onload;\r\nwindow.onload = WebForm_RestoreScrollPosition;\r\n", true); + $this->registerWebFormScript(); + $this->_requireScrollScript=true; + } + */ + if($this->_requirePostBackScript) + $this->renderPostBackScript($writer,$this->_form->getUniqueID()); + if($this->_requireWebFormsScript) + $this->renderWebFormsScript($writer); + } + $this->getClientScript()->renderClientScriptBlocks($writer); + // todo: more .... + } + + final public function getIsPostBackEventControlRegistered() + { + return $this->_registeredControlThatRequireRaiseEvent!==null; + } + + /** + * @internal + */ + final public function endFormRender($writer) + { + $cs=$this->getClientScript(); + if($this->getClientSupportsJavaScript()) + $cs->renderArrayDeclarations($writer); + $cs->renderHiddenFields($writer); + if($this->getClientSupportsJavaScript()) + { + if($this->_requirePostBackScript && !$this->_postBackScriptRendered) + $this->renderPostBackScript($writer); + if($this->_requireWebFormsScript && !$this->_webFormsScriptRendered) + $this->renderWebFormsScript($writer); + } + $cs->renderClientStartupScripts($writer); + $this->_inFormRender=false; + } + + final public function getClientScript() + { + if(!$this->_clientScript) + $this->_clientScript=new TClientScriptManager($this); + return $this->_clientScript; + } + + final public function getClientOnSubmitEvent() + { + // todo + if($this->getClientScript()->getHasSubmitStatements()) + return 'javascript:return WebForm_OnSubmit();'; + else + return ''; + } + + final public function getValidators($validationGroup='') + { + if(!$this->_validators) + $this->_validators=new TList; + if($validationGroup==='') + return $this->_validators; + $list=new TList; + foreach($this->_validators as $validator) + if($validator->getValidationGroup()===$validationGroup) + $list->add($validator); + return $list; + } + + protected function initializeCulture() + { + } + + /** + * @internal + */ + public function initializeStyleSheet() + { + if($this->_styleSheet!=='') + $this->_styleSheet=new TTheme($this->_styleSheetName); + } + + private function initializeThemes() + { + if($this->_themeName!=='') + $this->_theme=new TTheme($this->_themeName); + if($this->_styleSheetName!=='') + $this->_styleSheet=new TTheme($this->_styleSheetName); + } + + /** + * @internal + */ + public function loadScrollPosition() + { + if($this->_previousPagePath==='' && $this->_requestValueCollection) + { + if(isset($_REQUEST['__SCROLLPOSITIONX'])) + $this->_scrollPositionX=(integer)$_REQUEST['__SCROLLPOSITIONX']; + if(isset($_REQUEST['__SCROLLPOSITIONY'])) + $this->_scrollPositionX=(integer)$_REQUEST['__SCROLLPOSITIONY']; + } + } + + protected function onInit($param) + { + parent::onInit($param);/* + if($this->_theme) + $this->_theme->setStyleSheet(); + if($this->_styleSheet) + $this->_styleSheet->setStyleSheet();*/ + } + + protected function onInitComplete($param) + { + $this->raiseEvent('InitComplete',$this,$param); + } + + protected function onLoadComplete($param) + { + $this->raiseEvent('LoadComplete',$this,$param); + } + + protected function onPreInit($param) + { + $this->raiseEvent('PreInit',$this,$param); + } + + protected function onPreLoad($param) + { + $this->raiseEvent('PreLoad',$this,$param); + } + + protected function onPreRenderComplete($param) + { + $this->raiseEvent('PreRenderComplete',$this,$param); + } + + protected function onSaveStateComplete($param) + { + $this->raiseEvent('SaveStateComplete',$this,$param); + } + + final public function registerAsyncTask() + { + } + + final public function registerRequiresPostBack($control) + { + if(!$this->_registeredControlsThatRequirePostBack) + $this->_registeredControlsThatRequirePostBack=new TList; + $this->_registeredControlsThatRequirePostBack->add($control->getUniqueID()); + } + + final public function registerRequiresRaiseEvent($control) + { + $this->_registeredControlThatRequireRaiseEvent=$control; + } + + public function getApplication() + { + return $this->_application; + } + + public function loadStateField() + { + return base64_decode($this->_postData->itemAt('__STATE')); + } + + public function saveStateField($state) + { + $this->getClientScript()->registerHiddenField('__STATE',base64_encode($state)); + } + + protected function determinePostBackMode() + { + /* + $application=$this->getApplication(); + if($application->getPreventPostBack()) + return null; + */ + $postData=new TMap($this->_application->getRequest()->getItems()); + if($postData->itemAt('__STATE')!==null || $postData->itemAt('__EVENTTARGET')!==null) + return $postData; + else + return null; + } + + final public function getIsPostBack() + { + if($this->_postData) + { + if($this->_isCrossPagePostBack) + return true; + if($this->_previousPagePath!=='') + return false; + return !$this->_pageStateChanged; + } + else + return false; + } + + protected function getPageStatePersister() + { + require_once(PRADO_DIR.'/Web/UI/THiddenFieldPageStatePersister.php'); + return new THiddenFieldPageStatePersister($this); + } + + protected function loadPageState() + { + $persister=$this->getPageStatePersister(); + $state=$persister->load(); + $this->loadStateRecursive($state,$this->getEnableViewState()); + } + + protected function savePageState() + { + $state=&$this->saveStateRecursive($this->getEnableViewState()); + $persister=$this->getPageStatePersister(); + $persister->save($state); + } + + protected function processPostData($postData,$beforeLoad) + { + $eventTarget=$postData->itemAt('__EVENTTARGET'); + foreach($postData as $key=>$value) + { + if(in_array($key,self::$_systemPostFields)) + continue; + else if($control=$this->findControl($key)) + { + if($control instanceof IPostBackDataHandler) + { + if($control->loadPostData($key,$this->_postData)) + $this->_changedPostDataConsumers[]=$control; + unset($this->_controlsRequiringPostBack[$key]); + } + else + { + if(empty($eventTarget)) + { + if($control instanceof IPostBackEventHandler) + $this->registerRequiresRaiseEvent($control); + } + else + unset($this->_controlsRequiringPostBack[$key]); + } + } + else if($beforeLoad) + $this->_restPostData->add($key,$value); + } + $list=new TMap; + foreach($this->_controlsRequiringPostBack as $key=>$value) + { + if($control=$this->findControl($key)) + { + if($control instanceof IPostBackDataHandler) + { + if($control->loadPostData($key,$this->_postData)) + $this->_changedPostDataConsumers->add($control); + } + else + throw new THttpException('postback_control_not_found',$key); + } + else if($beforeLoad) + $list->add($key,null); + } + $this->_controlsRequiringPostBack=$list; + } + + final public function getAutoPostBackControl() + { + return $this->_autoPostBackControl; + } + + final public function setAutoPostBackControl($control) + { + $this->_autoPostBackControl=$control; + } + + private function raiseChangedEvents() + { + foreach($this->_changedPostDataConsumers as $control) + $control->raisePostDataChangedEvent(); + } + + private function raisePostBackEvent($postData) + { + if($this->_registeredControlThatRequireRaiseEvent) + { + $this->_registeredControlThatRequireRaiseEvent->raisePostBackEvent(null); + } + else + { + $eventTarget=$postData->itemAt('__EVENTTARGET'); + if(!empty($eventTarget) || $this->getAutoPostBackControl()) + { + if(!empty($eventTarget)) + $control=$this->findControl($eventTarget); + else + $control=null; + if($control instanceof IPostBackEventHandler) + $control->raisePostBackEvent($postData->itemAt('__EVENTPARAM')); + } + else + $this->validate(); + } + } + + public function run($writer) + { + $this->_postData=$this->determinePostBackMode(); + $this->_restPostData=new TMap; + + $this->onPreInit(null); + $this->initializeThemes(); + $this->_preInitWorkComplete=true; + + $this->initRecursive(null); + $this->onInitComplete(null); + + if($this->getIsPostBack()) + { + $this->loadPageState(); + $this->processPostData($this->_postData,true); + } + + $this->onPreLoad(null); + $this->loadRecursive(null); + if($this->getIsPostBack()) + { + $this->processPostData($this->_restPostData,false); + $this->raiseChangedEvents(); + $this->raisePostBackEvent($this->_postData); + } + $this->onLoadComplete(null); + + $this->preRenderRecursive(); + $this->onPreRenderComplete(null); + + $this->savePageState(); + $this->onSaveStateComplete(null); + + $this->renderControl($writer); + $this->unloadRecursive(); + } + + public function getTheme() + { + return $this->_themeName; + } + + public function setTheme($value) + { + $this->_themeName=$value; + } + + public function getStyleSheetTheme() + { + return $this->_styleSheetName; + } + + public function setStyleSheetTheme($value) + { + $this->_styleSheetName=$value; + } + + public function getContainsTheme() + { + return $this->_theme!==null; + } +} + +?> \ No newline at end of file diff --git a/framework/Web/UI/TPageStatePersister.php b/framework/Web/UI/TPageStatePersister.php new file mode 100644 index 00000000..6ec9527b --- /dev/null +++ b/framework/Web/UI/TPageStatePersister.php @@ -0,0 +1,22 @@ +_page=$page; + } + + public function getPage() + { + return $this->_page; + } + + abstract public function load(); + + abstract public function save($state); +} + +?> \ No newline at end of file diff --git a/framework/Web/UI/TPostBackOptions.php b/framework/Web/UI/TPostBackOptions.php new file mode 100644 index 00000000..ee615d0b --- /dev/null +++ b/framework/Web/UI/TPostBackOptions.php @@ -0,0 +1,36 @@ +ActionUrl=$actionUrl; + $this->AutoPostBack=$autoPostBack; + $this->ClientSubmit=$clientSubmit; + $this->PerformValidation=$performValidation; + $this->RequiresJavaScriptProtocol=$requiresJavaScriptProtocol; + $this->TargetControl=$targetControl; + $this->TrackFocus=$trackFocus; + $this->ValidationGroup=$validationGroup; + } +} + +?> \ No newline at end of file diff --git a/framework/Web/UI/TTemplate.php b/framework/Web/UI/TTemplate.php new file mode 100644 index 00000000..df9bf813 --- /dev/null +++ b/framework/Web/UI/TTemplate.php @@ -0,0 +1,494 @@ + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Revision: $ $Date: $ + * @package System.Web.UI + */ + +/** + * TTemplate implements PRADO template parsing logic. + * A TTemplate object represents a parsed PRADO control template. + * It can instantiate the template as child controls of a specified control. + * The template format is like HTML, with the following special tags introduced, + * - component tags: a component tag represents the configuration of a component. + * The tag name is in the format of com:ComponentType, where ComponentType is the component + * class name. Component tags must be well-formed. Attributes of the component tag + * are treated as either property initial values, event handler attachment, or regular + * tag attributes. + * - property tags: property tags are used to set large block of attribute values. + * The property tag name is in the format of prop:AttributeName, where AttributeName + * can be a property name, an event name or a regular tag attribute name. + * - directive: directive specifies the property values for the template owner. + * It is in the format of <% properyt name-value pairs %> + * - expressions: expressions are shorthand of {@link TExpression} and {@link TStatements} + * controls. They are in the formate of <= PHP expression > and < PHP statements > + * - comments: There are two kinds of comments, regular HTML comments and special template comments. + * The former is in the format of <!-- comments -->, which will be treated as text strings. + * The latter is in the format of <%* comments %>, which will be stripped out. + * + * Tags are not required to be well-formed. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web.UI + * @since 3.0 + */ +class TTemplate extends TComponent implements ITemplate +{ + /** + * '' - template comments + * '' - HTML comments + * '<\/?com:([\w\.]+)((?:\s*[\w\.]+=\'.*?\'|\s*[\w\.]+=".*?"|\s*[\w\.]+=<%.*?%>)*)\s*\/?>' - component tags + * '<\/?prop:([\w\.]+)\s*>' - property tags + * '<%@\s*(\w+)((?:\s*[\w\.]+=\'.*?\'|\s*[\w\.]+=".*?")*)\s*%>' - directives + * '<%=?(.*?)%>' - expressions + */ + const REGEX_RULES='/||<\/?com:([\w\.]+)((?:\s*[\w\.]+=\'.*?\'|\s*[\w\.]+=".*?"|\s*[\w\.]+=<%.*?%>)*)\s*\/?>|<\/?prop:([\w\.]+)\s*>|<%@\s*((?:\s*[\w\.]+=\'.*?\'|\s*[\w\.]+=".*?")*)\s*%>|<%=?(.*?)%>/msS'; + + /** + * @var array list of component tags and strings + */ + private $_tpl=array(); + /** + * @var array list of directive settings + */ + private $_directive=array(); + + /** + * Constructor. + * The template will be parsed after construction. + * @param string the template string + */ + public function __construct($template) + { + $this->parse($template); + } + + /** + * @return array name-value pairs declared in the directive + */ + public function getDirective() + { + return $this->_directive; + } + + /** + * Instantiates the template. + * Content in the template will be instantiated as components and text strings + * and passed to the specified parent control. + * @param TControl the parent control + * @throws TTemplateRuntimeException if an error is encountered during the instantiation. + */ + public function instantiateIn($tplControl) + { + $page=$tplControl->getPage(); + $controls=array(); + foreach($this->_tpl as $key=>$object) + { + if(isset($object[2])) // component + { + if(strpos($object[1],'.')===false) + $component=new $object[1]; + else + $component=Prado::createComponent($object[1]); + if($component instanceof TControl) + { + $controls[$key]=$component; + $component->setTemplateControl($tplControl); + if(isset($object[2]['id'])) + $tplControl->registerObject($object[2]['id'],$component); + if(isset($object[2]['skinid'])) + { + $component->setSkinID($object[2]['skinid']); + unset($object[2]['skinid']); + } + $component->applyStyleSheetSkin($page); + // apply attributes + foreach($object[2] as $name=>$value) + { + if($component->hasEvent($name)) // is an event + { + if(is_string($value)) + { + if(strpos($value,'.')===false) + $component->attachEventHandler($name,array($component,'TemplateControl.'.$value)); + else + $component->attachEventHandler($name,array($component,$value)); + } + else + throw new TTemplateRuntimeException('template_event_invalid',$name); + } + else if(strpos($name,'.')===false) // is simple property or custom attribute + { + if($component->hasProperty($name)) + { + if($component->canSetProperty($name)) + { + $setter='set'.$name; + if(is_string($value)) + $component->$setter($value); + else if($value[0]===0) + $component->bindProperty($name,$value[1]); + else + $component->$setter($component->evaluateExpression($value[1])); + } + else + throw new TTemplateRuntimeException('property_read_only',get_class($component).'.'.$name); + } + else if($component->getAllowCustomAttributes()) + $component->getAttributes()->add($name,$value); + else + throw new TTemplateRuntimeException('property_not_defined',get_class($component).'.'.$name); + } + else // complex property + { + if(is_string($value)) + $component->setPropertyByPath($name,$value); + else if($value[0]===0) + $component->bindProperty($name,$value[1]); + else + $component->setPropertyByPath($component->evaluateExpression($value[1])); + } + } + $parent=isset($controls[$object[0]])?$controls[$object[0]]:$tplControl; + $component->createdOnTemplate($parent); + } + else if($component instanceof TComponent) + { + if(isset($object[2]['id'])) + { + $tplControl->registerObject($object[2]['id'],$component); + if(!$component->hasProperty('id')) + unset($object[2]['id']); + } + foreach($object[2] as $name=>$value) + { + if($component->hasProperty($name)) + { + if($component->canSetProperty($name)) + { + $setter='set'.$name; + if(is_string($value)) + $component->$setter($value); + else if($value[0]===1) + $component->$setter($component->evaluateExpression($value[1])); + else + throw new TTemplateRuntimeException('template_component_property_unbindable',get_class($component),$name); + } + else + throw new TTemplateRuntimeException('property_read_only',get_class($component).'.'.$name); + } + else + throw new TTemplateRuntimeException('property_not_defined',get_class($component).'.'.$name); + } + $parent=isset($controls[$object[0]])?$controls[$object[0]]:$tplControl; + $parent->addParsedObject($component); + } + else + throw new TTemplateRuntimeException('must_be_component',$object[1]); + } + else // string + { + if(isset($controls[$object[0]])) + $controls[$object[0]]->addParsedObject($object[1]); + else + $tplControl->addParsedObject($object[1]); + } + } + } + + /** + * NOTE, this method is currently not used!!! + * Processes an attribute set in a component tag. + * The attribute will be checked to see if it represents a property or an event. + * If so, the value will be set to the property, or the value will be treated + * as an event handler and attached to the event. + * Otherwise, it will be added as regualr attribute if the control allows so. + * @param TComponent the component represented by the tag + * @param string attribute name + * @param string attribute value + * @throws TTemplateRuntimeException + */ + public static function applyAttribute($component,$name,$value) + { + $target=$component; + if(strpos($name,'.')===false) + $property=$name; + else + { + $names=explode('.',$name); + $property=array_pop($names); + foreach($names as $p) + { + if(($target instanceof TComponent) && $target->canGetProperty($p)) + { + $getter='get'.$p; + $target=$target->$getter(); + } + else + throw new TTemplateRuntimeException('invalid_subproperty',$name); + } + } + if($target instanceof TControl) + { + if($target->hasProperty($property)) + { + $setter='set'.$property; + if(is_string($value)) + $target->$setter($value); + else if($value[0]===0) + $target->bindProperty($property,$value[1]); + else + { + $target->$setter($target->evaluateExpression($value[1])); + } + } + else if($target->hasEvent($property)) + { + if(strpos($value,'.')===false) + $target->attachEventHandler($property,'TemplateControl.'.$value); + else + $target->attachEventHandler($property,$value); + } + else if($target->getAllowCustomAttributes()) + $target->getAttributes()->add($property,$value); + else + throw new TTemplateRuntimeException('property_not_defined',get_class($target).'.'.$property); + } + else if($target instanceof TComponent) + { + $setter='set'.$property; + $target->$setter($value); + } + else + throw new TTemplateRuntimeException('must_extend_TComponent',get_class($target)); + } + + /** + * Parses a template string. + * + * This template parser recognizes five types of data: + * regular string, well-formed component tags, well-formed property tags, directives, and expressions. + * + * The parsing result is returned as an array. Each array element can be of three types: + * - a string, 0: container index; 1: string content; + * - a component tag, 0: container index; 1: component type; 2: attributes (name=>value pairs) + * If a directive is found in the template, it will be parsed and can be + * retrieved via {@link getDirective}, which returns an array consisting of + * name-value pairs in the directive. + * + * Note, attribute names are treated as case-insensitive and will be turned into lower cases. + * Component and directive types are case-sensitive. + * Container index is the index to the array element that stores the container object. + * If an object has no container, its container index is -1. + * + * @param string the template string + * @return array the parsed result + * @throws TTemplateParsingException if a parsing error is encountered + */ + protected function &parse($input) + { + $tpl=&$this->_tpl; + $n=preg_match_all(self::REGEX_RULES,$input,$matches,PREG_SET_ORDER|PREG_OFFSET_CAPTURE); + $expectPropEnd=false; + $textStart=0; + $stack=array(); + $container=-1; + $c=0; + for($i=0;$i<$n;++$i) + { + $match=&$matches[$i]; + $str=$match[0][0]; + $matchStart=$match[0][1]; + $matchEnd=$matchStart+strlen($str)-1; + if(strpos($str,'$textStart) + $tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart)); + $textStart=$matchEnd+1; + $type=$match[1][0]; + $attributes=$this->parseAttributes($match[2][0]); + $tpl[$c++]=array($container,$type,$attributes); + if($str[strlen($str)-2]!=='/') // open tag + { + array_push($stack,$type); + $container=$c-1; + } + } + else if(strpos($str,'$textStart) + $tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart)); + $textStart=$matchEnd+1; + $type=$match[1][0]; + + if(empty($stack)) + { + $line=count(explode("\n",substr($input,0,$matchEnd+1))); + throw new TTemplateParsingException('unexpected_closing_tag',$line,""); + } + + $name=array_pop($stack); + if($name!==$type) + { + if($name[0]==='@') + $tag=''; + else + $tag=''; + $line=count(explode("\n",substr($input,0,$matchEnd+1))); + throw new TTemplateParsingException('expecting_closing_tag',$line,$tag); + } + $container=$tpl[$container][0]; + } + else if(strpos($str,'<%@')===0) // directive + { + if($expectPropEnd) + continue; + if($matchStart>$textStart) + $tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart)); + $textStart=$matchEnd+1; + if(isset($tpl[0])) + { + $line=count(explode("\n",substr($input,0,$matchEnd+1))); + throw new TTemplateParsingException('nonunique_template_directive',$line); + } + $this->_directive=$this->parseAttributes($match[4][0]); + } + else if(strpos($str,'<%')===0) // expression + { + if($expectPropEnd) + continue; + if($matchStart>$textStart) + $tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart)); + $textStart=$matchEnd+1; + if($str[2]==='=') + $tpl[$c++]=array($container,'TExpression',array('Expression'=>$match[5][0])); + else + $tpl[$c++]=array($container,'TStatements',array('Statements'=>$match[5][0])); + } + else if(strpos($str,'$textStart) + $tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart)); + $textStart=$matchEnd+1; + $expectPropEnd=true; + } + } + else if(strpos($str,'"); + } + $name=array_pop($stack); + if($name!=='@'.$prop) + { + if($name[0]==='@') + $tag=''; + else + $tag=''; + $line=count(explode("\n",substr($input,0,$matchEnd+1))); + throw new TTemplateParsingException('expecting_closing_tag',$line,$tag); + } + if(($last=count($stack))<1 || $stack[$last-1][0]!=='@') + { + if($matchStart>$textStart && $container>=0) + { + $value=substr($input,$textStart,$matchStart-$textStart); + if(preg_match('/^<%.*?%>$/msS',$value)) + { + if($value[2]==='#') // databind + $tpl[$container][2][$prop]=array(0,substr($value,3,strlen($value)-5)); + else if($value[2]==='=') // a dynamic initialization + $tpl[$container][2][$prop]=array(1,substr($value,3,strlen($value)-5)); + else + $tpl[$container][2][$prop]=$value; + } + else + $tpl[$container][2][$prop]=$value; + $textStart=$matchEnd+1; + } + $expectPropEnd=false; + } + } + else if(strpos($str,'