3

I'm trying to make a questionnaire that is administrable on backoffice.

To make it, I'm tying to add dynamicaly some new questions by clicking on a button using Javascript. enter image description here

To do so, I removed the data-bind attributes on the new questions inputs, I have no idea if this changes something (It probably does, But I don't get what exactly).

my view looks like this

<?php

$blockMain = $block->getLayout()->createBlock('VendorName\Questionnaire\Block\Adminhtml\Main');
$params = $blockMain->getRequest()->getParams();

$questions = $blockMain->getQuestions($params['questionnaire_id']);
?>
<script>
    function addNewQuestion(key){
        var val = '<fieldset class="admin__fieldset" id="Question_'+(key+1)+'">'+
        '<div class="admin__field">'+
            '<div class="admin__field-label">'+
            '<label>'+
            '<span>Question '+(key+1)+' </span>'+
            '</label>'+
            '</div>'+
            '<div class="admin__field-control">'+
            '<input class="admin__control-text" type="text" name="input_question_'+(key+1)+'" maxlength="255"'+
        'value="">'+
            '</div>'+
            '</div>'+
            '</fieldset>';
        key = key;
        document.getElementById('Question_'+key).insertAdjacentHTML('afterend', val);
        document.getElementById('addQuestion').setAttribute('onclick', 'addNewQuestion('+(key+1)+')')
    }
</script>
<?php

foreach ($questions as $key => $question) {
    ?>
    <fieldset class="admin__fieldset" id="Question_<?= $key+1 ?>">
        <div class="admin__field">
            <div class="admin__field-label">
                <label>
                    <span>Question <?= $key + 1 ?></span>
                </label>
            </div>
            <div class="admin__field-control">
                <input class="admin__control-text" type="text" name="input_question_<?= $key+1 ?>" maxlength="255"
                       value="<?= $question['title'] ?>">
            </div>
        </div>
    </fieldset>
    <?php
}
?>
<button id="addQuestion" onclick="addNewQuestion(<?= $key+1 ?>)"><?= __('Add a Question') ?></button>

and the layout like this

<?xml version="1.0" ?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="styles"/>
    <body>
        <referenceContainer name="content">
            <block class="Magento\Framework\View\Element\Template"
                   name="template_questions"
                   before="-"
                   template="Vendor_Questionnaire::Questionnaire/edit.phtml" />
            <uiComponent name="Vendor_questionnaire_questionnaire_form" before="template_questions"/>
        </referenceContainer>
    </body>
</page>

and my uiComponent

<?xml version="1.0" ?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">VendorName_questionnaire_questionnaire_form.questionnaire_form_data_source</item>
        </item>
        <item name="label" translate="true" xsi:type="string">General Information</item>
        <item name="template" xsi:type="string">templates/form/collapsible</item>
    </argument>
    <settings>
        <buttons>
            <button class="VendorName\Questionnaire\Block\Adminhtml\Questionnaire\Edit\BackButton" name="back"/>
            <button class="VendorName\Questionnaire\Block\Adminhtml\Questionnaire\Edit\DeleteButton" name="delete"/>
            <button class="VendorName\Questionnaire\Block\Adminhtml\Questionnaire\Edit\SaveButton" name="save"/>
            <button class="VendorName\Questionnaire\Block\Adminhtml\Questionnaire\Edit\SaveAndContinueButton" name="save_and_continue"/>
        </buttons>
        <namespace>vendorName_questionnaire_questionnaire_form</namespace>
        <dataScope>data</dataScope>
        <deps>
            <dep>vendorName_questionnaire_questionnaire_form.questionnaire_form_data_source</dep>
        </deps>
    </settings>
    <dataSource name="questionnaire_form_data_source">
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
            </item>
        </argument>
        <settings>
            <submitUrl path="*/*/save"/>
        </settings>
        <dataProvider class="VendorName\Questionnaire\Model\Questionnaire\DataProvider" name="questionnaire_form_data_source">
            <settings>
                <requestFieldName>questionnaire_id</requestFieldName>
                <primaryFieldName>questionnaire_id</primaryFieldName>
            </settings>
        </dataProvider>
    </dataSource>
    <fieldset name="general">
        <settings>
            <label/>
        </settings>
        <field formElement="input" name="name" sortOrder="10">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">Questionnaire</item>
                </item>
            </argument>
            <settings>
                <dataType>text</dataType>
                <label translate="true">name</label>
                <dataScope>name</dataScope>
                <validation>
                    <rule name="required-entry" xsi:type="boolean">false</rule>
                </validation>
            </settings>
        </field>
    </fieldset>
</form>

I found a similar Topic here How to create a dynamic fields in admin form?, but I cannot find out how to adapt their code (prince's code) to mine.

(I used mage2gen to generate the bases of the module, and an uicomponent to make the bases of the form. I guess there is a way to make that dynamic fields form using uicomponents, but I cannot find how)

  • You can look at this link bsscommerce.com/confluence/… , you will see how you can use the dataprovider to display the data and the controller to save the data. – Taschert Jul 25 at 15:33
0

After days of research (Not really, but I tryed a lot of things), I found a solution. Far to be the best and realy ugly but at least it works.

First of all, you need to know that I'm using three tables here : Questionnaire, Question, Response

The main problem was the fact that in my admin panel I wanted to have a ui_component using two tables (one for the questionnaire and one for its questions)

Actually, I did not found how to use two dataProviders/ dataSources in one ui_component, so there is my custom solution :

    <?xml version="1.0" ?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">vendor_questionnaire_questionnaire_form.questionnaire_form_data_source</item>
        </item>
        <item name="label" translate="true" xsi:type="string">General Information</item>
        <item name="template" xsi:type="string">templates/form/collapsible</item>
    </argument>
    <settings>
        <buttons>
            <button class="Vendor\Questionnaire\Block\Adminhtml\Questionnaire\Edit\BackButton" name="back"/>
            <button class="Vendor\Questionnaire\Block\Adminhtml\Questionnaire\Edit\DeleteButton" name="delete"/>
            <button class="Vendor\Questionnaire\Block\Adminhtml\Questionnaire\Edit\SaveButton" name="save"/>
            <button class="Vendor\Questionnaire\Block\Adminhtml\Questionnaire\Edit\SaveAndContinueButton" name="save_and_continue"/>
        </buttons>
        <namespace>vendor_questionnaire_questionnaire_form</namespace>
        <dataScope>data</dataScope>
        <deps>
            <dep>vendor_questionnaire_questionnaire_form.questionnaire_form_data_source</dep>
        </deps>
    </settings>
    <dataSource name="questionnaire_form_data_source">
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
            </item>
        </argument>
        <settings>
            <submitUrl path="*/*/save"/>
        </settings>
        <dataProvider class="Vendor\Questionnaire\Model\Questionnaire\DataProvider" name="questionnaire_form_data_source">
            <settings>
                <requestFieldName>questionnaire_id</requestFieldName>
                <primaryFieldName>questionnaire_id</primaryFieldName>
            </settings>
        </dataProvider>
    </dataSource>
    <fieldset name="general">
        <settings>
            <label/>
        </settings>
        <field formElement="input" name="name" sortOrder="1">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">Questionnaire</item>
                </item>
            </argument>
            <settings>
                <dataType>text</dataType>
                <label translate="true">name</label>
                <dataScope>name</dataScope>
                <validation>
                    <rule name="required-entry" xsi:type="boolean">false</rule>
                </validation>
            </settings>
        </field>

        <container name="dynamic_rows_container">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="component" xsi:type="string">Magento_Ui/js/dynamic-rows/dynamic-rows</item>
                    <item name="template" xsi:type="string">ui/dynamic-rows/templates/default</item>
                    <item name="componentType" xsi:type="string">dynamicRows</item>
                    <item name="recordTemplate" xsi:type="string">record</item>
                    <item name="addButtonLabel" xsi:type="string">Add Question</item>
                    <item name="deleteProperty" xsi:type="boolean">false</item>
                </item>
            </argument>
            <container name="record">
                <argument name="data" xsi:type="array">
                    <item name="config" xsi:type="array">
                        <item name="label" xsi:type="string" translate="true">Dynamic Rows</item>
                        <item name="component" xsi:type="string" translate="true">Magento_Ui/js/dynamic-rows/record
                        </item>
                        <item name="isTemplate" xsi:type="boolean">true</item>
                        <item name="is_collection" xsi:type="boolean">true</item>
                        <item name="showFallbackReset" xsi:type="boolean">false</item>
                    </item>
                </argument>
                <field name="question_id">
                    <argument name="data" xsi:type="array">
                        <item name="config" xsi:type="array">
                            <item name="dataType" xsi:type="string">text</item>
                            <item name="label" xsi:type="string" translate="true">ID</item>
                            <item name="visible" xsi:type="boolean">false</item>
                            <!--<item name="dataType" xsi:type="string">text</item>-->
                            <item name="formElement" xsi:type="string">input</item>
                            <item name="dataScope" xsi:type="string">question_id</item>
                        </item>
                    </argument>
                </field>
                <field name="title">
                    <argument name="data" xsi:type="array">
                        <item name="config" xsi:type="array">
                            <item name="dataType" xsi:type="string">text</item>
                            <item name="label" xsi:type="string" translate="true">Ennoncé</item>
                            <item name="formElement" xsi:type="string">input</item>
                            <item name="dataScope" xsi:type="string">title</item>
                            <item name="showFallbackReset" xsi:type="boolean">false</item>
                            <item name="validation" xsi:type="array">
                                <item name="required-entry" xsi:type="boolean">true</item>
                            </item>
                            <item name="sortOrder" xsi:type="string">10</item>
                        </item>
                    </argument>
                </field>


                <actionDelete>
                    <argument name="data" xsi:type="array">
                        <item name="config" xsi:type="array">
                            <item name="componentType" xsi:type="string">actionDelete</item>
                            <item name="dataType" xsi:type="string">text</item>
                            <item name="fit" xsi:type="boolean">false</item>
                            <item name="label" xsi:type="string">Actions</item>
                            <item name="additionalClasses" xsi:type="string">data-grid-actions-cell</item>
                            <item name="template" xsi:type="string">Magento_Backend/dynamic-rows/cells/action-delete
                            </item>
                        </item>
                    </argument>
                </actionDelete>

            </container>
        </container>
    </fieldset>
</form>

This looks like this on BO : enter image description here

But then, the datas entered are not saved. So I went in the Controler that saves the Questionnaires and added a simple function :

public function saveQuestions()
{
    $data = $this->getRequest()->getPostValue();
    $questions = $data['dynamic_rows_container'];
    foreach ($questions as $key => $question) {
        $model = $this->_objectManager->create(\Vendor\Questionnaire\Model\Question::class);
        $model->setData('title', $question['title']);
        $model->setData('questionnaire_id', $data['questionnaire_id']);
        $model->setData('type', 'text');
        try {
            $model->save();

        } catch (LocalizedException $e) {
            $this->messageManager->addErrorMessage($e->getMessage());
        } catch (\Exception $e) {
            $this->messageManager->addExceptionMessage($e, __('Something went wrong while saving the Questionnaire.'));
        }

    }
}

I'm using it just after the questionnaire's model save function.

But at this point, you cannot see the questions saved on the BO, because their data provider is not given in the ui_component. So I added a little template with its block :

there is the template

$blockMain = $block->getLayout()->createBlock('Vendor\Questionnaire\Block\Adminhtml\Main');
$params = $blockMain->getRequest()->getParams();

if(isset($params['questionnaire_id']) && $params['questionnaire_id']!= null)
    $questions = $blockMain->getQuestions($params['questionnaire_id']);
?>



    <?php
    if (isset($questions) && $questions != null) {
        foreach ($questions as $key => $question) {
            ?>
            <fieldset class="admin__fieldset" id="Question_<?= $key + 1 ?>">
                <div class="admin__field">
                    <div class="admin__field-label">
                        <label>
                            <span>Question <?= $key + 1 ?></span>
                        </label>
                    </div>
                    <div class="admin__field-control" style="display: flex">
                        <p style="margin-right: 20%;"><?= $question['title'] ?></p>
                        <a href="<?= $this->getUrl('vendor_questionnaire/question/delete',['question_id'=>$question['question_id']]); ?>"><button>x</button></a>
                    </div>
                </div>
            </fieldset>
            <?php
        }

    }
    ?>

And there is the block

<?php

namespace Vendor\Questionnaire\Block\Adminhtml;

use Symfony\Component\Config\Definition\Exception\Exception;

class Main extends \Magento\Framework\View\Element\Template
{

    protected $_questionCollection;
    protected $_questionnaireCollection;
    protected $_questionnaireModel;

    public function __construct(\Magento\Framework\View\Element\Template\Context $context,
                                \Vendor\Questionnaire\Model\ResourceModel\Question\CollectionFactory $questionCollection,
                                \Vendor\Questionnaire\Model\ResourceModel\Questionnaire\CollectionFactory $questionnaireCollection,
                                \Vendor\Questionnaire\Model\Questionnaire $questionnaireModel,
array $data = [])
    {
        $this->_questionCollection = $questionCollection;
        $this->_questionnaireCollection = $questionnaireCollection;
        $this->_questionnaireModel = $questionnaireModel;
        parent::__construct($context, $data);
    }

    public function getQuestions($idQuestionnaire){
        $questionnaire = $this->_questionnaireCollection->create()->addFieldToSelect("*")
            ->addFieldToFilter("questionnaire_id", $idQuestionnaire)
            ->getFirstItem();

        $questions = $this->_questionCollection->create()->addFieldToSelect('*')
            ->addFieldToFilter("questionnaire_id", $idQuestionnaire)
            ->getData();
       return $questions;
    }

}

And there you go, you can even delete the questions. I hope my solution could help some of you, and if you have a best way to do it, please share !

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.