diff --git a/code/ModelAdmin.php b/code/ModelAdmin.php new file mode 100644 index 00000000..b6cc53ec --- /dev/null +++ b/code/ModelAdmin.php @@ -0,0 +1,265 @@ + + * Director::addRules(50, array('admin/mymodel/$Class/$Action/$ID' => 'MyModelAdmin')); + * + * + * @todo saving logic (should mostly use Form->saveInto() and iterate over relations) + * @todo ajax form loading and saving + * @todo ajax result display + * @todo relation formfield scaffolding (one tab per relation) - relations don't have DBField sublclasses, we do + * we define the scaffold defaults. can be ComplexTableField instances for a start. + * @todo has_many/many_many relation autocomplete field (HasManyComplexTableField doesn't work well with larger datasets) + * + * Long term TODOs: + * @todo Hook into RESTful interface on DataObjects (yet to be developed) + * @todo Permission control via datamodel and Form class + * + * @uses {@link SearchContext} + * + * @package cms + */ +abstract class ModelAdmin extends LeftAndMain { + + /** + * List of all managed {@link DataObject}s in this interface. + * + * @var array + */ + protected static $managed_models = null; + + public static $allowed_actions = array( + 'add', + 'edit', + 'delete', + ); + + public function init() { + parent::init(); + + // security check for valid models + if(isset($this->urlParams['Model']) && !in_array($this->urlParams['Model'], $this->getManagedModels())) { + user_error('ModelAdmin::init(): Invalid Model class', E_USER_ERROR); + } + + Requirements::css('cms/css/ModelAdmin.css'); + } + + /** + * Return default link to this controller + * + * @todo We've specified this in Director::addRules(), why repeat? + * + * @return string + */ + public function Link() { + return 'admin/crm/' . implode('/', array_values(array_unique($this->urlParams))); + } + + /** + * Create empty edit form (scaffolded from DataObject->getCMSFields() by default). + * Can be called either through {@link AddForm()} or directly through URL: + * "/myadminroute/MyModelClass/add" + * + * @param array $data + * @param Form $form + */ + public function add($data, $form) { + $className = (isset($data['ClassName'])) ? $data['ClassName'] : $this->urlParams['ClassName']; + if(!isset($className) || !in_array($data['ClassName'], $this->getManagedModels())) return false; + + return $this->customise(array( + 'EditForm' => $this->getEditForm($data['ClassName']) + ))->renderWith('LeftAndMain'); + } + + /** + * Edit forms (scaffolded from DataObject->getCMSFields() by default). + * + * @param array $data + * @param Form $form + */ + public function edit($data, $form) { + if(!isset($data['ClassName']) || !in_array($data['ClassName'], $this->getManagedModels())) return false; + + // @todo generate editform + } + + /** + * @todo documentation + * + * @param array $data + * @param Form $form + */ + public function save($data, $form) { + // @todo implementation + } + + /** + * Allows to choose which record needs to be created. + * + * @return Form + */ + protected function AddForm() { + $models = $this->getManagedModels(); + $modelMap = array(); + foreach($models as $modelName) $modelMap[$modelName] = singleton($modelName)->singular_name(); + + $form = new Form( + $this, + 'AddForm', + new FieldSet( + new DropdownField( + 'ClassName', + 'Type', + $modelMap + ) + ), + new FieldSet( + new FormAction('add', _t('GenericDataAdmin.CREATE')) + ) + ); + + return $form; + } + + /** + * + * @uses {@link SearchContext} + * @uses {@link SearchFilter} + * @return Form + */ + protected function getSearchForms() { + $modelClasses = $this->getManagedModels(); + + $forms = new DataObjectSet(); + foreach($modelClasses as $modelClass) { + $modelObj = singleton($modelClass); + + $form = $this->getSearchFormForModel($modelClass); + + $forms->push(new ArrayData(array( + 'Form' => $form, + 'Title' => $modelObj->singular_name() + ))); + } + + return $forms; + } + + /** + * Enter description here... + * + * @todo Add customizeable validator + * + * @param string $modelClass + */ + protected function getEditForm($modelClass) { + $modelObj = singleton($modelClass); + + $fields = $this->getFieldsForModel($modelObj); + + $actions = $this->getActionsForModel($modelObj); + + $validator = $this->getValidatorForModel($modelObj); + + $form = new Form( + $this, + 'ModelEditForm', + $fields, + $actions, + $validator + ); + + return $form; + } + + + + // ############# Utility Methods ############## + + /** + * @return array + */ + protected function getManagedModels() { + $models = $this->stat('managed_models'); + if(!count($models)) user_error('ModelAdmin::getManagedModels(): + You need to specify at least one DataObject subclass in $managed_models', E_USER_ERROR); + + return $models; + } + + /** + * Get all cms fields for the model + * (defaults to {@link DataObject->getCMSFields()}). + * + * @todo Make method hook customizeable + * + * @param DataObject $modelObj + * @return FieldSet + */ + protected function getFieldsForModel($modelObj) { + return $modelObj->getCMSFields(); + } + + /** + * Get all actions which are possible in this controller, + * and allowed by the model security. + * + * @todo Hook into model security once its is implemented + * + * @param DataObject $modelObj + * @return FieldSet + */ + protected function getActionsForModel($modelObj) { + $actions = new FieldSet( + new FormAction('save', _t('GenericDataAdmin.SAVE')), + new FormAction('delete', _t('GenericDataAdmin.DELETE')) + ); + + return $actions; + } + + /** + * NOT IMPLEMENTED + * Get a valdidator for the model. + * + * @todo Hook into model security once its is implemented + * + * @param DataObject $modelObj + * @return FieldSet + */ + protected function getValidatorForModel($modelObj) { + return false; + } + + /** + * Get a search form for a single {@link DataObject} subclass. + * + * @param string $modelClass + * @return FieldSet + */ + protected function getSearchFormForModel($modelClass) { + $context = singleton($modelClass)->getDefaultSearchContext(); + + $form = new Form( + $this, + "SearchForm_{$modelClass}", + $context->getSearchFields(), + new FieldSet( + new FormAction('search', _t('MemberTableField.SEARCH')) + ) + ); + + return $form; + } +} +?> \ No newline at end of file diff --git a/css/ModelAdmin.css b/css/ModelAdmin.css new file mode 100644 index 00000000..52cee2f7 --- /dev/null +++ b/css/ModelAdmin.css @@ -0,0 +1,27 @@ +body.ModelAdmin #left { + width: 250px; +} + +body.ModelAdmin #Form_AddForm fieldset { + float: left; +} + +body.ModelAdmin #Form_AddForm #ClassName { + margin: 0; + padding: auto; + width: auto; +} + +body.ModelAdmin #Form_AddForm #ClassName label { + display: none; +} + +body.ModelAdmin #SearchForm_holder, +body.ModelAdmin #AddForm_holder { + padding: 1em; +} + +body.ModelAdmin #SearchForm_holder { + overflow: auto; + height: 300px; +} \ No newline at end of file diff --git a/javascript/LeftAndMain.js b/javascript/LeftAndMain.js index ab86d88e..6cd8d756 100644 --- a/javascript/LeftAndMain.js +++ b/javascript/LeftAndMain.js @@ -765,7 +765,7 @@ GBModalDialog.prototype = { function hideLoading() { $('Loading').style.display = 'none'; - document.body.className = ''; + Element.removeClassName(document.body, 'stillLoading'); } function baseHref() { var baseTags = document.getElementsByTagName('base'); diff --git a/javascript/ModelAdmin.js b/javascript/ModelAdmin.js new file mode 100644 index 00000000..e69de29b diff --git a/templates/BlankPage.ss b/templates/BlankPage.ss index 0d401623..db9b11e8 100644 --- a/templates/BlankPage.ss +++ b/templates/BlankPage.ss @@ -5,7 +5,7 @@ $Title <% base_tag %> - + $Content
$Form diff --git a/templates/ImageEditor.ss b/templates/ImageEditor.ss index 6070e332..beb56676 100644 --- a/templates/ImageEditor.ss +++ b/templates/ImageEditor.ss @@ -6,7 +6,7 @@ <% _t('UNTITLED','Untitled Document') %> - +
Loading...