Backbone.js router triggering on put, not on post - backbone.js

So I have been working with basic Backbone.js to create a simple demo page that is a basic User Manager. I run the same function whether I am creating (POST) or updating (PUT) a user's information. Both the POST and PUT requests are successful (I.E. calling my "success" function) however only the PUT command will trigger the router, the POST request does not trigger the same line of code while it DOES trigger the success function.
Below is my events code. The saveUser function takes care of both the PUT and POST requests but the router in success is only fired from a successful PUT request, not from a successful POST request (even though both are successful in updating the database).
events: {
'submit .edit-user-form': 'saveUser',
'click .delete': 'deleteUser'
},
saveUser: function (ev) {
var userDetails = $(ev.currentTarget).serializeObject();
var user = new User();
reply = user.save(userDetails, {
success: function (){
router.navigate('', {trigger: true});
},
error: function(){
$("#editError").toggle();
$("#editError").html("Error:<br/>"+reply.status+" : "+reply.statusText+"<hr/>");
}
});
return false;
},
deleteUser: function (ev){
$.ajaxSetup({
headers: {
'method':"DeleteUser"
}
});
reply = this.user.destroy({
success: function (){
router.navigate('', {trigger: true});
},
error: function(){
$("#editError").toggle();
$("#editError").html("Error:<br/>"+reply.status+" : "+reply.statusText+"<hr/>");
}
})
return false;
}
Here is the router code:
var Router = Backbone.Router.extend({
routes:{
'new': 'editUser',
'edit/:id': 'editUser',
'': 'home'
}
});
var router = new Router();
//ROUTES
router.on('route:home', function(){
alert("1");
userList.render(),
editUser.render({});
});
router.on('route:editUser', function(id){
userList.render();
editUser.render({id: id});
});
//NECESSARY
Backbone.history.start();`
Any help would be appreciated!
Thanks!

The problem is that when you're creating a new user, you're already at the url "", and router.navigate(newUrl) only triggers if newUrl is different than the current page url.
See https://github.com/documentcloud/backbone/issues/652.
To fix, change
router.navigate('', {trigger: true});
To
Backbone.history.fragment = null; // Forces the current URL to be dirty
router.navigate('', {trigger: true}); // Now, Backbone thinks the new URL is different

When you post, the current url is "http://gocella.com/backbone/#", after you post you navigate to "http://gocella.com/backbone/#" which doesn't change the url, which won't fire the event.
Router only triggers the event when the url changes, it works from update because when you hit edit your url changes to "http://gocella.com/backbone/#/edit/1", and then the trigger changes back to "http://gocella.com/backbone/#"
I think the approach of using the router for this problem is just overall incorrect.
You should do something more along the lines of:
when you hit save, create a new model or update the existing model with the form values, then use collection.create(modelFromForm) or, if you use model.save, then on the success callback, add it to the collection

Related

detect error: "popup_blocked_by_browser" for google auth2 in javascript

After lot of googling didn't find solution how to catch error of window popup blocker for google auth2
getting an error in console error: "popup_blocked_by_browser".
all I want is to do is tell user that pop up should be enabled for auth.
samples using window.open() are not good, since they open useless window.
As I see a lot of people searching for this.
Any advice?
Was having same problem. Seems browser(s) (or at least chrome) will block any "window.open" call which it was invoked not as part of a user interaction.
Refer to here for a deeper explanation.
__
I used to have this next code inside click event listener:
gapi.load('auth2', function() {
var auth2 = gapi.auth2.init({
client_id: '.apps.googleusercontent.com',
fetch_basic_profile: true,
scope: 'profile'
});
auth2.signIn()
.then(function() {
var profile = auth2.currentUser.get().getBasicProfile();
...
})
.catch(function(err) { ... });
});
Note the asynchronous way of loading 'auth2', which is how google docu says.
I changed it to:
// way earlier, before click event can happen
// we call the gapi.load method.
gapi.load('auth2', function() {});
Then, inside click event handler, we can do:
var auth2 = gapi.auth2.init({
client_id: '.apps.googleusercontent.com',
fetch_basic_profile: true,
scope: 'profile'
});
auth2.signIn()
.then(function() { ... })
.catch(function(err) { ... });
... so browser does not block google login popup
Finally!!
signIn() method uses JS Promise. So code can be used is:
gapi.auth2.getAuthInstance().signIn().then(function(){}, function(error){ if (error) alert('please allow popup for this app')})
Hope this will help!
In your constructor (service) or ngOnInit (for component) do the following:
googleAuthService.getAuth().subscribe(
(auth) => {
this.googleAuth = auth;
});
Then, in your login function, call:
this.googleAuth.signIn()
.then((user) => {
this.signInSuccessHandler(user as GoogleUser);
},
(error) => console.error(error));
Update:
Now you need to use Firebase auth
https://firebase.google.com/docs/auth/
Answer:
For me I changed init method to have a callback function (empty one)... Here's how:
Wrong (popup blocked by browser) (no call back function):
.init({ client_id: main.clientId })
Correct working like charm:
.init({ client_id: main.clientId, () => { } })

Handling secure login page in protractor

My team is working to use AngularJs and Polymer components for a new web app. I am looking into how to create a UI automation suite for this. After lots of research looks like Protractor may help me out here with some tweaks to handle Polymer. But, the current challenge is as follows -
I navigate to the app
As part of our company policy, the every web visit is validated (unless within same session). Here is how the validation works -
A login page (non-Anugular) page appears after one types the required url. Sign in with the credentials
Another intermediate page appears where it asks to wait for page to load or click a link to go to next page. Click the link
Url changes back to the original used in #1
Note: These validation pages take hell lot of time to load (changes to different internal urls). Also, the validation is skipped sometimes (within same session or through some other logic)
I have been struggling to design a prototype to handle all these. I am also trying to use Page Object while designing the prototype. Here is what I have so far.
login.js
________________________________________________________
var currentUrl;
var lastChangedUrl;
var secureUrl = 'corplogin.ssogen2.corporate.company.com';
var getwayUrl = 'gateway.zscalertwo.net';
var loginSuite = function(driver) {
var defer = protractor.promise.defer();
describe('Handle login', function() {
/*afterEach(function() {
//driver.manage().deleteAllCookies();
})*/
//it('Login to security test', function(){
//********** Wait for page to load/URL to change to secure login page ************
driver.getCurrentUrl().then(function(url) {
currentUrl = url;
}).then(function() {
driver.wait(function() {
return driver.getCurrentUrl().then(function (url) {
lastChangedUrl = url;
return url !== currentUrl;
});
});
}).then(function() {
//********** login to secure page ************
if (lastChangedUrl.indexOf(secureUrl) > -1 || lastChangedUrl.indexOf(getwayUrl) > -1) {
var element = driver.findElement(By.name("username"));
element.sendKeys("Username");
element = driver.findElement(By.name("password"));
element.sendKeys("password"); //Give password
element = driver.findElement(By.name("submitFrm"));
element.click();
}
}).then (function() {
//********** page is slow. wait for page to load/URL to change ************
driver.getCurrentUrl().then(function(url) {
currentUrl = url;
}).then(function() {
driver.wait(function() {
return driver.getCurrentUrl().then(function (url) {
lastChangedUrl = url;
return url !== currentUrl;
});
});
}).then (function() {
//********** Click on the link to to go to test page ***********
if (lastChangedUrl.indexOf(getwayUrl) > -1) {
var element = driver.findElement(By.tagName("a"));
console.log("before click............");
element.click();
}
//********** page is slow. wait for page to load/URL to change ************
driver.getCurrentUrl().then(function(url) {
currentUrl = url;
}).then(function() {
driver.wait(function() {
return driver.getCurrentUrl().then(function (url) {
lastChangedUrl = url;
return url !== currentUrl;
});
});
})
.then (function() {
//return defer.promise;
//browser.pause();
});
}, 60000);
});
//});
}, 60000);
return defer.promise;
};
module.exports = loginSuite;
spec.js
___________________________________________________________________________
describe('Protractor Demo App', function() {
var myUrl = 'http://<my test app url>/';
var driver = browser.driver;
beforeEach(function() {
driver.get(myUrl);
});
it('should login', function() {
loginSuite(driver)
.then(
function(){
console.log("End of tests:");
expect(driver.getCurrentUrl()).toBe(myUrl);
});
});
The issue here -
My expectation here is to have the promise returns to spec.js after the secure login page is handled so that I can continue with other testing using the driver object. For the sake testing I am logging 'End of tests' message and doing a dummy validation. But, looks like those two lines don't get executed.
Login to the secure site works and I see page changes to original test page. I tested that with Browser.pause(). But, the logging 'End of test' never happens, nor the validation.
I need to handle the scenario where the secure login page doesn't appear. Not sure what adjustment I need to do in login.js page
Is my approach for page object and handling the promises wrong here? I am able to go to one step further on the test app page when all the code are placed under one js file instead of splitting them for page object. Please help here.
I wanted to share with you the "polymer way" of solving your problem.
The code below use two elements to monitor the URL, the auth flow, the previous page visited and log the user in/out of the app
The first will bind to the origin route, so you can send the user back there
<app-route
route="{{route}}"
pattern="/origin/:page"
data="{{data}}"
tail="{{subroute}}">
</app-route>
The second will bind to the authPage, allowing you to show/hide the auth page.
<app-route
route="{{subroute}}"
pattern=":authPage"
data="{{data}}
active="{{authPageActive}}">
</app-route>
User auth, monitoring and page redirecting
Use the element: <firebase-auth>
Is the user singned in?: signedIn="{{isSignedIn}}"
<firebase-auth id="auth" user="{{user}}" provider="google" on-
error="handleError" signedIn="{{isSignedIn}}"></firebase-auth>
Add an observer
observers: [
'_userSignedInStatus(isSignedIn)' // observe the user in/out
],
Add a Function
_userSignedInStatus: function (isSignedIn) {
if (isSignedIn === false) {
this.page = 'view404'; // redirect the user to another page
// import an element that cover the view
} else {
//send a log message to your database
}
}

Protractor E2E test - async Rest API call

I want to test a simple end to end flow - create a new account - using Protractor.
I have an AngularJS application contains a create account page where the user needs to fill a simple form and click submit.
Clicking submit triggers a method that is calling to an async method to create the account in my server.
When that function returns, the user is directed to a different page.
Here is my test (creatAccount.spec.js):
describe('Create Account Page Tests', function() {
it('createAccount success', function(){
browser.get('http://localhost:9001/#/createAccount');
element(by.model('user.organizationName')).sendKeys('Westeros');
element(by.model('user.firstName')).sendKeys('John');
element(by.model('user.lastName')).sendKeys('Snow');
element(by.model('user.email')).sendKeys('johnSnow#westeros.com');
element(by.model('user.password')).sendKeys('123');
element(by.model('confirmPassword')).sendKeys('123');
element(by.id('submitBtn')).click();
expect(browser.getCurrentUrl()).toEqual('http://localhost:9001/#/userManagement');
});
});
Here is my submit method:
$scope.submit = function() {
$scope.submitted = true;
UserService.createAccount($scope.user)
.then(function success(){
$state.go('userManagement');
}, function failure(){
$log.error("something went wrong");
});
};
Here is my UserService.createAccount method:
function createAccount(user){
var deferred = $q.defer();
APIService.sendRequest(APIService.ACTION().createAccount, undefined, user)
.then(function success(res) {
deferred.resolve();
}, function failure(reason){
$log.debug('create account failed. reason: ', reason);
deferred.reject(reason);
});
return deferred.promise;
}
And here is my APIService.sendRequest method:
function sendRequest(action, params, data){
var defferd = $q.defer();
var request = $resource(action.url, {}, {send: {method: action.method, isArray: action.isArray ? action.isArray : false, withCredentials: true}});
if (!params){
params = {};
}
request.send(params, data, function(res) {
defferd.resolve(res);
}, function(error) {
defferd.reject(getErrorReponseListError(error));
});
return defferd.promise;
}
My test is failing all the time since the page is not directed to the next page. It seems to me like the test does not wait for the async method to return even thought it should...
I've tried to call browser.waitForAngular() or browser.wait(5000) but nothing helps...
This line:
expect(browser.getCurrentUrl()).toEqual('http://localhost:9001/#/userManagement');
expects the browser URL to be that value right away. It does not wait for the URL to change. The click before that line completes, but all that means is that the click was delivered to the browser, not that the handler in the browser has completed (and its async anyway).
You need to teach your Protractor test to wait for the browser to reach an expected state. Something like this should work:
browser.wait(function() {
return browser.getCurrentUrl().then(function(url) {
return url === 'http://localhost:9001/#/userManagement';
});
});
So, instead of expecting the url to be what you say, this waits for the url to be what you want. After most interactions with the browser (e.g., submitting anything or any other interaction with the server) you need to wait for the page to get to its new state.

Backbone.js: Dynamic model/collection url based on user input

I'm total new to Backbone so please pointing me the right way to do it. Here is what I have:
A Backbone login View with 3 inputs: serverIP, username, password. I am doing all the validation and send jquery ajax() request to my backend host based on the serverIP that the user has to enter earlier.
My backend is js PHP using Slim restful framework, check user, password like usual basic stuff.
On the callback of successful ajax() call, I want to set the urlRoot for latter use of all models and collections as I'm using Slim for all database interactions and this PHP file located on the server.
I tried to set it on the global app variable like this in app.js:
var app = {
api_url: '',
views: {},
models: {},
routers: {},
utils: {},
adapters: {}
};
In the callback of login view I set:
app.api_url = "http://"+serverIP;
And try to use app.api_url in backbone model url but it's apparently undefined.
May be this is not the correct approach I'm trying and I messed up with the variable scope? Then how can I set model.url from the view? Please, any suggestions are much appreciated.
Thanks,
Hungnd
EDIT: Ok, I will try to elaborate my problem again:
Here is the login function in my LoginView.js, basically it take user inputs and send to my model to interact with the server, if success navigate to home view:
var user = new app.models.Login();
var userDetails = {
serverIP: $('#serverIP').val(),
username: $('#username').val(),
password: $('#password').val()
};
user.save(userDetails, {
success: function(data) {
/* update the view now */
if(data.error) { // If there is an error, show the error messages
}
else { // If not, send them back to the home page
app.router = new app.routers.AppRouter();
app.router.navigate('home',true);
}
},
error: function() {
/* handle the error code here */
}
Here is my LoginModel.js, get the serverIP from user input on login form and send to the server to process
app.models.Login = Backbone.Model.extend({
urlRoot: function(){
var serverIP = this.get('serverIP');
return "http://"+serverIP+"/api/login";
},
defaults: {
'serverIP': '',
'username': '',
'password': '',
}
});
Now, after successful login, navigate to HomeView.js, on initialize it calls to EmployeeCollection, so far so good
initialize: function () {
//Search result
this.searchResults = new app.models.EmployeeCollection();
this.searchResults.fetch({data: {name: ''}});
this.searchresultsView = new app.views.EmployeeListView({model: this.searchResults});
}
Here is my EmployeeModel.js where I have the problem, I dont know how to access the serverIP variable.
app.models.Employee = Backbone.Model.extend({
urlRoot:"api/employees",
//urlRoot: app.api_url+"/api/employees",
initialize:function () {
this.reports = new app.models.EmployeeCollection();
this.reports.url = app.api_url+'/api/employees/' + this.id + '/reports';
}
});
app.models.EmployeeCollection = Backbone.Collection.extend({
model: app.models.Employee,
//url: "api/employees",
url: function() {
//How to get serverIP?
},
});
All models in backbone already have an url property which will be used to fetch data. In your case you could define it as a function to generate url dynamically.
Here is an example :
//we are inside the definition of the loginModel
data: {
serverIP : null,
username : null,
password : null
},
url: function() {
var url = "rootUrl",
data = this.get("data");
return function() {
return url + '?' + $.param(data);
};
}
url is then defined as a closure, and object being references in javascript, the url generated will use the current values in the data object.

How to structure backbone.js so it returns template + data and ( login-based reroute in mind )

I am building a small app that uses backbone.js on the client side, node.js/socket.io on the server side, and the connection goes trough websockets only.
Now how would I make my setup if I want to get the template and the data at once.
I do a fetch() in the router, which gets the data and the template. With this I construct my view -> collection -> model.
I do a fetch() in the view itself. (Maybe in the initialize)
===
I want to extend this problem with the following.
Let's say a user browses to http://mysite.com/products and the user is not logged in yet, he is not allowed to view the page. He has to be rerouted to /login.
The problem is I can only verify this on the server, so the server has to send back the correct data whether the user is logged in or not, and backbone.js has to deal with this.
Summarized:
I make a fetch to the server which will send back data + template html.
Backbone.js has to render the template with the data,
or reroute (Backbone.history.navigate('login', {trigger: true})) when the server sends back a flag.
You could use parse method in your Backbone collection for example :
Collections.Products = Backbone.Collection.extend({
url : '/products',
parse : function (response) {
//
// you should return JSON from your server and the object must be smth like
// { template : "<p>template for products</p>", data : productsInJSON }
//
if ( response.template && response.data ) {
this.trigger('template', response.template);
return response.data;
} else {
return response;
}
}
});
Views.Page = Backbone.View.extend({
initialize : function () {
_.bind(this, 'render');
var self = this;
this.collection = new Collections.Products();
this.collection.on('template', function(template) {
self.render(template);
});
},
render: function(template) {
$("div#page").html(template);
}
});
$(function() {
window.app = {};
window.app.view = new Views.Page();
// here you are sending {template:true} to '/products' in your server
window.app.view.collection.fetch( { data : { template : true } } );
});
If you are using socket.io, you should create a new Backbone.sync method for your requests.
It is a little bit out of the Backbone philosophy and the integrated GET, PUT, POST, DELETE methods, so there will be a lot of coding.
You could send a template for unlogged in users with no data for the collection.

Resources