Forms Set

In component based web development, it is quite common to arrange more than one form on the same page. As opposed to form submissions via application/x-www-form-urlencoded or multipart/form-data, we can, thanks to Ajax, submit the content of more than one form using a single HTTP-request. This requires to dispatch the submitted data on the server to each form class, but if we prefix them with unique identifiers, that’s a no-brainer.

Directive djng-forms-set

To achieve this, we can reuse the same Form mixin classes as we have used in the previous examples. The main difference is that we must wrap the set of forms into the AngularJS directive, <djng-forms-set endpoint="/some/endpoint">...</djng-forms-set>. Inside this directive, we render the forms as usual using {⁠{ some_form.as_div }⁠}.

Forms Submission

The submit button(s) can now be placed outside of the <form>...</form> element. This allows us to submit the content from multiple forms altogether. We now however must specify the common endpoint to accept our form submissions; this is, as you might have expected, the attribute endpoint="/some/endpoint" in our forms wrapping directive ``djng-forms-set. To send the forms content to the server, add ng-click="do(update())" to the submission button. By itself however, this invocation of update() does not execute any further action on the client. We have to start this expression with do(...), in order to emulate the first promise, see below.

By itself, sending some data to the server does not cause any further action on the client. We therefore must tell our directive, what we want to do next. For this, django-angular’s button directive offers a few prepared targets, such as reloadPage() or redirectTo(). They typically shall be executed asynchronouosly, after the server replied to the update request.

Chaining Targets

Since form submission is asynchronous, here we extensively use the promises functions provided by AngularJS.

If we change the button element to <button ng-click="do(update()).then(reloadPage())">, then after our successful Ajax submission, the current page is reloaded.

Another useful target is redirectTo('/path/to/view'), which, after a successful submission, redirects the user to another page. If the response contains {data: {success_url: "/path/to/other/view"}}, then the URL provided to the redirectTo(...) function is overridden by success_url.

If we override the button directive in our own application, we can add as many alternative targets as we want. This can be used to create execution chains as just demonstrated.

Forms Validation

All Forms wrapped inside our djng-forms-set directive, are validated. This can and shall be used to prevent submitting data, if at least one of the forms does not validate. For this, just add ng-disabled="isDisabled()" to the submission button.

Form Submission Methods

By using the update() function, django-angular submits the forms data with an HTTP-request using method PUT. To submit the same data using HTTP method POST, use the provided function create(). To submit via HTTP method DELETE, use the provided function delete().

Form Processing Delays

Sometimes processing form data can take additional time. To improve the user experience, we shall add some feedback to the submission button. By changing the submit action to ng-click="do(disableButton()).then(update()).then(redirectTo()).finally(reenableButton())" the submit button is deactivated (disableButton) during the form submission and will be reactivated (reenableButton) as soon as the server responded. Here we use finally, since we want to reactivate the button, regardless of the servers’s success status. Remember, ...then(redirectTo()) is only invoked on success.

If the <button> element contains an <i> element, during the timeout period, the CSS classes are replaced by glyphicon glyphicon-refresh djng-rotate-animate. This adds a rotating spinner wheel to the button until reenableButton() is executed.

Passing Extra Data

Sometimes we might want to use more than one submit button. In order to distinguish which of those buttons has been pressed, add for instance ng-click="do(update({foo: 'bar'}))" to the corresponding <button> element. That dictionary then is added to the submitted payload and can be extracted by the server’s view for further analysis.

Scroll to Rejected Field

Forms sometimes extend over more than one screen height. If a form validation fails, the message near a rejected field may be outside the visible area. To improve the user experience, it therefore is good practice to point the user to the field(s), which have been rejected. This can by achieved by adding a target such as ng-click="do(...).then(...).catch(scrollToRejected()) to our promises chain. Now, whenever a form validation fails, django-angular looks for the first rejected field and scrolls the page content, so that it shows up on top of the visible area.