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
Line 51: Line 51:
===Example of a pre-rendered form with AJAX submission===
===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:
PHP: Render the form on a page like you would normally do but don't forget to add a container element, for example:
<code php>
<code php>
// In this example the form has arguments ['arg1' => 'val1']:
// In this example the form has arguments ['arg1' => 'val1']:
Line 61: Line 61:
</code>
</code>


In the Javascript initialize this form:
Javascript: Initialize this form and register listener to submit/cancel events:
<code javascript>
<code javascript>
import DynamicForm from 'core_form/dynamicform';
import DynamicForm from 'core_form/dynamicform';
Line 78: Line 78:
     dynamicForm.load({arg1: 'val1'});
     dynamicForm.load({arg1: 'val1'});
}
}
// Similar listener can be added for the FORM_CANCELLED event.


</code>
</code>

Revision as of 21:36, 18 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 write a small JS function that will:

  • Create a class with the form definition extending \core_form\dynamic_form, 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)
  • Add listener to event modalForm.events.FORM_SUBMITTED to perform some action when the form is submitted (display notification, redirect, refresh part of the page, etc). The event listener in the JS will receive everything that dynamic_form::process_dynamic_submission() returned in PHP.
  • Optionally listen to other events, for example, you may wish to add "Are you sure?" dialogue before the form is cancelled or submitted

Example of modal form initialization in the Javascript

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

// .....

   // In this example we will open the modal dialogue with the form when user clicks on the edit link:
   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,
       });
       // Listen to events if you want to execute something on form submit. Event detail will contain everything the process() function returned:
       modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, (e) => window.console.log(e.detail));
       // Show the form.
       modalForm.show();
   });

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

PHP: 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']); // Set the form data with the same method that is called when loaded from JS. It should correctly set the data for the supplied arguments. $form->set_data_for_dynamic_submission(); // Render the form in a specific container, there should be nothing else in the same container. echo html_writer::div($form->render(), , ['id' => 'formcontainer']);

Javascript: Initialize this form and register listener to submit/cancel events: import DynamicForm from 'core_form/dynamicform'; // ...

// Initialize the form - pass the container element and the form class name. 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) => {

   e.preventDefault();
   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'});

} // Similar listener can be added for the FORM_CANCELLED event.

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

How it works

PHP class dynamic_form

The class \core_form\dynamic_form must be used as a base class for all modal and AJAX forms. It extends class moodleform and contains additional functions for:

  • checking that current user has access to the form - get_context_for_dynamic_submission(), check_access_for_dynamic_submission()
  • preparing data for the form when it is displayed for the first time (before submission) - set_data_for_dynamic_submission()
  • processing submission - process_dynamic_submission()
  • identifying this form for the atto auto-save functionality - get_page_url_for_dynamic_submission()

In case of the normal moodleforms, the access check, preparation of the data and processing of the data is performed in a PHP page that contains the form but modal/AJAX forms do not have such page and they are rendered from a single web service. Therefore the form itself has to implement all necessary checks, pre- and post- processing of data.

The function process_dynamic_submission can return some data that JS will receive in the event handler for FORM_SUBMITTED. The examples of such data can be: id of the new entity, text for the notifications, url to redirect to, etc. This has to be a simple array/object that can be JSON-encoded (for example, do not return instance of moodle_url, return the string).

Web service core_form_dynamic_form

A single web-service core_form_dynamic_form is responsible for rendering and submission of the dynamic forms. It takes the form class name as an argument. Both modalform.js and dynamicform.js call this web service to render the form for the first time and to process form submission. There is normally no need for developers to work with this web service directly.