Event is firing multiple times (Parse.com) - javascript

I'm writing an app in Parse, using the JavaScript framework. In my view, I have a link with the class 'new-page'. In the JS code, I have:
events: {
"click .new-page" : "createPage",
}
createPage is:
createReel: function() {
var self = this;
// Get current pagelist
var pages= new PageList;
pages.query = new Parse.Query(Page);
pages.query.equalTo("owner", Parse.User.current());
pages.query.ascending("order");
pages.fetch({
success: function(pagelist) {
var newPage = new Page;
newPage .save({
success: function(newpage) {
// Redirect to page edit
new PageEditView();
}
});
}
});
}
First time round, this works fine - the new page is created, and it goes to edit mode for that page. But if I then go back to the view with the 'Add Page' button and click it again, I get 2 new pages. If I do it again, I get 4, and so on.
I assume the event is 'building up', so that the more times the button is clicked, the more times the event gets fired.
I'm not sure where to start looking.

Late answer, but I just had a similar issue.
The mistake I made was that I created the view instance again every time I needed it.
That is not how Backbone is ment to work.
Views are (usually) created once. And when their model changes, the are being rendered again.
you seem to be instantiating your view every time the user saves at this line
PageEditView();
That's when the events stack up.
Instantiate that view once when you start up the page and then render it whenever you update the model for that view.
In native bakbone, event handlers can listen to model changes. Somehow it won't work in the Parse backbone version (or I can't get it to work). So for now, I do that manually with
PageEditView.model = model
PageEditView.render();

Make sure you undelegate events before calling the new PageEditView.

Related

Using WebSpeech API through AngularJS

I have been trying to use the WebSpeech Api through angularjs. Everything seems to work but the model doesn't get updated at once.
If I start the recognition again, the model updates. Seems like some inner loop/other construct is holding angular to see the changes.
Here is the codepen that I made.
Steps to reproduce:
1. Click start, and speak
2. After recognition detects end of speech hit start once again to start another recognition.
3. As soon as the second recognition is started, the model is updated with previous transcript.
Note: If a do console.log as below then it shows correct transcript, means the recognition part is working fine.
if(event.results[i].isFinal) {
self.final = self.final.concat(event.results[i][0].transcript);
console.log(event.results[i][0].transcript);
}
Everything seems perfect, except you forgot to call $scope.$apply(); when you modified values to get it effect on view. So it should be like this,
angular.module('speech',[]);
angular.module('speech').controller('speechController', function($scope) {
this.rec = new webkitSpeechRecognition();
this.interim = [];
this.final = '';
var self = this;
this.rec.continuous = false;
this.rec.lang = 'en-US';
this.rec.interimResults = true;
this.rec.onerror = function(event) {
console.log('error!');
};
this.start = function() {
self.rec.start();
};
this.rec.onresult = function(event) {
for(var i = event.resultIndex; i < event.results.length; i++) {
if(event.results[i].isFinal) {
self.final = self.final.concat(event.results[i][0].transcript);
console.log(event.results[i][0].transcript);
$scope.$apply();
} else {
self.interim.push(event.results[i][0].transcript);
}
}
};
});
I have updated your codepen with working solution.
AngularJs creates a "watch" internally for the all data-bindings created in view and call $scope.$digest() which inturns iterate thorugh all watches and checks if any of the watched variables have changed. When you call $scope.$apply() it internally calls $scope.$digest() so data-binding gets refreshed.
Listener directives, such as ng-click, register a listener with the DOM. When the DOM listener fires, the directive executes the associated expression and updates the view using the $apply() method.
When an external event (such as a user action, timer or XHR) is received, the associated expression must be applied to the scope through the $apply() method so that all listeners are updated correctly (ref).
So in your case view gets update when you click next start button again (ng-click) and not when recording event occurs.
Also would be usefult to read this

Notify main page of variable changed by external js

I'm attempting to create a modular sign in script for some webpages I'm developing. In short, I load the script on the main page, fire the main signIn function from a button press, and an overlay div is created on the main page which is managed by the external signIn.js. The external js sets some sessionStorage variables that will be utilized in the main page.
The hope for modularity would be to have signIn.js handle the authentication from the database and have the main page do with the process of signing in as needed (in this specific instance, it gives users access to their projects). Ideally, the sign in will not force a refresh of the main page due to other project goals.
The problem I'm encountering, is how do I notify the main page that the user has signed in without destroying any sense of modularity?
On top of other efforts, the most hopeful was attempting to create a custom event on the main page's document using $(document).on('userSignedIn', function() {...}); but signIn.js apparently cannot trigger this event.
Any suggestions for how to accomplish this or am I just going about this entirely wrong?
EDIT:
So, this was definitely a scope related issue I was experiencing. To flesh out the process, if anyone finds it relevant, signIn.js adds an overlay div to mainPage.html. $("#signInContainerDiv").load("signIn.html") is used to load the sign in form into the page. It turns out, when I was trying to reference $(document), it was using signIn.html's document, and not mainPage.html's. Upon that realization, I just created a div (signInNotify) on the mainPage that I bind the event to ($("#signInNotify").on("userSignedIn", function() {...});) and trigger it in signIn.js.
My own inexperience has conquered me, yet again.
jQuery can help you out when it comes to this. Here's an example from the main page for trigger
$( "#foo" ).on( "custom", function( event, param1, param2 ) {
alert( param1 + "\n" + param2 );
});
$( "#foo").trigger( "custom", [ "Custom", "Event" ] );
jQuery Page Reference
Another solution is to use some library like amplify.js, it has publish/subscribe functionality which can be useful for implementing the "observer pattern". You could also implement your own library for that, the code could be something like this:
// the implementation
function Notify () {
this.listeners = {};
}
Notify.prototype.subscribe = function (event, callback, context) {
this.listeners[event] = this.listeners[event] || [];
this.listeners[event].push({ callback: callback, context: context || null});
};
Notify.prototype.publish = function (event/*, args...*/) {
var args = Array.prototype.slice.call(arguments, 1);
(this.listeners[event] || []).forEach(function (x) {
x.callback.apply(x.callback.context, args);
});
};
// usage:
// an instance, or can be implemented as a singleton
var global_events = new Notify();
// wherever you want to be notified of login events
global_events.subscribe('login_success', function () {
// do something with the arguments
}, myContext/*optional*/);
// after success login
global_events.publish('login_success', user_credentials, other_data);
// and all subscribers (listeners) will be called after this
I have used that code for similar purposes and also used amplifyjs a couple times, you can read more about Amplify Pub/Sub.

Backbone.js getting a list to ensure only one instance of a certain model exists

Forgive me, I am new to backbone, and the MVC javascript concept.
So, I am making a comments system:
createComment: function () {
// create new comment model
var comment = new CommentModel({});
// render form view right after new button
var formview = new FormView({model: comment});
this.$el.after(formview.render().$el);
// add saved model to collection after form was submitted successfully
formview.on('success', this.handleFormSuccess, this);
// finally, return false to stop event propagation
return false;
},
What I can't understand is how to get a list of comments which have been rendered, but have not been sent to the collection. See, I want to ensure that only one comment box is opened at once.
My approach is to do a check to see how many comments are open, and close everyone except the current model.
Using Backbone.js & Underscore, how to get a count of items from the model? seems to give advice for how to do this after the model hits a collection.
I am very new with backbone, so it is entirely possible I am in the exact wrong direction with this.
How do I get the list?
As Joe suggested, I think your problem is this line:
formview.on('success', this.handleFormSuccess, this);
However, I don't think his suggestion (of changing "success" to "sync") will work either, because formview is a View, not a Model or Collection, so it doesn't even have an on method.
What does have an on method is the view's element, so you can do:
formview.$el.on('success', this.handleFormSuccess, this);
Two problems with that though:
jQuery is lame and doesn't let you set the context like that
"success" isn't a form event; you want this code to trigger on "submit"
so to fix those two issues you need to change the line to:
formview.$el.on('submit', _(this.handleFormSuccess).bind(this));
Alternatively you could also call:
_(this).bindAll('handleFormSuccess');
in FormView's initialize, which would make it so that you don't need to bind this.handleFormSuccess):
formview.$el.on('submit', this.handleFormSuccess);
Hope that helps.

Bind class method to AJAX success callback within event handler bound outside of class

My issue:
I have created a JavaScript class that is used by our dev team across our site. It is essentially functionality for a grid/table like structure that allows the user to select items and perform actions on these items with provided action buttons.
Action button workflow:
User clicks action button
Popup appears: "Are you sure you want to perform this action on these items?"
User clicks "Yes": AJAX call is made and popup closes upon AJAX success.
User clicks "No": Popup closes.
Right now, these action buttons are individually bound in jQuery by our Devs on each page that needs it. Any given page could have a handful of event bindings.
After successful completion of any of these actions, I would like to run Grid.afterActionComplete() from any given instantiation. I would like to run Grid.afterActionComplete() within the actions AJAX success callback. I know I could expose (return) afterActionComplete in my class and have the Devs run the function themselves, but this is not ideal.
My requirements:
Would like to keep the amount of additional code for Devs to a minimum
Many AJAX request can be made from any given page (some from non-action buttons), so using a global ajaxSuccess event wouldn't necessarily work. Plus, I would hate to use an event with that global of a scope.
My question is two-fold:
How could I dynamically bind Grid.afterActionComplete() to any given action's AJAX success callback? (if possible)
How would I best incorporate the action bindings into the Grid class upon instantiation to further encapsulate my code?
My sample code:
/* [START] Pre-existing code */
var Grid = function(gridID){
var gridID = $(gridID),
afterActionComplete = function(){
// Ideally, I'd like to bind the function here
},
refresh = function(){
// Refresh grid
},
return {
refresh : refresh
}
}
var popup = function(){
$('.popup').show();
// Pops up a window with an Action button and Cancel button
// Just a placeholder to help explain concept
}
/* [END] Pre-existing code */
/*
[START] Dev defined code
Devs will be creating these event bindings across the
site.
*/
var myGrid = new Grid("#grid1");
$('#actionPopupButton').click(function(){
popup();
$('.popup #actionButton').click(function(){
$.post( "ajax/test.html", function( data ) {
myGrid.refresh();
$('.popup').hide();
// I'd like to inject Grid.afterActionComplete() here
// Maybe create custom event and trigger() it here?
// Ideally, I would love to not require the Devs insert additional code hre, but I'm not sure that's possible
});
});
});
/* [END] Dev defined code */
I've been pondering these questions for a week or so now, and would love any suggestions to help me solve this issue. Thanks!
Assuming all of the "developer code" is very similar, I would think ideally you would want to have the developers pass in appropriate parameters instead of create a bunch of very similar code.
For instance, if you made the popup method part of Grid and has the url and callback passed to the function you could do something like this:
popup = function(url, callback){
var that = this;
$('.popup').show();
$('.popup #actionButton').click(function(){
$.post( url, function( data ) {
// call the passed in callback
callback(data);
// do your post-callback stuff
that.refresh(); // assuming this happens in every callback
$('.popup').hide(); // assuming this happens in every callback
that.afterActionComplete();
});
});
}
Then your example developer code would become this:
var myGrid = new Grid("#grid1");
$('#actionPopupButton').click(function(){
myGrid.popup("ajax/test.html", function(data){
// do instance-specific stuff here
});
});
Correct me if I am wrong. You want Grid.afterActionComplete() called only on specific AJAX requests, correct? This is why you cannot use .ajaxSuccess()? If that is the case, the best thing you can do is to trigger a custom event.
If you feel that is too much work for the developers, you can abstract the $.post functionality inside a custom function of your Grid class. After you execute the callback, you can then make the call to Grid.afterActionComplete(). If it is mandatory that Grid.afterActionComplete() be called after those requests, it would make more sense to take this route since it seems to be part of the contract. This way you can protect the developers from themselves (i.e., if they forgot to call the function or trigger the custom event) by making it so that they can only make the post using the Grid API.

How to Connect dojo/aspect to a Widget?

I am having trouble figuring out how to use dojo/aspect with widgets.
Consider the following:
require( [ 'dijit/form/Button' ],
function( Button)
{
var myButton = new Button({label: 'Click me!'});
} );
How would I connect to the button's postCreate() or startup() methods to discover when it has been rendered?
There seems to be no point when I can add advice to a method. See the comments, here:
require( [ 'dijit/form/Button', 'dojo/aspect' ],
function( Button, aspect )
{
// I cannot use aspect.after() here as I have no instance yet
var myButton = new Button({label: 'Click me!'});
// ...but I cannot do it here either as the lifecycle has already kicked off
} );
(The button is just to make it easier to explain the issue. My real-world problem involves widgets that contain other widgets, so I need to know when the whole lot have rendered before performing an action).
By instantiating the widget programmatically, the postCreate method of the widget is implicitly being called. As far as I know there isn't an easy (or really a good reason to) to connect to the postCreate stage in the widget lifecycle.
startup, on the other hand you need to call explicitly when programmatically instantiating a widget:
var myButton = new Button({label: 'Click me!'});
aspect.after(myButton, 'startup', function(){
console.log('startup called');
});
//place button in page (since startup implies the widget has been placed in the page
myButton.placeAt(someDomNode)
myButton.startup();
If you want to do work during the postCreate lifecycle of a widget, you'll likely want to subclass that widget. Doing so would look something like this:
//in a file like my/custom/widget.js
define(['dojo/_base/declare','dijit/form/Button'],function(declare,Button){
return declare('my.custom.widget',[Button],{
postCreate:function(){
//we still want to call the parent class's postCreate function
this.inherited(arguments);
//create some other widgets as well
}
});
});

Categories

Resources