From 5c0517b7748dcfae1264d28df7ea111a67bd3aa4 Mon Sep 17 00:00:00 2001 From: xue <> Date: Thu, 22 Dec 2005 21:22:22 +0000 Subject: --- framework/Web/UI/WebControls/TDataBoundControl.php | 192 +++++++++++++-------- framework/Web/UI/WebControls/TListBox.php | 58 ++++++- framework/Web/UI/WebControls/TListControl.php | 63 ++++++- framework/Web/UI/WebControls/TWebControl.php | 2 +- 4 files changed, 234 insertions(+), 81 deletions(-) (limited to 'framework') diff --git a/framework/Web/UI/WebControls/TDataBoundControl.php b/framework/Web/UI/WebControls/TDataBoundControl.php index 032370f7..8f447607 100644 --- a/framework/Web/UI/WebControls/TDataBoundControl.php +++ b/framework/Web/UI/WebControls/TDataBoundControl.php @@ -6,8 +6,11 @@ abstract class TDataBoundControl extends TWebControl private $_dataSource=null; private $_requiresBindToNull=false; private $_requiresDataBinding=false; - private $_throwOnDataPropertyChange=false; private $_prerendered=false; + private $_currentView=null; + private $_currentDataSource=null; + private $_currentViewValid=false; + private $_currentDataSourceValid=false; /** * @return Traversable data source object, defaults to null. @@ -18,14 +21,16 @@ abstract class TDataBoundControl extends TWebControl } /** + * Sets the data source object associated with the databound control. + * The data source must implement Traversable interface. + * If an array is given, it will be converted to xxx. + * If a string is given, it will be converted to xxx. * @param Traversable|array|string data source object */ public function setDataSource($value) { - if($value!==null) - $this->validateDataSource($value); - $this->_dataSource=$value; - $this->onDataPropertyChanged(); + $this->_dataSource=$this->validateDataSource($value);; + $this->onDataSourceChanged(); } /** @@ -46,22 +51,34 @@ abstract class TDataBoundControl extends TWebControl if($dsid!=='' && $value==='') $this->_requiresBindToNull=true; $this->setViewState('DataSourceID',$value,''); - $this->onDataPropertyChanged(); + $this->onDataSourceChanged(); } /** + * @return boolean if the databound control uses the data source specified + * by {@link setDataSourceID}, or it uses the data source object specified + * by {@link setDataSource}. + */ + protected function getUsingDataSourceID() + { + return $this->getDataSourceID()!==''; + } + + /** + * Sets {@link setRequiresDataBinding RequiresDataBinding} as true if the control is initialized. * This method is invoked when either {@link setDataSource} or {@link setDataSourceID} is changed. */ - protected function onDataPropertyChanged() + protected function onDataSourceChanged() { - if($this->_throwOnDataPropertyChanged) - throw new TInvalidOperationException('databoundcontrol_dataproperty_unchangeable'); + $this->_currentViewValid=false; + $this->_currentDataSourceValid=false; if($this->getInitialized()) $this->setRequiresDataBinding(true); } /** * @return boolean whether the databound control has been initialized. + * By default, the control is initialized after its viewstate has been restored. */ protected function getInitialized() { @@ -69,6 +86,10 @@ abstract class TDataBoundControl extends TWebControl } /** + * Sets a value indicating whether the databound control is initialized. + * If initialized, any modification to {@link setDataSource DataSource} or + * {@link setDataSourceID DataSourceID} will set {@link setRequiresDataBinding RequiresDataBinding} + * as true. * @param boolean a value indicating whether the databound control is initialized. */ protected function setInitialized($value) @@ -77,13 +98,19 @@ abstract class TDataBoundControl extends TWebControl } /** - * @return boolean if the databound control uses the data source control specified - * by {@link setDataSourceID}, or it uses the data source object specified - * by {@link setDataSource}. + * @return boolean if databind has been invoked in the previous page request */ - protected function getUsingDataSourceID() + protected function getIsDataBound() { - return $this->getDataSourceID()!==''; + return $this->getViewState('IsDataBound',false); + } + + /** + * @param boolean if databind has been invoked in this page request + */ + protected function setIsDataBound($value) + { + $this->setViewState('IsDataBound',TPropertyValue::ensureBoolean($value),false); } /** @@ -103,7 +130,7 @@ abstract class TDataBoundControl extends TWebControl protected function setRequiresDataBinding($value) { $value=TPropertyValue::ensureBoolean($value); - if($value && $this->_prerendered && $this->getUsingDataSourceID()) + if($value && $this->_prerendered) { $this->_requiresDataBinding=true; $this->ensureDataBound(); @@ -112,42 +139,89 @@ abstract class TDataBoundControl extends TWebControl $this->_requiresDataBinding=$value; } + /** + * Ensures any pending {@link dataBind} is called. + * This method calls {@link dataBind} if the data source is specified + * by {@link setDataSourceID} or if {@link getRequiresDataBinding RequiresDataBinding} + * is true. + */ + protected function ensureDataBound() + { + if($this->_requiresDataBinding && ($this->getUsingDataSourceID() || $this->_requiresBindToNull)) + { + $this->dataBind(); + $this->_requiresBindToNull=false; + } + } + /** * Performs databinding. * This method overrides the parent implementation by calling * {@link performSelect} which fetches data from data source and does * the actual binding work. - * @param boolean whether to raise DataBind event. This parameter is ignored. */ - public function dataBind($raiseDataBindingEvent=true) + public function dataBind() { - $this->performSelect(); + // TODO: databinding should only be raised after data is ready + // what about property bindings? should they be after data is ready? + $this->setRequiresDataBinding(false); + $this->dataBindProperties(); + $view=$this->getDataSourceView(); + $data=$view->select($this->getSelectParameters()); + $this->onDataBinding(null); + $this->performDataBinding($data); + $this->setIsDataBound(true); + $this->onDataBound(null); } - /** - * Ensures any pending {@link dataBind} is called. - * This method calls {@link dataBind} if the data source is specified - * by {@link setDataSourceID} or if {@link getRequiresDataBinding RequiresDataBinding} - * is true. - */ - protected function ensureDataBound() + public function dataSourceViewChanged($sender,$param) + { + if(!$this->_ignoreDataSourceViewChanged) + $this->setRequiresDataBinding(true); + } + + protected function getDataSourceView() { - try + if(!$this->_currentViewValid) { - $this->_throwOnDataPropertyChange=true; - if($this->_requiresDataBinding && ($this->getUsingDataSourceID() || $this->_requiresBindToNull)) - { - $this->dataBind(); - $this->_requiresBindToNull=false; - } + if($this->_currentView && $this->_currentViewIsFromDataSourceID) + $handlers=$this->_currentView->detachEventHandler('DataSourceViewChanged',array($this,'dataSourceViewChanged')); + $dataSource=$this->determineDataSource(); + if(($view=$dataSource->getView($this->getDataMember()))===null) + throw new TInvalidDataValueException('databoundcontrol_datamember_invalid',$this->getDataMember()); + if($this->_currentViewIsFromDataSourceID=$this->getUsingDataSourceID()) + $view->attachEventHandler('DataSourceViewChanged',array($this,'dataSourceViewChanged')); + $this->_currentView=$view; + $this->_currentViewValid=true; } - catch(Exception $e) + return $this->_currentView; + } + + protected function determineDataSource() + { + if(!$this->_currentDataSourceValid) { - $this->_throwOnDataPropertyChange=false; - throw $e; + $dsid=$this->getDataSourceID(); + if($dsid!=='') + { + if(($dataSource=$this->getNamingContainer()->findControl($dsid))===null) + throw new TInvalidDataValueException('databoundcontrol_datasourceid_inexistent',$dsid); + else if(!($dataSource instanceof IDataSource)) + throw new TInvalidDataValueException('databoundcontrol_datasourceid_invalid',$dsid); + else + $this->_currentDataSource=$dataSource; + } + else + { + $this->_currentDataSource=new TReadOnlyDataSource($this->getDataSource(),$this->getDataMember()); + } + $this->_currentDataSourceValid=true; } + return $this->_currentDataSource; } + abstract protected function performDataBinding($data); + /** * Raises DataBound event. * This method should be invoked after a databind is performed. @@ -170,8 +244,6 @@ abstract class TDataBoundControl extends TWebControl parent::onInit($param); $page=$this->getPage(); $page->attachEventHandler('PreLoad',array($this,'onPagePreLoad')); - if(!$this->getEnableViewState(true) && $page->getIsPostBack()) - $this->setRequiresDataBinding(true); } /** @@ -183,6 +255,9 @@ abstract class TDataBoundControl extends TWebControl protected function onPagePreLoad($sender,$param) { $this->_initialized=true; + $isPostBack=$this->getPage()->getIsPostBack(); + if(!$isPostBack || ($isPostBack && (!$this->getEnableViewState(true) || !$this->getIsDataBound()))) + $this->setRequiresDataBinding(true); } /** @@ -199,39 +274,17 @@ abstract class TDataBoundControl extends TWebControl /** * Validates if the parameter is a valid data source. - * @return boolean if the parameter is a valid data source + * If it is a string or an array, it will be converted as a TList object. + * @return Traversable the data that is traversable + * @throws TInvalidDataTypeException if the data is neither null nor Traversable */ protected function validateDataSource($value) { - if(!is_array($value) && !($value instanceof Traversable)) + if(is_array($value)) + $value=new TList($value); + else if($value!==null && !($value instanceof Traversable)) throw new TInvalidDataTypeException('databoundcontrol_datasource_invalid'); - } - - /** - * @return ??? - */ - protected function performSelect() - { - if(!$this->getUsingDataSourceID()) - $this->onDataBinding(null); - $view=$this->getDataSourceView(); - $this->setRequiresDataBinding(false); - $this->setDataBound(true); - $data=$view->select($this->getSelectParameters()); - if($this->getUsingDataSourceID()) - $this->onDataBinding(null); - $this->performDataBinding($data); - $this->onDataBound(null); - } - - protected function getDataSourceView() - { - $source=$this->getDataSourceByID(); - return $source->getView($this->getDataMember()); - } - - protected function performDataBinding($data) - { + return $value; } public function getDataMember() @@ -247,14 +300,9 @@ abstract class TDataBoundControl extends TWebControl public function getSelectParameters() { if(!$this->_parameters) - $this->_parameters=$this->createSelectParameters(); + $this->_parameters=new TDataSourceSelectParameters; return $this->_parameters; } - - protected function createSelectParameters() - { - return new TDataSourceSelectParameters; - } } ?> \ No newline at end of file diff --git a/framework/Web/UI/WebControls/TListBox.php b/framework/Web/UI/WebControls/TListBox.php index 79afc133..4aabbbac 100644 --- a/framework/Web/UI/WebControls/TListBox.php +++ b/framework/Web/UI/WebControls/TListBox.php @@ -6,14 +6,17 @@ class TListBox extends TListControl implements IPostBackDataHandler { $rows=$this->getRows(); $writer->addAttribute('size',"$rows"); - $writer->addAttribute('name',$this->getUniqueID()); + if($this->getSelectionMode()==='Multiple') + $writer->addAttribute('name',$this->getUniqueID().'[]'); + else + $writer->addAttribute('name',$this->getUniqueID()); parent::addAttributesToRender($writer); } protected function onPreRender($param) { parent::onPreRender($param); - if($this->getSelectionMode()==='Multiple' && $this->getEnabled(true)) + if($this->getEnabled(true)) $this->getPage()->registerRequiresPostData($this); } @@ -21,7 +24,56 @@ class TListBox extends TListControl implements IPostBackDataHandler { if(!$this->getEnabled(true)) return false; - // ensure DataBound??? + $selections=isset($values[$key])?$values[$key]:null; + $this->ensureDataBound(); + if($selections!==null) + { + $items=$this->getItems(); + if($this->getSelectionMode()==='Single') + { + $selection=is_array($selections)?$selections[0]:$selections; + $index=$items->findIndexByValue($selection,false); + if($this->getSelectedIndex()!==$index) + { + $this->setSelectedIndex($index); + return true; + } + else + return false; + } + if(!is_array($selections)) + $selections=array($selections); + $list=array(); + foreach($selections as $selection) + $list[]=$items->findIndexByValue($selection,false); + $list2=$this->getSelectedIndices(); + $n=count($list); + $flag=false; + if($n===count($list2)) + { + sort($list,SORT_NUMERIC); + for($i=0;$i<$n;++$i) + { + if($list[$i]!==$list2[$i]) + { + $flag=true; + break; + } + } + } + else + $flag=true; + if($flag) + $this->setSelectedIndices($list); + return $flag; + } + else if($this->getSelectedIndex()!==-1) + { + $this->clearSelection(); + return true; + } + else + return false; } public function raisePostDataChangedEvent() diff --git a/framework/Web/UI/WebControls/TListControl.php b/framework/Web/UI/WebControls/TListControl.php index c965ddb0..7e81fd6c 100644 --- a/framework/Web/UI/WebControls/TListControl.php +++ b/framework/Web/UI/WebControls/TListControl.php @@ -54,9 +54,47 @@ abstract class TListControl extends TDataBoundControl $this->getItems()->add($object); } + protected function validateDataSource($value) + { + if(is_string($value)) + { + $list=new TList; + foreach(TPropertyValue::ensureArray($value) as $key=>$value) + $list->add(array($value,is_string($key)?$key:$value)); + return $list; + } + else + return parent::validateDataSource($value); + return $value; + } + protected function performDataBinding($data) { - // TODO; + if($data instanceof Traversable) + { + $textField=$this->getDataTextField(); + if($textField==='') + $textField=0; + $valueField=$this->getDataValueField(); + if($valueField==='') + $valueField=1; + $textFormat=$this->getDataTextFormatString(); + $items=$this->getItems(); + if(!$this->getAppendDataBoundItems()) + $items->clear(); + foreach($data as $object) + { + $item=new TListItem; + if(isset($object[$textField])) + $text=$object[$textField]; + else + $text=TPropertyValue::ensureString($object); + $item->setText($textFormat===''?$text:sprintf($textFormat,$text)); + if(isset($object[$valueField])) + $item->setValue($object[$valueField]); + $items->add($item); + } + } } protected function onSaveState($param) @@ -204,7 +242,7 @@ abstract class TListControl extends TDataBoundControl $index=TPropertyValue::ensureInteger($index); if($this->_items) { - $this->_items->clearSelection(); + $this->clearSelection(); if($index>=0 && $index<$this->_items->getCount()) $this->_items->itemAt($index)->setSelected(true); } @@ -223,6 +261,20 @@ abstract class TListControl extends TDataBoundControl return $selections; } + protected function setSelectedIndices($indices) + { + if($this->_items) + { + $this->clearSelection(); + $n=$this->_items->getCount(); + foreach($indices as $index) + { + if($index>=0 && $index<$n) + $this->_items->itemAt($index)->setSelected(true); + } + } + } + /** * @return TListItem|null the selected item with the lowest cardinal index, null if no selection. */ @@ -308,6 +360,7 @@ abstract class TListControl extends TDataBoundControl $this->raiseEvent('SelectedIndexChanged',$this,$param); } + // ???? public function onTextChanged($param) { $this->raiseEvent('TextChanged',$this,$param); @@ -351,12 +404,12 @@ class TListItemCollection extends TList throw new TInvalidDataTypeException('listitemcollection_item_invalid'); } - public function addAt($index,$item) + public function insert($index,$item) { if(is_string($item)) - parent::addAt($index,new TListItem($item)); + parent::insert($index,new TListItem($item)); else if($item instanceof TListItem) - parent::addAt($index,$item); + parent::insert($index,$item); else throw new TInvalidDataTypeException('listitemcollection_item_invalid'); } diff --git a/framework/Web/UI/WebControls/TWebControl.php b/framework/Web/UI/WebControls/TWebControl.php index 33f35c72..4f20b227 100644 --- a/framework/Web/UI/WebControls/TWebControl.php +++ b/framework/Web/UI/WebControls/TWebControl.php @@ -28,7 +28,7 @@ Prado::using('System.Web.UI.WebControls.TStyle'); * such as {@link getBackColor BackColor}, {@link getBorderWidth BorderWidth}, etc. * * Subclasses of TWebControl typically needs to override {@link addAttributesToRender} - * and {@link renderContent}. The former is used to render the attributes + * and {@link renderContents}. The former is used to render the attributes * of the HTML tag associated with the control, while the latter is to render * the body contents enclosed within the HTML tag. * -- cgit v1.2.3