Select2 : Update data source dynamically

I would like to find the cleanest ways to do this (I working with Symfony 4, EasyAdmin Bundle, Webpack encore, Select2 and jquery):

I a page to create range of manufacturing. To do this, for each operation (line) you have to chose a Workplace, an Activity and a Rule (and some others things but we don’t care here).
By default all the existing workplaces and all the activities are display in the select2 field.
If user selects one Workplace, the activity on the same line as to be update. Only the Activity link to Workplace has to be displayed. And vice versa if the user chose an activty first.
If the user remove the Workplace or the Activity, all data should be displayed again.
The Rules are displayed according to a parameter sent when the page is opened. The Rules are never updated dynamically once the page is loaded.

It’s look like this :
Screenchot of the page
I currently have a code which is functional but dirty :

Template (config.html.twig)

{% block main %}
    <div class="container">
        {{ form_start(form) }}
        {{ form_widget(form.id) }}
        <div class="my-custom-class-for-errors">
            {{ form_errors(form) }}
        </div>
        <table class="table table-striped">
            <thead class="thead-dark">
            <tr>
                <th scope="col">Séq</th>
                <th scope="col">Code PDT - Désignation PDT</th>
                <th scope="col">Code activité - Désignation activité</th>
                <th scope="col">Règle</th>
                <th scope="col">Branche(s)</th>
                <th scope="col" >#</th>
            </tr>
            </thead>
            <tbody>
            {% for key, operation in form.operations %}
            <tr class="ligne-{{ key }}">
                {{ form_widget(operation.id) }}
                <th scope="row" >{{ form_widget(operation.numero) }}</th>
                <td>{{ form_widget(operation.pdt) }}</td>
                <td>{{ form_widget(operation.activite)  }}</td>
                <td>{{ form_widget(operation.linkregleoperation.regle) }}</td>
                <td>{{ form_widget(operation.linkregleoperation.branche) }}</td>
                <td data-key="{{ key }}"><i class="fas fa-times"></i></td>
            </tr>
            {% endfor %}
            </tbody>
        </table>
        {{ form_end(form) }}
    </div>
{% endblock %}

Builder form with data form the Workplace and Activity (OperationType.php):

class OperationType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('id', HiddenType::class)
            ->add('numero', IntegerType::class, [
                'attr' => ['min' => '10', 'max' => '500', 'step' => '10', 'class' => 'form-control', 'data-type_object' => 'activites'],
                'required' => false
            ])
            //Workplace
            ->add('pdt', null, [
                'attr' => ['class' => 'form-control select2-form', 'placeholder' => 'Entrez le PDT', 'data-type_object'=> 'pdts'],
                'required' => false

            ])
            //Activity
            ->add('activite',null, [
                'attr' => ['class' => 'form-control select2-form', 'placeholder' => 'Entrez le activité'],
                'required' => false

            ])
            ->add('linkregleoperation', LinkRegleOperationType::class)

        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Operation::class,
        ]);
    }
}

Builder form with data for Rule (linkregleoperationType.php) :

class LinkRegleOperationType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            //Rule
            ->add('regle',null, [
                'attr' => ['class' => 'form-control select2-form select2-form-regle', 'data-type_object'=> 'regles'],
                'required' => false

            ])
            ->add('branche', TextType::class, [
                'required' => false,
                'attr' => ['class' => 'form-control', 'placeholder' => 'Entrez le numéro de branche'],
            ])

            ->get('branche')
                ->addModelTransformer(new CallbackTransformer(
                    function ($branchesAsArray) {
                        if ($branchesAsArray === null)
                            return "";
                        // transform the array to a string
                        return implode('-', $branchesAsArray);
                    },
                    function ($branchesAsString) {
                        if ($branchesAsString === '')
                            return null;
                        // transform the string back to an array
                        return explode('-', $branchesAsString);
                    }
                ))
            ;
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => LinkRegleOperation::class,
        ]);
    }
}

Javascript File (config.js) :

// Custom adapter to dynamically to the Workplace and Activity-'s Select2
$.fn.select2.amd.define('select2/data/customAdapter',
    ['select2/data/array', 'select2/utils'],
    function (ArrayAdapter, Utils) {
        function CustomDataAdapter ($element, options) {
            CustomDataAdapter.__super__.constructor.call(this, $element, options);
        }
        Utils.Extend(CustomDataAdapter, ArrayAdapter);
        CustomDataAdapter.prototype.updateOptions = function (data) {
            this.$element.find('option').remove(); // remove all options
            this.addOptions(this.convertToOptions(data));
        }
        return CustomDataAdapter;
    }
);

var customAdapter = $.fn.select2.amd.require('select2/data/customAdapter');

// My AJAX request to get the data (Workplace, Activity and Rule)
async function getDatas(object, constraint) {
    let link = 'api/ge/' + object + '/' + constraint;
    const rawResponse = await fetch(link, {
        method: 'POST',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },

    });
    return await rawResponse.json();
}

$(document).ready(async function() {
    let  gamme_enveloppe_id = $('#gamme_enveloppe_id').val()
    // I associate the custom adapter with the Select2 for Workplace and Activity
    $('.select2-form').select2({
        dataAdapter: customAdapter,
        placeholder: {
            id: '', // the value of the option
            text: 'Choisissez une option'
        },
        allowClear: true,
        multiple: false
    });
    // I associate an AJAX ressource with the Select2 for Rule
    $('.select2-form-regle').select2({
        ajax: {
            url: 'api/ge/regles/' + gamme_enveloppe_id,
            dataType: 'json',
            data: function (term) {
                return {
                    term: term
                };
            },
            results: function (data) {
                return {
                    results: $.map(data, function (item) {
                        return {
                            text: item.text,
                            slug: item.text,
                            id: item.id
                        }
                    })
                };
            }
        }
    });

    // Select2 for Workplace and Activity are updated when depending of the selectWorkplace or Activity
    $('.select2-form').change(async function () {
        let type_object = $(this).data('type_object');

        if (type_object !== 'regles') {
            let id_op = $(this).closest('tr').attr('class').split('ligne-')[1];
            let tmpString = '';
            let constraint = '';

            if (type_object === 'pdts')
                tmpString = 'activites';
            else if (type_object === 'activites')
                tmpString = 'pdts';

            let select2_ = '#gamme_enveloppe_operations_' + id_op + '_' + tmpString.slice(0, -1);

            if ($(this).val())
                constraint = $(this).children('option:selected').val();
            else {
                constraint = 'null';
                try {
                    const options = await getDatas(tmpString, constraint);
                    if (options.success) {
                        $(this).data('select2').dataAdapter.updateOptions(options.results).trigger('change');
                    }
                } catch (error) {
                    $(this).val(null);
                    console.log(error);
                }
            }
            let value = $(select2_).val();
            try {
                const datas = await getDatas(type_object, constraint);
                if (datas.success) {
                    $(select2_).data('select2').dataAdapter.updateOptions(datas.results).trigger('change');
                    $(select2_).val(value);
                }
            } catch (error) {
                $(select2_).val(value);
                console.log(error);
            }
        }
    });
});

What i would like to do :

I have to limit the ajax requests ans i would like to know your point of view to know what is the best way :

? Define 3 constants (workplaces, acivities, rules) in the javascript file which are updated when the page is load thanks to a Ajax request. The Select2 options are by default define with this constants (but i need to be able to update dynamically Workplaces and Activities)

? Create cache on my ajax request

Thank you for your help !

Sorry for my bad english

Source: Symfony Questions

Was this helpful?

0 / 0

Leave a Reply 0

Your email address will not be published. Required fields are marked *