From 7fa1200b5e589a47884aa4d62630ce9099fccee1 Mon Sep 17 00:00:00 2001
From: wei <>
Date: Tue, 30 Jan 2007 11:36:13 +0000
Subject: Add basic Scaffold view for Active Record
---
.gitattributes | 18 ++
demos/quickstart/protected/controls/TopicList.tpl | 1 +
.../pages/Database/Samples/Scaffold/Home.page | 7 +
.../pages/Database/Samples/Scaffold/Home.php | 33 +++
.../pages/Database/Samples/Scaffold/sqlite.db | Bin 0 -> 4096 bytes
.../protected/pages/Database/Samples/config.xml | 5 +
.../protected/pages/Database/Scaffold.page | 99 ++++++++
.../Scaffold/InputBuilder/TMysqlScaffoldInput.php | 75 ++++++
.../Scaffold/InputBuilder/TPgsqlScaffoldInput.php | 48 ++++
.../Scaffold/InputBuilder/TScaffoldInputBase.php | 87 +++++++
.../Scaffold/InputBuilder/TScaffoldInputCommon.php | 280 +++++++++++++++++++++
.../Scaffold/InputBuilder/TSqliteScaffoldInput.php | 93 +++++++
.../Data/ActiveRecord/Scaffold/TScaffoldBase.php | 113 +++++++++
.../ActiveRecord/Scaffold/TScaffoldEditView.php | 119 +++++++++
.../ActiveRecord/Scaffold/TScaffoldEditView.tpl | 18 ++
.../ActiveRecord/Scaffold/TScaffoldListView.php | 172 +++++++++++++
.../ActiveRecord/Scaffold/TScaffoldListView.tpl | 56 +++++
.../Data/ActiveRecord/Scaffold/TScaffoldView.php | 72 ++++++
.../Data/ActiveRecord/Scaffold/TScaffoldView.tpl | 7 +
framework/Data/ActiveRecord/Scaffold/style.css | 123 +++++++++
.../Vendor/TPgsqlMetaDataInspector.php | 2 +-
21 files changed, 1427 insertions(+), 1 deletion(-)
create mode 100644 demos/quickstart/protected/pages/Database/Samples/Scaffold/Home.page
create mode 100644 demos/quickstart/protected/pages/Database/Samples/Scaffold/Home.php
create mode 100644 demos/quickstart/protected/pages/Database/Samples/Scaffold/sqlite.db
create mode 100644 demos/quickstart/protected/pages/Database/Samples/config.xml
create mode 100644 demos/quickstart/protected/pages/Database/Scaffold.page
create mode 100644 framework/Data/ActiveRecord/Scaffold/InputBuilder/TMysqlScaffoldInput.php
create mode 100644 framework/Data/ActiveRecord/Scaffold/InputBuilder/TPgsqlScaffoldInput.php
create mode 100644 framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputBase.php
create mode 100644 framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputCommon.php
create mode 100644 framework/Data/ActiveRecord/Scaffold/InputBuilder/TSqliteScaffoldInput.php
create mode 100644 framework/Data/ActiveRecord/Scaffold/TScaffoldBase.php
create mode 100644 framework/Data/ActiveRecord/Scaffold/TScaffoldEditView.php
create mode 100644 framework/Data/ActiveRecord/Scaffold/TScaffoldEditView.tpl
create mode 100644 framework/Data/ActiveRecord/Scaffold/TScaffoldListView.php
create mode 100644 framework/Data/ActiveRecord/Scaffold/TScaffoldListView.tpl
create mode 100644 framework/Data/ActiveRecord/Scaffold/TScaffoldView.php
create mode 100644 framework/Data/ActiveRecord/Scaffold/TScaffoldView.tpl
create mode 100644 framework/Data/ActiveRecord/Scaffold/style.css
diff --git a/.gitattributes b/.gitattributes
index 97551364..651baea6 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1133,6 +1133,11 @@ demos/quickstart/protected/pages/Controls/Wizard.page -text
demos/quickstart/protected/pages/Controls/wizard.gif -text
demos/quickstart/protected/pages/Database/ActiveRecord.page -text
demos/quickstart/protected/pages/Database/DAO.page -text
+demos/quickstart/protected/pages/Database/Samples/Scaffold/Home.page -text
+demos/quickstart/protected/pages/Database/Samples/Scaffold/Home.php -text
+demos/quickstart/protected/pages/Database/Samples/Scaffold/sqlite.db -text
+demos/quickstart/protected/pages/Database/Samples/config.xml -text
+demos/quickstart/protected/pages/Database/Scaffold.page -text
demos/quickstart/protected/pages/Database/SqlMap.page -text
demos/quickstart/protected/pages/Database/diagram.png -text
demos/quickstart/protected/pages/Database/object_states.png -text
@@ -1441,6 +1446,19 @@ framework/Configuration/Provider/TProviderException.php -text
framework/Configuration/TProtectedConfiguration.php -text
framework/Data/ActiveRecord/Exceptions/TActiveRecordException.php -text
framework/Data/ActiveRecord/Exceptions/messages.txt -text
+framework/Data/ActiveRecord/Scaffold/InputBuilder/TMysqlScaffoldInput.php -text
+framework/Data/ActiveRecord/Scaffold/InputBuilder/TPgsqlScaffoldInput.php -text
+framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputBase.php -text
+framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputCommon.php -text
+framework/Data/ActiveRecord/Scaffold/InputBuilder/TSqliteScaffoldInput.php -text
+framework/Data/ActiveRecord/Scaffold/TScaffoldBase.php -text
+framework/Data/ActiveRecord/Scaffold/TScaffoldEditView.php -text
+framework/Data/ActiveRecord/Scaffold/TScaffoldEditView.tpl -text
+framework/Data/ActiveRecord/Scaffold/TScaffoldListView.php -text
+framework/Data/ActiveRecord/Scaffold/TScaffoldListView.tpl -text
+framework/Data/ActiveRecord/Scaffold/TScaffoldView.php -text
+framework/Data/ActiveRecord/Scaffold/TScaffoldView.tpl -text
+framework/Data/ActiveRecord/Scaffold/style.css -text
framework/Data/ActiveRecord/TActiveRecord.php -text
framework/Data/ActiveRecord/TActiveRecordConfig.php -text
framework/Data/ActiveRecord/TActiveRecordCriteria.php -text
diff --git a/demos/quickstart/protected/controls/TopicList.tpl b/demos/quickstart/protected/controls/TopicList.tpl
index 698cbeea..8d68c2e2 100644
--- a/demos/quickstart/protected/controls/TopicList.tpl
+++ b/demos/quickstart/protected/controls/TopicList.tpl
@@ -72,6 +72,7 @@
+
+Active Record Scaffold Views
+Active Record classes can be used together with
+
+and
+
+(
+links both TScaffoldListView and TScaffoldEditView) to create
+simple Create/Read/Update/Delete (CRUD) web applications.
+
+The scaffold views are intended to assist in prototyping web application,
+they are not designed to be as customiziable as more complex components such as
+TDataGrid. The scaffold views provide
+the following builtin functionality:
+
+
+
+ - Listing of all active record items.
+ - Paging and sorting.
+ - Deleting an item.
+ - Inserting a new item.
+ - Updating an existing item.
+ - Validates required fields and basic data types.
+ - Presents specialized controls such as date pickers.
+
+
+Scaffold views are dependent on Active Records and currently supports
+the following databases: Mysql, Sqlite and Postgres SQL. Support for other databases
+can be considered when there are sufficient demand.
+
+Setting up a Scaffold View
+To use the scaffold view, we first define an Active Record
+class that represents a table or view in the database. Consider the following
+Active Record class that corresponds to the users
+table as defined in the Active Record quickstart page.
+
+
+
+class UserRecord extends TActiveRecord
+{
+ public $username;
+ public $email;
+
+ public static $_tablename='users';
+}
+
+
+The scaffold view classes are in the System.Data.ActiveRecord.Scaffold.*
+namespace.
+This namespace can be "imported" in the
+Application Configuration
+using the application.xml file or through the php code using the Prado::using()
+method. The simplest way to provide CRUD functional is to use the
+
+where the RecordClass property value equals to an Active Record
+class name.
+
+
+
+<com:TScaffoldView RecordClass="UserRecord" />
+
+
+Todo...
+
+Other views... list view
+
+
+<com:TScaffoldListView RecordClass="UserRecord" />
+
+
+edit view...
+
+<com:TScaffoldEditView RecordPk="user1" RecordClass="UserRecord" />
+
+
+Combining list + edit views
+
+
+<com:TScaffoldEditView ID="edit_view" RecordClass="UserRecord" />
+<com:TScaffoldListView EditViewID="edit_view" RecordClass="UserRecord" />
+
+
+custom list view...
+
+<com:TScaffoldView RecordClass="UserRecord" >
+ <prop:ListView.List.ItemTemplate>
+ <%# $this->DataItem->username %>
+ <com:TLinkButton Text="Edit" CommandName="edit" />
+ </prop:ListView.List.ItemTemplate>
+</com:TScaffoldView/>
+
+To be completed...
+
+Address book example...
+
+
+
+$Id$
\ No newline at end of file
diff --git a/framework/Data/ActiveRecord/Scaffold/InputBuilder/TMysqlScaffoldInput.php b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TMysqlScaffoldInput.php
new file mode 100644
index 00000000..cb56d598
--- /dev/null
+++ b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TMysqlScaffoldInput.php
@@ -0,0 +1,75 @@
+getType()))
+ {
+ case 'date':
+ return $this->createDateControl($container, $column, $record);
+ case 'blob': case 'tinyblob': case 'mediumblob': case 'longblob':
+ case 'text': case 'tinytext': case 'mediumtext': case 'longtext':
+ return $this->createMultiLineControl($container, $column, $record);
+ case 'year':
+ return $this->createYearControl($container, $column, $record);
+ case 'int': case 'integer': case 'tinyint': case 'smallint': case 'mediumint': case 'bigint':
+ return $this->createIntegerControl($container, $column, $record);
+ case 'decimal': case 'double': case 'float':
+ return $this->createFloatControl($container, $column, $record);
+ case 'time' :
+ return $this->createTimeControl($container, $column, $record);
+ case 'datetime': case 'timestamp':
+ return $this->createDateTimeControl($container, $column, $record);
+ case 'set':
+ return $this->createSetControl($container, $column, $record);
+ case 'enum':
+ return $this->createEnumControl($container, $column, $record);
+ default:
+ return $this->createDefaultControl($container, $column, $record);
+ }
+ }
+
+ protected function getControlValue($container, $column, $record)
+ {
+ switch(strtolower($column->getType()))
+ {
+ case 'date':
+ return $container->findControl(self::DEFAULT_ID)->getDate();
+ case 'year':
+ return $container->findControl(self::DEFAULT_ID)->getSelectedValue();
+ case 'time':
+ return $this->getTimeValue($container, $column, $record);
+ case 'datetime': case 'timestamp':
+ return $this->getDateTimeValue($container,$column, $record);
+ case 'tinyint':
+ return $this->getIntBooleanValue($container,$column, $record);
+ case 'set':
+ return $this->getSetValue($container, $column, $record);
+ case 'enum':
+ return $this->getEnumValue($container, $column, $record);
+ default:
+ return $this->getDefaultControlValue($container,$column, $record);
+ }
+ }
+
+ protected function createIntegerControl($container, $column, $record)
+ {
+ if($column->getLength()==1)
+ return $this->createBooleanControl($container, $column, $record);
+ else
+ parent::createIntegerControl($container, $column, $record);
+ }
+
+ protected function getIntBooleanValue($container,$column, $record)
+ {
+ if($column->getLength()==1)
+ return (int)$container->findControl(self::DEFAULT_ID)->getChecked();
+ else
+ return $this->getDefaultControlValue($container,$column, $record);
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/framework/Data/ActiveRecord/Scaffold/InputBuilder/TPgsqlScaffoldInput.php b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TPgsqlScaffoldInput.php
new file mode 100644
index 00000000..40a14fbc
--- /dev/null
+++ b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TPgsqlScaffoldInput.php
@@ -0,0 +1,48 @@
+getType()))
+ {
+ case 'boolean':
+ return $this->createBooleanControl($container, $column, $record);
+ case 'date':
+ return $this->createDateControl($container, $column, $record);
+ case 'text':
+ return $this->createMultiLineControl($container, $column, $record);
+ case 'smallint': case 'integer': case 'bigint':
+ return $this->createIntegerControl($container, $column, $record);
+ case 'decimal': case 'numeric': case 'real': case 'double precision':
+ return $this->createFloatControl($container, $column, $record);
+ case 'time without time zone' :
+ return $this->createTimeControl($container, $column, $record);
+ case 'timestamp without time zone':
+ return $this->createDateTimeControl($container, $column, $record);
+ default:
+ return $this->createDefaultControl($container,$column, $record);
+ }
+ }
+
+ protected function getControlValue($container, $column, $record)
+ {
+ switch(strtolower($column->getType()))
+ {
+ case 'boolean':
+ return $container->findControl(self::DEFAULT_ID)->getChecked();
+ case 'date':
+ return $container->findControl(self::DEFAULT_ID)->getDate();
+ case 'time without time zone':
+ return $this->getTimeValue($container, $column, $record);
+ case 'timestamp without time zone':
+ return $this->getDateTimeValue($container,$column, $record);
+ default:
+ return $this->getDefaultControlValue($container,$column, $record);
+ }
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputBase.php b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputBase.php
new file mode 100644
index 00000000..c06f2fb6
--- /dev/null
+++ b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputBase.php
@@ -0,0 +1,87 @@
+_parent;
+ }
+
+ public static function createInputBuilder($record)
+ {
+ $record->getDbConnection()->setActive(true); //must be connected before retrieving driver name!
+ $driver = $record->getDbConnection()->getDriverName();
+ switch(strtolower($driver))
+ {
+ case 'sqlite': //sqlite 3
+ case 'sqlite2': //sqlite 2
+ require_once(dirname(__FILE__).'/TSqliteScaffoldInput.php');
+ return new TSqliteScaffoldInput($conn);
+ case 'mysqli':
+ case 'mysql':
+ require_once(dirname(__FILE__).'/TMysqlScaffoldInput.php');
+ return new TMysqlScaffoldInput($conn);
+ case 'pgsql':
+ require_once(dirname(__FILE__).'/TPgsqlScaffoldInput.php');
+ return new TPgsqlScaffoldInput($conn);
+ default:
+ throw new TConfigurationException(
+ 'scaffold_invalid_database_driver',$driver);
+ }
+ }
+
+ public function createScaffoldInput($parent, $item, $column, $record)
+ {
+ $this->_parent=$parent;
+ $item->setCustomData($column->getProperty());
+ $this->createControl($item->_input, $column, $record);
+ if($item->_input->findControl(self::DEFAULT_ID))
+ $this->createControlLabel($item->_label, $column, $record);
+ }
+
+ protected function createControlLabel($label, $column, $record)
+ {
+ $fieldname = ucwords(str_replace('_', ' ', $column->getProperty())).':';
+ $label->setText($fieldname);
+ $label->setForControl(self::DEFAULT_ID);
+ }
+
+ public function loadScaffoldInput($parent, $item, $column, $record)
+ {
+ $this->_parent=$parent;
+ if($this->getIsEnabled($column, $record))
+ {
+ $prop = $column->getProperty();
+ $record->{$prop} = $this->getControlValue($item->_input, $column, $record);
+ }
+ }
+
+ protected function getIsEnabled($column, $record)
+ {
+ return !($this->getParent()->getRecordPk() !== null
+ && $column->getIsPrimaryKey() || $column->hasSequence());
+ }
+
+ protected function getRecordPropertyValue($column, $record)
+ {
+ return $record->{$column->getProperty()};
+ }
+
+ protected function setRecordPropertyValue($item, $record, $input)
+ {
+ $record->{$item->getCustomData()} = $input->getText();
+ }
+
+ protected function createControl($container, $column, $record)
+ {
+ }
+
+ protected function getControlValue($container, $column, $record)
+ {
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputCommon.php b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputCommon.php
new file mode 100644
index 00000000..049ceb1f
--- /dev/null
+++ b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputCommon.php
@@ -0,0 +1,280 @@
+setID(self::DEFAULT_ID);
+ $control->setEnabled($this->getIsEnabled($column, $record));
+ $container->Controls[] = $control;
+ }
+
+ protected function setNotNullProperty($container, $control, $column, $record)
+ {
+ $this->setDefaultProperty($container, $control, $column, $record);
+ if($column->getNotNull() && !$column->hasSequence())
+ $this->createRequiredValidator($container, $column, $record);
+ }
+
+ protected function createBooleanControl($container, $column, $record)
+ {
+ $value = $this->getRecordPropertyValue($column, $record);
+ $control = new TCheckBox();
+ $control->setChecked(TPropertyValue::ensureBoolean($value));
+ $control->setCssClass('boolean-checkbox');
+ $this->setDefaultProperty($container, $control, $column, $record);
+ return $control;
+ }
+
+ protected function createDefaultControl($container, $column, $record)
+ {
+ $value = $this->getRecordPropertyValue($column, $record);
+ $control = new TTextBox();
+ $control->setText($value);
+ $control->setCssClass('default-textbox scaffold_input');
+ if(($len=$column->getLength())!==null)
+ $control->setMaxLength($len);
+ $this->setNotNullProperty($container, $control, $column, $record);
+ return $control;
+ }
+
+ protected function getDefaultControlValue($container,$column, $record)
+ {
+ $control = $container->findControl(self::DEFAULT_ID);
+ if($control instanceof TCheckBox)
+ return $control->getChecked();
+ else if($control instanceof TControl)
+ return $control->getText();
+ }
+
+ protected function createMultiLineControl($container, $column, $record)
+ {
+ $value = $this->getRecordPropertyValue($column, $record);
+ $control = new TTextBox();
+ $control->setText($value);
+ $control->setTextMode(TTextBoxMode::MultiLine);
+ $control->setCssClass('multiline-textbox scaffold_input');
+ $this->setNotNullProperty($container, $control, $column, $record);
+ return $control;
+ }
+
+ protected function createYearControl($container, $column, $record)
+ {
+ $value = $this->getRecordPropertyValue($column, $record);
+ $control = new TDropDownList();
+ $years = array();
+ $current = intval(@date('Y'));
+ $from = $current-10; $to=$current+10;
+ for($i = $from; $i <= $to; $i++)
+ $years[$i] = $i;
+ $control->setDataSource($years);
+ $control->setSelectedValue(empty($value) ? $current : $value);
+ $control->setCssClass('year-dropdown');
+ $this->setNotNullProperty($container, $control, $column, $record);
+ return $control;
+ }
+
+ protected function createIntegerControl($container, $column, $record)
+ {
+ $control = $this->createDefaultControl($container, $column, $record);
+ $val = $this->createTypeValidator($container, $column, $record);
+ $val->setDataType(TValidationDataType::Integer);
+ $val->setErrorMessage('Please entery an integer.');
+ return $control;
+ }
+
+ protected function createFloatControl($container, $column, $record)
+ {
+ $control = $this->createDefaultControl($container, $column, $record);
+ $val = $this->createTypeValidator($container, $column, $record);
+ $val->setDataType(TValidationDataType::Float);
+ $val->setErrorMessage('Please entery a decimal number.');
+ return $control;
+ }
+
+ protected function createRequiredValidator($container, $column, $record)
+ {
+ $val = new TRequiredFieldValidator();
+ $val->setErrorMessage('*');
+ $val->setControlCssClass('required-input');
+ $val->setCssClass('required');
+ $val->setControlToValidate(self::DEFAULT_ID);
+ $val->setValidationGroup($this->getParent()->getValidationGroup());
+ $val->setDisplay(TValidatorDisplayStyle::Dynamic);
+ $container->Controls[] = $val;
+ return $val;
+ }
+
+ protected function createTypeValidator($container, $column, $record)
+ {
+ $val = new TDataTypeValidator();
+ $val->setControlCssClass('required-input2');
+ $val->setCssClass('required');
+ $val->setControlToValidate(self::DEFAULT_ID);
+ $val->setValidationGroup($this->getParent()->getValidationGroup());
+ $val->setDisplay(TValidatorDisplayStyle::Dynamic);
+ $container->Controls[] = $val;
+ return $val;
+ }
+
+ protected function createTimeControl($container, $column, $record)
+ {
+ $value = $this->getRecordPropertyValue($column, $record);
+ $hours=array();
+ for($i=0;$i<24;$i++) $hours[] = str_pad($i,2,'0',STR_PAD_LEFT);
+ $mins=array();
+ for($i=0;$i<60;$i++) $mins[] = str_pad($i,2,'0',STR_PAD_LEFT);
+ $hour = intval(@date('H'));
+ $min = intval(@date('i'));
+ $sec = intval(@date('s'));
+ if(!empty($value))
+ {
+ $match=array();
+ if(preg_match('/(\d+):(\d+):?(\d+)?/', $value, $match))
+ {
+ $hour = $match[1];
+ $min = $match[2];
+ if(isset($match[3]))
+ $sec=$match[3];
+ }
+ }
+
+ $hcontrol = new TDropDownList();
+ $hcontrol->setDataSource($hours);
+ $hcontrol->setID(self::DEFAULT_ID);
+ $hcontrol->dataBind();
+ $hcontrol->setSelectedValue(intval($hour));
+ $container->Controls[] = $hcontrol;
+ $container->Controls[] = ' : ';
+
+ $mcontrol = new TDropDownList();
+ $mcontrol->setDataSource($mins);
+ $mcontrol->dataBind();
+ $mcontrol->setID('scaffold_time_min');
+ $mcontrol->setSelectedValue(intval($min));
+ $container->Controls[] = $mcontrol;
+ $container->Controls[] = ' : ';
+
+ $scontrol = new TDropDownList();
+ $scontrol->setDataSource($mins);
+ $scontrol->dataBind();
+ $scontrol->setID('scaffold_time_sec');
+ $scontrol->setSelectedValue(intval($sec));
+ $container->Controls[] = $scontrol;
+
+ return array($hcontrol,$mcontrol,$scontrol);
+ }
+
+
+ protected function createDateControl($container, $column, $record)
+ {
+ $value = $this->getRecordPropertyValue($column, $record);
+ $control = new TDatePicker();
+ $control->setInputMode(TDatePickerInputMode::DropDownList);
+ $control->setDateFormat('yyyy-MM-dd');
+ if(!empty($value))
+ $control->setDate(substr($value,0,10));
+ $control->setCssClass('date-dropdown');
+ $this->setNotNullProperty($container, $control, $column, $record);
+ return $control;
+ }
+
+ protected function createDateTimeControl($container, $column, $record)
+ {
+ $value = $this->getRecordPropertyValue($column, $record);
+ $control = $this->createDateControl($container, $column, $record);
+ $container->Controls[] = ' @ ';
+ $time = $this->createTimeControl($container, $column, $record);
+ if(!empty($value))
+ {
+ $match=array();
+ if(preg_match('/(\d+):(\d+):?(\d+)?/', substr($value, 11), $match))
+ {
+ $time[0]->setSelectedValue(intval($match[1]));
+ $time[1]->setSelectedValue(intval($match[2]));
+ if(isset($match[3]))
+ $time[2]->setSelectedValue(intval($match[3]));
+ }
+ }
+ $time[0]->setID('scaffold_time_hour');
+ return array($control, $time[0], $time[1], $time[2]);
+ }
+
+ protected function getDateTimeValue($container, $column, $record)
+ {
+ $date = $container->findControl(self::DEFAULT_ID)->getDate();
+ $hour = $container->findControl('scaffold_time_hour')->getSelectedValue();
+ $mins = $container->findControl('scaffold_time_min')->getSelectedValue();
+ $secs = $container->findControl('scaffold_time_sec')->getSelectedValue();
+ return "{$date} {$hour}:{$mins}:{$secs}";
+ }
+
+ protected function getTimeValue($container, $column, $record)
+ {
+ $hour = $container->findControl(self::DEFAULT_ID)->getSelectedValue();
+ $mins = $container->findControl('scaffold_time_min')->getSelectedValue();
+ $secs = $container->findControl('scaffold_time_sec')->getSelectedValue();
+ return "{$hour}:{$mins}:{$secs}";
+ }
+
+ protected function createSetControl($container, $column, $record)
+ {
+ $value = $this->getRecordPropertyValue($column, $record);
+ $selectedValues = preg_split('/\s*,\s*/', $value);
+ $control = new TCheckBoxList();
+ $values = $column->getTypeValues();
+ $control->setDataSource($values);
+ $control->dataBind();
+ $control->setSelectedIndices($this->getMatchingIndices($selectedValues, $values));
+ $control->setID(self::DEFAULT_ID);
+ $control->setCssClass('set-checkboxes');
+ $this->setNotNullProperty($container, $control, $column, $record);
+ return $control;
+ }
+
+ protected function getMatchingIndices($checks, $values)
+ {
+ $index=array();
+ for($i=0, $k=count($checks); $i<$k; $i++)
+ {
+ if(in_array($checks[$i], $values))
+ $index[] = $i;
+ }
+ return $index;
+ }
+
+ protected function createEnumControl($container, $column, $record)
+ {
+ $value = $this->getRecordPropertyValue($column, $record);
+ $selectedValues = preg_split('/\s*,\s*/', $value);
+ $control = new TRadioButtonList();
+ $values = $column->getTypeValues();
+ $control->setDataSource($values);
+ $control->dataBind();
+ $index = $this->getMatchingIndices($selectedValues, $values);
+ if(count($index) > 0)
+ $control->setSelectedIndex($index[0]);
+ $control->setID(self::DEFAULT_ID);
+ $control->setCssClass('enum-radio-buttons');
+ $this->setNotNullProperty($container, $control, $column, $record);
+ return $control;
+ }
+
+ protected function getSetValue($container, $column, $record)
+ {
+ $value=array();
+ foreach($container->findControl(self::DEFAULT_ID)->getItems() as $item)
+ {
+ if($item->getSelected())
+ $value[] = $item->getText();
+ }
+ return implode(',', $value);
+ }
+
+ protected function getEnumValue($container, $column, $record)
+ {
+ return $container->findControl(self::DEFAULT_ID)->getSelectedItem()->getText();
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/framework/Data/ActiveRecord/Scaffold/InputBuilder/TSqliteScaffoldInput.php b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TSqliteScaffoldInput.php
new file mode 100644
index 00000000..b26d44fd
--- /dev/null
+++ b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TSqliteScaffoldInput.php
@@ -0,0 +1,93 @@
+getType()))
+ {
+ case 'boolean':
+ return $this->createBooleanControl($container, $column, $record);
+ case 'date':
+ return $this->createDateControl($container, $column, $record);
+ case 'blob': case 'tinyblob': case 'mediumblob': case 'longblob':
+ case 'text': case 'tinytext': case 'mediumtext': case 'longtext':
+ return $this->createMultiLineControl($container, $column, $record);
+ case 'year':
+ return $this->createYearControl($container, $column, $record);
+ case 'int': case 'integer': case 'tinyint': case 'smallint': case 'mediumint': case 'bigint':
+ return $this->createIntegerControl($container, $column, $record);
+ case 'decimal': case 'double': case 'float':
+ return $this->createFloatControl($container, $column, $record);
+ case 'time' :
+ return $this->createTimeControl($container, $column, $record);
+ case 'datetime': case 'timestamp':
+ return $this->createDateTimeControl($container, $column, $record);
+ default:
+ return $this->createDefaultControl($container,$column, $record);
+ }
+ }
+
+ protected function getControlValue($container, $column, $record)
+ {
+ switch(strtolower($column->getType()))
+ {
+ case 'boolean':
+ return $container->findControl(self::DEFAULT_ID)->getChecked();
+ case 'date':
+ return $container->findControl(self::DEFAULT_ID)->getDate();
+ case 'year':
+ return $container->findControl(self::DEFAULT_ID)->getSelectedValue();
+ case 'time':
+ return $this->getTimeValue($container, $column, $record);
+ case 'datetime': case 'timestamp':
+ return $this->getDateTimeValue($container,$column, $record);
+ default:
+ return $this->getDefaultControlValue($container,$column, $record);
+ }
+ }
+
+ protected function createDateControl($container, $column, $record)
+ {
+ $control = parent::createDateControl($container, $column, $record);
+ $value = $this->getRecordPropertyValue($column, $record);
+ if(!empty($value) && preg_match('/timestamp/i', $column->getType()))
+ $control->setTimestamp(intval($value));
+ return $control;
+ }
+
+ protected function createDateTimeControl($container, $column, $record)
+ {
+ $value = $this->getRecordPropertyValue($column, $record);
+ $time = parent::createDateTimeControl($container, $column, $record);
+ if(!empty($value) && preg_match('/timestamp/i', $column->getType()))
+ {
+ $s = Prado::createComponent('System.Util.TDateTimeStamp');
+ $date = $s->getDate(intval($value));
+ $time[1]->setSelectedValue($date['hours']);
+ $time[2]->setSelectedValue($date['minutes']);
+ $time[3]->setSelectedValue($date['seconds']);
+ }
+ return $time;
+ }
+
+ protected function getDateTimeValue($container, $column, $record)
+ {
+ if(preg_match('/timestamp/i', $column->getType()))
+ {
+ $time = $container->findControl(self::DEFAULT_ID)->getTimestamp();
+ $s = Prado::createComponent('System.Util.TDateTimeStamp');
+ $date = $s->getDate($time);
+ $hour = $container->findControl('scaffold_time_hour')->getSelectedValue();
+ $mins = $container->findControl('scaffold_time_min')->getSelectedValue();
+ $secs = $container->findControl('scaffold_time_sec')->getSelectedValue();
+ return $s->getTimeStamp($hour,$mins,$secs,$date['mon'],$date['mday'],$date['year']);
+ }
+ else
+ return parent::getDateTimeValue($container, $column, $record);
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/framework/Data/ActiveRecord/Scaffold/TScaffoldBase.php b/framework/Data/ActiveRecord/Scaffold/TScaffoldBase.php
new file mode 100644
index 00000000..dc464245
--- /dev/null
+++ b/framework/Data/ActiveRecord/Scaffold/TScaffoldBase.php
@@ -0,0 +1,113 @@
+_meta===null)
+ {
+ $finder = $this->getRecordFinder();
+ $gateway = $finder->getRecordManager()->getRecordGateWay();
+ $this->_meta = $gateway->getMetaData($finder);
+ }
+ return $this->_meta;
+ }
+
+ protected function getRecordProperties($record)
+ {
+ $data = array();
+ foreach($this->getTableMetaData()->getColumns() as $name=>$column)
+ $data[] = $record->{$name};
+ return $data;
+ }
+
+ public function getRecordObjectPk($record)
+ {
+ $pk = array();
+ foreach($this->getTableMetaData()->getColumns() as $name=>$column)
+ {
+ if($column->getIsPrimaryKey())
+ $data[] = $record->{$name};
+ }
+ return $data;
+ }
+
+ public function getRecordClass()
+ {
+ return $this->getViewState('RecordClass');
+ }
+
+ public function setRecordClass($value)
+ {
+ $this->setViewState('RecordClass', $value);
+ }
+
+ public function copyFrom(TScaffoldBase $obj)
+ {
+ $this->_record = $obj->_record;
+ $this->_meta = $obj->_meta;
+ $this->setRecordClass($obj->getRecordClass());
+ }
+
+ protected function clearRecordObject()
+ {
+ $this->_record=null;
+ $this->_meta=null;
+ }
+
+ public function getRecordObject($pk=null)
+ {
+ if($this->_record===null)
+ {
+ if($pk!==null)
+ $this->_record=$this->getRecordFinder()->findByPk($pk);
+ else
+ {
+ $class = $this->getRecordClass();
+ if($class!==null)
+ $this->_record=Prado::createComponent($class);
+ else
+ throw new TConfigurationException('scaffold_invalid_record_class', $this->getID());
+ }
+ }
+ return $this->_record;
+ }
+
+ public function getRecordFinder()
+ {
+ return TActiveRecord::getRecordFinder(get_class($this->getRecordObject()));
+ }
+
+ public function setRecordObject($value)
+ {
+ if($value instanceof TActiveRecord)
+ $this->_record=$value;
+ else
+ throw new TConfigurationException('scaffold_object_must_be_tactiverecord', $this->getID());
+ }
+
+ public function getDefaultStyle()
+ {
+ return $this->getViewState('DefaultStyle', 'style');
+ }
+
+ public function setDefaultStyle($value)
+ {
+ $this->setViewState('DefaultStyle', TPropertyValue::ensureString($value), 'style');
+ }
+
+ public function onPreRender($param)
+ {
+ parent::onPreRender($param);
+ $url = $this->publishAsset($this->getDefaultStyle().'.css');
+ $cs = $this->getPage()->getClientScript();
+ $cs->registerStyleSheetFile($url,$url);
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/framework/Data/ActiveRecord/Scaffold/TScaffoldEditView.php b/framework/Data/ActiveRecord/Scaffold/TScaffoldEditView.php
new file mode 100644
index 00000000..28fc8fb9
--- /dev/null
+++ b/framework/Data/ActiveRecord/Scaffold/TScaffoldEditView.php
@@ -0,0 +1,119 @@
+initializeEditForm();
+ }
+
+ public function setRecordPk($value)
+ {
+ $this->clearRecordObject();
+ $val = TPropertyValue::ensureArray($value);
+ $this->setViewState('PK', count($val) > 0 ? $val : null);
+ }
+
+ public function getRecordPk()
+ {
+ return $this->getViewState('PK');
+ }
+
+ protected function getCurrentRecord()
+ {
+ return $this->getRecordObject($this->getRecordPk());
+ }
+
+ public function initializeEditForm()
+ {
+ $this->getCurrentRecord();
+ $columns = $this->getTableMetaData()->getColumns();
+ $this->_repeater->setDataSource($columns);
+ $this->_repeater->dataBind();
+ }
+
+ public function repeaterItemCreated($sender, $param)
+ {
+ $type = $param->getItem()->getItemType();
+ if($type==TListItemType::Item || $type==TListItemType::AlternatingItem)
+ {
+ $item = $param->getItem();
+ $column = $item->getDataItem();
+ if($column===null)
+ return;
+
+ $record = $this->getCurrentRecord();
+ $builder = $this->getScaffoldInputBuilder($record);
+ $builder->createScaffoldInput($this, $item, $column, $record);
+ }
+ }
+
+ public function bubbleEvent($sender, $param)
+ {
+ switch(strtolower($param->getCommandName()))
+ {
+ case 'save':
+ if($this->getPage()->getIsValid())
+ return $this->doSave() === true ? false : true;
+ return true;
+ case 'clear':
+ $this->setRecordPk(null);
+ $this->initializeEditForm();
+ return false;
+ default:
+ return false;
+ }
+ }
+
+ protected function doSave()
+ {
+ $record = $this->getCurrentRecord();
+ $table = $this->getTableMetaData();
+ $builder = $this->getScaffoldInputBuilder($record);
+ foreach($this->_repeater->getItems() as $item)
+ {
+ $column = $table->getColumn($item->getCustomData());
+ $builder->loadScaffoldInput($this, $item, $column, $record);
+ }
+ $record->save();
+ return true;
+ }
+
+ public function getSaveButton()
+ {
+ $this->ensureChildControls();
+ return $this->getRegisteredObject('_save');
+ }
+
+ public function getClearButton()
+ {
+ $this->ensureChildControls();
+ return $this->getRegisteredObject('_clear');
+ }
+
+ public function getCancelButton()
+ {
+ $this->ensureChildControls();
+ return $this->getRegisteredObject('_cancel');
+ }
+
+ protected function getScaffoldInputBuilder($record)
+ {
+ $class = get_class($record);
+ if(!isset(self::$_builders[$class]))
+ self::$_builders[$class] = TScaffoldInputBase::createInputBuilder($record);
+ return self::$_builders[$class];
+ }
+
+ public function getValidationGroup()
+ {
+ return 'group_'.$this->getUniqueID();
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/framework/Data/ActiveRecord/Scaffold/TScaffoldEditView.tpl b/framework/Data/ActiveRecord/Scaffold/TScaffoldEditView.tpl
new file mode 100644
index 00000000..fd599a20
--- /dev/null
+++ b/framework/Data/ActiveRecord/Scaffold/TScaffoldEditView.tpl
@@ -0,0 +1,18 @@
+