Flame_Phoenix
Flame_Phoenix

Reputation: 17574

Easiest way to create bootstrap modals that can run javascript in MVC5

I have an MVC5, C# application using the Razor engine.

In my application I have a view, called "Index.cshtml". In this view I want to present a bootstrap modal to the user.

When presented to the user, this modal will have buttons that should be capable of running JS functions and methods using jQuery.

I have achieved this in the past using a dynamic method:

However, after discussion with the community, we concluded that was a poor design pattern. Therefore, I must ask, what is the easiet way of achieving this objective?

Upvotes: 1

Views: 989

Answers (1)

roryok
roryok

Reputation: 9645

Having read your original thread, I think there was a mix up. TrueBlueAussie was saying that you should use delegated events.

$(document).on("click", "#savePackageChangesBtn", doSomething);

Instead of

$("#savePackageChangesBtn").on("click", doSomething);

Personally I don't see anything wrong with the way you are doing things, other than that. I'm writing an MVC app myself and using bootstrap modals for certain functions.

I have written two functions which create a modal Alert and modal Confirm, these are reusable.

function malert(options) {
    if (typeof options == 'string') { 
        // if all we got was a string, pass that string as a message param
        malert({ message: options });
    }
    else {
        var _options = $.extend({
            title: "Alert", 
            message: "", 
            cssclass: "",
            ok: "OK", 
            ok_class: "btn-primary",
            ok_icon: "ok-circle"
        }, options);

        // find out how many modals are currently VISIBLE on the page
        var level = $(".modal:visible").length;

        // if the title is Error, set our css class to error. Saves a little time
        if (_options.title == "Error")
            _options.cssclass = "error";

        // this is for nested modals, and I have a feeling there's a bug in here somewhere
        // I have encountered instances when the backdrop gets stuck over modals
        // use at your own risk!
        if (level > 0) {
            // move the backdrop in front of the last modal 
            $(".modal-backdrop").css({ "z-index": (1040 + (20 * level)) });
            // move our alert in front of that
            $(".modal.malert").show().removeClass("fade").css({ "z-index": (1050 + (20 * level)) });
        } else {
            $('.modal.malert').modal('show')
        }

        $('.modal.malert').addClass(_options.cssclass)
        $('.modal.malert .modal-header h5').html(_options.title);
        $('.modal.malert .modal-body p').html(_options.message);
        $('.modal.malert .modal-body .btnOK')
            .text('<i class="halflings ok-circle"></i>' + _options.ok)
            .click(function (e) {
                // this is an alert - all the ok button should ever do is close it. 
                // normally we'd just use data-dismiss="modal" but
                // we have to handle nested modals again
                e.preventDefault();
                if (level > 0) {
                    $(".modal-backdrop").css({ "z-index": (1040 + (20 * (level - 1))) });
                    $(".modal.malert").hide().addClass("fade").css({ "z-index": (1050 + (20 * (level - 1))) });
                } else {
                    $('.modal.malert').modal('hide');
                }
                $('.modal.malert').removeClass(_options.cssclass)
            });

        $('.modal.malert .modal-body .btnOK i').attr("class", "halflings").addClass(_options.ok_icon);
    }
}

HTML for modal alert window

<div class="modal slim fade malert" tabindex="-1" role="dialog" aria-hidden="true">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5>Alert</h5>
            </div>
            <div class="modal-body">
                <p></p>
                <a href="#" class="btn btn-primary btnOK"><i class="halflings ok-circle"></i>Ok</a>
            </div>
        </div>
    </div>
</div>

malert can be called in two ways. Either with full options

malert({ message: "File Not Found", title: "Error", ok_class: "btn-danger", ok_icon: "ban-circle" });

... or just with a message, which it will show within a standard alert modal

malert("Something terrible has happened!");

mconfirm is a little more complex.

function mconfirm(options, true_callback, false_callback) {
    // check for existing modal windows

    var _options = $.extend({
        title: "Confirm",
        message: "",
        cssclass: "",
        ok: "OK",
        ok_class: "btn-primary",
        ok_icon: "ok-circle",
        cancel: "Cancel",
        cancel_class: "btn-danger",
        cancel_icon: "ban-circle",
        true_callback: null,
        false_callback: null
    }, options);

    // find out how many modals are currently VISIBLE on the page
    var level = $(".modal:visible").length;

    // this is for nested modals, and I have a feeling there's a bug in here somewhere
    // I have encountered instances when the backdrop gets stuck over modals
    // use at your own risk!
    if (level > 0) {
        $(".modal.mconfirm .modal-body .field-validation-error").remove();
        $(".modal-backdrop").css({ "z-index": (1040 + (20 * level)) });
        $(".modal.mconfirm").show().removeClass("fade").css({ "z-index": (1050 + (20 * level)) });
    } else {
        $('.modal.mconfirm').modal('show')
    }

    $('.modal.mconfirm').addClass(_options.cssclass)
    $('.modal.mconfirm .modal-header h5').html(_options.title);
    $('.modal.mconfirm .modal-body p').html(_options.message);

    $('.modal.mconfirm .modal-body .btnCancel')
        .addClass(_options.cancel_class)
        .html('<i class="halflings ban-circle"></i>' + options.cancel) 
        .click(function (e) {
            e.preventDefault();
            if (level > 0) {
                $(".modal-backdrop").css({ "z-index": (1040 + (20 * (level - 1))) });
                $(".modal.mconfirm").hide().addClass("fade").css({ "z-index": (1050 + (20 * (level - 1))) });
            } else {
                $('.modal.mconfirm').modal('hide');
            }
            $('.modal.mconfirm').removeClass(_options.cssclass);
            $('.modal.mconfirm .modal-body .btnCancel').removeClass(_options.cancel_class);
            // cancel callback can be either a url or a function
            (typeof _options.false_callback == 'string') ? window.location.href = _options.false_callback : _options.false_callback();
        });
    $('.modal.mconfirm .modal-body .btnCancel i').attr("class", "halflings").addClass(_options.cancel_icon);


    $('.modal.mconfirm .modal-body .btnOK')
        .addClass(_options.ok_class)
        .html('<i class="halflings ok-circle"></i>' + _options.ok)
        .click(function (e) {
            e.preventDefault();
            if (level > 0) {
                $(".modal-backdrop").css({ "z-index": (1040 + (20 * (level - 1))) });
                $(".modal.mconfirm").hide().addClass("fade").css({ "z-index": (1050 + (20 * (level - 1))) });
            } else {
                $('.modal.mconfirm').modal('hide');
            }
            $('.modal.mconfirm').removeClass(_options.cssclass);
            $('.modal.mconfirm .modal-body .btnCancel').removeClass(_options.ok_class);
            // cancel callback can be either a url or a function
            (typeof _options.true_callback == 'string') ? window.location.href = _options.true_callback : _options.true_callback();
        });
    $('.modal.mconfirm .modal-body .btnOK i').attr("class", "halflings").addClass(_options.ok_icon);
}

HTML for modal confirm window

<div class="modal slim fade mconfirm" tabindex="-1" role="dialog" aria-hidden="true">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5>Confirm</h5>
            </div>
            <div class="modal-body">
                <p></p>
                <a href="#" class="btn btnCancel"><i class="halflings ban-circle"></i>Cancel</a>&nbsp;<a href="#" class="btn btnOK"><i class="halflings ok-circle"></i>Ok</a>
            </div>
        </div>
    </div>
</div>

mconfirm has two buttons - OK and Cancel. You can configure the text, icons and classes of those buttons, and provide a callback function for each one.

mconfirm({
        message: "Warning: this will delete all of your current message content. Continue?", title: "Upload HTML",
        ok: "Upload", ok_icon: "open", ok_class: "btn-danger",
        cancel: "Cancel", cancel_class: "btn-info",
        true_callback: function () {
            var formData = new FormData($('#upload_html')[0]);
            $.ajax({
                url: "/Messages/_HtmlUploadSingle",  //Server script to process data
                type: "POST",
                // Form data
                data: formData,
                //Options to tell jQuery not to process data or worry about content-type.
                dataType: "json",
                cache: false,
                contentType: false,
                processData: false
            })
            .done(function (json) {
                if (json.status == "success") {
                    $.get("/Messages/GetHTMLPart/" + json.MessageID, function (data) {
                        CKEDITOR.instances["editor_visual"].setData(data)
                    });
                }
                else {
                    malert({ message: json.error, title: "Error" });
                }
            });
        }
    });

The callbacks for OK / Cancel can be either a function or a URL. Sometimes you want to run some code, sometimes you just want to navigate away.

You will run into issues, as I did, if you want to launch nested bootstrap modals - open a second modal over the first one. I had trouble with this and had to write separate js to launch nested modals by adjusting the z-index of the overlay to move it in front of the previous modal.

You can see that in the code of each method - the "if (level > 0)" block.

I get a LOT of reuse out of these. I've called mconfirm maybe 15 times so far in the app. Where it breaks down is when I need to have other controls in a modal, like dropdowns or more than two buttons. Then it's not worth trying to reuse code - I just throw a separate modal into the view and some code to launch it

Upvotes: 2

Related Questions