This forum has moved to a new location and is in read-only mode. Please visit talk.octobercms.com to access the new location.

Meysam
Meysam

I want to manually render a form widget. For this purpose, whenever the user clicks on one of the records of the list, I will display a form in a popup that opens.

I have created a test plugin with two models: Story and Word. There is a many-to-many relationship between the two models.

So first I modify the constrollers\stories\config_list.yaml file, to open a popup whenever a record is clicked:

list: $/meysam/test/models/story/columns.yaml
modelClass: Meysam\Test\Models\Story
title: Stories
noRecordsMessage: 'backend::lang.list.no_records'
showSetup: true
showCheckboxes: true
recordsPerPage: 20
toolbar:
    buttons: list_toolbar
    search:
        prompt: 'backend::lang.list.search_prompt'
recordUrl: 'meysam/test/stories/update/:id'
recordOnClick: "$.popup({ handler: 'onLoadWordForm', extraData: { record_id: ':id' } })"

And there is an ajax handler in the Stories.php controller which renders the form widget:

public function onLoadWordForm()
{
    $config            = $this->makeConfig('$/meysam/test/models/story/fields_short.yaml');
    $config->alias     = 'storyForm';
    $config->arrayName = 'Story';
    $config->model     = StoryModel::find(post('record_id'));
    $storyFormWidget   = $this->makeFormWidget('Backend\Widgets\Form', $config);
    $storyFormWidget->bindToController();

    $this->initForm($config->model);

    $this->vars['storyFormWidget'] = $storyFormWidget;
    $this->vars['recordId'] = post('record_id');

    return $this->makePartial('story_form');
}

In the models\story\short_fields.yaml file, there is only a relationship field defined, called words:

fields:
    words:
        label: Words
        type: partial
        address: words

And here's the _story_form.htm partial. The most important piece of code in it is <?= $storyFormWidget->render() ?> which renders the form widget:

<?= Form::open(['id' => 'updateForm']) ?>
<input type="hidden" name="record_id" value="<?= $recordId ?>" />
<div class="modal-header">
    <button type="button" class="close" data-dismiss="popup">×</button>
    <h4 class="modal-title"><?= e($this->pageTitle) ?></h4>
</div>

<?php if (!$this->fatalError): ?>

    <div class="modal-body">
        <?= $storyFormWidget->render() ?>
    </div>
    <div class="modal-footer">
        <button
            type="submit"
            data-request="onUpdate"
            onclick="$(this).data('request-data', {
                    redirect: 0
                })"
            data-hotkey="ctrl+s, cmd+s"
            data-popup-load-indicator
            class="btn btn-primary">
            <u>S</u>ave
        </button>

        <button
            type="button"
            class="btn btn-default"
            data-dismiss="popup">
            <?= e(trans('backend::lang.form.cancel')) ?>
        </button>
    </div>

<?php else: ?>

    <div class="modal-body">
        <p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
    </div>
    <div class="modal-footer">
        <button
            type="button"
            class="btn btn-default"
            data-dismiss="popup">
            <?= e(trans('backend::lang.form.close')) ?>
        </button>
    </div>

<?php endif ?>

<script>
  setTimeout(
    function(){ $('#updateForm input.form-control:first').focus() },
    310
  )
</script>

<?= Form::close() ?>

Now if I click on a record, a popup is shown with the form:

Now to add a new related word to the current story, I click on "Add Word", and get this error:

Relation behavior used in Meysam\Test\Controllers\Stories does not have a model defined.

How can I fix this?

To see the problem yourself, you can clone the following test plugin into your plugins directory: https://github.com/meysammahfouzi/test-oc-relations

Then you should see a new menu added to your backend: "Stories".
There are two controllers for managing two models: Stories and Words.

mjauvin
mjauvin

Yes, this is currently a problem when using popups with relations in the Form.

I didn't find a clean solution yet.

The problem is that the controller's create/update action is not called and that creates problem in the Relation Controller behavior.

Meysam
Meysam

Got it

That's because RelationController has been designed to be used only where the create or update functions are available, with record_id found in the URL of the page.

This class is "Tightly Coupled", which is not good. I am afraid it needs to be redesigned!

JeffGoldblum
JeffGoldblum

Both of the above statements are mostly incorrect.

What is happening here is that an AJAX request is being made to the RelationController (i.e. onRelationButtonAdd does not have the required information to bootstrap the RelationController as it would normally in the update context.

You can intercept the handling of that AJAX handler to initialize the RelationController with the model information yourself by running $this->initForm($model) (which in turn calls $this->initRelation($model)) yourself before the request is handled by the RelationController (either in the constructor or by hooking into backend.ajax.beforeRunHandler). Additionally, you will need to include the _relation_field hidden input in your popup's form element containing the name of the relationship being managed (in this case words).

What I recommend doing is moving the majority of the code in your AJAX handler into a new method called initStoryForm() and have that method called from your AJAX handler and from the constructor (when a hidden input you add to your popup's form element is present). This will ensure the containing Form widget and all of it's child widgets (i.e. the RelationController) and every other widget using an AJAX handler) will always be properly initialized on every AJAX request that needs them to function and not just on the first AJAX request you make to render those widgets.

1-4 of 4

You cannot edit posts or make replies: the forum has moved to talk.octobercms.com.