page refresh unsetting Ember controller property - ruby-on-rails

If I do a manual page refresh or set a redirect like this
}).then(function() {
document.location = "/";
}, function() {
in a function in my Ember app, an Ember controller property is becoming unset. I'm not sure if it matters which property is becoming unset, but in case it does, here's the code. In my application template, I check for whether a user is authenticated
{{#if isAuthenticated}}
blah blah
{{else}}
blah blah
{{/if}}
The property is set in the AuthController
App.AuthController = Ember.ObjectController.extend({
currentUser: null,
isAuthenticated: Em.computed.notEmpty("currentUser.email"),
login: function(route) {
var me;
me = this;
return $.ajax({
url: "/users/sign_in.json",
type: "POST",
data: {
"user[email]": route.currentModel.email,
"user[password]": route.currentModel.password
},
success: function(data) {
me.set('currentUser', data.user);
So, after I do a page refresh, my currentUser is (according to Ember) no longer showing as authenticated. However, on the (Ruby on Rails) server side the user is still authenticated. I put this code in the layout to check and after Ember thinks that I'm signed out (with a page refresh), my server code is still telling me I'm signed in (which in fact I am).
<% if current_user %>
there is a current user
<% end %>
Can you explain why this might be happening?

Your best is to set up is probably to make isAuthenticated a computed property that does an ajax GET to /users/current if currentUser is null. /users/current should return {} if the user is not authenticated, and the current user data if they are.
App.AuthController = Ember.ObjectController.extend({
currentUser: null,
isAuthenticated: function() {
var currentUser = this.get('currentUser');
if(Ember.isEmpty(currentUser)) {
$.ajax({
type: "GET",
url: "/users/current.json",
async: true
}).done( function( data ) {
me.set('currentUser', data.user);
});
return false;
} else {
return true;
}
}.property('currentUser'),
});

Why are you doing a manual page refresh? This way you are starting the browser completely from scratch and therefore the Ember App initializes again and therefore the property currentUser is not set on your Controller.
Instead of doing this:
}).then(function() {
document.location = "/";
}, function() {
I assume that you want to go to the start page of your app (since you want to go to "/"). Instead you should use the Ember feature of transitioning between routes. I do not know, where you execute the code above, but you can do this either from one of your controllers or in a route.
In a controller you could use transitionToRoute:
// index is a Route which is implicitly generated by Ember for you
this.transitionToRoute('index');
In a router you could use transitionTo:
this.transitionTo('index');

Related

How can I verify authenticity token when submitting form through ajax success?

I have a condition in which firstly the ajax request is triggered to fetch respective host url. In case the condition returned is true I want to submit the rails form. The form is submitted correctly but I get the error can't verify authenticity token.
$(document).ready(function() {
var url, email;
$("form#new_user").submit(function(e){
e.preventDefault();
email = document.getElementById("user_email").value;
$.ajax("/return_host", {
type: "GET",
data: {
email: email
},
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
},
success: function(data) {
if(data['goto'] == true){
host = data["host"];
url = host + "/users/sign_in";
$("form").attr("action", url);
$("form").trigger('submit.rails');
} else {
location.reload();
}
}
});
});
});
How can I send authenticity token when triggering rails form through ajax success?
Please check whether your form has a hidden input field named authenticity_token or not, for forms generated with rails helpers this field is included by default.
If not then this is what is causing error as rails expects a param called authenticity_token when you post a form.
You can add this on the fly in your success callback as shown below:
success: function(data) {
if(data['goto'] == true){
host = data["host"];
url = host + "/users/sign_in";
var form = $("form");
var authenticityToken = document.createElement("input");
authenticityToken.setAttribute("type", "hidden");
authenticityToken.setAttribute("name", "authenticity_token");
authenticityToken.setAttribute("value", $('meta[name="csrf-token"]').attr('content'));
form.append(authenticityToken);
form.attr("action", url);
form.trigger('submit.rails');
} else {
location.reload();
}
}
Hope it helps!
If you're on Rails 5.1+ I'd recommend using form_with helper to handle the ajax request [https://api.rubyonrails.org/v5.2.1/classes/ActionView/Helpers/FormHelper.html#method-i-form_with].
You'll still be able to hook into the success and error callbacks using code as I've shared below.
```
let form = document.getElementById('my_form')
form.addEventListener('ajax:error', (event) => {
let form = event.target
let [errors, status, xhr] = event.detail
// DO SOMETHING
})
form.addEventListener('ajax:success', (event) => {
let form = event.target
// DO SOMETHING
})
form.addEventListener('ajax:before', (event) => {
let form = event.target
// DO SOMETHING
})
```
This way rails will handle the authenticity token/csrf yet you'll still be able to use Ajax - with option data: { remote: true } to ensure no page reload.
Hope this helps.

ASP.NET MVC Ajax form submitted cross domain not sending Cookies

I have a site that has some forms that use ASP.NET MVC Ajax. An example of BeginForm method:
#using (Ajax.BeginForm("HandleSignin", "Profile", null, new AjaxOptions() {
HttpMethod = "POST",
Url = Url.Action("HandleSignin", "Profile", null, Request.Url.Scheme),
OnBegin = "SetWithCredentialsTrue(xhr)",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "signin-form-container" },
new { id = "sign-in-form", #class = "text-left-desktop group" }))
{
#Html.AntiForgeryToken()
#Html.TextBoxFor(x => Model.Email, new { placeholder = "Email" })
#Html.PasswordFor(x => Model.Password, new { placeholder = "Password" })
<input type="submit" value="SignIn" class="button small-button">
}
Note that because of the Request.Url.Scheme param in the of the Url.Action method, the URL is being set to a different domain than the domain that the browser is getting this from. This is done because the main site is hosted statically using a CDN while the form is loaded from another domain using AJAX. This works, except that the cookies are not sent in the AJAX request. I tried to have the cookies sent by setting xhr.withCredentials = true by using the OnBegin event and this JavaScript:
<script type="text/javascript">
function SetWithCredentialsTrue(xhr) {
console.log("SetWithCredentialsTrue(xhr)", xhr);
xhr.withCredentials = true;
}
</script>
While I can see that SetWithCredentialsTrue() method gets called, it does not seem to work in that the HTTP Request generated when the form is submitted does not have the Cookie header.
All of the server-side handlers are setting the Access-Control-Allow-Credentials response header to true and the Access-Control-Allow-Origin to the main (static) site domain.
UPDATE: With some more console logging, I have verified that the xhr parameter passed to my OnBegin event handler (SetWithCredentialsTrue) is NOT an XMLHttpRequest object and hence setting withCredentials on it does not have an affect. So the question is how can I access the XMLHttpRequest object?
I finally figured this out. The XMLHttpRequest object is not exposed via the ASP.NET MVC library. I was able to alter jquery.unobtrusive-ajax.js, the JS library used by the ASP.NET MVC helper so that it sets withCredentials to true:
$(document).on("submit", "form[data-ajax=true]", function (evt) {
var clickInfo = $(this).data(data_click) || [],
clickTarget = $(this).data(data_target),
isCancel = clickTarget && clickTarget.hasClass("cancel");
evt.preventDefault();
if (!isCancel && !validate(this)) {
return;
}
asyncRequest(this, {
url: this.action,
type: this.method || "GET",
data: clickInfo.concat($(this).serializeArray()),
xhrFields: {
withCredentials: true
}
});
});
Note: xhrFields is the part that I added.
as per my understanding what you trying to do is, you want to send data from one domain to another domain using AJAX post right?
If it is true then, i want to tell you that browser doesn't allow you to do that because of security issue.
If you still want to do that then you have two option's by using CORS and JSONP.
http://csharp-video-tutorials.blogspot.in/2016/09/calling-aspnet-web-api-service-in-cross.html
http://csharp-video-tutorials.blogspot.in/2016/09/cross-origin-resource-sharing-aspnet.html

Ajax not allowing View rendering

I have a couple of variations on the ajax depending on the flow of interactions on the page. But it's only the variables that changes. here is one of them:
$('#btn_skickaEnkel').bind('click', function () {
$.ajax({
type: 'POST',
url: '/Contact/IntresseAnmälan/',
dataType: 'json',
data: {
Namn: $('#namn').val(),
Mail: $('#mail').val(),
Info: $('#meddelande').val(),
Nivå: $('#nivå').find(":selected").text(),
IsEnkel: true,
Telefon: $('#nr').val(),
ID: function () {
var url = window.location.pathname;
var id = url.substring(url.lastIndexOf('/') + 1);
return id;
}
},
traditional: true
});
});
In my controller i am unable to redirect or return a different view. At this point the data from JSON is no longer relevant because it's already been saved to DB.
My Controller:
public ActionResult IntresseAnmälan(BokningContainer bokning)
{
db = new DbContext();
//Saving some data to database(removed)
//Just determening the state of container obj.
if (bokning.IsEnkel)
{
//Geting som information from db (removed)
//Creating a mail (removed)
email.Send(bokning.Namn, bokning.Mail, body);
}
else
{
}
//db.SaveChanges();
//This part is not working, I think it's beacuase of the Ajax
return View("IntresseAnmälan");
}
The view is not rendered and I think it's related to the ajax. The view is simply not rendered. Is there some way to force returning it and ignoring the ajax? As I said the data is no longer needed because the content is already saved to the DB.
You cannot render view on ajax call,simply you can use form post method or just redirect it to desired action on "succcess" of ajax call as below:
$('#btn_skickaEnkel').bind('click', function () {
$.ajax({
type: 'POST',
url: '/Contact/IntresseAnmälan/',
dataType: 'json',
data: {
Namn: $('#namn').val(),
Mail: $('#mail').val(),
Info: $('#meddelande').val(),
Nivå: $('#nivå').find(":selected").text(),
IsEnkel: true,
Telefon: $('#nr').val(),
ID: function () {
var url = window.location.pathname;
var id = url.substring(url.lastIndexOf('/') + 1);
return id;
}
},
traditional: true,
success: function(result) {
window.location.href = '#Url.Action("action", "Controller")';
}
});
});
I couldn't believe my eyes when I figured out this "Bugg". The problem was that I, at some point, changed the submit to a button. So the form was never submiting. Well, at least I learnt a bit about views and Ajax.
Sorry for taking your time.

AngularJS + Rails: get a resource but stay on current page

I have a page with a button that will change the state of the resource on the server. Everything is wired up and working fine. But I had to resort to a server response that sent the browser "back" because I don't want to display a new template for the action url.
This seems like a typical thing to do: ask the server to do something, then get the current state of the resource and redisplay it in the current template without refetching the template.
The markup on the page is like this:
<a ng-href="/api/preference/{{video._id}}/add_to_watchlist" data-method="get">
<i rel="tooltip" data-original-title="Add to watchlist" class="icon-3x action-icon
icon-plus-sign" ng-class="{iconSelected : video.user_watchlist == 1}">
</i>
</a>
This highlights the icon depending on the value of $scope.video.watchlist. When clicked it fires a custom action on the server to add the video being viewed to the current user's watchlist. The url created is /api/preference/{{video._id}}/add_to_watchlist, this fires the server controller, which does the right thing but instead of going to a template for /api/preference/{{video._id}}/add_to_watchlist the server responds by telling the client to go "back" in Rails this is redirect_to :back.
Clearly this is wrong. It works but refetches the entire page markup and data.
The AngularJS controller that loads the original data is here:
GuideControllers.controller('VideoDetailCtrl', ['$scope', 'Video',
function($scope, Video) {
var pattern = new RegExp( ".*/([0-9,a-f]*)", "gi" );
//ugh, there must be a better way to get the id!
$scope.video = Video.get({ id: pattern.exec( document.URL )[1] });
}
]);
Here is the resource that gets the json
var VideoServices = angular.module('VideoServices', ['ngResource']);
VideoServices.factory('Video', ['$resource',
function($resource){
return $resource("/api/videos/:id", {id: "#id"}, {
update: {method: "PUT"},
query: {
isArray: true,
method: "GET",
headers: {
"Accept": "application/json",
"X-Requested-With": "XMLHttpRequest"
}
},
get: {
isArray: false,
method: "GET",
headers: {
"Accept": "application/json",
"X-Requested-With": "XMLHttpRequest"
}
}
});
}
]);
Somehow I need to
tell the server to add_to_watchlist without changing the browser URL
trigger a refetch of the video json without reloading the template.
It seems like you're relying on routing to handle a model update on the server, instead of doing it through angular's $resource service. Here's a quick and dirty:
Instead of routing, call a function when the user clicks:
<a ng-click="addToWatchlist(video._id)">
<i rel="tooltip" data-original-title="Add to watchlist" class="icon-3x action-icon
icon-plus-sign" ng-class="{iconSelected : video.user_watchlist == 1}">
</i>
</a>
Add the click handler function to your controller (I'm using $http here, but you would be better off adding a custom action to your Video resource). On the success callback you can reload the video. Or, better yet, you can return the video from the add_to_watchlist action.
Check out http://docs.angularjs.org/tutorial/step_07 which explains the $routeParams (to answer your comment about getting the video id).
GuideControllers.controller('VideoDetailCtrl', ['$scope', '$http', '$routeParams', 'Video',
function($scope, $http, $routeParams, Video) {
$scope.video = Video.get({ id: $routeParams.videoId });
$scope.addToWatchlist = function(videoId) {
$http.get('/api/preference/'+videoId+'/add_to_watchlist').success(function() {
$scope.video = Video.get({ id: $routeParams.videoId });
};
};
}
]);

backbone router globbing non backbone routes

I'm using devise, rails with backbone. All my backbone routes are working just fine. But non-backbone routes like /accounts/login that are supposed to rendered by rails are being globbed with backbone router.
SS.Routers.ApplicationRouter = Backbone.Router.extend({
initialize: function() {
this.el = $("#content");
},
routes: {
"": "home"
},
"home": function () {
console.debug("Got home request");
var view = new SS.Views.Home();
this.el.empty().append(view.render());
}
});
The actual request/response to the /accounts/login is happening from rails logs. But Backbone home root gets triggered afterwards and my home page is rendered.
My layout has
$(function () {
SS.init();
});
from
window.SS = {
Models: {},
Collections: {},
Views: {Providers: {}},
Routers: {},
init: function (data) {
console.debug("Initializing Backbone Components");
new SS.Routers.ApplicationRouter();
new SS.Routers.ProvidersRouter();
if (!Backbone.history.started) {
Backbone.history.start();
Backbone.history.started = true;
};
}
};
Which is triggering my home route again.
"" route in backbone is not supposed to be globbing /accounts/login but it is.
A little bit of debugging is showing me that /accounts/login is being gobbled by "" since the fragment is an empty string.
And the fragment is an empty string in the all the cases where there is no match for backbone routes.
Code from backbone 0.9.2
loadUrl: function(fragmentOverride) {
var fragment = this.fragment = this.getFragment(fragmentOverride);
var matched = _.any(this.handlers, function(handler) {
if (handler.route.test(fragment)) {
console.debug(handler);
console.debug(fragment);
handler.callback(fragment);
return true;
}
});
return matched;
},
Any suggestions?
Add a class ('passThrough') or 'data-passThrough=true' attribute to the link. Catch this class/attribute in your router and return true so Backbone stops handling it and the browser treats it as a regular link.

Resources