silverstripedata-objects

Silverstripe 3.2: How to make a custom action button in the CMS to create a new Dataobject and populate it from another one


I'm searching for a way to create a custom action button which allows me to make a new DataObject with pre-filled content from another DataObject. As a simple example: When I have an email and click the "answer"-button in my email-client, I get a new window with pre-filled content from the email before. I need exactly this functionality for my button. This button should appear next to each DataObject in the GridField.

So I know how to make a button and add it to my GridField (--> https://docs.silverstripe.org/en/3.2/developer_guides/forms/how_tos/create_a_gridfield_actionprovider/) and I know how to go to a new DataObject:

Controller::curr()->redirect($gridField->Link('item/new'));

I also found out that there is a duplicate function for DataObjects:

public function duplicate($doWrite = true) {
        $className = $this->class;
        $clone = new $className( $this->toMap(), false, $this->model );
        $clone->ID = 0;

        $clone->invokeWithExtensions('onBeforeDuplicate', $this, $doWrite);
        if($doWrite) {
            $clone->write();
            $this->duplicateManyManyRelations($this, $clone);
        }
        $clone->invokeWithExtensions('onAfterDuplicate', $this, $doWrite);

        return $clone;
    }

Perhaps it's easier than I think but at the moment I just don't get how to rewrite this to get what I need. Can somebody give me a hint?


Solution

  • That's for sure not the cleanest solution but I think it should do the trick.

    At first let's create the custom gridfield action. Here we will save all accessible records in a session and add a query string to the url so that we'll know which object we want to "clone"

    public function getColumnContent($gridField, $record, $columnName) {
        if(!$record->canEdit()) return;
    
        $field = GridField_FormAction::create(
            $gridField,
            'clone'.$record->ID,
            'Clone',
            'clone',
            array('RecordID' => $record->ID)
        );
    
        $values = Session::get('ClonedData');
        $data = $record->data()->toMap();
    
        if($arr = $values) {
            $arr[$record->ID] = $data;
        } else {
            $arr = array(
                $record->ID => $data
            );
        }
    
        Session::set('ClonedData', $arr);
    
        return $field->Field();
    }
    
    public function getActions($gridField) {
        return array('clone');
    }
    
    public function handleAction(GridField $gridField, $actionName, $arguments, $data) {
        if($actionName == 'clone') {
            $id = $arguments['RecordID'];
            Controller::curr()->redirect($gridField->Link("item/new/?cloneID=$id"));
        }
    }
    

    after adding this new component to our gridfield,

    $gridField->getConfig()->addComponent(new GridFieldCustomAction());
    

    we'll need to bring the data into the new form. To do so, add this code directly above "return $fields" on your getCMSFields function so it will be executed every time we'll open this kind of object.

    $values = Session::get('ClonedData');
    
    if($values) {
      Session::clear('ClonedData');
      $json = json_encode($values);
      $fields->push(LiteralField::create('ClonedData', "<div id='cloned-data' style='display:none;'>$json</div>"));
    }
    

    At the end we need to bring the content back into the fields. We'll do that with a little bit of javascript so at first you need to create a new script.js file and include it in the ss backend (or just use an existing one).

    (function($) {
      $('#cloned-data').entwine({
        onmatch: function() {
          var data = JSON.parse($(this).text()),
              id = getParameterByName('cloneID');
    
          if(id && data) {
            var obj = data[id];
    
            if(obj) {
              $.each(obj, function(i, val) {
                $('[name=' + i + ']').val(val);
              });
            }
          }
        }
      });
    
      // http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript#answer-901144
      function getParameterByName(name) {
        name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
        var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
            results = regex.exec(location.search);
        return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
      }
    })(jQuery);
    

    And that's it ... quite tricky. Hope it will solve your problem.