jqueryajaxyii2jquery-select2kartik-v

Yii2: How to use option groups with AJAX-based Kartik's Select2


At present, I have a form field to render a static dropdown with option groups based on Kartik's Select2. Data is populated using the following:

public function getBibliographyList()
{ 
    return ArrayHelper::map($this->find()->select(['id','title','author'])
              ->orderBy('author','title')->all(), 'id', 'title', 'author');
}

So that titles are shown under their respective author option groups.

Now I want to revamp the form to take advantage from AJAX, so I took the example at Krajee Demo Site for Select2 and reworked it as follows:

------ VIEW ----------

<?= $form->field($model, 'orig_id')->widget(Select2::classname(), [
        'options' => ['placeholder' => Yii::t('app', 'Select a title...)],
        'pluginOptions' => [
            'allowClear'         => true,
            'minimumInputLength' => 3,
            'language'           => Yii::$app->language,
            'theme'              => 'krajee',
            'ajax' => [
                'url'      => \yii\helpers\Url::to(['search-doc']),
                'dataType' => 'json',
                'data'     => new JsExpression('function (params) { return {q:params.term}; }'),
            ],
            'errorLoading'      => new JsExpression("function () { return '".Yii::t('app', 'Waiting for data...')."'; }"),
            'escapeMarkup'      => new JsExpression("function (markup) { return markup; }"),
            'templateResult'    => new JsExpression("function (bibliography) { return bibliography.title; }"),
            'templateSelection' => new JsExpression("function (bibliography) { return bibliography.title; }"),
        ],
]) ?>

-------- CONTROLLER -------------

public function actionSearchDoc($q = null)
{
    Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
    $out = ['id' => '', 'title' => '', 'author' => ''];
    if ($q) {
        $query = new yii\db\Query;
        $query->select('id, title, author')
              ->from('bibliography')
              ->where(['like', 'title', $q])
              ->orWhere(['like', 'author', $q])
              ->limit(50);
        $command = $query->createCommand();
        $data    = $command->queryAll();
        $out = array_values($data);
    }
    return \yii\helpers\ArrayHelper::map($out, 'id', 'title', 'author');
}

As per Kartik's Select2 docs, ArrayHelper::map is the way to go when optgroups are around but I'm unable to figure this out as resulting dropdown is always empty. Here's a sample JSON string from ArrayHelper::map:

{"results":{"Author1":{"4":"DocumentFoo1","121":"DocumentFoo2","219":"DocumentFoo3","197":"DocumentFoo4","198":"DocumentFoo5","2":"DocumentFoo6","273":"DocumentFoo7"},"Author2":{"68":"DocumentThee1"}}}

Any ideas?


Solution

  • I figured this out by myself. First we must care about needed JSON string to feed AJAX-based Select2 which must be like this (notice special keywords results, id, text and children demanded by AJAX-based Select2 to build optgroups):

    Array
    (
    [results] => Array
        (
            [0] => Array
                (
                    [text] => "author1"
                    [children] => Array
                        (
                            [0] => Array
                                (
                                    [id] => "id1"
                                    [text] => "title1"
                                )
                            [1] => Array
                                (
                                    [id] => "id2"
                                    [text] => "title2"  
                                )                           
                        )
                )
            [1] => Array
                (
                    [text] => "author2"
                    [children] => Array
                        (
                            [0] => Array
                                (
                                    [id] => "id3"
                                    [text] => "title3"
                                )
                        )
                )
        )
    )
    

    However, four-argument ArrayHelper::map output is as follows:

    Array
    (
        [author1] => Array
            (
                [id1] => "title1"
                [id2] => "title2"
            )
    
        [author2] => Array
            (
                [id3] => "title3"
            )
    )
    

    So we must rearrange stuff and incorporate those special keywords. Hence, a proper controller action should be like this:

    public function actionSearchDoc($q = null)
    {
        // JSON format result.
        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
        $out['results'] = '';
        if ($q) {
            $query = Bibliography::find()->select(['id', 'title', 'author'])
                                         ->where(['like', 'title', $q])
                                         ->orWhere(['like', 'author', $q])
                                         ->all();
            // Group titles by author.
            $authorArray = ArrayHelper::map($query, 'id', 'title', 'author');
            // Previous array lacks keywords needed to use optgroups
            // in AJAX-based Select2: 'results', 'id', 'text', 'children'.
            // Let's insert them.
            $results = []; 
            foreach ($authorArray as $author => $docArray) {
                $docs  = [];
                foreach ($docArray as $id => $title) {
                    $docs[] = ['id' => $id, 'text' => $title];
                }
                $results[] = ['text' => $author, 'children' => $docs];
            }
            $out['results'] = $results;
        }
        return $out;
    }
    

    So, that's it. Hope it helps.