summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitattributes18
-rw-r--r--demos/quickstart/protected/controls/TopicList.tpl1
-rw-r--r--demos/quickstart/protected/pages/Database/Samples/Scaffold/Home.page7
-rw-r--r--demos/quickstart/protected/pages/Database/Samples/Scaffold/Home.php33
-rw-r--r--demos/quickstart/protected/pages/Database/Samples/Scaffold/sqlite.dbbin0 -> 4096 bytes
-rw-r--r--demos/quickstart/protected/pages/Database/Samples/config.xml5
-rw-r--r--demos/quickstart/protected/pages/Database/Scaffold.page99
-rw-r--r--framework/Data/ActiveRecord/Scaffold/InputBuilder/TMysqlScaffoldInput.php75
-rw-r--r--framework/Data/ActiveRecord/Scaffold/InputBuilder/TPgsqlScaffoldInput.php48
-rw-r--r--framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputBase.php87
-rw-r--r--framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputCommon.php280
-rw-r--r--framework/Data/ActiveRecord/Scaffold/InputBuilder/TSqliteScaffoldInput.php93
-rw-r--r--framework/Data/ActiveRecord/Scaffold/TScaffoldBase.php113
-rw-r--r--framework/Data/ActiveRecord/Scaffold/TScaffoldEditView.php119
-rw-r--r--framework/Data/ActiveRecord/Scaffold/TScaffoldEditView.tpl18
-rw-r--r--framework/Data/ActiveRecord/Scaffold/TScaffoldListView.php172
-rw-r--r--framework/Data/ActiveRecord/Scaffold/TScaffoldListView.tpl56
-rw-r--r--framework/Data/ActiveRecord/Scaffold/TScaffoldView.php72
-rw-r--r--framework/Data/ActiveRecord/Scaffold/TScaffoldView.tpl7
-rw-r--r--framework/Data/ActiveRecord/Scaffold/style.css123
-rw-r--r--framework/Data/ActiveRecord/Vendor/TPgsqlMetaDataInspector.php2
21 files changed, 1427 insertions, 1 deletions
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 @@
<ul>
<li><a href="?page=Database.DAO">Data Access Objects</a></li>
<li><a href="?page=Database.ActiveRecord">Active Record</a></li>
+ <li><a href="?page=Database.Scaffold">Active Record Scaffold</a></li>
<li><a href="?page=Database.SqlMap">SqlMap Data Mapper</a></li>
</ul>
</div>
diff --git a/demos/quickstart/protected/pages/Database/Samples/Scaffold/Home.page b/demos/quickstart/protected/pages/Database/Samples/Scaffold/Home.page
new file mode 100644
index 00000000..21f508f7
--- /dev/null
+++ b/demos/quickstart/protected/pages/Database/Samples/Scaffold/Home.page
@@ -0,0 +1,7 @@
+<com:TContent ID="body" >
+
+<h1>Active Record Scaffold Example</h1>
+
+<com:TScaffoldView RecordClass="AddressRecord" />
+
+</com:TContent>
diff --git a/demos/quickstart/protected/pages/Database/Samples/Scaffold/Home.php b/demos/quickstart/protected/pages/Database/Samples/Scaffold/Home.php
new file mode 100644
index 00000000..9bfe3f6d
--- /dev/null
+++ b/demos/quickstart/protected/pages/Database/Samples/Scaffold/Home.php
@@ -0,0 +1,33 @@
+<?php
+
+Prado::using('System.Data.ActiveRecord.TActiveRecord');
+Prado::using('System.Data.ActiveRecord.Scaffold.TScaffoldView');
+
+class AddressRecord extends TActiveRecord
+{
+ public $id;
+ public $username;
+ public $phone;
+
+ public static $_tablename='addresses';
+
+ //for demo, we use static db here
+ //otherwise we should use TActiveRecordConfig in application.xml
+ private static $_db;
+ public function getDbConnection()
+ {
+ if(self::$_db===null)
+ {
+ $file = dirname(__FILE__).'/sqlite.db';
+ self::$_db = new TDbConnection("sqlite:{$file}");
+ }
+ return self::$_db;
+ }
+}
+
+class Home extends TPage
+{
+
+}
+
+?> \ No newline at end of file
diff --git a/demos/quickstart/protected/pages/Database/Samples/Scaffold/sqlite.db b/demos/quickstart/protected/pages/Database/Samples/Scaffold/sqlite.db
new file mode 100644
index 00000000..65d77a66
--- /dev/null
+++ b/demos/quickstart/protected/pages/Database/Samples/Scaffold/sqlite.db
Binary files differ
diff --git a/demos/quickstart/protected/pages/Database/Samples/config.xml b/demos/quickstart/protected/pages/Database/Samples/config.xml
new file mode 100644
index 00000000..51da9ed0
--- /dev/null
+++ b/demos/quickstart/protected/pages/Database/Samples/config.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<configuration>
+ <pages MasterClass="SampleLayout" />
+</configuration> \ No newline at end of file
diff --git a/demos/quickstart/protected/pages/Database/Scaffold.page b/demos/quickstart/protected/pages/Database/Scaffold.page
new file mode 100644
index 00000000..36a0ec21
--- /dev/null
+++ b/demos/quickstart/protected/pages/Database/Scaffold.page
@@ -0,0 +1,99 @@
+<com:TContent ID="body" >
+<!-- $Id$ -->
+<h1>Active Record Scaffold Views</h1>
+<p><a href="?page=Database.ActiveRecord">Active Record</a> classes can be used together with
+<com:DocLink ClassPath="System.Data.ActiveRecord.Scaffold.TScaffoldListView" Text="TScaffoldListView"/>
+and
+<com:DocLink ClassPath="System.Data.ActiveRecord.Scaffold.TScaffoldEditView" Text="TScaffoldEditView"/>
+( <com:DocLink ClassPath="System.Data.ActiveRecord.Scaffold.TScaffoldView" Text="TScaffoldView"/>
+links both <tt>TScaffoldListView</tt> and <tt>TScaffoldEditView</tt>) to create
+<i>simple</i> Create/Read/Update/Delete (CRUD) web applications.</p>
+
+<p>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
+<a href="?page=Controls.DataGrid">TDataGrid</a>. The scaffold views provide
+the following builtin functionality:
+</p>
+
+<ul>
+ <li>Listing of all active record items.</li>
+ <li>Paging and sorting.</li>
+ <li>Deleting an item.</li>
+ <li>Inserting a new item.</li>
+ <li>Updating an existing item.</li>
+ <li>Validates required fields and basic data types.</li>
+ <li>Presents specialized controls such as date pickers.</li>
+</ul>
+
+<p>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.</p>
+
+<h2>Setting up a Scaffold View</h2>
+<p>To use the scaffold view, we first define an <a href="?page=Database.ActiveRecord">Active Record</a>
+class that represents a table or view in the database. Consider the following
+Active Record class that corresponds to the <tt>users</tt>
+table as defined in the <a href="?page=Database.ActiveRecord">Active Record</a> quickstart page.
+</p>
+
+<com:TTextHighlighter Language="php" CssClass="source">
+class UserRecord extends TActiveRecord
+{
+ public $username;
+ public $email;
+
+ public static $_tablename='users';
+}
+</com:TTextHighlighter>
+
+<p>The scaffold view classes are in the <tt>System.Data.ActiveRecord.Scaffold.*</tt>
+<a href="?page=Fundamentals.Components#704">namespace</a>.
+This <a href="?page=Fundamentals.Components#704">namespace</a> can be "imported" in the
+<a href="?page=Configurations.AppConfig">Application Configuration</a>
+using the <tt>application.xml</tt> file or through the php code using the <tt>Prado::using()</tt>
+method. The simplest way to provide CRUD functional is to use the
+<com:DocLink ClassPath="System.Data.ActiveRecord.Scaffold.TScaffoldView" Text="TScaffoldView"/>
+where the <tt>RecordClass</tt> property value equals to an Active Record
+class name.
+</p>
+
+<com:TTextHighlighter Language="prado" CssClass="source">
+&lt;com:TScaffoldView RecordClass="UserRecord" /&gt;
+</com:TTextHighlighter>
+
+<h2>Todo...</h2>
+
+<p>Other views... list view</p>
+
+<com:TTextHighlighter Language="prado" CssClass="source">
+&lt;com:TScaffoldListView RecordClass="UserRecord" /&gt;
+</com:TTextHighlighter>
+
+<p>edit view...</p>
+<com:TTextHighlighter Language="prado" CssClass="source">
+&lt;com:TScaffoldEditView RecordPk="user1" RecordClass="UserRecord" /&gt;
+</com:TTextHighlighter>
+
+<p>Combining list + edit views</p>
+
+<com:TTextHighlighter Language="prado" CssClass="source">
+&lt;com:TScaffoldEditView ID="edit_view" RecordClass="UserRecord" /&gt;
+&lt;com:TScaffoldListView EditViewID="edit_view" RecordClass="UserRecord" /&gt;
+</com:TTextHighlighter>
+
+<p>custom list view...</p>
+<com:TTextHighlighter Language="prado" CssClass="source">
+&lt;com:TScaffoldView RecordClass="UserRecord" &gt;
+ &lt;prop:ListView.List.ItemTemplate&gt;
+ &lt;%# $this->DataItem->username %&gt;
+ &lt;com:TLinkButton Text="Edit" CommandName="edit" /&gt;
+ &lt;/prop:ListView.List.ItemTemplate&gt;
+&lt;/com:TScaffoldView/&gt;
+</com:TTextHighlighter>
+<p>To be completed...</p>
+
+<p>Address book example...</p>
+
+<com:RunBar PagePath="Database.Samples.Scaffold.Home" />
+
+<div class="last-modified">$Id$</div></com:TContent> \ 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 @@
+<?php
+
+Prado::using('System.Data.ActiveRecord.Scaffold.InputBuilder.TScaffoldInputCommon');
+
+class MysqlScaffoldInput extends ScaffoldInputCommon
+{
+ protected function createControl($container, $column, $record)
+ {
+ switch(strtolower($column->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 @@
+<?php
+
+Prado::using('System.Data.ActiveRecord.Scaffold.InputBuilder.TScaffoldInputCommon');
+
+class PgsqlScaffoldInput extends ScaffoldInputCommon
+{
+ protected function createControl($container, $column, $record)
+ {
+ switch(strtolower($column->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 @@
+<?php
+
+class TScaffoldInputBase
+{
+ const DEFAULT_ID = 'scaffold_input';
+ private $_parent;
+
+ protected function getParent()
+ {
+ return $this->_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 @@
+<?php
+
+class TScaffoldInputCommon extends TScaffoldInputBase
+{
+ protected function setDefaultProperty($container, $control, $column, $record)
+ {
+ $control->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 @@
+<?php
+
+Prado::using('System.Data.ActiveRecord.Scaffold.InputBuilder.TScaffoldInputCommon');
+
+class TSqliteScaffoldInput extends TScaffoldInputCommon
+{
+ protected function createControl($container, $column, $record)
+ {
+ switch(strtolower($column->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 @@
+<?php
+
+Prado::using('System.Data.ActiveRecord.TActiveRecord');
+
+abstract class TScaffoldBase extends TTemplateControl
+{
+ private $_record;
+ private $_meta;
+
+ protected function getTableMetaData()
+ {
+ if($this->_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 @@
+<?php
+
+Prado::using('System.Data.ActiveRecord.Scaffold.TScaffoldBase');
+Prado::using('System.Data.ActiveRecord.Scaffold.InputBuilder.TScaffoldInputBase');
+
+class TScaffoldEditView extends TScaffoldBase
+{
+ private static $_builders=array();
+
+ public function onLoad($param)
+ {
+ $this->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 @@
+<div class="edit-inputs">
+<com:TRepeater ID="_repeater" onItemCreated="repeaterItemCreated">
+ <prop:ItemTemplate>
+ <div class="edit-item item_<%# $this->ItemIndex % 2 %> input_<%# $this->ItemIndex %>">
+ <com:TLabel ID="_label" CssClass="item-label"/>
+ <span class="item-input">
+ <com:TPlaceHolder ID="_input" />
+ </span>
+ </div>
+ </prop:ItemTemplate>
+</com:TRepeater>
+</div>
+
+<div class="edit-page-buttons">
+<com:TButton ID="_save" Text="Save" CommandName="save" ValidationGroup=<%= $this->ValidationGroup %>/>
+<com:TButton ID="_clear" Text="Clear" CommandName="clear" CausesValidation="false"/>
+<com:TButton ID="_cancel" Text="Cancel" CommandName="cancel" CausesValidation="false" Visible="false"/>
+</div> \ No newline at end of file
diff --git a/framework/Data/ActiveRecord/Scaffold/TScaffoldListView.php b/framework/Data/ActiveRecord/Scaffold/TScaffoldListView.php
new file mode 100644
index 00000000..98c0aab8
--- /dev/null
+++ b/framework/Data/ActiveRecord/Scaffold/TScaffoldListView.php
@@ -0,0 +1,172 @@
+<?php
+
+Prado::using('System.Data.ActiveRecord.Scaffold.TScaffoldBase');
+
+class TScaffoldListView extends TScaffoldBase
+{
+ public function onLoad($param)
+ {
+ parent::onLoad($param);
+ if(!$this->getPage()->getIsPostBack())
+ $this->initializeSort();
+ }
+
+ protected function initializeSort()
+ {
+ $table = $this->getTableMetaData();
+ $sorts = array('Sorty By', str_repeat('-',15));
+ $headers = array();
+ foreach($table->getColumns() as $name=>$colum)
+ {
+ $fname = ucwords(str_replace('_', ' ', $name));
+ $sorts[$name.' ASC'] = $fname .' Ascending';
+ $sorts[$name.' DESC'] = $fname .' Descending';
+ $headers[] = $fname ;
+ }
+ $this->_sort->setDataSource($sorts);
+ $this->_sort->dataBind();
+ $this->_header->setDataSource($headers);
+ $this->_header->dataBind();
+ }
+
+ public function onPreRender($param)
+ {
+ parent::onPreRender($param);
+ $this->initializeItemCount();
+ $this->loadRecordData();
+ }
+
+ protected function initializeItemCount()
+ {
+ $this->_list->setVirtualItemCount($this->getRecordFinder()->count());
+ }
+
+ protected function loadRecordData()
+ {
+ $finder = $this->getRecordFinder();
+ $criteria = $this->getPagingCriteria();
+ $this->_list->setDataSource($finder->findAll($criteria));
+ $this->_list->dataBind();
+ }
+
+ protected function getPagingCriteria()
+ {
+ $total = $this->_list->getVirtualItemCount();
+ $limit = $this->_list->getPageSize();
+ $offset = $this->_list->getCurrentPageIndex()*$limit;
+ if($offset + $limit > $total)
+ $limit = $total - $offset;
+ $criteria = new TActiveRecordCriteria;
+ $criteria->setLimit($limit);
+ $criteria->setOffset($offset);
+ $order = explode(' ',$this->_sort->getSelectedValue(), 2);
+ if(is_array($order) && count($order) === 2)
+ $criteria->OrdersBy[$order[0]] = $order[1];
+ return $criteria;
+ }
+
+ public function bubbleEvent($sender, $param)
+ {
+ switch(strtolower($param->getCommandName()))
+ {
+ case 'delete':
+ return $this->deleteRecord($sender, $param);
+ case 'edit':
+ if(($ctrl=$this->getEditViewControl())!==null)
+ {
+ if($param instanceof TRepeaterCommandEventParameter)
+ {
+ $pk = $param->getItem()->getCustomData();
+ $ctrl->setRecordPk($pk);
+ $ctrl->initializeEditForm();
+ }
+ }
+ }
+ $this->raiseBubbleEvent($this, $param);
+ return true;
+ }
+
+ public function deleteRecord($sender, $param)
+ {
+ if($param instanceof TRepeaterCommandEventParameter)
+ {
+ $pk = $param->getItem()->getCustomData();
+ $this->getRecordFinder()->deleteByPk($pk);
+ }
+ }
+
+ protected function listItemCreated($sender, $param)
+ {
+ $type = $param->getItem()->getItemType();
+ if($type==TListItemType::Item || $type==TListItemType::AlternatingItem)
+ $this->populateField($sender, $param);
+ }
+
+ protected function populateField($sender, $param)
+ {
+ $item = $param->getItem();
+ $data = $item->getDataItem();
+ if($data !== null)
+ {
+ $item->setCustomData($this->getRecordObjectPk($data));
+
+ if(($prop = $item->findControl('_properties'))!==null)
+ {
+ $item->_properties->setDataSource($this->getRecordProperties($data));
+ $item->_properties->dataBind();
+ }
+ }
+ }
+
+ protected function pageChanged($sender, $param)
+ {
+ $this->_list->setCurrentPageIndex($param->getNewPageIndex());
+ }
+
+ public function getList()
+ {
+ $this->ensureChildControls();
+ return $this->getRegisteredObject('_list');
+ }
+
+ public function getPager()
+ {
+ $this->ensureChildControls();
+ return $this->getRegisteredObject('_pager');
+ }
+
+ public function getSort()
+ {
+ $this->ensureChildControls();
+ return $this->getRegisteredObject('_sort');
+ }
+
+ public function getHeader()
+ {
+ $this->ensureChildControls();
+ return $this->getRegisteredObject('_header');
+ }
+
+ public function getEditViewID()
+ {
+ return $this->getViewState('EditViewID');
+ }
+
+ public function setEditViewID($value)
+ {
+ $this->setViewState('EditViewID', $value);
+ }
+
+ protected function getEditViewControl()
+ {
+ if(($id=$this->getEditViewID())!==null)
+ {
+ $ctrl = $this->getParent()->findControl($id);
+ if($ctrl===null)
+ throw new TConfigurationException('scaffold_unable_to_find_edit_view', $id);
+ return $ctrl;
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Data/ActiveRecord/Scaffold/TScaffoldListView.tpl b/framework/Data/ActiveRecord/Scaffold/TScaffoldListView.tpl
new file mode 100644
index 00000000..7b8854f0
--- /dev/null
+++ b/framework/Data/ActiveRecord/Scaffold/TScaffoldListView.tpl
@@ -0,0 +1,56 @@
+
+<div class="item-header">
+<com:TRepeater ID="_header">
+ <prop:ItemTemplate>
+ <com:TLabel Text=<%# $this->DataItem %> CssClass="field field_<%# $this->ItemIndex %>"/>
+ </prop:ItemTemplate>
+</com:TRepeater>
+
+<span class="sort-options">
+ <com:TDropDownList ID="_sort" AutoPostBack="true"/>
+</span>
+
+</div>
+
+<div class="item-list">
+<com:TRepeater ID="_list"
+ AllowPaging="true"
+ AllowCustomPaging="true"
+ onItemCommand="bubbleEvent"
+ onItemCreated="listItemCreated"
+ PageSize="10">
+ <prop:ItemTemplate>
+ <div class="item item_<%# $this->ItemIndex % 2 %>">
+
+ <com:TRepeater ID="_properties">
+ <prop:ItemTemplate>
+ <span class="field field_<%# $this->ItemIndex %>">
+ <%# htmlspecialchars($this->DataItem) %>
+ </span>
+ </prop:ItemTemplate>
+ </com:TRepeater>
+
+ <span class="edit-delete-buttons">
+ <com:TButton Text="Edit"
+ Visible=<%# $this->NamingContainer->Parent->EditViewID !== Null %>
+ CommandName="edit"
+ CssClass="edit-button"
+ CausesValidation="false" />
+ <com:TButton Text="Delete"
+ CommandName="delete"
+ CssClass="delete-button"
+ CausesValidation="false"
+ Attributes.onclick="if(!confirm('Are you sure?')) return false;" />
+ </span>
+
+ </div>
+ </prop:ItemTemplate>
+</com:TRepeater>
+</div>
+
+<com:TPager ID="_pager"
+ CssClass="pager"
+ ControlToPaginate="_list"
+ PageButtonCount="10"
+ Mode="Numeric"
+ OnPageIndexChanged="pageChanged" /> \ No newline at end of file
diff --git a/framework/Data/ActiveRecord/Scaffold/TScaffoldView.php b/framework/Data/ActiveRecord/Scaffold/TScaffoldView.php
new file mode 100644
index 00000000..668ede61
--- /dev/null
+++ b/framework/Data/ActiveRecord/Scaffold/TScaffoldView.php
@@ -0,0 +1,72 @@
+<?php
+
+Prado::using('System.Data.ActiveRecord.Scaffold.TScaffoldBase');
+Prado::using('System.Data.ActiveRecord.Scaffold.TScaffoldListView');
+Prado::using('System.Data.ActiveRecord.Scaffold.TScaffoldEditView');
+
+class TScaffoldView extends TScaffoldBase
+{
+ public function onLoad($param)
+ {
+ parent::onLoad($param);
+ $this->getListView()->copyFrom($this);
+ $this->getEditView()->copyFrom($this);
+ }
+
+ public function getListView()
+ {
+ $this->ensureChildControls();
+ return $this->getRegisteredObject('_listView');
+ }
+
+ public function getEditView()
+ {
+ $this->ensureChildControls();
+ return $this->getRegisteredObject('_editView');
+ }
+
+ public function getAddButton()
+ {
+ $this->ensureChildControls();
+ return $this->getRegisteredObject('_newButton');
+ }
+
+ public function bubbleEvent($sender,$param)
+ {
+ switch(strtolower($param->getCommandName()))
+ {
+ case 'edit':
+ return $this->showEditView($sender, $param);
+ case 'new':
+ return $this->showAddView($sender, $param);
+ default:
+ return $this->showListView($sender, $param);
+ }
+ return false;
+ }
+
+ protected function showEditView($sender, $param)
+ {
+ $this->getListView()->setVisible(false);
+ $this->getEditView()->setVisible(true);
+ $this->getAddButton()->setVisible(false);
+ $this->getEditView()->getCancelButton()->setVisible(true);
+ $this->getEditView()->getClearButton()->setVisible(false);
+ }
+
+ protected function showListView($sender, $param)
+ {
+ $this->getListView()->setVisible(true);
+ $this->getEditView()->setVisible(false);
+ $this->getAddButton()->setVisible(true);
+ }
+
+ protected function showAddView($sender, $param)
+ {
+ $this->getEditView()->setRecordPk(null);
+ $this->getEditView()->initializeEditForm();
+ $this->showEditView($sender, $param);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Data/ActiveRecord/Scaffold/TScaffoldView.tpl b/framework/Data/ActiveRecord/Scaffold/TScaffoldView.tpl
new file mode 100644
index 00000000..0ae8b8b0
--- /dev/null
+++ b/framework/Data/ActiveRecord/Scaffold/TScaffoldView.tpl
@@ -0,0 +1,7 @@
+<com:TScaffoldListView ID="_listView" EditViewID="_editView" />
+
+<div class="auxilary-button buttons">
+<com:TButton ID="_newButton" Text="Add new record" CssClass="new-button" CommandName="new" />
+</div>
+
+<com:TScaffoldEditView ID="_editView" Visible="false"/> \ No newline at end of file
diff --git a/framework/Data/ActiveRecord/Scaffold/style.css b/framework/Data/ActiveRecord/Scaffold/style.css
new file mode 100644
index 00000000..eb31e9a5
--- /dev/null
+++ b/framework/Data/ActiveRecord/Scaffold/style.css
@@ -0,0 +1,123 @@
+body
+{
+ font-family: Cambria, Georgia, "Times New Roman", Times, serif;
+}
+
+.pager
+{
+ display: block;
+ border-top: 1px solid #ccc;
+ padding: 1em;
+}
+
+.pager span, .pager a
+{
+ border: 1px solid #ccc;
+ padding: 0.3em 0.7em;
+ font-weight: bold;
+}
+
+.pager a
+{
+ background-color: #E0FFFF;
+ border-color: #87CEFA;
+}
+
+.pager a:hover
+{
+ background-color: White;
+}
+
+
+.item, .item-header
+{
+ border-top: 1px dashed #B0C4DE;
+ padding: 0.5em;
+ clear: both;
+}
+
+.item-header
+{
+ border-top: 0 none;
+ font-weight: bold;
+}
+
+.item_1
+{
+ background-color: #F0F8FF;
+}
+
+.field_0, .field
+{
+ padding: 0.2em;
+ float: left;
+ width: 150px;
+}
+
+.field_0
+{
+ width: 40px;
+ text-align: center;
+}
+
+.auxilary-button
+{
+ padding: 1em;
+}
+
+.edit-inputs label
+{
+ width: 150px;
+ float: left;
+ text-align: right;
+ padding: 0 0.5em;
+ font-weight: bold;
+}
+
+
+.item-input label
+{
+ float: none;
+ font-weight: normal;
+}
+
+
+.edit-page-buttons
+{
+ padding-left: 120px;
+ padding-top: 20px;
+}
+
+.edit-item
+{
+ padding: 0.4em;
+}
+
+.edit-inputs .scaffold_input
+{
+ width: 250px;
+ border: 1px solid Highlight;
+ padding: 0.2em;
+}
+
+.edit-inputs .multiline-textbox
+{
+ width: 500px;
+ height: 100px;
+}
+
+.edit-inputs .input_0 .scaffold_input
+{
+ width: 50px;
+}
+
+.edit-inputs .required
+{
+ font-weight: bold;
+ padding: 0.2em;
+}
+.edit-inputs .required-input
+{
+ border: 1px solid red;
+ background-color: #FFF5EE;
+} \ No newline at end of file
diff --git a/framework/Data/ActiveRecord/Vendor/TPgsqlMetaDataInspector.php b/framework/Data/ActiveRecord/Vendor/TPgsqlMetaDataInspector.php
index 6f787942..2f7202cf 100644
--- a/framework/Data/ActiveRecord/Vendor/TPgsqlMetaDataInspector.php
+++ b/framework/Data/ActiveRecord/Vendor/TPgsqlMetaDataInspector.php
@@ -146,7 +146,7 @@ EOD;
$type = $col['type'];
// A specific constant in the 7.0 source, the length is offset by 4.
- $length = $col['atttypmod'] > 0 ? $col['atttypmod'] - 4 : -1;
+ $length = $col['atttypmod'] > 0 ? $col['atttypmod'] - 4 : null;
$notNull = $col['attnotnull'];
$serial = $col['attisserial'] ? $schema.'.'.$this->getSerialName($col['adsrc']) : null;
$default = $serial === null && $col['atthasdef'] ? $col['adsrc'] : null;