This forum has moved to a new location and is in read-only mode. Please visit talk.octobercms.com to access the new location.
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.
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.
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!
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