October: OctoberCMS repeater: How to store index/id inside the json array

Created on 13 Jan 2019  路  19Comments  路  Source: octobercms/october

Hello everyone,

after 3 days of researching for a way to accomplish my need i thought its best to ask and hope to get an answer.

In this post

https://github.com/octobercms/october/issues/2772#issuecomment-296411282

it is exactly described what i am trying to do.

I have a repeater in my backend form and the data gets saved in the database as a json like that:


"topics": [

{
"topic_title": "timeslot 1",
"topic_description": "timeslot 1 descr",
},
{
"topic_title": "timeslot 2",
"topic_description": "timeslot 2 descr",
},
],

What i would like to achieve is that the repeater items get indexed like that:


"topics": [

{
"topic_id": "1",
"topic_title": "topic 1",
"topic_description": "topic 1 descr",
},
{
"topic_id": "2",
"topic_title": "topic 2",
"topic_description": "topic 2 descr",
},
],

In another github post https://github.com/octobercms/october/issues/2451#issuecomment-259804208 it is stated that this could only be achieved by manipulating the array with


formBeforeSave()

But the information is already available in the code as


$indexCount:

input type="hidden" name="___index_topics[]" value="2"

Can you please tell me how to get index/id into a repeater field so it gets stored inside the json array?

Question

All 19 comments

@nolyboms could you please provide more information on why you need the index to be stored? It used to be stored (at least, that's what I remember) but it was removed because it made it overly complex to store that information in two ways.

@LukeTowers thank you for responding.
I am generating an API out of the OctoberCMS Backend which provides all data to be used by an vuejs website.
The API contains all information about different topics.
Each topic in the array needs to be identified with a unique id because the logic of the website allows users to send questions or participate on a survey which are assigned to the topic by its ID.
Hope i explained it well.

@nolyboms hmm. If you have a need for a unique ID you should be using unique records with a RelationController, not a Repeater. The reason for this is that the "id" of a repeater is not unique, it's just an integer describing it's position within the array. If you really must have a unique ID then I recommend you look at adding to the data in formBeforeSave(), perhaps with a hidden guid field instead (and I would recommend a GUID for this if you went that route, not simply an integer).

@LukeTowers
If i could get the integer which describes the position to be stored with the array that would be absolutely perfect.

repeater id

Since that was possible in a previous OctoberCMS version - do you have an idea on how to get that info and store it in the array?

That number that you see is just straight CSS, it doesn't exist anywhere else. You'll notice that if you drag and reorder records the numbers stay in the same position even though the item contents are now different. That is why it is a bad idea to use the position of the element as an ID for other purposes.

@LukeTowers
I understand. Thank you! It was not clear to me that the number is based on CSS.

We are getting close. :-)

In my controller i am trying to append a value to the json array. But when i add this function to my controller it tries to append the value to a column:


public function formBeforeSave($model)
{
$model->topics= 'x1';
}

According to the docs i would have to define a accessor which i think is not necessary in my case.

So the question is now:
How to append an additional value to an existing json array in OctoberCMS?

@nolyboms same as you would with any array in general.

public function formBeforeSave($model)
{
    $model->topics = array_map($model->topics, function ($topic) {
         $topic['id'] = uniqid();
    });
}

@LukeTowers
I really REALLY appreciate your helping hand!!! THANK YOU!

Adding this to my controller


public function formBeforeSave($model)
    {

        $model->topics = array_map($model->topics, function ($topic) {
         $topic['id'] = uniqid();
        });

     }

throws the error:


 "array_map() expects parameter 1 to be a valid callback, array must have exactly two members"

And when i adjust the function according to a solution provided on stackoverflow to look like this...


public function formBeforeSave($model)
    {

        $model->topics = array_map(array($model, 'topics'), function ($topic) {
         $topic['id'] = uniqid();
        });

     }

I get the error:


 "array_map(): Argument #2 should be an array

Any idea how to make OctoberCMS happy and not throwing errors anymore? :)

Whoops, order of arguments was backwards. array_map(callbackFunction, $inputArray)
do it array_map(function () {}, $model->topics);

My function looks now like this:


 public function formBeforeSave($model)
    {


        $model->timeslots = array_map(function ($timeslot) {
             $timeslot['id'] = uniqid();
             return $timeslot;
        }, $model->timeslots);

        var_dump($model->timeslots); 
    }



The output of var_dump states that the id is been appended but checking the db column and generated API, it seems that the value is not being stored. Very strange. -_-

@nolyboms try attaching to the model's beforeSave event.

Nope. Didn't work. but...

So here is the strange thing which i can not understand:

Repeater Field Name: topics
DB Column: topics

Controller function:

    
$model->topics = array_map(function ($topic) {
    $topic['topic_id'] = uniqid();
    return $topic;
}, $model->topics);

NO TOPIC_ID IS STORED!

Now watch this...

Repeater Field Name: no change
DB: Created another column and called it: themes
Changed function to:

     
$model->themes = array_map(function ($topic) {
    $topic['topic_id'] = uniqid();
    return $topic;
}, $model->themes);

TOPIC_ID is appended to the column themes!

??????

@nolyboms post your model class.

sidenote: i have changed the column name and all corresponding assets now from "topics" to "timeslots"

model class:


use Model;

class Events extends Model
{
    use \October\Rain\Database\Traits\Validation;

    public $timestamps = false;

    protected $jsonable = [
        'event_manager',
        'event_news',
        'timeslots',
        'apptheme'
    ];

    public $table = 'xxx_xxx_events';

    public $rules = [
    ];

    public $belongsTo = [
        'speaker_id' => [
            'Xxx\Xxx\Models\Speakers',
            'table' => 'acme_xxx_speakers',
        ],
    ];

}

controller:


use Backend\Classes\Controller;
use BackendMenu;

class Events extends Controller
{
    public $implement = [
        'Backend\Behaviors\ListController',
        'Backend\Behaviors\FormController',
        ];

    public $listConfig = 'config_list.yaml';
    public $formConfig = 'config_form.yaml';

    public function __construct()
    {
        parent::__construct();
        BackendMenu::setContext('Xxx.Xxx', 'main-menu-item', 'side-menu-item');
    }

    public function formBeforeSave($model)
    {
        $model->timeslots = array_map(function ($meooow) {

                $meooow['id'] = uniqid();
                return $meooow;

        }, $model->timeslots);
    }

}    

is it working now?

No. :(

@nolyboms did you try putting the logic in the beforeSave() method on the model? Changing $model to $this instead of course

clap

THANK YOU

And for anyone in the same situation, here is the solution:

public function beforeSave()
{
    $this->_FieldName_ = array_map(function ($miau) {

        $miau['id'] = uniqid();
        return $miau;

    }, $this->_FieldName_);
}
Was this page helpful?
0 / 5 - 0 ratings