django-bootstrap-modal-forms
django-bootstrap-modal-forms copied to clipboard
Adding callback function to Async Update
I'm having an issue with the asynchronous ajax updates. I have a table that is rendered using datatables.js, and I want to have an ajax create button that adds new items to the table. I can successfully create the items asynchronously, but it kills the datatable, which is rendered in Javascript.
When the form is submitted, I want to be able to add in a callback that rerenders the table.
Additionally, as a separate issue, there's some problems with closing the modal on submit.
@kpdebree Following line in .js of django-bootstrap-modal-forms should rerender your table https://github.com/trco/django-bootstrap-modal-forms/blob/master/bootstrap_modal_forms/static/js/jquery.bootstrap.modal.forms.js#L89. Element with dataElementId should be filled with string you prepare with separate view like in package examples https://github.com/trco/django-bootstrap-modal-forms/blob/master/examples/views.py#L99.
Have you already tried to implement callback in .js file included in this package? I believe this shouldn't be hard at all. I would approach it his way (1) add callback field to asyncSettings here https://github.com/trco/django-bootstrap-modal-forms/blob/master/bootstrap_modal_forms/static/js/jquery.bootstrap.modal.forms.js#L154, which will take your custom function and then (2) run this callback function instead of default update in this place https://github.com/trco/django-bootstrap-modal-forms/blob/master/bootstrap_modal_forms/static/js/jquery.bootstrap.modal.forms.js#L88.
I would be interested in including this if you can confirm it and maybe share some code. Unfortunately I don't have enough time at the moment to start implementing it from scratch.
This is what I did (my quick and dirty work), as I also needed capability to reload my datatable whenever a new record was added.
- Modified the original code to allow passing a callback function
asyncSettings.fnPostUpdateRefreshto be precise- Moved the native functionality into a separate function
postUpdateRefresh - Other Key changes:
validateAsyncSettingsnow ignores other settings iffnPostUpdateRefreshis defined- model is always closed before making call to
fnPostUpdateRefresh - cosmetic change .. now submit shows a spinner while ajax queries are being performed.. gives user a sense that something is happening.. as I could take some time with large forms / slow response
One of the key reason for refactoring code was avoiding following line, as that is something I surely don't want in my use case.
$(asyncSettings.dataElementId).html(response[asyncSettings.dataKey]);
I have added this file to my static folder, which overrides the file used by library.
I am sure there can be better implementation of it.. but this is my 1st cut.. and I thought to share it..
/*
django-bootstrap-modal-forms
version : 2.0.1
Copyright (c) 2020 Uros Trstenjak
https://github.com/trco/django-bootstrap-modal-forms
*/
;(function ($) {
"use strict";
// Open modal & load the form at formURL to the modalContent element
var modalForm = function (settings) {
$(settings.modalID).find(settings.modalContent).load(settings.formURL, function () {
$(settings.modalID).modal("show");
$(settings.modalForm).attr("action", settings.formURL);
addEventHandlers(settings);
});
};
var addEventHandlers = function (settings) {
// submitBtn click handler
$(settings.submitBtn).on("click", function (event) {
isFormValid(settings, submitForm);
});
// Modal close handler
$(settings.modalID).on("hidden.bs.modal", function (event) {
$(settings.modalForm).remove();
});
};
// Check if form.is_valid() & either show errors or submit it via callback
var isFormValid = function (settings, callback) {
$.ajax({
type: $(settings.modalForm).attr("method"),
url: $(settings.modalForm).attr("action"),
data: new FormData($(settings.modalForm)[0]),
contentType: false,
processData: false,
beforeSend: function () {
let spinnerHtml = "<i style='margin-left: 5px' class='fa fa-spinner fa-spin'/>";
let btn = $(settings.submitBtn);
btn.prop("disabled", true);
// Display spinner to denote action is underway
btn.data('inner', btn.html());
btn.html(btn.html() + spinnerHtml);
},
success: function (response) {
if ($(response).find(settings.errorClass).length > 0) {
// Form is not valid, update it with errors
$(settings.modalID).find(settings.modalContent).html(response);
$(settings.modalForm).attr("action", settings.formURL);
// Reinstantiate handlers
addEventHandlers(settings);
} else {
// Form is valid, submit it
callback(settings);
}
}
});
};
var postUpdateRefresh = function () {
$.ajax({
type: "GET",
url: asyncSettings.dataUrl,
dataType: "json",
success: function (response) {
// Update page
$(asyncSettings.dataElementId).html(response[asyncSettings.dataKey]);
// Add modalForm to trigger element after async page update
if (asyncSettings.addModalFormFunction) {
asyncSettings.addModalFormFunction();
}
if (asyncSettings.closeOnSubmit) {
$(settings.modalID).modal("hide");
} else {
// Reload form
$(settings.modalID).find(settings.modalContent).load(settings.formURL, function () {
$(settings.modalForm).attr("action", settings.formURL);
addEventHandlers(settings);
});
}
}
});
}
// Submit form callback function
var submitForm = function (settings) {
if (!settings.asyncUpdate) {
$(settings.modalForm).submit();
} else {
var asyncSettingsValid = validateAsyncSettings(settings.asyncSettings);
var asyncSettings = settings.asyncSettings;
if (asyncSettingsValid) {
var formdata = new FormData($(settings.modalForm)[0]);
// Add asyncUpdate and check for it in save method of CreateUpdateAjaxMixin
formdata.append("asyncUpdate", "True");
$.ajax({
type: $(settings.modalForm).attr("method"),
url: $(settings.modalForm).attr("action"),
data: formdata,
contentType: false,
processData: false,
success: function (response) {
var body = $("body");
if (body.length === 0) {
console.error("django-bootstrap-modal-forms: <body> element missing in your html.");
}
body.prepend(asyncSettings.successMessage);
// Update page without refresh
if (asyncSettings.fnPostUpdateRefresh) {
// dismiss modal
$(settings.modalID).modal("hide");
//Call custom function if defined
asyncSettings.fnPostUpdateRefresh();
} else {
postUpdateRefresh();
}
}
});
}
}
};
var validateAsyncSettings = function (settings) {
var missingSettings = [];
if (settings.fnPostUpdateRefresh && typeof (settings.fnPostUpdateRefresh) === "function") {
// Ignore other settings in case of user provider post-op function
// and replace the standard function with current function
postUpdateRefresh = settings.fnPostUpdateRefresh
return true;
}
if (!settings.successMessage) {
missingSettings.push("successMessage");
console.error("django-bootstrap-modal-forms: 'successMessage' in asyncSettings is missing.");
}
if (!settings.dataUrl) {
missingSettings.push("dataUrl");
console.error("django-bootstrap-modal-forms: 'dataUrl' in asyncSettings is missing.");
}
if (!settings.dataElementId) {
missingSettings.push("dataElementId");
console.error("django-bootstrap-modal-forms: 'dataElementId' in asyncSettings is missing.");
}
if (!settings.dataKey) {
missingSettings.push("dataKey");
console.error("django-bootstrap-modal-forms: 'dataKey' in asyncSettings is missing.");
}
if (!settings.addModalFormFunction) {
missingSettings.push("addModalFormFunction");
console.error("django-bootstrap-modal-forms: 'addModalFormFunction' in asyncSettings is missing.");
}
return missingSettings.length <= 0;
};
$.fn.modalForm = function (options) {
// Default settings
var defaults = {
modalID: "#modal",
modalContent: ".modal-content",
modalForm: ".modal-content form",
formURL: null,
errorClass: ".invalid",
submitBtn: ".submit-btn",
asyncUpdate: false,
asyncSettings: {
closeOnSubmit: false,
successMessage: null,
dataUrl: null,
dataElementId: null,
dataKey: null,
addModalFormFunction: null,
fnPostUpdateRefresh: null // reference to custom function that should be called after refresh
}
};
// Extend default settings with provided options
var settings = $.extend(defaults, options);
this.each(function () {
// Add click event handler to the element with attached modalForm
$(this).click(function (event) {
// Instantiate new form in modal
modalForm(settings);
});
});
return this;
};
}(jQuery));
Hi @kpdebree @trco Do you have an idea how to get created object id in the callback of the async modal?