1

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?

Highly Irregular
  • 38,000
  • 12
  • 52
  • 70
iraira
  • 315
  • 2
  • 13
  • So you don't know how to combine the custom gridfield action and the duplicate action? Did I correctly understand your problem? – csy_dot_io Jan 28 '16 at 17:57
  • Yes. I could use a part of the duplicate action but the new dataobject should not be added to the database yet. The user should be able to change the content before saving and creating the new dataobject so there have to be a redirect (or popup) to the not yet added but already pre-filled new dataobject. Is this possible? – iraira Jan 28 '16 at 18:05
  • Ah, hmm don't know but that'll be usefull. I'll try to find something out when I'm at home in a few hours – csy_dot_io Jan 28 '16 at 18:09
  • Ok that was a little bit tricky but I think a found a solution. let me know if it worked for you. – csy_dot_io Jan 28 '16 at 23:38

1 Answers1

1

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.

csy_dot_io
  • 1,199
  • 6
  • 19
  • Thank you sooooo much! This is exactly what I searched for. Works perfectly for me! Thank you for the really good step by step description! It was easy to understand and integrate in my code! You made my day! :) – iraira Jan 29 '16 at 06:13
  • After a while of testing there seems to be a problem with the entwine code and a htmleditorfield. It works only the first time. When I go back from the cloned new Dateobject without saving and then click on "clone" again the cloned content of the htmleditorfield is missing. All other fields works fine. Do you have an idea how to deal with that? – iraira Mar 17 '16 at 13:32
  • sry I've got very less time at the moment, but i think to make this work with htmleditor fields, you need to modify the function to detect if the field is an htmleditorfield and then use the tinymce setContent methode http://archive.tinymce.com/wiki.php/API3:method.tinymce.Editor.setContent – csy_dot_io Mar 18 '16 at 14:12