Taming the Beast: Remodeling ModelAdmin

25 Feb

Taming the Beast: Remodeling ModelAdmin

Why Remodel?

We love SilverStripe for its seemingly infinite flexibility. It’s a box of tools and raw materials just waiting to be told what structure to become — a pile of ingredients in search of a recipe. We love that it stands out of our way. Developers want a framework — a support system — not an arbitrary dogma of theories and patterns to which we may not subscribe. That’s why SilverStripe is great.

But what happens when SilverStripe takes this principle a bit too far, and leaves something, well, too raw and unfinished? Most of us can agree that ModelAdmin is a product of an over-minimalist approach. We got what we had been awaiting a long time — generic data management in SilverStripe, but it seems like the interface got left behind. ModelAdmin simply isn’t ready for prime-time for any audience outside of database geeks and ardent programmers. I hasten to add that I cringe at the thought of putting my clients in front of it, but with no other option, I’m stuck with it, aren’t I?

Or is this a fixable problem? Can we remodel ModelAdmin?

Don’t get your hopes up. This article isn’t going to show you anything too groundbreaking. With SilverStripe 3.0 on the way, I’m not sure it would be worth the effort. But in the meantime, here are a few tricks I use to re-model ModelAdmin to add some arrantly lacking features including:

  • A list view
  • Management of SiteTree objects outside of CMSMain

Laying Some Groundwork

ModelAdmin uses three primary controllers:

  • ModelAdmin (the main LeftAndMain controller that defines the CMS interface)
  • ModelAdmin_RecordController (handles requests for specific data records, i.e. the edit form)
  • ModelAdmin_CollectionController (handles datasets, i.e. search, and what we’ll come to understand as “list view”)

We’ll need to subclass all of these classes. Luckily, due to the ClassName[underscore] convention, they can all be in the same file. Let’s break this out:

/remodeladmin/code/RemodelAdmin.php

class RemodelAdmin extends ModelAdmin {

	public static $collection_controller_class = "RemodelAdmin_CollectionController";

	public static $record_controller_class = "RemodelAdmin_RecordController";

	public function init() {
	    parent::init();
	    $config = HtmlEditorConfig::get_active();
	    $buttons = array('undo','redo','separator','cut','copy','paste','pastetext','pasteword','spellchecker','separator','sslink','unlink','anchor','separator','advcode','search','replace','selectall','visualaid','separator');
	    $config->setButtonsForLine(2,$buttons);
	    Requirements::javascript('remodeladmin/javascript/remodeladmin.js');
	    Requirements::css('remodeladmin/css/remodeladmin.css');

	}

}

class RemodelAdmin_CollectionController extends ModelAdmin_CollectionController {

}

class RemodelAdmin_RecordController extends ModelAdmin_RecordController {

}

We need to tell our ModelAdmin subclass to use our custom RecordController and CollectionController. Also, let’s add an init() function to require some dependencies. Since we’ll be working with SiteTree objects in ModelAdmin, we’re going to need a lot of TinyMCE. Let’s clean it up so it plays nicely. We cut out all the buttons that will pop open the right panel in CMSMain. Unfortunately, we’ll lose the functionality of browsing for local pages and assets. That whole interface is just a mess of spaghetti code that is baked into CMSMain. It’s just not going to work for now.

Adding a List View

For all intents and purposes, in the world of ModelAdmin, “list view” is defined as an empty search — that is, an all-inclusive result set. The easiest way to do this is to force a submission of an empty search form on page load. Here’s the Javascript we’ll need to do that:

remodeladmin/javascript/remodeladmin.js

(function($) {
$(document).ready(function() {   
   var doList = function() {
     var currentModel = $('#ModelClassSelector').children('select');
     var currentModelName = $('option:selected', currentModel).val();
     var strFormname = "#Form_SearchForm" + currentModelName.replace('Form','');
     $(strFormname).submit();
     return false;
   }

   $('#ModelClassSelector').live("change",doList);
   $('button[name=action_clearsearch]').click(doList);
   $('#list_view').live("click",doList);

   if($('#list_view_loading').length) {
     doList();
   }
});
})(jQuery);

The Javascript looks at the current class selected with the dropdown and submits the search form associated with that model. We also bind a change event to the dropdown so that when the user changes models, the list view updates. The “clear search” button should also not return an empty page. We set that to return a list view, as well.

Next, we add an event for a “back to list view” button that we’ll add to the detail page. Note that this button doesn’t exist yet. We’re just creating the event for it. Finally, to force the initial list view, we’ll trigger the doList() function on load, but only if we detect a “loading” div in the right panel. This is an easy workaround for determining if ModelAdmin is on its initial “use the search form on the right” screen. (Sidenote: can you think of a more purposeless use of real estate?) We’ll add our override template for ModelAdmin_right.ss:

remodeladmin/templates/Includes/RemodelAdmin_right.ss



$EditForm  
 
     

Now, when our script sees the “loading” div, it knows to execute a “list view.” Ignore the empty form element for now. That is there to accommodate legacy Javascript in ModelAdmin. It’s just the nature of the beast.

One thing you’ll notice in the script is that all these event handlers are dependent on a dropdown menu for the model selector, in lieu of tabs. Let’s force that in our ModelAdmin class:

remodeladmin/code/RemodelAdmin.php

	public function SearchClassSelector() {
		return "dropdown";
	}

Now you should have a working “list view” in your ModelAdmin interface!

Supporting SiteTree Objects

This one is a little trickier. In theory, ModelAdmin manages generic data, and it shouldn’t care what kind of data we feed to it, but SiteTree objects have some inherent idiosyncrasies that we’ll need to incorporate in our remodeled interface, including:

  • Publish/Unpublish
  • Delete from stage/Delete from Live
  • Determining the parent

For the first two, we’ll need to add some methods to our RecordController class. Pretty straight forward stuff, although it will require some duplicate code from CMSMain. (DRY zealots, keep quiet). For determining the current parent, there are two ways to do this. One, we’ll have a dropdown for the ParentID, just like we do in CMSMain. But it would be nice if our edit form knew inherently which parent to assign by default. Let’s add that as a static var to our RemodelAdmin class.

remodeladmin/code/RemodelAdmin.php

	public static $parent_page_type = "SiteTree";

	public function getParentPage() {
		if($parent = $this->stat('parent')) {
			if(is_numeric($parent)) {
				return DataObject::get_by_id($this->modelClass(), $parent);
			}
			elseif(is_string($parent)) {
				return SiteTree::get_by_link($parent);
			}			
		}
		return false;	
	}

We’ll assume the parent page type is a generic “SiteTree.” This can be overloaded as needed, e.g.

public static $parent_page_type = "BlogEntry";

In the getParent() function, we look to see if a $parent static var has been defined. For flexibility, it accepts either an ID or a URLSegment, e.g. “my-blog”.

Now let’s work on the CollectionController. We’ll need to support an add() function as well as a filtered search so that only the children of our current parent show in the list view. Notice that we keep these functions backward compatible with standard DataObjects.

remodeladmin/code/RemodelAdmin.php

class RemodelAdmin_CollectionController extends ModelAdmin_CollectionController {

	function add($request) {
		$class = $this->modelClass;
		$record = new $class();
		$record->write();
		$class = $this->parentController->getRecordControllerClass($this->getModelClass());
		$response = new $class($this, $request, $record->ID);
		return $response->edit($request);
	}

	function getSearchQuery($searchCriteria) {
		$query = parent::getSearchQuery($searchCriteria);
		if(!is_subclass_of($this->getModelClass(),"SiteTree")) {
			return $query;
		}
		$query->orderby("`SiteTree`.LastEdited DESC");
		if($page = $this->parentController->getParentPage()) {
			$query->where[] = "ParentID = $page->ID";					
		}
		return $query;
	}
}

Let’s tackle the edit form next.

remodeladmin/code/RemodelAdmin.php

class RemodelAdmin_RecordController extends ModelAdmin_RecordController {

	public function EditForm() {
		$form = parent::EditForm();
		if(is_subclass_of($this->currentRecord->class,"SiteTree")) {

			$live_link = Controller::join_links($this->currentRecord->Link(),'?stage=Live');
			$stage_link = Controller::join_links($this->currentRecord->Link(),'?stage=Stage');

			$form->setActions($this->currentRecord->getCMSActions());
			$form->Fields()->insertFirst(new LiteralField('view','




'
			));

			if($parent = $this->parentController->parentController->getParentPage()) {
				$form->Fields()->push(new HiddenField('ParentID','', $parent->ID));
			}
			elseif($parent_class = $this->parentController->stat('parent_page_type')) {
				$form->Fields()->push(new SimpleTreeDropdownField('ParentID', _t('RemodelAdmin.PARENTPAGE','Parent page'), $parent_class));
			}

		}		
    	$form->Fields()->insertFirst(new LiteralField('back',
    		'

' )); return $form; } }

There are several things going on here. If the current model class is a SiteTree descendant, we add a bunch of new features, including links to the stage and live versions of the current page, using some LiteralFields that make the MVC gods angry. Next, if a parent has been defined and is valid, we push a HiddenField for the ParentID, otherwise, we give the user a choice of parents in a SimpleTreeDropdownField.

I can hear the DOM dependency complaints already. If you want to use a TreeDropdownField here, it will probably work fine. I just can’t stand them. Sorry.

Lastly, remember that “back to list” button to which we referred in the Javascript? Here she is. We’ll add it in for both SiteTree and DataObject descendants.

Let’s now add all of the action handlers for saving a SiteTree object:

remodeladmin/code/RemodelAdmin.php

	public function publish($data, $form, $request) {
		if($this->currentRecord && !$this->currentRecord->canPublish()) 
			return Security::permissionFailure($this);

		$form->saveInto($this->currentRecord);		
		$this->currentRecord->doPublish();

		if(Director::is_ajax()) {
			return new SS_HTTPResponse(
				Convert::array2json(array(
					'html' => $this->EditForm()->forAjaxTemplate(),
					'message' => _t('ModelAdmin.PUBLISHED','Published')
				)),				
				200
			);
		} else {
			Director::redirectBack();
		}
	}

	public function unpublish($data, $form, $request) {
		if($this->currentRecord && !$this->currentRecord->canDeleteFromLive()) 
			return Security::permissionFailure($this);

		$this->currentRecord->doUnpublish();

		if(Director::is_ajax()) {
			return new SS_HTTPResponse(
				Convert::array2json(array(
					'html' => $this->EditForm()->forAjaxTemplate(),
					'message' => _t('ModelAdmin.UNPUBLISHED','Unpublished')
				)),				
				200
			);
		} else {
			Director::redirectBack();
		}

	}

	protected function performRollback($id, $version) {
		$record = DataObject::get_by_id($this->currentRecord->class, $id);
		if($record && !$record->canEdit()) 
			return Security::permissionFailure($this);

		$record->doRollbackTo($version);
		return $record;
	}

	public function rollback($data, $form, $request) {
		$record = $this->performRollback($this->currentRecord->ID, "Live");
		if(Director::is_ajax()) {
			return new SS_HTTPResponse(
				Convert::array2json(array(
					'html' => $this->EditForm()->forAjaxTemplate(),
					'message' => _t('ModelAdmin.ROLLEDBACK','Rolled back version')
				)),				
				200
			);
		} else {
			Director::redirectBack();
		}
	}

	public function delete($data, $form, $request) {
		$record = $this->currentRecord;
		if($record && !$record->canDelete())
			return Security::permissionFailure();

		// save ID and delete record
		$recordID = $record->ID;
		$record->delete();

		if(Director::is_ajax()) {
			$body = "";
			return new SS_HTTPResponse(
				Convert::array2json(array(
					'html' => $this->EditForm()->forAjaxTemplate(),
					'message' => _t('ModelAdmin.DELETED','Deleted')
				)),				
				200
			);
		} else {
			Director::redirectBack();
		}
	}

	public function save($data, $form, $request) {
		if($this->currentRecord && !$this->currentRecord->canEdit()) 
			return Security::permissionFailure($this);

		$form->saveInto($this->currentRecord);		
		$this->currentRecord->write();

		if(Director::is_ajax()) {
			return new SS_HTTPResponse(
				Convert::array2json(array(
					'html' => $this->EditForm()->forAjaxTemplate(),
					'message' => _t('ModelAdmin.SAVED','Saved')
				)),				
				200
			);
		} else {
			Director::redirectBack();
		}
	}

	public function deletefromlive($data, $form, $request) {
		Versioned::reading_stage('Live');
		$record = $this->currentRecord;
		if($record && !($record->canDelete() && $record->canDeleteFromLive())) 
			return Security::permissionFailure($this);

		$descRemoved = '';
		$descendantsRemoved = 0;

		// before deleting the records, get the descendants of this tree
		if($record) {
			$descendantIDs = $record->getDescendantIDList();

			// then delete them from the live site too
			$descendantsRemoved = 0;
			foreach( $descendantIDs as $descID )
				if( $descendant = DataObject::get_by_id('SiteTree', $descID) ) {
					$descendant->doDeleteFromLive();
					$descendantsRemoved++;
				}

			// delete the record
			$record->doDeleteFromLive();
		}

		Versioned::reading_stage('Stage');

		if(Director::is_ajax()) {
			$body = $this->parentController->ListView()->getBody();
			return new SS_HTTPResponse(
				Convert::array2json(array(
					'html' => $body,
					'message' => _t('ModelAdmin.DELETEDFROMLIVE','Deleted')
				)),				
				200
			);
		} else {
			Director::redirectBack();
		}
	}

Though fairly verbose, these methods are actually pretty straightforward. They return a JSON response including the HTML (e.g. the edit form) and a message (e.g. “Saved” or “Deleted”) for the lower left status bar.

Most of these methods follow a basic pattern, except for the “deletefromlive()” method. We don’t want to end up with an empty screen when deleting a page, so we return the response from the “ListView()” method in the RemodelAdmin controller. Let’s define that method in RemodelAdmin:

remodeladmin/code/RemodelAdmin.php

	public function ListView() {
		return $this->search(array(), $this->SearchForm());
	}
Lastly, we need to light up these buttons in our Javascript, since the ModelAdmin scripts are not expecting them.
remodeladmin/javascript/remodeladmin.js
	$('#right input:submit').unbind('click').live('click', function(){
		var form = $('#right form');
		var formAction = form.attr('action') + '?' + $(this).fieldSerialize();
		if(typeof tinyMCE != 'undefined') tinyMCE.triggerSave();
		$.ajax({
			url : formAction,
			data : form.formToArray(),
			dataType : "json",
			success : function(json) {
				tinymce_removeAll();

				$('#right #ModelAdminPanel').html(json.html);
				if($('#right #ModelAdminPanel form').hasClass('validationerror')) {
					statusMessage(ss.i18n._t('ModelAdmin.VALIDATIONERROR', 'Validation Error'), 'bad');
				} else {
					statusMessage(json.message, 'good');
				}

				Behaviour.apply();
				if(window.onresize) window.onresize();
			}
		});
		return false;
	});
OK, so there’s a bit of Frankenstein Javascript in there, but it does the job. There’s a lot of competition going on in this interface, so we have to be somewhat diplomatic about dealing with the other players, for instance, the Behaviour object.

Usage

Using our new RemodelAdmin interface is pretty easy. You only need a few lines of code. Simply define the standard $url_segment and $menu_title fields, along with the $parent_page_type and $parent variables.

your_project/code/BlogAdmin.php

class BlogAdmin extends RemodelAdmin {

	static $managed_models = array (
		'BlogEntry'
	);

	static $url_segment = "blog";

	static $parent = "blog";
}

Finishing Touches

We can make some minor aesthetic improvements to the ModelAdmin UI in a CSS file.

remodeladmin/css/remodeladmin.css

/* clearfix */
.clr:after, .clearfix:after { content:" "; display:block; height:0; clear:both; visibility:hidden; font-size:0; }
* html .clr, * html .clearfix { zoom:1; } /* ie6 */
*:first-child+html .clr, *:first-child+html .clearfix { zoom:1; } /* ie7 */

input#Form_ResultsForm_action_goForward {display:none;}

#ModelClassSelector select {font-size:14px !important;width:100%;}
#ModelClassSelector select option {font-size:14px;height:1.5em;}

button#list_view { font-size:12px; padding:4px 6px; border:1px solid #aaa; border-top-color:#bbb; border-left-color:#bbb; background:#ccc; color:#555; font-weight:700; cursor:pointer; -webkit-border-radius:5px; -moz-border-radius:5px; border-radius:5px; }
button#list_view:hover { background-color:#bbb; }

.modelpagenav { padding:0 0 10px; }
.modelpagenav button { float:left; }

.publishpreviews { float:left; margin:0 0 0 20px; font-size:12px; font-weight:700; line-height:22px; }
.publishpreviews a { margin:0 0 0 7px; }

These are certainly a matter of preference, but the highlights are:

  • Englarge the model selector menu
  • Style the “back to list” button
  • Style the “stage” and “live” links

There’s one last improvement we can make in the CSS. Since RemodelAdmin involves editing SiteTree objects, it’s really more of a workflow change than a simple presentational improvement. If you’re managing blogs in RemodelAdmin, it becomes a little awkward to see them in the SiteTree when you make the trip to CMSMain. There’s an easy way to hide them from the site tree:

remodeladmin/css/remodeladmin.css

#sitetree_ul li.nositetree {display:none;}

And in your model:

your_project/code/YourHiddenSiteTree.php

	function CMSTreeClasses($controller) {
		return parent::CMSTreeClasses($controller) . ' nositetree';
	}

Since these changes affect multiple views in the CMS, we include the stylesheet globally in all LeftAndMain descendants.

remodeladmin/_config.php

LeftAndMain::require_css('remodeladmin/css/remodeladmin.css');

Final Thoughts

Though it may seem futile to spend time improving something that will, with any luck, be rendered obsolete by year’s end, we’re still stuck with ModelAdmin until then. I invite you to share your own contributions to this module in the comments below. Enjoy!

31 Responses to “Taming the Beast: Remodeling ModelAdmin”

  1. Chris 25. Feb, 2011 at 2:10 pm #

    What’s the demo login?

  2. DRye 25. Feb, 2011 at 2:38 pm #

    @Chris it is admin and password

  3. unclecheese 25. Feb, 2011 at 2:42 pm #

    Good catch. Added it to the login page.

  4. DRye 25. Feb, 2011 at 2:51 pm #

    Nice post, lots of good tricks highlighted here. Thanks for sharing.

  5. Martijn 25. Feb, 2011 at 11:05 pm #

    Shameless plugging :)

    You might want to take a look at my PanelModelAdmin which let you set CTF’s per DataObject and create custom results panels:

    http://svn.axyrmedia.nl/
    http://www.slideshare.net/marvanni/panelmodeladmin-example
    http://www.slideshare.net/marvanni/panel-modeladmin

  6. max 28. Feb, 2011 at 4:16 am #

    great insight into modeladmin, not sure though about using it in client projects, because of the tinymce limitations you mention. really hope for a more generic wysiwyg solution in 3.0…
    thanks for this anyway!

  7. Borriej 01. Mar, 2011 at 10:32 am #

    Nice, can you also add multiple summary/searchfields?

    Normally we do:

    static $searchable_fields = array(
    ‘date’,
    );

    static $summary_fields = array(
    ‘date’,
    );

    How does this work with RemodelAdmin?

  8. MattClegg 08. Mar, 2011 at 7:05 am #

    Great article. Didn’t realise customising ModelAdmin could be so simple.

    There seems a closing div missing in remodeladmin/code/RemodelAdmin.php
    $form->Fields()->insertFirst(new LiteralField(‘back’…

  9. Mick 10. Mar, 2011 at 4:21 am #

    Hi ya,

    Nice tutorial cheers, i have a small issue’et, i have a dataobject that has a many_many relationship, this all works and displays a second tab to use which has a list table in it to pick or add items to the dataobject. The problem is the useable area(Root_Main .tab current) shrinks so that the table only shows the heading and the first line, it then puts a scroll bar down the side to see the rest. This will stay this size even if i select the main tab that was ok before. Any ideas?

    Cheers

    Mick

    • Shea 31. Aug, 2011 at 7:03 pm #

      Hey Mick I just stumbled across that problem too did you happen to find a solution? Cheers!

      • Liam 07. Sep, 2011 at 4:09 am #

        Hey I am also having this problem. Did you have any luck Shea?

  10. Ingo 11. Mar, 2011 at 9:46 pm #

    Hey Aaron – great article! Its interesting to see where other devs see ModelAdmin lacking – I agree that the UI is quite spartan… but its encouraging that the current UI *can* be customized without jumping through too many hoops (e.g. showing the default search results with a couple of lines of JS).

    From a core architecture point of view, your customizations are a good example where it hurts. You shouldn’t need to copypaste that much code from CMSMain (e.g. the publish() action) in order to get versioned support in ModelAdmin. One possible solution is the command pattern, where these actions live in their own (reuseable classes). But we also want to slim down controllers in favour of more business logic being contained in the model. This will make it easier to write controller code, because you simply need less of it. To illustrate this point, have a look e.g. at AssetAdmin.php in 2.4 vs. trunk, its a lot smaller already :)

    As far as reusing frontend components goes (e.g. the versions panel from CMSMain), we hope to make this possible by decoupling the javascript logic, and relying on forms encapsulating the functionality, rather than hardcoded URLs and DOM relationships.

    OK, enough theoretical rambling – back to fixing the CMS UI in master ;)

  11. MattClegg 23. Mar, 2011 at 9:26 am #

    If found that the javascript function associated to “$(‘#right input:submit’)” needs to “return true;” for validation to work correctly -otherwise you lose the action buttons.

    Pushed to GitHub https://github.com/mattclegg/remodeladmin

  12. Shea 01. May, 2011 at 8:18 am #

    Thank you so much for this, much needed!

    I am having trouble with dropdowns in CMSFields with this though. It is working on the demo site but not for my site…. I downloaded the source code direct from here and am using it to manage blog entries, but I noticed that under the behavior tab, page location, the dropdown for choose parent page is not available and additional dropdown fields I create are also not rendered to the form…

  13. Shea 01. May, 2011 at 8:34 am #

    Ok so it wasn’t dropdowns it’s any parentID field it doesn’t like.

  14. Shea 13. May, 2011 at 4:36 am #

    So removing static $parent = “blog”; in BlogAdmin class fixed that problem and now everything is running great… except the insert image button is missing from tinymce when editing a blog entry in modeladmin. I had a look at your demo and the same has happened there…. Anyone else have trouble with that? Any solutions?

    Ta!

    • Klemen 22. Oct, 2011 at 8:14 pm #

      Shea, the issue lies in the getParentPage() method. If you set it to a string, it works fine, but if you set a number, you get an error (that’s what happened to me).

      This line needs to be edited in the method, so:

      if(is_numeric($parent)) {
      return DataObject::get_by_id($this->stat(‘parent_page_type’), $parent);
      }

      I also added a ParentID to be forced before a new object is written in the add() method, otherwise clicking New and leaving unsaved clutters the root, and I added a “New ClassName” title, too. Here’s my add() method:

      function add($request) {
      $class = $this->modelClass;
      $record = new $class();
      $record->Title = “New “.$class;
      if($p=$this->parentController->getParentPage()) {
      $record->ParentID = $p->ID;
      };
      $record->write();
      $class = $this->parentController->getRecordControllerClass($this->getModelClass());
      $response = new $class($this, $request, $record->ID);
      return $response->edit($request);
      }

    • Ben 15. Feb, 2012 at 3:17 pm #

      I know it’s after the fact, but were you ever able to find out how to get the image button back into TinyMCE in RemodelAdmin? It’s driving me nuts!

  15. Taras Bilynskyi 17. Jun, 2011 at 10:43 am #

    Hello, UC
    Some time ago you posted on the silverstripe forum that DOM is not ready to replace model admin search results with it. Does anything changed?
    I need to make possible to sort pages, which are managed by model admin, with drag and drop.
    I can’t see a better way, how to achive this, than insert DOM into model admin. Maybe you can give me a hint?

  16. Frank 27. Jun, 2011 at 12:40 am #

    Thanks UC, thats a pretty epic tutorial. I find working with ModelAdmin quite fiddly. Its never really felt natural for me to work with it, I guess because there’s so much going on.

    I had been trying to use ModelAdmin::getEditForm() to generate the list view on the first load, rather than using js to submit the form. I got to the stage where I could even pass some search criteria to filter using a bit of a hack:

    function getEditForm(){
    $searchCriteria = new SS_HTTPRequest(‘GET’, ‘/’, array(‘SomeField’ => ’1′));
    return $this->bindModelController(‘SomeObject’)->ResultsForm($searchCriteria);
    }

    This would show the listing, but when I clicked to edit a record the CMS actions for ‘save’ and ‘back’ would not appear. So I resorted to your method. Looking forward to ModelAdmin in SS3.

  17. Simon 24. Jul, 2011 at 5:44 pm #

    Hey UncleCheese,

    Is there a way to push a field into a ModelAdmin, just like a Decorator from a plugin can add stuff to an existing DataObject?

    Right now, I have hardcoded a DataObject in the core of my system, but I want it to be pushed by the actual plugin that adds this functionality.

  18. Flo 05. Oct, 2011 at 9:44 pm #

    Hi UncleCheese,
    I have 2 questions:
    1. Is there a way to display all versions of the page objects in the results table? When I delete a page without unpublishing it before that it stays on the live site but is not visible anymore in the remodeladmin.
    2. with your changes the pages are just hidden in the SiteTree. Is there a way to actually not output them in the tree view? That would improve performance a lot if you have a lot of child pages, right?
    Thanks.

    • unclecheese 13. Oct, 2011 at 10:17 am #

      Hi, Flo,

      With regard to versioning, there’s no support for that, and to add it would be quite a lot of work. That’s the major sacrifice you make by moving pages into ModelAdmin. As far as hiding the pages from the site tree, that’s another major concession. It’s basically next to impossible to filter the records out of the tree without hacking the core, so hiding them with CSS is the only realistic option. If you come across a way to physically remove them from the result set, that would be awesome, because it does add a lot of unnecessary overhead.

      • Flo 13. Oct, 2011 at 5:11 pm #

        Hi UncleCheese,
        Thank you very much. so I will probably just remove the publish/unpublish buttons in the remodeladmin and do that automatically in the background on save and delete.
        Since I have a lot of pages I will try to get a solution for the sitetree. I will let you know if I found something..
        Cheers, Flo

  19. Frank 16. Oct, 2011 at 8:48 pm #

    Thanks again UC, are you going to put this on your github as a module? I noticed that when you are managing data objects that are not SiteTrees EditForm() inserts the ‘Back to list view’ button without a closing div and so the size of the content area gets a bit messed up when clicking around tabs.

    • unclecheese 16. Oct, 2011 at 11:13 pm #

      Awesome catch!!! That’s been driving me crazy for months and I had no idea why it was happening. Thank you!

      • Frank 26. Oct, 2011 at 9:09 pm #

        Sweet! Also, the TableListField->delete() method does not unpublish a SiteTree object, so if you hit the red cross to delete the page in the search results the page might still be available on the website.

        Returning false from canDelete() in my SiteTree object removed the red cross, but also the buttons for ‘Delete from draft’ and stuff like that.

        So instead I used onAfterDelete() in my SiteTree class:
        function onAfterDelete() {
        parent::onAfterDelete();

        if ($this->isPublished()) {
        $this->doUnpublish();
        }
        }

  20. Xurk 13. Dec, 2011 at 6:14 am #

    First off, I have to stress how awesome this take on the ModelAdmin is. It gives you the best of both worlds – managing parts of the SiteTree as a module (decluttering the SiteTree and giving a less cluttered overview) while still retaining the benefits of subclassing “Page”, compared to DataObjects. It’s just what we were looking for in our current project.

    Also, the comments and tweaks given in them were invaluable. Therefore, I’d like to add my own! It’s probably server-related, but I found that submission of the EditForm through a GET Ajax request failed when the querystring became too long (which happens fairly quickly when the “Content” WYSIWYG is available). So I added a line into the “$.ajax()” part of the JavaScript file to prevent this. To be specific, I added in: “type: ‘POST’”. Hope this helps someone else out as well.

    Thanks again!

  21. Aram 18. Jan, 2012 at 11:52 am #

    Just a quick note for anyone still using this, the AJAX submit function needs [code]type : "POST"[/code}] to be added to the parameter list, as it defaults to GET which has a charachter limit, which will usually prevent anything with a fair amount of content in a WYSIWYG from saving.

    Aram

  22. Thunderbird 26. Aug, 2013 at 7:15 pm #

    Hi,

    Is it possible to show some of the records in the summary table?

    To explain bit better we got few records that linked to subsite and few that are with main site. Now if in admin you are on main site you should be able to do only changes to records that are linked to main site, same for records linked to subsites.

    Now when I display summary table it’s showing all records instead of selected records how can I override results to display selected records only in summary field.

    Thanks