Rudiger
Rudiger

Reputation: 6769

handleRequest() called twice in same request, $Action is different

I have a custom route:

/create//$Action/$ID

With an Action:

public function Edit(SS_HTTPRequest $request) {
    $id = $this->getRequest()->params()['ID'];
    if($this->request->isPost()) return UploadForm::create($this, 'Edit/' . $id, 'SubmitImportedModule');;
    return $this->renderWith(array("Edit", "Page"));
}

And a custom form:

class UploadForm extends Form {
    public function __construct($controller, $name, $action) {
        $fields = FieldList::create(
            TextField::create('Title', 'Title'),
            $course = UploadField::create('Upload', 'Upload')
        );

        $actions = FieldList::create(
            FormAction::create($action, 'Add Resource')
        );
        $validator = RequiredFields::create('Name');

        parent::__construct($controller, $name, $fields, $actions, $validator);
    }
}

If the path is /create/Edit/ the upload field works fine. However if the path is /create/Edit/1/ the server responds with:

Action '1' isn't available on class UploadImportedCourseForm.

Delving into the handleRequest in RequestHandler I've found this is called twice, each time gets to line 180 where it looks at the object latestParam(). The first time it's called the Action is Edit, to be expected however the second time it's 1, which is actually the ID.

How has the action changed in the same request and how to I go about fixing my problem? The only way I think I can work around this problem is to have a hidden field with the ID instead.

Upvotes: 2

Views: 182

Answers (1)

UncleCheese
UncleCheese

Reputation: 1584

In general, methods that create forms should not be overly complex. They just need to return a form. With regard to the dynamic ID, you could do this in a couple different ways -- you could pass the ID in through a hidden field and create a method like getIDFromRequest() that checks both the URL and the request body.

private static $allowed_actions = ['EditForm'];

public function EditForm() {
    return UploadForm::create($this, __FUNCTION__, 'yourAction');
}

class UploadForm extends Form {
    protected function getIDFromRequest(SS_HTTPRequest $r)
    {
        return $r->param('ID') ?: $r->postVar('ID');
    }

    public function __construct($controller, $name, $action) {
        $fields = FieldList::create(
            TextField::create('Title', 'Title'),
            HiddenField::create('ID','', $this->getIDFromRequest($controller->getRequest())),
            $course = UploadField::create('Upload', 'Upload')
        );

        $actions = FieldList::create(
            FormAction::create($action, 'Add Resource')
        );
        $validator = RequiredFields::create('Name');

        parent::__construct($controller, $name, $fields, $actions, $validator);
    }
}

Alternatively, based on what I can infer that you're trying to do, I think a subcontroller would make a lot more sense here. Create a separate request handler that is ID-aware. This will let you scale much more cleanly if you're doing a lot of these types of operations.

private static $url_handlers [
    'yourpath/$ID' => 'handleEditForm'
];
private static $allowed_actions = ['handleEditForm'];

public function handleEditForm(SS_HTTPRequest $r)
{
    $obj = SomeObject::get()->byID($r->param('ID')) {
        if(!$obj) $this->httpError(404);
    }

    $handler = new MyController_EditRequest($this, $obj);
    return $handler->handleRequest($r, DataModel::inst());
}

class MyController_EditRequest extends RequestHandler
{
    protected $parent;

    protected $obj;

    private static $allowed_actions = ['edit', 'EditForm'];

    public function __construct(MyController $controller, SomeDataObject, $obj)
    {
        $this->parent = $controller;
        $this->obj = $obj;

        parent::__construct();
    }

    public function edit(SS_HTTPRequest $r) 
    {
        return $this->renderWith(['SomeTemplate_edit','Page']);
    }

    public function Link()
    {
        return $this->parent->Link('yourpath/'.$this->obj->ID);
    }

    public function EditForm()
    {
        return UploadForm::create($this, __FUNCTION__, 'yourAction', $obj->ID)
    }
}

class UploadForm extends Form {

    public function __construct($controller, $name, $action, $id) {
        $fields = FieldList::create(
            TextField::create('Title', 'Title'),
            HiddenField::create('ID','', $id),
            $course = UploadField::create('Upload', 'Upload')
        );

        $actions = FieldList::create(
            FormAction::create($action, 'Add Resource')
        );
        $validator = RequiredFields::create('Name');

        parent::__construct($controller, $name, $fields, $actions, $validator);
    }
}

Your subcontroller is DataObject-aware, so there's never any confusion about where things are coming from. You've taken the abstraction of post vars and URLs out of it.

Upvotes: 1

Related Questions