Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Modal and AJAX forms: Difference between revisions

From MoodleDocs
No edit summary
Line 10: Line 10:


<code javascript>
<code javascript>
import ModalForm from 'core_form/modal';
import ModalForm from 'core_form/modalform';
import {get_string as getString} from 'core/str';
import {get_string as getString} from 'core/str';


Line 18: Line 18:
         e.preventDefault();
         e.preventDefault();
         const element = e.target;
         const element = e.target;
         const modal = new ModalForm({
         const modalForm = new ModalForm({
             // Name of the class where form is defined (must extend \core_form\modal):
             // Name of the class where form is defined (must extend \core_form\dynamic_form):
             formClass: "core_customfield\\field_config_form",  
             formClass: "core_customfield\\field_config_form",  
             // Add as many arguments as you need, they will be passed to the form:
             // Add as many arguments as you need, they will be passed to the form:
Line 28: Line 28:
             returnFocus: element
             returnFocus: element
         });
         });
        // Show the form.
        modalForm.show();
         // Override onSubmitSuccess() method if you want to execute something on form submit. Response will contain everything the process() function returned:
         // Override onSubmitSuccess() method if you want to execute something on form submit. Response will contain everything the process() function returned:
         modal.onSubmitSuccess = (response) => {
         modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, (e) => window.console.log(e.detail));
            console.log(response);
        };  
     });
     });
</code>
</code>
Line 37: Line 37:
=== How it works ===
=== How it works ===


* When an instance of the ModalForm class is created in the Javascript, the AJAX requests calls the web service '''core_form_modal_form_body'''. This web service creates a new instance of the form (specified in the formClass parameter), passes the arguments (args parameter), and returns the form HTML and JS similar to how [[Fragment]] does it. The form itself is then displayed inside the popup.  
* When an instance of the ModalForm class is created in the Javascript, the AJAX requests calls the web service '''core_form_modal'''. This web service creates a new instance of the form (specified in the formClass parameter), passes the arguments (args parameter), and returns the form HTML and JS similar to how [[Fragment]] does it. The form itself is then displayed inside the popup.  
* The ModalForm class listens to the click events on no-submit buttons, if such button is pressed, the new request is sent to the same web service and the form is re-rendered, client-side validation is not called in this case (example: adding a repeated element);
* The ModalForm class listens to the click events on no-submit buttons, if such button is pressed, the new request is sent to the same web service and the form is re-rendered, client-side validation is not called in this case (example: adding a repeated element);
* When "Cancel" or "Close" buttons in the modal dialogue are pressed, the ModalForm triggers necessary javascript events (to reset Atto autosave, to reset "dirty" form state, etc), the modal dialogue is closed and destroyed.  
* When "Cancel" or "Close" buttons in the modal dialogue are pressed, the ModalForm triggers necessary javascript events (to reset Atto autosave, to reset "dirty" form state, etc), the modal dialogue is closed and destroyed.  
* When the "Save" button in the modal dialogues is pressed and form is validated on the client side, the ModalForm collects form data and sends it in a AJAX request to the same web service '''core_form_modal_form_body'''. If the server-side validation failed, a form is re-rendered with all the errors information. If the form was submitted successfully, the modal dialogue is closed and destroyed automatically and the onSubmitSuccess() method is called.
* When the "Save" button in the modal dialogues is pressed and form is validated on the client side, the ModalForm collects form data and sends it in a AJAX request to the same web service '''core_form_modal'''. If the server-side validation failed, a form is re-rendered with all the errors information. If the form was submitted successfully, the modal dialogue is closed and destroyed automatically and the onSubmitSuccess() method is called.


== AJAX forms ==
== AJAX forms ==
Line 48: Line 48:
* as a response to some user action (i.e. clicking a button), the form is dynamically loaded and added to the page, on submit/cancel this form is unloaded and removed from the page
* as a response to some user action (i.e. clicking a button), the form is dynamically loaded and added to the page, on submit/cancel this form is unloaded and removed from the page


Both cases are handled with the same javascript module '''core_form/ajaxform''' and have the same requirements:
Both cases are handled with the same javascript module '''core_form/dynamicform''' and have the same requirements:
* form definition has to be in a class extending \core_form\modal (but unlike modal forms, for AJAX forms you will need to add the regular submit/cancel buttons);
* form definition has to be in a class extending \core_form\dynamic_form (but unlike modal forms, for AJAX forms you will need to add the regular submit/cancel buttons);
* there must be a specific container element in the DOM that will not have any other children except for the form;
* there must be a specific container element in the DOM that will not have any other children except for the form;
* the form must be initialized in the Javascript (initialization requires at least form class name and the container element);
* the form must be initialized in the Javascript (initialization requires at least form class name and the container element);
Line 64: Line 64:
In the Javascript initialize this form:
In the Javascript initialize this form:
<code javascript>
<code javascript>
import AjaxForm from 'core_form/ajaxform';
import DynamicForm from 'core_form/dynamicform';
// ...
// ...


// Initialize the form.
// Initialize the form.
const form = new AjaxForm(document.querySelector('#formcontainer'), 'core_user\\form\\private_files');
const dynamicForm = new DynamicForm(document.querySelector('#formcontainer'), 'core_user\\form\\private_files');
// By default the form is removed from the DOM after it is submitted, you may want to change this behavior:
// By default the form is removed from the DOM after it is submitted, you may want to change this behavior:
form.onSubmitSuccess = (response) => {
dynamicForm.addEventListener(dynamicForm.events.FORM_SUBMITTED, e => {
    const response = e.detail;
     console.log(response);
     console.log(response);
     // It is recommended to reload the form after submission because the elements may change.
     // It is recommended to reload the form after submission because the elements may change.
     // This will also remove previous submission errors. You will need to pass the same arguments to the form
     // This will also remove previous submission errors. You will need to pass the same arguments to the form
     // that you passed when you rendered the form on the page.
     // that you passed when you rendered the form on the page.
     form.load({arg1: 'val1'});
     dynamicForm.load({arg1: 'val1'});
}
}


Line 91: Line 92:
In the Javascript initialize this form:
In the Javascript initialize this form:
<code javascript>
<code javascript>
import AjaxForm from 'core_form/ajaxform';
import DynamicForm from 'core_form/dynamicform';
// ...
// ...


     // Initialize the form.
     // Initialize the form.
     const form = new AjaxForm(document.querySelector('[data-region=form]'), 'local_modalformexamples\\testform');
     const dynamicForm = new DynamicForm(document.querySelector('[data-region=form]'), 'local_modalformexamples\\testform');
     // When form is submitted - remove it from DOM:
     // When form is submitted - remove it from DOM:
     form.onSubmitSuccess = (response) => {
     dynamicForm.addEventListener(dynamicForm.events.FORM_SUBMITTED, e => {
        const response = e.detail;
         console.log(response);
         console.log(response);
         form.container.innerHTML = '';
         dynamicForm.container.innerHTML = '';
     }
     }
      
      
Line 105: Line 107:
     document.querySelector('[data-action=loadform]').addEventListener('click', (e) => {
     document.querySelector('[data-action=loadform]').addEventListener('click', (e) => {
         e.preventDefault();
         e.preventDefault();
         form.load({arg1: 'val1'});
         dynamicForm.load({arg1: 'val1'});
     });
     });
</code>
</code>


Error to avoid: Make sure that you initialize the form only once on the page. After that you can call load() as many times as necessary
Error to avoid: Make sure that you initialize the form only once on the page. After that you can call load() as many times as necessary

Revision as of 19:45, 12 February 2021

Important: This documentation is for Moodle 3.11 and above, for previous versions please use MForm Modal tutorial on how to create your own modal form.

Modal forms

To display a moodleform in a modal popup you need to:

  • create a class with the form definition extending \core_form\modal , do not add submit/cancel buttons to the form definition;
  • initialize the form in the listener to some javascript event (for example, a click on the "Add" or "Edit" link)

Example of modal form initialization in the Javascript

import ModalForm from 'core_form/modalform'; import {get_string as getString} from 'core/str';

// .....

   document.querySelectorAll('[data-role=editfield][data-id]').addEventListener('click', e => {
       e.preventDefault();
       const element = e.target;
       const modalForm = new ModalForm({
           // Name of the class where form is defined (must extend \core_form\dynamic_form):
           formClass: "core_customfield\\field_config_form", 
           // Add as many arguments as you need, they will be passed to the form:
           args: {id: element.getAttribute('data-id')},
           // Pass any configuration settings to the modal dialogue, for example, the title:
           modalConfig: {title: getString('editingfield', 'core_customfield', element.getAttribute('data-name'))},
           // DOM element that should get the focus after the modal dialogue is closed:
           returnFocus: element
       });
       // Show the form.
       modalForm.show();
       // Override onSubmitSuccess() method if you want to execute something on form submit. Response will contain everything the process() function returned:
       modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, (e) => window.console.log(e.detail));
   });

How it works

  • When an instance of the ModalForm class is created in the Javascript, the AJAX requests calls the web service core_form_modal. This web service creates a new instance of the form (specified in the formClass parameter), passes the arguments (args parameter), and returns the form HTML and JS similar to how Fragment does it. The form itself is then displayed inside the popup.
  • The ModalForm class listens to the click events on no-submit buttons, if such button is pressed, the new request is sent to the same web service and the form is re-rendered, client-side validation is not called in this case (example: adding a repeated element);
  • When "Cancel" or "Close" buttons in the modal dialogue are pressed, the ModalForm triggers necessary javascript events (to reset Atto autosave, to reset "dirty" form state, etc), the modal dialogue is closed and destroyed.
  • When the "Save" button in the modal dialogues is pressed and form is validated on the client side, the ModalForm collects form data and sends it in a AJAX request to the same web service core_form_modal. If the server-side validation failed, a form is re-rendered with all the errors information. If the form was submitted successfully, the modal dialogue is closed and destroyed automatically and the onSubmitSuccess() method is called.

AJAX forms

There are two use cases for the AJAX forms:

  • the form is already present on the page, we just want to handle submit button in the AJAX requests and not refresh the whole page (in this case there is not much point in having a "Cancel" button)
  • as a response to some user action (i.e. clicking a button), the form is dynamically loaded and added to the page, on submit/cancel this form is unloaded and removed from the page

Both cases are handled with the same javascript module core_form/dynamicform and have the same requirements:

  • form definition has to be in a class extending \core_form\dynamic_form (but unlike modal forms, for AJAX forms you will need to add the regular submit/cancel buttons);
  • there must be a specific container element in the DOM that will not have any other children except for the form;
  • the form must be initialized in the Javascript (initialization requires at least form class name and the container element);

Example of a pre-rendered form with AJAX submission

Render the form on a page like you would normally do but don't forget to add a container element, for example: // In this example the form has arguments ['arg1' => 'val1']: $form = new local_myplugin\myform(null, null, 'post', , [], true, ['arg1' => 'val1']); echo html_writer::div($form->render(), , ['id' => 'formcontainer']);

In the Javascript initialize this form: import DynamicForm from 'core_form/dynamicform'; // ...

// Initialize the form. const dynamicForm = new DynamicForm(document.querySelector('#formcontainer'), 'core_user\\form\\private_files'); // By default the form is removed from the DOM after it is submitted, you may want to change this behavior: dynamicForm.addEventListener(dynamicForm.events.FORM_SUBMITTED, e => {

   const response = e.detail;
   console.log(response);
   // It is recommended to reload the form after submission because the elements may change.
   // This will also remove previous submission errors. You will need to pass the same arguments to the form
   // that you passed when you rendered the form on the page.
   dynamicForm.load({arg1: 'val1'});

}

Example of a dynamically loaded form

In your PHP or template add a container element but do not actually render the form. In this case we'll also add a button to load the form:

echo html_writer::div(html_writer::link('#', 'Load form', ['data-action' => 'loadform'])); echo html_writer::div(, , ['data-region' => 'form']);

In the Javascript initialize this form: import DynamicForm from 'core_form/dynamicform'; // ...

   // Initialize the form.
   const dynamicForm = new DynamicForm(document.querySelector('[data-region=form]'), 'local_modalformexamples\\testform');
   // When form is submitted - remove it from DOM:
   dynamicForm.addEventListener(dynamicForm.events.FORM_SUBMITTED, e => {
       const response = e.detail;
       console.log(response);
       dynamicForm.container.innerHTML = ;
   }
   
   // Add listener to the click event that will load the form.
   document.querySelector('[data-action=loadform]').addEventListener('click', (e) => {
       e.preventDefault();
       dynamicForm.load({arg1: 'val1'});
   });

Error to avoid: Make sure that you initialize the form only once on the page. After that you can call load() as many times as necessary