SSbits - Home page
Site by Carbon Crayon
Submit a Post >

Tutorials - Big bits of code to help you do more

Add a duplicate button to Model Admin

SSBmodeladminduplicate

 Source files (2.1 KB)

Often you will want to add some extra features to your Model Admin interface. Luckily it's pretty straightforward when you know how! In this tutorial we are going to add a simple 'Duplicate' button for our managed Model.

Getting Setup

FIrst things first let's get our model admin panel setup. We are going to keep things as basic as possible, first a simple DataObject with a Title field and second the ModelAdmin panel class. Excuse the uninspiring class names....it's one of those days ;)

mysite/code/CustomDataobject.php

<?php

class CustomDataObject extends DataObject {
	
	static $db = array(
		'Title' => 'Varchar(255)'
	);
	
	public function getCMSFields()
	{
		$fields = parent::getCMSFields();

		$fields->addFieldToTab("Root.Main", new TextField('Title'));

		return $fields;
	}

}

mysite/code/CustomAdmin.php

<?php

class CustomAdmin extends ModelAdmin {
   
	public static $managed_models = array( 
		'CustomDataObject' => array('record_controller' =>  'CustomAdmin_RecordController')
	);

	static $url_segment = 'custom-data-objects';
	static $menu_title = 'Custom Objects';
}

class CustomAdmin_RecordController extends ModelAdmin_RecordController {
	
}		

Now notice in MyAdmin.php I have sneaked in a bit of extra code to what you might be used to: I have defined the record_controller class as CustomAdmin_RecordController for our CustomDataObject and then I have defined that class, albeit empty for now. This is telling SilverStripe that we want it to use our custom class when performing actions on our records, giving us a nice place to put our duplicate() action.

Adding the Action

Now that we have our basic setup, let's go about adding the actual 'duplicate' button to the interface. To do this we are going to define the getCMSActions() function within our CustomDataObject class.

class CustomDataObject extends DataObject {
	
.
.
.
	
	//Create our duplicate button
	public function getCMSActions()
	{
		$Actions = parent::getCMSActions();
		
		//Create the new action
		$DuplicateAction = FormAction::create('duplicate', 'Duplicate Object');
		$DuplicateAction->describe("Duplicate this item");
		
		//add it to the existing actions
		$Actions->insertFirst($DuplicateAction);
		
		return $Actions;
	}	
}

So the code it self is pretty explanitory and works very much like getCMSFields() in that you simply fetch the existing actions, create your custom action then insert it back into the ones you just got. You could shorten it all onto one line with $Actions->insertFirst(new FormAction('duplicate', 'Duplicate object'); but I thought I'd use the same layout that the SiteTree class does, defining a popup description too.

Now we need to create the duplicate() function in our CustomAdmin_RecordController like so:

mysite/code/CustomAdmin.php

class CustomAdmin extends ModelAdmin {
   .
   .
   .
	
}

class CustomAdmin_RecordController extends ModelAdmin_RecordController {


	public function duplicate($data, $form, $request) {	
		
		//Duplicate the object
		$Clone = $this->currentRecord->duplicate();

		//Change the title so we know we are looking at the copy
		$Clone->Title = 'Copy of ' . $this->currentRecord->Title;
		
		$Clone->write();

		//Set the view to be our new duplicate
		$this->currentRecord = $Clone;

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

All this function is doing is first creating a duplicate of the current record, then it changes it's title by amending 'Copy of' to the start so that when the page reloads the user knows they are looking at the copy rather then the original. It then Writes the copy, set's the current record to the copy (so that when the form reloads it is the visible object rather than the one we copied from) and finally returns us to the interface, displaying a nice 'Duplicated!' message to the user.

Making it Work

If you tried the above code, you will see the button just hangs and doesn't do a whole lot at all. That is because we need a bit of Javascript in there to call the function. Thanks to UncleCheese (aka Arron Carlino) we have a slightly cleaner solution than that which ModelAdmin it self uses. So in mysite/javascript/customadmin.js add the following:

(function($) { 
	$(document).ready(function() { 
	
		$('#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;
		});
		    
	})
})(jQuery);

The last thing to do is to include this javascript in our CustomAdmin interface by adding the following to your CustomAdmin class :

mysite/code/CustomAdmin.php 

 

class CustomAdmin extends ModelAdmin {

.
.
.

	public function init() 
	{
	    parent::init();

	    Requirements::javascript('mysite/javascript/customadmin.js');
	}	
}
.
.
.

And that is it! You can now duplicate to your hearts content! 

Note about Relations - By default the duplicate() function on DataObject won't duplicate any has_many or many_many relations that might be on the object. To do this you need to add your own code to our custom duplicate() function in CustomAdmin_RecordController, reffering to $this->currentRecord and $Clone when copying over the relations.

Want more ModelAdmin customisation?

If you enjoyed this tinkering with ModelAdmin and fancy something a little more adventurous then check out Arron Carlinos awsome tutorials on really getting your hands dirty and managing versioned SiteTree objects from within ModelAdmin among other things:

Taming the Beast: Remodelling ModelAdmin

Remodeling ModelAdmin, Part II (Date Range Filtering)

Special Thanks

Special thanks go to Aaron Carlino for their contributions to this post.

Aram Balakjian avatar

Aram Balakjian

Aram is a web developer running London based agency Aab Web. He has a strong passion for developing attractive, usable sites around the SilverStripe CMS.

  • swaiba
    19/04/2011 5:17pm (3 years ago)

    Nice article Aram - I've been planning to use CMSActions and AJAX - and this has all this info in one place!

  • dendeffe
    19/04/2011 5:23pm (3 years ago)

    Ah, this is the kind of info I have been looking for for quite a while, very interesting.

    Small typo: straightforward instead straitforward

  • Aram Balakjian
    19/04/2011 5:26pm (3 years ago)

    Thanks guys glad it's useful! And thanks for the typo @dendeffe, fixed :)

  • joel
    20/04/2011 9:38am (3 years ago)

    Hi Aram

    Thanks for this. Great stuff.

    Joel

  • dendeffe
    21/04/2011 11:26am (3 years ago)

    Just had the time to work through this. I was looking up core Sapphire and CMS stuff all the time and learned a lot.

    The JavaScript remains complicated, but it seems Uncle Cheese's script does make it a bit easier. Let's hope this will all get easier in SS 3.

    I'll have to go through Uncle Cheese's tutorials to further improve my ModelAdmin skills.

    Lastly, I did find another error: in the first code example you create a DataObject called MyDataObject, but in the rest of the tutorial you use CustomDataobject.

  • Ed
    22/06/2011 3:09am (3 years ago)

    I'm getting a 404 (in Javascript console) whenever I click any of these buttons.

    My code is at http://www.sspaste.com/paste/show/4e015412e3e54 and the URL it's trying to GET is http://dev.snappernet.co.nz/admin/ra/RA/3249/EditForm?action_nextStep=Send+to+Admin&....

    Any idea what the heck I'm doing wrong?

  • Frank Mullenger
    27/06/2011 5:21am (3 years ago)

    Thanks Aram this was a really helpful, simplified example. Just what I needed to help get my head around ModelAdmin a bit more.

  • jonshutt
    04/10/2011 10:32pm (3 years ago)

    Hi, this all works well, but the duplicate button stays on screen when i click the 'back' button and return to the list... how would i got about a function to clear it off, and add it back on again when you press 'forward' or click on one of the items in the list...

    cheers

  • Bruce Walter
    19/10/2011 3:38pm (3 years ago)

    Thanks for the awesome article Aram!

    I only had one problem with this code, and that was due to a DataObject with alot of data. The javascript code above uses the default ajax method (GET) so some webservers (Apache for me) complain about the request URI being too long (error 414). If you add:

    type: 'POST'

    into the jQuery options for the .ajax call, the form will be submitted via POST and solve your issues.

    Cheers!

  • Klemen
    11/11/2011 3:40pm (3 years ago)

    Hi, this is great. Can you suggest how would we be able to add a button like this to the results list e.g. as a SummaryField, next to the red X (delete) button?

  • Wade
    11/01/2012 9:15pm (3 years ago)

    if you are getting a page not found error in firebug add the following line to your custom admin class:

    public static $record_controller_class = "CustomAdmin_RecordController";

    this cleared up my problems

  • Judderman
    05/03/2012 12:10pm (2 years ago)

    I'm also getting the (error 414) but have found type : 'POST' , doesn't fix the issue. doesn't datatype : "json" force WGET?

    is there a solution to this ?

  • Derek Leinbach
    17/08/2012 10:05pm (2 years ago)

    Question for Aram... Is it possible to duplicate a data object and save it as a new different data object? Can you help me on accomplishing this?

  • an_Eskimo
    11/02/2014 3:53am (6 months ago)

    Hey Aram,

    Was having a look at this, it is quite nice, I have just implemented it, got the ModelAdmin_RecordController error, so I replaced that with Gridfields, now I am getting a Notice stating an underfinded index on $options['Title'] in ModelAdmin

    Any ideas?

  • an_Eskimo
    11/02/2014 3:53am (6 months ago)

    Hey Aram,

    Was having a look at this, it is quite nice, I have just implemented it, got the ModelAdmin_RecordController error, so I replaced that with Gridfields, now I am getting a Notice stating an underfinded index on $options['Title'] in ModelAdmin

    Any ideas?

Post a comment ...

You cannot post comments until you have logged in. Login Here.

Advertisement

Site of the Month

Find SSbits on

Top Contributers

Rank Avatar Name
1 article image Aram Balakjian
2 article image Daniel Hensby
3 article image Marcus Dalgren
4 article image Hamish Campbell
5 article image njorndare
6 article image Ty Barho
7 article image Martijn van Nieuwenhoven
8 article image Darren-Lee
9 article image Roman Schmid
10 article image Matt Clegg

View full leaderboard


Advertisement