Why is my client-side save function firing multiple times? - javascript

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;
},

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;

Related

Can I use ActionCable to refresh the page?

I've recently been trying to create a live-scoring system for squash matches. I've managed to use ActionCable with Rails 5 to auto-update the score on the page, but I'd like to know if it's possible to tell Rails to refresh the page if a certain condition is met.
For example, if the game has finished, a different page is shown to say that the players are having a break between games. I need the page to refresh completely for this to happen.
In my database the boolean 'break' is marked as true when a game ends, and then the view uses a conditional if/else statement to decide what to show.
The code I use to update the score is attached below, I was thinking something along the lines of if data.break == true then the page will automatically refresh.
// match_channel.js (app/assets/javascripts/channels/match_channel.js)
$(function() {
$('[data-channel-subscribe="match"]').each(function(index, element) {
var $element = $(element),
match_id = $element.data('match-id')
messageTemplate = $('[data-role="message-template"]');
App.cable.subscriptions.create(
{
channel: "MatchChannel",
match: match_id
},
{
received: function(data) {
var content = messageTemplate.children().clone(true, true);
content.find('[data-role="player_score"]').text(data.player_score);
content.find('[data-role="opponent_score"]').text(data.opponent_score);
content.find('[data-role="server_name"]').text(data.server_name);
content.find('[data-role="side"]').text(data.side);
$element.append(content);
}
}
);
});
});
I don't know if this sort of thing is possible, and I'm not much good at anything Javascript related so I'd appreciate any help on this.
Thanks.
Reloading the current page is relatively straightforward. If you are using Turbolinks, you can use Turbolinks.visit(location.toString()) to trigger a revisit to the current page. If you aren't using Turbolinks, use location.reload(). So, your received function might look like:
received: function(data) {
if (data.break) {
return location.reload();
// or...
// return Turbolinks.visit(location.toString());
}
// your DOM updates
}
Either way is the equivalent to the user hitting the reload button, so it will trigger another GET, which calls your controller and re-renders the view.

Parse create user example not working

I'm trying to make a registration screen for a new web project I'm working on. It didn't seem to be working so I tried out just using the basic create user example on Parse.com
function register()
{
var user = new Parse.User();
user.set("username", "my name");
user.set("password", "my pass");
user.set("email", "email#example.com");
user.signUp(null, {
success: function(user) {
// Hooray! Let them use the app now.
},
error: function(user, error) {
// Show the error message somewhere and let the user try again.
alert("Error: " + error.code + " " + error.message);
}
});
}
Every time the call gets made I get this error...
Any ideas would be gratefully appreciated.
Thanks
You can find the specifics about Error codes for Parse here : https://www.parse.com/docs/js/guide#errors
100 is "ConnectionFailed". Seems like you (or at least your app ^^) has a problem connecting to Parse servers...
Hope it helps.
(on another note, you should edit your first post instead of answering if you want to add infos, follow-ups etc to your question ;) )
Just checked the database... turns out it was working but still throwing the error state. Guess I just have to check specific errors for now

What would cause "Request timed out" in parse.com cloud code count?

One of my cloud functions is timing out occasionally. It seems to have trouble with counting, although there are only around 700 objects in the class. I would appreciate any tips on how to debug this issue.
The cloud function works correctly most of the time.
Example error logged:
E2015-02-03T02:21:41.410Z] v199: Ran cloud function GetPlayerWorldLevelRank for user xl8YjQElLO with:
Input: {"levelID":60}
Failed with: PlayerWorldLevelRank first count error: Request timed out
Is there anything that looks odd in the code below? The time out error is usually thrown in the second count (query3), although sometimes it times out in the first count (query2).
Parse.Cloud.define("GetPlayerWorldLevelRank", function(request, response) {
var query = new Parse.Query("LevelRecords");
query.equalTo("owner", request.user);
query.equalTo("levelID", request.params.levelID);
query.first().then(function(levelRecord) {
if (levelRecord === undefined) {
response.success(null);
}
// if player has a record, work out his ranking
else {
var query2 = new Parse.Query("LevelRecords");
query2.equalTo("levelID", request.params.levelID);
query2.lessThan("timeSeconds", levelRecord.get("timeSeconds"));
query2.count({
success: function(countOne) {
var numPlayersRankedHigher = countOne;
var query3 = new Parse.Query("LevelRecords");
query3.equalTo("levelID", request.params.levelID);
query3.equalTo("timeSeconds", levelRecord.get("timeSeconds"));
query3.lessThan("bestTimeUpdatedAt", levelRecord.get("bestTimeUpdatedAt"));
query3.count({
success: function(countTwo) {
numPlayersRankedHigher += countTwo;
var playerRanking = numPlayersRankedHigher + 1;
levelRecord.set("rank", playerRanking);
// The SDK doesn't allow an object that has been changed to be serialized into a response.
// This would disable the check and allow you to return the modified object.
levelRecord.dirty = function() { return false; };
response.success(levelRecord);
},
error: function(error) {
response.error("PlayerWorldLevelRank second count error: " + error.message);
}
});
},
error: function(error) {
response.error("PlayerWorldLevelRank first count error: " + error.message);
}
});
}
});
});
I don't think the issue is in your code. Like the error message states: the request times out. That is, the Parse API doesn't respond within the period of the timeout or the network causes it to timeout. As soon as you do .count some API call is probably done, which then can't connect or times out.
Apparently more people have this issue: https://www.parse.com/questions/ios-test-connectivity-to-parse-and-timeout-question. It doesn't seem possible to increase the timeout, so the suggestion in this post states:
For that reason, I suggest setting a NSTimer prior to executing the
query, and invalidating it when the query returns. If the NSTimer
fires before being invalidated, ask the user if they want to keep
waiting for the results to come back, or show them a message
indicating that the request is taking a long time to complete. This
gives the user the chance to wait more if they know their current
network conditions are not ideal.
In case you are dealing with networks, and especially on the mobile platform, you need to prepare for network hickups. So like the post suggests: offer the option to user to try again.

Creating relations in parse.com with multiple classes (JavaScript SDK)

Using parse.com and JavaScript SDK.
I've read alot about this, but cannot find any examples that fit my issue I'm trying to solve.
Here is what I'm attempting to achieve.
I want class "FriendRequest" to have a one to many relation with "myBadges". The child objects held in "myBadges" will be increasing overtime.
The child objects are generated by a function that runs when the user selects a badge and uses "Yes, do it now!" to store it in "myBadges".
At the moment I can only seem to create a relationship within the active class that the function uses. This means I have a relation set up in "myBadges" that just points to the User class, not to the class "FriendRequest".
The relation is in this part of code
success: function(results) {
// The object was saved successfully.
userbadges.relation('BadgeConnect').add(userbadges);
userbadges.save();
So the problem I'm trying to solve is to move the relation link from the "myBadges" class to the "FriendRequest" class???
An example answer how how to achieve this would be great to help me learn the correct approach.
FULL CODE.
<script type="text/javascript">
Parse.initialize("xxxx", "xxxxx");
var MyBadges = Parse.Object.extend("myBadges");
var friendRequest = Parse.Object.extend("FriendRequest");
var userbadges = new MyBadges();
var friendRequest = new friendRequest();
var user = Parse.User.current();
$(document).ready(function() {
$("#send").click(function() {
var badgeselected = $('#badgeselect .go').attr("src");
userbadges.set("BadgeName", badgeselected);
userbadges.set("uploadedBy", user);
//userbadges.set('BadgeName');
//userbadges.save();
userbadges.save(null, {
success: function(results) {
// The object was saved successfully.
userbadges.relation('BadgeConnect').add(userbadges);
userbadges.save();
//location.reload();
},
error: function(contact, error) {
// The save failed.
// error is a Parse.Error with an error code and description.
alert("Error: " + error.code + " " + error.message);
}
});
});
});
Like most problems there are many ways to solve it.
If your goal is to be able to query this relationship from either side then I see two main options:
Create a Cloud Function for after-save of your myBadges class that creates a duplicate of the relation on the FriendRequest side. I'm not sure how much support there is for this as I've never tried it.
Create your own relationship class, e.g. myBadges_FriendRequest, inside it have a pointer to each of the other classes, plus anything else you might want to know about that relationship
Personally I recommend the 2nd option. For querying you can easily request information based on either side of the relationship and use include() to populate both pointers in the result as needed.

creating new message notifications within <title></title> from a chat box

I'm trying to create notifications for my chat box, as seen beside the 'talk' title when ever some one new messages you. I've tried multiple things that never work.
a busy cat http://goawaymom.com/damit.png
here is my code
$(document).ready(function(){
//If user submits the form
$("#submitmsg").click(function(){
var clientmsg = $("#usermsg").val();
$.post("post.php", {text: clientmsg});
$("#usermsg").attr("value", "");
return false;
});
//Load the file containing the chat log
function loadLog(){
$.ajax({
url: "log.html",
cache: false,
success: function(html){
var chatbox= $("#chatbox");
var atBottom = (chatbox[0].scrollHeight - chatbox.scrollTop() == chatbox.outerHeight());
chatbox.html(html);
if (atBottom )
chatbox.animate({ scrollTop: 99999 }, 'normal');
}
});
}
setInterval (loadLog, 2500); //Reload file every 2.5 seconds
//If user wants to end session
$("#exit").click(function(){
var exit = confirm("Are you sure you want to end the session?");
if(exit==true){window.location = 'index.php?logout=true';}
});
});
does any body know how i would go about this. Everything I've tried so far has failed. I tried
using a set interval function that didn't work.
So, if I'm understanding correctly, submitting a message will append it to the end of log.html? I don't think that's going to work for what you're trying to do. You need to have a service to keep track of new posts so that you can check for updates, rather than just reloading the innerHTML of a div.
Once you have that in place, you can keep a count of how many new messages you've loaded since the chat had focus and reset it to 0 every time the chat gets focus.
You can update the title using document.title. So whenever you need to update the count, you can do
document.title = 'my normal title (' + newCount + ')';
Simply put, this isn't a problem you can solve with javascript, you need to redesign your app.
Your question is a bit incomplete / unclear. When you say "whenever someone new messages you", do you mean just when a new message appears (not necessary for you, but rather it's for the whole chat room).
Assuming that's the case, then the number is going to keep increasing whenever someone types something, which leads to the question: when do you decrement the number?
Here's a piece of code that will help you get started. It won't decrement the notification number, because in your question you didn't clarify when that number resets.
In $(document).ready add the following line in the end
// Load log and cache number of message on page when it first loaded
// Pass in a callback function to cache the message count.
loadLog(function(){
window.lastReadMessageCount = $('.msgln').length;
});
Update the loadLog to take in callback:
function loadLog(callback){
...
success: function(html){
if (callback) callback();
...
}
...
}
Now you know how many message the user has seen when the page loaded. Now whenever the page updates the chat, we want to update the new message count.
In your loadLog(), Add the following lines to the end
newMessagesCount = $('.msgln').length - lastReadMessageCount;
document.title = newMessagesCount > 0 ? 'title (' newMessagesCount ')' : 'title';
That's it! All you have to do now is clarify when you want lastReadMessageCount to be updated.

Categories

Resources