Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding callback function to Async Update #144

Open
kpdebree opened this issue Dec 15, 2020 · 3 comments
Open

Adding callback function to Async Update #144

kpdebree opened this issue Dec 15, 2020 · 3 comments
Labels
enhancement New feature or request low priority

Comments

@kpdebree
Copy link

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.

@trco
Copy link
Owner

trco commented Feb 13, 2021

@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.

@trco trco added the enhancement New feature or request label Feb 13, 2021
@PritamDutt
Copy link

PritamDutt commented Feb 18, 2021

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.

  1. Modified the original code to allow passing a callback function
  2. asyncSettings.fnPostUpdateRefresh to be precise
  3. Moved the native functionality into a separate function postUpdateRefresh
  4. Other Key changes:
    • validateAsyncSettings now ignores other settings if fnPostUpdateRefresh is 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));

@rez0n
Copy link

rez0n commented Jun 10, 2022

Hi @kpdebree @trco
Do you have an idea how to get created object id in the callback of the async modal?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request low priority
Projects
None yet
Development

No branches or pull requests

4 participants