0

There are a lot of things going on here, so I'll try to run the line between being brief and being fully descriptive.

Overall goal: I'm creating a barebones (no CSS) admin panel that interfaces with a Parse.com NoSQL/MongoDB database, using HTML forms and Backbone.js to control the communication and save user-inputted information into the Parse database. I am not performing any validation other than some basic stuff client side.

Background: For one specific task, I am doing two things: 1) Grabbing information from Parse.com's database on "page load" (not EXACTLY at page load, but I am not certain how to render the view correctly without doing this - when the user selects the "add purchase" option, the addPurchase View is generated, and I make form submit once with a 0 value to query the database and append the results to the screen - I think this may be causing the issue) and using jQuery to render the information to the HTML page. 2) Then, I am asking the user to select one of 4 options, upon submission of which I will record this as a transaction and generate an entry in a "purchase log" table also with Parse.com.

The problem: The view renders successfully, and the values are successfully retrieved from the database and placed on the page. However, when the user submits the form indicating which option they choose, the resulting transaction record is being stored in the database at a rate of some function of 'n', where n is the number of times a transaction record has already been saved in the current session. If I refresh the view, the 'n' resets. The more times I save without refreshing the page, the more duplicate entries are saved.

I'm new to BackboneJS, Parse.com, and MV* frameworks in general. So I definitely think my code could use some cleanup, and I also feel like I'm missing a key piece of information about how Backbone/HTML forms/Views/the DOM works. So I apologize in advance for any sloppy code; I'm totally cool with any suggestions for cleanup you have as well =P.

My Code: There are a number of places where something could be going wrong, I'll try to keep it succinct.

In the HTML, I have a set of radio buttons that allows the user to decide what action they want to perform. Depending on which button is selection, different forms will render to the browser. Once a form is submitted, Parse attempts to save the values in the database and upon successful save, calls back to the browser with an alert saying save was successful, and then a new AdminPanelView() is called.

The problem of multiple saves happened with other actions too, but I solved the problem by adding the following two lines immediately after the save function:

this.undelegateEvents();
delete this;

However, when I do that inside the query's success block in the addPurchase function, I get a console error telling me that undelegateEvents() is not a function of 'this', and the debugger tells me that 'this' is assigned to the Window at the time this call is made.

If I try to move those same two lines to just after the query function as well, it appears that nothing gets saved at all. The view just immediately switches back to a fresh AdminPanelView, without saving.

The behavior also changes depending on where I put 'return false' statements throughout the addPurchase function, but I haven't been able to figure out exactly what the returns are doing and I've just been playing whack-a-mole with them without really understanding what is happening in the code.

Please help me figure out what I'm doing wrong here! I feel like I'm misunderstanding a core concept here, so I'm eager to find out what it is and learn more about how Backbone and Parse works.

The behavior of the form is governed by some Backbone code:

var AdminPanelView = Parse.View.extend({
  events: {
    "click form.manageOperations": "manageOperations",
    "submit form#newPurchaseInfo": "addPurchase",
    //other stuff too
  },
    el: ".content",
initialize: function(){
    this.render();
    _.bindAll(this, "manageOperations", "addPurchase");
    this.manageOperations();
},

render: function(){
  this.$el.html(_.template($("#admin_panel_template").html()))
  this.delegateEvents();  
},

manageOperations: function() 
{
    this.$(".inputInfo").hide();
    var v = this.$("input[name=defineOperation]:checked").val();

    if (v=="add-purchase") { this.$("#newPurchaseInfo").show(); 
                            this.$("#newPurchaseInfo").submit();}
    else if //cases for the other options - normally I just 
        call this.$("#new[Whatever]Info.show();
},
addPurchase: function() {

    var Perk = Parse.Object.extend("Perk");
    var query = new Parse.Query(Perk);

    query.find({
        success: function(results) 
        {
            var i = 1;
            for (var r in results) 
            {
                this.$("#perk"+i+"co").html(results[r].get("company"));
                this.$("#perk"+i+"desc").html(results[r].get("description"));
                i++;
            }
            var purchase = Number(this.$("#newPurchaseInfo .perkChoice").val());

            // I think this line may be causing the issue. 
            if (purchase!=0)
            {
                alert("you indicated you want to use perk # " + purchase +", "
                + "which indicates " + results[purchase-1].get("description")
                + " from " + results[purchase-1].get("company"));

                var Purchase = Parse.Object.extend("PurchaseLog");
                var purchaseEntry = new Purchase();

                user = Parse.User.current();

                purchaseEntry.save(
                {
                    info        : info,
                }, {
                    success: function(purchase) 
                    {
                        alert("purchase saved successfully");
                        new AdminPanelView();
                        return false;
                    },
                    error: function(purchase, error) 
                    {
                        alert("purchase NOT saved: code " + error.code + ", " + error.message);
                    }
                });
            }

        return false;
        },
        error: function(error) 
        {
            alert("error code " + error.code + ": " + error.message);
        }
    });
    // this.undelegateEvents();
    // delete this;
    return false;
},
jdogg
  • 268
  • 2
  • 14
  • `delete this` does nothing, omit it. If the `this` context is wrong and you cannot call your method, see [this post](http://stackoverflow.com/q/20279484/1048572) to fix it. I'm unsure whether this is the proper MV* approach though, you might not need it at all after fixing the underlying conceptual problems. – Bergi Aug 22 '15 at 20:58

1 Answers1

0

Actually, I just figured it out. I love stackoverflow, sometimes just writing the problem description is enough to find the solution! If only I didn't answer my own questions so much, I would have more reputation =P

So, the problem was indeed within my addPurchase function(code highlighted below). What was happening is that the addPurchase view was always instantiated with an initial value of 0 being submitted in order to render the screen. However, as I was doing it defore, the 'this.undelegateEvents()' and 'delete this' were being called on the initial submit regardless. So I was basically deleting the views functionality without fully deleting the view until I tried to submit the form again, at which point I would get an entirely new AdminPanelView() and no save message (the value never saved because the submit value was 0, meaning the save() function was never called).

I simply made it so that the 'this.undelegateEvents()' and 'delete this' methods ONLY get called if the input value is NOT 0. Because in my program logic (however stupid it may be =P), 0 would only be input upon rendering the addPurchase view, in order to render the Perk information to the DOM.

I still feel like my code is convoluted as shit, and I'm open to suggestions on better algorithms, logic, and structure. Part of it is difficult because database return values are only in scope in the success() blocks of the Parse and Backbone functions, so a certain amount of "wall of doom" is hard to avoid.

query.find({
success: function(results) 
{

var purchase = Number(this.$("#newPurchaseInfo .perkChoice").val());
if (purchase!=0) {
  // attempt to save user in database
        purchaseEntry.save(
        {
            //definition of the object goes here
        }, {
            success: function(purchase) 
            {
                alert("purchase saved successfully");
                new AdminPanelView();
                return false;
            },
            error: function(purchase, error) 
            {
                alert("purchase NOT saved: code " + error.code + ", " + error.message);
            }
        });

    }
            return false;
},
error: function(error) 
{
    alert("error code " + error.code + ": " + error.message);
    // return false;
}
});

//This is the solution:
if (Number(this.$("#newPurchaseInfo .perkChoice").val()) != 0) 
{
    this.undelegateEvents();
    delete this;
}
return false;
jdogg
  • 268
  • 2
  • 14