I have an ajax call that fires every five seconds to update some data on the screen. It seems that the getJSON breaks after a page refresh. What appears to be happening is that if the page refreshes via F5 while the call is happening, the future calls won't work. They simply fail with no error message. The textStatus is error but errorthrown is empty. I'm calling into an Asp.Net MVC controller and no error is fired there. The subsequent getJSON calls never hit the server. It's pretty much hosed at this point until I start and stop my website again. No idea how to fix this as I can't stop the user from refreshing the page and it's a pain for me to test updates.
Here is my code:
var RealTimeActivity = (function () {
var realTimeActivity = {};
var timer = null;
var active = false;
realTimeActivity.LastUpdated = new Date();
function queueUpdate() {
timer = setTimeout("RealTimeActivity.Update();", 5000);
}
function reset() {
clearTimer();
queueUpdate();
}
function clearTimer() {
if (timer) {
clearTimeout(timer);
timer = null;
}
}
realTimeActivity.Update = function () {
try {
clearTimer();
if (active === true) {
$.getJSON("realTimeActivity/GetRealTimeData",
function (result) {
if (result.Success) {
// do stuff
} else {
// Other stuff
}
queueUpdate();
}
).fail(function (jqXHR, textStatus, errorThrown) {
console.log(textStatus + ": " + errorThrown);
reset();
})
}
} catch (ex) {
reset();
}
}
realTimeActivity.Start = function (i) {
active = true;
if (i) {
realTimeActivity.Update();
} else {
queueUpdate();
}
}
realTimeActivity.Stop = function () {
active = false;
clearTimer();
}
return realTimeActivity;
}());
EDIT:
This appears to only be reproducible in Chrome. A bug in chrome perhaps or am I missing something?
This appears to be a caching issue with Chrome and aborted requests. It will not allow me to send another request to the same URL once the request is aborted even after refreshing the page. I worked around it by making every request unique (via a timestamp).
postRequest('realTimeActivity/GetRealTimeData?u=' + (new Date).getTime(), null, function (response) {
Apparently you can also call $.ajax and just set cache to false as well which does the same thing behind the scenes.
Related
I have a view with a hidden field
#Html.HiddenFor(model => model.Driver.ID, new { htmlAttributes = new { id = "driver" } })
I have a controller with a method I want to call passing that field "driver", but debugging I see this method doesn't even get called.
public string GetEditWarningMessage(int? driverID)
{
Driver driver = unitOfWork.DriverRepository.Get().Where(d => d.ID == driverID).FirstOrDefault();
string message = null;
if(!driver.Status.Equals("A"))
{
message = "This driver is assigned to current or future schedule movements";
}
return new JavaScriptSerializer().Serialize(message);
}
and finally the javascript, which I'm sure is the source of the problem
$().ready(function () {
$("#driver").change(function () {
$.get("../GetEditWarningMessage/" + $(this).val() + '?' + $.now(), function (response) {
var warning;
warning = $.parseJSON(response);
});
$("form").submit(function () {
var performEdit = false;
if (warning != null) {
performEdit = confirm(warning);
return performDelete;
}
});
});
});
basically if the message I pass from the GetEditWarningMessage() is null, I don't want the popup to appear, otherwise have it popup with the warning message passed on.
Help much appreciated, thank you all
Change the following
$.get("../GetEditWarningMessage/" + $(this).val() + '?' + $.now(), function (response) {
var warning;
warning = $.parseJSON(response);
});
To
var warning;
$.get("../GetEditWarningMessage/" + $(this).val() + '?' + $.now(), function (response) {
warning = $.parseJSON(response);
});
In your code the variable "warning" has scope inside only $.get call. So it cannot be used in form submit call..
Declaring it outside the $.get call will solve the issue.
If the hidden input value doesn't change then you must call GetEditWarningMessage inside the form submit call.. even that Synchronous ajax call so that form submittion can wait for the response of warning message.
I'm attempting to use signalr for a board game app so that when a player moves a piece on one client, all clients can get updated. The problem I'm running into is not having a clear way to abstract the signalr parts of the app so that consumers of it don't need to worry about things like starting connections etc. I decided to skip sing the generated proxy and instead invoke things directly. The only remaining hurdle is that when the request is received by the client from the server, the this context in the event callback is of the hub, not of the owner of the callback. I'd like to know if there's a way to pass in a context.
Code:
Signalr service (like a base class to manage the connection and eventually events):
define('service.signalr', ['jquery'], function ($) {
var connection = $.hubConnection();
var start = function () {
connection.start();
};
return {connection: connection, start: start}
});
Service with specific functionality - in this case handling piece movement:
define('service.game', ['jquery', 'service.signalr'], function ($, signalr) {
var gameHubProxy = signalr.connection.createHubProxy('gameHub');
var addHandler = function (name, callback) {
gameHubProxy.on(name, callback);
}
var moveFigure = function (figureId, targetSpaceId) {
var deferred = $.Deferred();
gameHubProxy.invoke('moveFigure', figureId, targetSpaceId).done(function () { deferred.resolve(); });
return deferred.promise();
};
return { moveFigure: moveFigure, addHandler: addHandler }
});
Calling the server method on the service (the event trigger is for the client performing the action so it doesn't process twice):
define('model.space', ['backbone', 'service.game'], function (Backbone, gameService) {
var Space = Backbone.Model.extend({
defaults: { id: 0, figure: null },
moveFigure: function (figureId) {
var self = this;
var spaceId = self.get('id');
gameService.moveFigure(figureId, spaceId).done(function () {
self.trigger('figureMoved', figureId, spaceId, false);
});
}
});
return Space;
});
And trying to listen to the server's response:
define('view.board', ['jquery', 'underscore', 'backbone', 'helpers', 'bootstrapData', 'service.game', 'aggregate.space'], function ($, _, Backbone, helpers, bootstrapData, gameService, Space) {
var Board = Backbone.View.extend({
initialize: function () {
this.spaces = new Space.Collection(bootstrapData.spaces);
this.listenTo(this.spaces, 'figureMoved', this.updateFigurePosition);
gameService.addHandler('figureMoved', this.updateFigurePosition);
},
updateFigurePosition: function (figureId, spaceId, drawFigure) {
var figure = null;
var oldSpace = this.spaces.find(function (space) {
figure = space.get('figure');
return figure && figure.id == figureId;
});
//code continues, but has already failed on this.spaces
},
});
return Board;
});
The obvious answer here after some sleep is to wire up the callback to a function in the module whose context I want that is just a passthrough to the actual method I want to call, so that this is then set appropriately. Such as:
initialize: function () {
var self = this;
self.spaces = new Space.Collection(bootstrapData.spaces);
self.listenTo(self.spaces, 'figureMoved', self.updateFigurePosition);
gameService.addHandler('figureMoved', function (figureId, spaceId, drawFigure) {
self.updateFigurePosition(figureId, spaceId, drawFigure);
});
},
I still wonder if this can be a viable solution in all cases. Seems odd that I have to wrap my function in another function just so I can reference the variables I want. But I suspect this is less a problem with signalr, backbone, or require and more with a C# guy that's late to the Javascript party and still stumbles on some of the language's "features".
I am trying to solve an issue with webservice call in my MVC 4 project. What I am doing is that when user submits form on page, I call webservice and wait for result. Meanwhile server returns callbacks (it is returning strings containing finished steps of work on server side). What I would like to do is to display contents of "log" I am building from callback on the page and refresh it repeatedly to display the progress.
The issue I run into is, that either I will have asynchronous call of webservice in my controller in which case I am not waiting for result from webservice and that means that user will not stay on the page that should be displaying progress, or I will call it synchronously, in which case javascript on the page will not get response from controller until the final response arrives (so no updates are displayed).
My desired flow is:
User submits the form
Server is called in controller
Server sends callbacks, controller processess them by expanding "log" variable with whatever arrives
User still sees the same page, where "log" (contained in specific div) is being periodically refreshed by javascript while controller waits for final result from server
Server returns final result
Controller finishes its code and returns new view
This is my post method, which currently doesnt wait for the response and proceeds immediately further:
[HttpPost]
public async Task<ActionResult> SubmitDetails
(DocuLiveInstallationRequest submittedRequest, string command)
{
request = submittedRequest;
try
{
switch (command)
{
case "Test":
{
request.OnlyTest = true;
DocuLiveInstallationStatus installStatus
= await IsValidStatus();
if (installStatus == null)
{
ViewBag.Fail = Resources.AppStart.TestNoResult;
return View("SubmitDetails", request); ;
}
else
{
status = installStatus;
if (status.Result)
{
ViewBag.Success = Resources.AppStart.TestSucces;
ViewBag.Log = installLog;
}
TempData["installationStatus"] = installStatus;
return View("SubmitDetails", request);
}
}
case "Submit":
{
request.OnlyTest = false;
DocuLiveInstallationStatus installStatus = await Install();
if (installStatus == null)
{
ViewBag.Fail = Resources.AppStart.InstallationNoResult;
return View("SubmitDetails", request); ;
}
else
{
status = installStatus;
TempData["installationStatus"] = installStatus;
TempData["installLog"] = installLog;
return RedirectToAction("Login",controllerName:"Login");
}
}
}
ViewBag.TestFail = Resources.AppStart.SubmitFailure;
return View("SubmitDetails", request); ;
}
catch
{
return View();
}
}
this is javascript I prepared for the view:
<script type="text/javascript">
$(document).ready(function () {
//$("#submitDetails").click(function () {
var progress = 0;
//$("#submitDetails").attr('disabled', 'disabled');
var statusUpdate = setInterval(function () {
$.ajax({
type: 'GET',
url: "/AppStart/GetInstallProgress",
datatype: "application/html; charset=utf-8",
success: function (data) {
if (data && data != "") {
$("div.status-message").text(progress);
}
}
});
}, 2000);
//});
});
</script>
Currently I just display the log on the next page (at this stage of development server returns response very swiftly), but I need to call the server, display progress from callbacks while waiting for result and THEN navigate to another page (depending on the result). I feel like I am very close, but I can't get it working.
PS: I am open to other solutions than updating page content. I don't really mind how the goal will be accomplished, but updating the page is preferred by the customer.
I am making an AJAX call using the XMLHttpRequest.
It's working fine in IE7, but when i try the same in Firefox, I am not able to get it back thru the response.write
I am using the function below:
<script type="text/javascript">
function ddSelect_Change() {
var xmlhttp;
if (window.XMLHttpRequest) { // Mozilla, Safari, ...
xmlhttp = new XMLHttpRequest();
}
else if (window.ActiveXObject) { // IE
try {
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
}
catch (e)
{
try
{
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
catch (e)
{
}
}
}
xmlhttp.onreadystatechange = function () {
//alert(xmlhttp.responseText);
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
}
}
var url = "http://" + location.hostname + "Locationurl?Method=methodname";
xmlhttp.open("POST", url);
xmlhttp.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
xmlhttp.send();
}
ADDED
I have two separate web application one is tridion web application and other is custom web application. and i am making interaction from tridion web application to custom web application. Both the url are having different domain.and state i am getting 0 in firefox, and for readystate i am not getting (3) in my alert.
The code you've shown so far should work in Firefox. Firefox support XHR.
This might be at help: https://developer.mozilla.org/en-US/docs/AJAX/Getting_Started
Update:
onreadystatechange is fired several times during an AJAX call, so you probably want to extend your callback to something like this:
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4) {
if (xmlhttp.status === 200) {
alert(xmlhttp.responseText);
} else {
alert('There was a problem with the request.');
}
}
}
xmlhttp.readyState === 4 verifies that the request has completed, so that you don't try to alert the response before you actually have it. xmlhttp.status === 200 verifies that you recieved a 200 OK from the server, to make sure that there were no server-side errors, or that the URL was incorrect.
Have you considered using a library like jQuery? It already has taken care of these issues for you.
If you are working on a SDL Tridion GUI extension, check out the PowerTools project for a ton of examples. (http://code.google.com/p/tridion-2011-power-tools/)
I've seen this question asked a few ways and the solutions are generally for other languages and don't apply to ASP.NET MVC 2.
I am using Jquery & Jquery forms to auto-save user data at a set interval. I still want the application to be able to time out, but the auto-saves via jquery forms keep refreshing the server.
My initial idea to fix this was pretty simple. I've already got an ActionFilter I use to see if the session expires. Well, the session won't ever expire; however, I just keep track of how many auto saves occurr based on a value in session and when it reaches a limit (specified in the web.config), it does a:
filterContext.Result = new RedirectResult("~/Account.aspx/LogOn");
Well, this doesn't work because the auto save is doing an ajaxFormSubmit to call the action in the first place. I've tried changing the action to redirect to the login page, but the same thing happens....it just doesn't do a redirect. The only thing the action can return is a Json result. In my latest version (code below) I'm setting the json return value to false and calling a redirectToLogin() function to send the page over to the login page. It doesn't work and i'm not sure why.
Any thoughts on this would be most helpful.
Excerpt of code that sets up the interval for autosaving on the view (placed just before the form is closed):
<%
double sessionTimeoutInMinutes = double.Parse(ConfigurationManager.AppSettings["SESSION_TIMEOUT_IN_MINUTES"].ToString());
double maxContiguousAutoSaves = double.Parse(ConfigurationManager.AppSettings["MAX_CONTIGUOUS_AUTO_SAVES"].ToString());
double autoSaveInterval = (sessionTimeoutInMinutes / maxContiguousAutoSaves) * 60 * 1000;
%>
<%= Html.Hidden("autoSaveInterval", autoSaveInterval) %>
<script type="text/javascript">
$(document).ready(function() {
var autoSaveFrequency = $('[id=autoSaveInterval]').val();
//alert(' Auto Save Interval in miliseconds: ' + autoSaveFrequency);
setInterval(
"initAutoSave('AutoSaveGoals', 'message')"
, autoSaveFrequency);
});
</script>
"AutoSaveGoals" goals is the name of one of my actions. It handles the post, updates certain items in session, and calls the repository.update. It is defined below:
[HttpPost]
public ActionResult AutoSaveGoals(Data data)
{
Data sessdata = Data();
sessdata.MpaGoals = data.Goals;
sessdata.MpaStatus = data.MpaStatus;
sessdata.StartPeriodDate = data.StartPeriodDate;
sessdata.EndPeriodDate = data.EndPeriodDate;
sessdata.AssociatePassword = data.AssociatePassword;
try
{
_repository.update(sessdata);
}
catch (Exception e)
{
LogUtil.Write("AutoSaveGoals", "Auto Save Goals Failed");
LogUtil.WriteException(e);
}
if (!autoLogOffUser(RouteData.GetRequiredString("action")))
return Json(new { success = true });
else
return Json(new { success = false });
}
The initAutoSave function is javascript that uses Jquery & Jquery Forms plugin. Here it is:
function initAutoSave(targetUrl, messageDivId) {
var options = {
url: targetUrl,
type: 'POST',
beforeSubmit: showRequest,
success: function(data, textStatus) {
//alert('Returned from save! data: ' + data);
if (data.success) {
var currDateAndTime = " Page last saved on: " + getCurrentDateAndTime();
$('[id=' + messageDivId + ']').text(currDateAndTime).show('normal', function() { })
}
else {
alert('redirecting to login page');
redirectToLogin();
//$('[id=' + messageDivId + ']').text(' An error occurred while attempting to auto save this page.').show('normal', function() { })
//alert('ERROR: Page was not auto-saved properly!!!!');
}
}
};
$('form').ajaxSubmit(options);
}
I try doing a javascript redirect in redirectToLogin() but it doesn't seem to get the url or something behind the scenes is blowing up. Here is how it's defined:
function redirectToLogin() {
window.location = "Account.aspx/LogOn";
}
best way to solve this is to have your code always return an Json result, i use a model called StandardAjaxResponse that has an ID, a Message and an answer answer is always false unless my code completes in the correct way and sets this to true. Any errors from try / catch are placed into the message field, so if !data.Answer and the Message is equal to not loggged in the you can then location.href to the login page, without getting the login page as your ajax response.
for example:
public class AjaxGenericResponse{
public bool Answer {get;set; }
public int Id {ge; set; } // this is for cases when i want an ID result
public string Mesage {get;set;} // this is so i can show errors from ajax
}
the controller / action
public JsonResult DoAutoSave(Data data){
var JsonResults = new AjaxGenericResponse{Answer=false};
// do code here to save etc
// no matter what always return a result, even if code is broken
return Json(model);
}
your Javascript:
$.ajax({
url:"",
dataTYpe: 'json',
success:function(data){
if(data.Answer) {
// all is good
} else {
if(data.Message === "logout') { href.location=('login'); } else { alert(data.Message); }
}
}
});
thats one solution anyway!
Stupid me. Thanks for your response minus, but I think our solutions coincided for the answer. My issue was I didn't have the right url to redirect to in the redirectToLogin method. I've made minor tweaks, and presto, its redirecting.
Javascript changes:
function redirectToLogin(url) {
window.location = url;
}
function initAutoSave(targetUrl, messageDivId) {
var options = {
url: targetUrl,
type: 'POST',
beforeSubmit: showRequest,
success: function(data, textStatus) {
//alert('Returned from save! data: ' + data);
if (data.success) {
var currDateAndTime = " Page last saved on: " + getCurrentDateAndTime();
$('[id=' + messageDivId + ']').text(currDateAndTime).show('normal', function() { })
}
else {
alert('redirecting to login page');
redirectToLogin(data.url);
//$('[id=' + messageDivId + ']').text(' An error occurred while attempting to auto save this page.').show('normal', function() { })
//alert('ERROR: Page was not auto-saved properly!!!!');
}
}
};
$('form').ajaxSubmit(options);
}
Action changes
if (!shouldAutoLogOffUser(RouteData.GetRequiredString("action")))
return Json(new { success = true, url = "" });
else
return Json(new { success = false , url = Url.Action("LogOff","Account").ToString() });
The shouldAutoLogOffUser checks a session variable that was updated by an action filter to track the # of contiguous auto saves and handles the logic to see if that value has exceeded the max # of contiguous autosaves allowed. The action filter checked the actionname for 'AutoSave' and if it found it, the counter was incremented. Otherwise the counter was reset to 0 (a non autosave post occurred).
One more random question. If this application were loaded in an IFrame and the window.location call is made, would the IFrame content be changed or the entire page (the container in essence) be changed? Our company is looking to run some of our asp.net mvc 2 apps in IFrame's via websphere portal (yeah, I know....it's not my choice).
Now this is just absurd...So, I was looking over my applications (I've got several going to QA soon) and noted that I've already solved this very question with a much better solution - it was ALL handled in an ActionFilter. I wanted this from the getgo when I asked this question, but to have already implemented it, forgot about that, AND ask again on Stack Overflow...well, I hope my memory issues helps somebody with this. Below is the full action filter code. As always, I'm open to criticism so mock it, revise it, copy it, etc, etc.
public class UserStillActiveAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
int sessionTimeoutInMinutes = int.Parse(ConfigurationManager.AppSettings["SESSION_TIMEOUT"].ToString());
int maxContiguousAutoSaves = int.Parse(ConfigurationManager.AppSettings["MAX_CONSEC_SAVES"].ToString());
int autoSaveIntervalInMinutes = int.Parse(ConfigurationManager.AppSettings["AUTO_SAVE_INTERVAL"].ToString());
string actionName = filterContext.ActionDescriptor.ActionName;
string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
HttpContext currentSession = HttpContext.Current;
LogAssociateGoalsSessionStatus(filterContext.HttpContext, actionName);
if (actionName.ToLower().Contains("autosave"))
{
int autoSaveCount = GetContigousAutoSaves(filterContext.HttpContext);
if (autoSaveCount == maxContiguousAutoSaves)
{
var result = new RedirectResult("~/Account.aspx/LogOff");
if (result != null && filterContext.HttpContext.Request.IsAjaxRequest())
{
//Value checked on Logon.aspx page and message displayed if not null
filterContext.Controller.TempData.Add(PersistenceKeys.SessionTimeOutMessage,
StaticData.MessageSessionExpiredWorkStillSaved);
string destinationUrl = UrlHelper.GenerateContentUrl(
result.Url,
filterContext.HttpContext);
filterContext.Result = new JavaScriptResult()
{
Script = "window.location='" + destinationUrl + "';"
};
}
}
else
{
RefreshContiguousAutoSaves(filterContext.HttpContext, autoSaveCount + 1);
}
}
else
{
RefreshContiguousAutoSaves(filterContext.HttpContext, 1);
}
}
private int GetContigousAutoSaves(HttpContextBase context)
{
Object o = context.Session[PersistenceKeys.ContiguousAutoUpdateCount];
int contiguousAutoSaves = 1;
if (o != null && int.TryParse(o.ToString(), out contiguousAutoSaves))
{
return contiguousAutoSaves;
}
else
{
return 1;
}
}
private void RefreshContiguousAutoSaves(HttpContextBase context,
int autoSavecount)
{
context.Session.Remove(PersistenceKeys.ContiguousAutoUpdateCount);
context.Session.Add(PersistenceKeys.ContiguousAutoUpdateCount,
autoSavecount);
}
private void LogAssociateGoalsSessionStatus(HttpContextBase filterContext, string actionName)
{
AssociateGoals ag = (AssociateGoals)filterContext.Session[(PersistenceKeys.SelectedAssociateGoals)];
bool assocGoalsIsNull = false;
bool assocGoalsInformationIsNull = false;
if (ag == null)
{
assocGoalsIsNull = true;
assocGoalsInformationIsNull = true;
}
else if (ag != null && ag.AssociateInformation == null)
assocGoalsInformationIsNull = true;
}
}
always use double quote in java script and jquery to avoid browser specific issues
like
dataTYpe: 'json' must be as "dataTYpe:"json"