Search code examples
javascripttwitter-bootstrapbackbone.jspugbackgrid

How to pass a callback function to a compiled Jade template without declaring it globally in javascript


I have created a Backgrid table to manage users. One of the functions of that table is to allow paswords to be changed by an administrator. This is achieved by adding a button cell column to the backgrid table that launches a modal change password dialog. On entering the passwords and clicking change, the new password is passed back to the backgrid cell and inserted to the backbone model in the callback.

The problem is passing the callback, because the change password dialog is a compiled client side Jade template, so I can't pass a function in the options when the html is rendered, I can only pass a function name.

Here is what I have so far (showing only the Jade and the Backgrid PasswordCell definition).

The client side Jade template:

#user-passwd-dialog.modal
.modal-dialog
    .modal-content
        .modal-header.bg-warning
            button.close(type="button" data-dismiss="modal" aria-label="Close")
                span(aria-hidden="true") ×
            h4.modal-title
                span.glyphicon.glyphicon-log-in
                span  Set User Password
        .modal-body
            if message.length > 0
                // show any messages that come back with authentication
                .alert.alert-info #{message}

            // LOGIN FORM
            form
                .form-group
                    label(for="user-password") Password
                    input.form-control#user-password(type="password" placeholder="New Password")
                .form-group
                    label(for="user-confirm") Confirm Password
                    input.form-control#user-confirm(type="password" disabled="disabled" placeholder="Confirm Password")

        .modal-footer
            button.btn.btn-warning.btn-lg#user-change-password(type="button" disabled="disabled") Change Password
            button.btn.btn-warning.btn-lg(type="button" data-dismiss='modal') Cancel

script(type="text/javascript").
    function checkMatch() {
        var password = $("#user-password").val();
        var confirmPassword = $("#user-confirm").val();

        if (password !== confirmPassword) {
            $("#user-confirm").removeClass("alert alert-success").addClass("alert alert-danger");
            $("#user-change-password").prop("disabled", true);
            return false;
        } else {
            $("#user-confirm").removeClass("alert alert-danger").addClass("alert alert-success");
            $("#user-change-password").prop("disabled", false);
            return true;
        }
    }
    $("#user-password").keyup(function() {
         var password = $("#user-password").val();
         if (password.length >= #{ minLen }) {
             $("#user-confirm").prop("disabled", false);
            checkMatch();
        } else { 
            $("#user-confirm").prop("disabled", true);
        }
    });
    $("#user-confirm").keyup(function() {
        var password = $("#user-password").val();
        if (password.length >= #{ minLen }) {
            checkMatch();
        }
    });
    $("#user-change-password").click(function() {
        var password = $("#user-password").val();
        #{result}(password);
        $('#user-passwd-dialog').modal('hide');
    });

The Backgrid cell is defined as (the compiled Jade template is Templates.set_password(opts)):

    var PasswordCell = Backgrid.Cell.extend({
    className: "button-cell",
    template: function () {
        return '<button class="btn btn-sm btn-warning"><span class="glyphicon glyphicon-lock"></span></button>';
    },
    events: {
        "click": "setPassword"
    },
    setPassword: function (e) {
        e.preventDefault();
        // XXX binding to global variable so modal can set password
        // must be more elegant way to do this
        mycallbackwiththelongname = (function(password) {
            this.model.set({ password : password});
        }).bind(this);
        var opts = {
            message:"The password must be at least 8 characters long",
            minLen: 8,
            result: "mycallbackwiththelongname"
        };
        $("#dialog-wrapper").html(Templates.set_password(opts));
        $("#user-passwd-dialog").modal({ keyboard: true });
    },
    render: function () {
        this.$el.html(this.template());
        this.delegateEvents();
        return this;
    }
});

The question is in the code: Is there a more elegant way to pass the callback such that a global function is not needed. A local function would be preferable, but I am not sure how to specify the name.

I have a simplified jsfiddle working using the global function.


Solution

  • I figured out a way to use a function local to the PasswordCell 'click' handler by passing it using the jQuery data API. I attach the callback function to the parent element and then pass the name of the parent element to the function that renders the Jade template.

    In the PasswordCell change setPasswd to:

        setPassword: function (e) {
            e.preventDefault();
            var passwordResult = (function(password) {
                this.model.set({ password : password});
            }).bind(this);
            var opts = {
                name: "change-password-modal",
                message:"The password must be at least 8 characters long",
                minLen: 8,
                parent: "dialog-wrapper"
            };
            $("#dialog-wrapper").html(Templates.set_password(opts));
            $("#dialog-wrapper").data("onChange", passwordResult);
            $("#user-passwd-dialog").modal({ keyboard: true });
        },
    

    In the Jade template change the button click event handler:

    $("#user-change-password").click(function() {
        var password = $("#user-password").val();
        $("##{ parent }").data("onChange")(password);
        $('#user-passwd-dialog').modal('hide');
    });
    

    The jsfiddle has been updated.