Refresh a repeater field on a backend form

Hi,

I have a backend form with a repeater element. I have added a partial button to it that when clicked calls a function on the controller.

The function carries out various tasks that include adding another repeater item. I would like after everything has completed to refresh the repeater element with the new entries.

I have tried various combinations of this code - https://octobertricks.com/tricks/update-single-field-in-formcontroller-using-ajax - with no success.

I can see using a debugger that the $this->formRenderField('fieldToUpdate', ['useContainer'=>false]); is using the originally loaded model not the modified version. Even if it’s saved back to the database and refetched prior to $this->initForm($model);

Any pointers will be gratefully received.

Hi

Yes, it will contain the unmodified model since the handler won’t automatically apply the form values. Try running this command inside the AJAX handler to log all the post variables to the system log (storage/logs/system.log) to inspect what is inside, then populate the model

trace_log(post());

This can be helpful to understand what data you have available. The following example can be useful

public function onDoSomething()
{
    // Get an instance of the model
    $model = $this->formGetModel();

    // Sets all the values from the current form
    $this->formGetWidget()->setFormValues();

    // Sets an individual field
    $this->formGetWidget()->setFormValues([
        'my_field' => 'foobar'
    ]);

    // Refreshes specific fields
    return $this->formRefreshFields([
        'my_field'
    ]);
}
2 Likes

Thank you. That works perfectly.

1 Like

@daft This method does not work with the Expando model, please advise how to update the repeater with this type of model

@Cryden

A repeater is a more complicated example. What form do you want to manipulate, specifically? Consider the following structure:

Parent Form
 ^- Repeater Field
    ^- Child Form 1
    ^- Child Form 2
    ^- Child Form 3

Do you want to set values on a single Child Form? Or do you want to establish the entire set of Child Forms?

To follow on from this solution

$parentForm = $this->formGetWidget();

$repeaterWidget = $parentForm->getFormWidget('repeater');

$repeaterForm = ... // No method exists to locate a child form

@daft I add new records to the repeater, which depend on the fields of the main form, that is, I create new Expando models, if I simply refresh the page I see them, but when I call the Ajax handler, the repeater is not updated

@daft

in modules\backend\formwidgets\repeater\HasRelationStore.php

/**
 * getLoadValueFromRelation
 */
protected function getLoadValueFromRelation()
{
    //  This code blocks the addition of new relations if they are created elsewhere

    // if ($this->relatedRecords !== null) {
    //     return $this->relatedRecords;
    // }

    // @deprecated This could be refactored to always use deferred binding
    // and make sure the array keys match the model keys and drop the _id
    // if ($this->isLoaded) {
    //     $value = $this->getLoadedValueFromPost();
    //     $ids = is_array($value) ? array_map(function ($v) {
    //         return $v['_id'] ?? 0;
    //     }, $value) : [];

    //     $records = $this->getRelationQuery()->find($ids);

    //     if ($records) {
    //         $indexes = array_flip($ids);
    //         foreach ($records as $model) {
    //             $rIndex = $indexes[$model->getKey()] ?? null;
    //             if ($rIndex !== null) {
    //                 $this->relatedRecords[$rIndex] = $model;
    //             }
    //         }
    //     }
    // } else {
    $this->relatedRecords = $this->getRelationObject()
        ->withDeferred($this->getSessionKey())
        ->get()
        ->all();
    //}

    // Store the results locally on the model to make it available to the
    // RelationController via the initNestedRelation method
    if ($this->relatedRecords) {
        [$model, $attribute] = $this->resolveModelAttribute($this->valueFrom);
        $model->setRelation($attribute, $model->newCollection($this->relatedRecords));
    }

    return $this->relatedRecords;
}

This code is work, and Repeater correctly updated

Hi @Cryden

Thanks for this. Since you haven’t provided your code, we can’t test whether it works for you.

However, I have removed this deprecated approach in v4. The relational repeater now solely relies on deferred binding.

1 Like