I built an ajax chat in one of my mvc website. everything is working fine. I am using polling. At certain interval i am using $.post to get the messages from the db. But there is a problem. The message retrieved using $.post keeps on repeating. here is my javascript code and controller method.
var t;
function GetMessages() {
var LastMsgRec = $("#hdnLastMsgRec").val();
var RoomId = $("#hdnRoomId").val();
//Get all the messages associated with this roomId
$.post("/Chat/GetMessages", { roomId: RoomId, lastRecMsg: LastMsgRec }, function(Data) {
if (Data.Messages.length != 0) {
$("#messagesCont").append(Data.Messages);
if (Data.newUser.length != 0)
$("#usersUl").append(Data.newUser);
$("#messagesCont").attr({ scrollTop: $("#messagesCont").attr("scrollHeight") - $('#messagesCont').height() });
$("#userListCont").attr({ scrollTop: $("#userListCont").attr("scrollHeight") - $('#userListCont').height() });
}
else {
}
$("#hdnLastMsgRec").val(Data.LastMsgRec);
}, "json");
t = setTimeout("GetMessages()", 3000);
}
and here is my controller method to get the data:
public JsonResult GetMessages(int roomId,DateTime lastRecMsg)
{
StringBuilder messagesSb = new StringBuilder();
StringBuilder newUserSb = new StringBuilder();
List<Message> msgs = (dc.Messages).Where(m => m.RoomID == roomId && m.TimeStamp > lastRecMsg).ToList();
if (msgs.Count == 0)
{
return Json(new { Messages = "", LastMsgRec = System.DateTime.Now.ToString() });
}
foreach (Message item in msgs)
{
messagesSb.Append(string.Format(messageTemplate,item.User.Username,item.Text));
if (item.Text == "Just logged in!")
newUserSb.Append(string.Format(newUserTemplate,item.User.Username));
}
return Json(new {Messages = messagesSb.ToString(),LastMsgRec = System.DateTime.Now.ToString(),newUser = newUserSb.ToString().Length == 0 ?"":newUserSb.ToString()});
}
Everything is working absloutely perfect. But i some messages getting repeated. The first time page loads i am retrieving the data and call GetMessages() function. I am loading the value of field hdnLastMsgRec the first time page loads and after the value for this field are set by the javascript.
I think the message keeps on repeating because of asynchronous calls. I don't know, may be you guys can help me solve this.
or you can suggest better way to implement this.
Kaivalya is correct about the caching, but I'd also suggest that your design could and should be altered just a tad.
I made a very similar app recently, and what I found was that my design was greatly enhanced by letting the controllers work with the fairly standard PRG pattern (post-redirect-get). Why enhanced? well, because POST methods are built to add stuff to an app, GET methods are supposed to be used to get information without side effects. Your polling should be just getting new messages w/o side effects.
So rather than your $.post call expecting data and handling the callback, what I'd recommend is having your controller expose a method for creating new chat messages via POST and then another method that get the last X chat messages, or the messages since a certain timestamp or whatever.
The javascript callback from the post action, then can update some variables (e.g. the last message id, timestamp of the last message, or even the whole URL of the next message based on the info contained in a redirect, whatever).
The $.post would fire only in response to user input (e..g type in a box, hit 'send') Then, you have (separately) a $.get call from jquery that's set up to poll like you said, and all it does is fetch the latest chat messages and it's callback updates the chat UI with them.
I got my answer here: ASP.NET AJAX CHAT
The names below i am referring to are from above link.
i think the actual problem was with the timestamp thing and asynchronous behaviour of $.post. after calling "GetMessages()" method, even if the previous request to retrive chat message was not complete anathor call to same method used to fire due to setting timeout for "GetMessages()" method outside the $.post method. In my question you can see that timeout for "GetMessages()" method is set outside the $.post method. Now i set the timeout for "GetMessages()" method inside the $.post method. so that next call to "GetMessages()" only occur after 3 seconds of completion of current $.post method. I have posted the code below.
var t;
function GetMessages() {
var LastMsgRec = $("#hdnLastMsgRec").val();
var RoomId = $("#hdnRoomId").val();
//Get all the messages associated with this roomId
$.post("/Chat/GetMessages", { roomId: RoomId, lastRecMsg: LastMsgRec }, function(Data) {
if (Data.LastMsgRec.length != 0)
$("#hdnLastMsgRec").val(Data.LastMsgRec);
if (Data.Messages.length != 0) {
$("#messagesCont").append(Data.Messages);
if (Data.newUser.length != 0)
$("#usersUl").append(Data.newUser);
$("#messagesCont").attr({ scrollTop: $("#messagesCont").attr("scrollHeight") - $('#messagesCont').height() });
$("#userListCont").attr({ scrollTop: $("#userListCont").attr("scrollHeight") - $('#userListCont').height() });
}
else {
}
t = setTimeout("GetMessages()", 3000);
}, "json");
}
I addition to that i also changed few things. As suggested by ignatandrei i placed $("#hdnLastMsgRec").val(Data.LastMsgRec); immediately after function(Data) {.
and also
as said by MikeSW i changed the data retrieval process. Previously i was extracting data on the basis of timespan(retrieve all the data associated with
this room id that has greater timespan than last data retrieved message timespan) but now i keep track of the messageid. Now i retrieve only those data that
has message id greater than last retrieved message id.
and guess what no repeataion and perfectly working chat application so far on my intranet.
I still got to see it's performance when deployed on internet.
i think it solved my problem.
i will still test the system and let u guys know if there is any problem.
By default $.post() caches the results
You can either call $.ajaxSetup ({ cache: false}); before JS GetMessages function call to ensure caching is disabled or change the $.post to $.ajax and set cache attribute to false. In the end $.post() is a short cut to this:
$.ajax({
type: 'POST',
url: url,
data: data,
success: success
dataType: dataType
});
Related
I'm still very new to appcelerator but I'm trying to do a small experiment with geolocation. I have some code similar to below, which returns the long and lat to the console. What I would like to is get the long and lat and append them to a URL, e.g http://www.mywebsite.com/lat/long.
I've tried creating a simple alert to show me the current location in the but all it says is Alert: [object GeolocationModule].
Could somebody point me in the right direction so I can learn some more? Thank you
if (Ti.Geolocation.locationServicesEnabled) {
Titanium.Geolocation.purpose = 'Get Current Location';
Titanium.Geolocation.getCurrentPosition(function(e) {
if (e.error) {
Ti.API.error('Error: ' + e.error);
} else {
Ti.API.info(e.coords);
}
});
} else {
alert('Please enable location services');
}
This is how you need to follow the API documentation:
You can have a look at the LocationResults page: https://docs.appcelerator.com/platform/latest/#!/api/LocationResults which leads you to LocationCoordinates: https://docs.appcelerator.com/platform/latest/#!/api/LocationCoordinates
There you can see, that you can use e.coords.latitude or longitude to get the values. Or have a look at the console output. It should show you a JSON output with the key-value pairs.
Once you have the values you can create a HTTP request (demo: https://docs.appcelerator.com/platform/latest/#!/guide/HTTPClient_and_the_Request_Lifecycle) and open your page:
var url = "https://www.appcelerator.com/"+e.coords.longitude+"/"+e.coords.latitude;
var xhr = Ti.Network.createHTTPClient({
onload: function(e) {
// this function is called when data is returned from the server and available for use
// this.responseText holds the raw text return of the message (used for text/JSON)
// this.responseXML holds any returned XML (including SOAP)
// this.responseData holds any returned binary data
Ti.API.debug(this.responseText);
alert('success');
},
onerror: function(e) {
// this function is called when an error occurs, including a timeout
Ti.API.debug(e.error);
alert('error');
},
timeout:5000 /* in milliseconds */
});
xhr.open("GET", url);
xhr.send(); // request is actually sent with this statement
or if you plan to use more request have a look at RESTe (https://github.com/jasonkneen/RESTe) which is an awesome library that makes it easy to create API requests
I'm working in an implementation using SignalR and the Kendo Scheduler. When a new task is created (for exemple), the SchedulerDataSource transport send the connection hub id to the server as an additional parameter:
transport: {
read: { url: global.web_path + 'Home/Tasks' },
update: { url: global.web_path + 'Home/UpdateTask', type: 'PUT', contentType: 'application/json' },
create: { url: global.web_path + 'Home/CreateTask', type: 'POST', contentType: 'application/json' },
destroy: { url: global.web_path + 'Home/DeleteTask', type: 'DELETE', contentType: 'application/json' },
parameterMap: function (options, operation) {
if (operation == "destroy" && options.models) {
return JSON.stringify({ taskId: options.models[0].Id, callerId: $.connection.hub.id });
}
if (operation !== "read" && options.models) {
return JSON.stringify({ tasks: options.models, callerId: $.connection.hub.id });
}
}
},
The server do whatever it has to do, and send a notification to every other user, except de caller:
[HttpPost]
public JsonResult CreateTask(List<ScheduledEvent> tasks, string callerId)
{
...create task and other stuff
//broadcast the newly created object to everyone except caller
var hubContext = GlobalHost.ConnectionManager.GetHubContext<Notebooks.Hubs.SchedulerHub>();
hubContext.Clients.AllExcept(callerId).UpdateSchedule(task);
//return the object to caller
return Json(task);
}
Once the other clients receive a new task from the hub, it is added to the SchedulerDataSource:
hub.client.updateSchedule = function (scheduledEvent) {
schedulerDataSource.add(scheduledEvent);
}
Everything seems to work fine, and it really took me some time to realize this behavior: if a client have the scheduler window open, this window is closed once the schedulerDataSource is updated. This is expected or am I doing something wrong?
Edit: I just realized how old this question is, so you have probably moved on to other things by now, or the pushCreate method may not have existed back then.
I think this may be how it works, but it seems like it should be able to add those events behind the scenes without having to close the edit window. Have you tried the pushCreate method? If that doesn't work, since the add automatically closes the edit dialog, maybe when the events come in, if the dialog is open, you could store the new events, then add them when the user closes the edit dialog.
My answer is now even older ;) but I faced this very same issue today.
First, I'm quite sure this is indeed the expected behavior. You can see in the kendo sources the call of the close editor window method in the transport update and create methods of the scheduler.
Below is what I've done to bypass the issue .
The idea is as simple as to prevent the edit window to close when an appointment modification comes from another hub client.
Server-side : modify the hub methods (example with update method)
public MyAppointmentViewModel Update(MyAppointmentViewModel appointment)
{
if (!appointmentService.Update(appointment))
throw new InvalidOperationException("Something went wrong");
else
{
Clients.Others.PrepareBeforeAddOrUpdateSignal(appointment.Id);
Clients.Others.Update(appointment);
return appointment;
}
}
Here you see we inform every other clients (through PrepareBeforeAddOrUpdate) we're about to update an appintment.
Client-side now (in index.cshtml for instance)
schedulerHub.client.prepareBeforeAddOrUpdateSignal = function(id){ lastModifiedRdvId = id; };
schedulerHub.client.create = function(appointment){ lastModifiedRdvId = 0; }; /* reset the variable for next time */
schedulerHub.client.update = function(appointment){ lastModifiedRdvId = 0; }; /* reset the variable for next time */
function SchedulerEditor()
{
return $(".k-scheduler-edit-form").data("kendoWindow");
}
var eventBeforeChanges = null;
var lastModifiedRdvId = 0;
function onEditorClose(e) {
if (eventBeforeChanges != null) {
if (lastModifiedRdvId > 0 && eventBeforeChanges.Id != lastModifiedRdvId)
e.preventDefault();
else {
var editWin = SchedulerEditor(); /* unbind this callback and use default behavior again */
editWin.unbind('close', onEditorClose);
}
}}
function onEditRdv(e) {
var editWin = SchedulerEditor();
if (editWin != null) /*Bind the close event */
editWin.unbind('close', onEditorClose).bind('close', onEditorClose);
eventBeforeChanges = e.event;
/* continue onEditRdv */
}
you see here the close event is prevented when the appointment id is not the appointment id beeing updated by the current client.
And fortunately, preventing the close event prevents the annoying behavior of having a sort of ghost appointment after one has been changed by another hub client.
I'm sorry if I'm not clear or if the code isn't clear enough. I can provide more information if necessary.
Bye
This is my Titanium code:
var loginReq = Titanium.Network.createHTTPClient({
onload: function(e){
// just displays the response
var webview = Titanium.UI.createWebView({html:this.responseText});
win.add(webview);
}
});
loginReq.open("POST",url);
var params = {
email: email.value,
passowrd: password.value
};
loginReq.send(params); // this is sending nothing according to a CF variable dump
The ColdFusion page just dumps all the variables, and it shows up on the iPhone emulator. But it's giving me an empty struct for the variables, which means no variables are actually getting sent in.
How do I fix my Titanium code to actually post data?
If you want to send post data to a script you will have to to set the header accordingly:
loginReq.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
If you are sending JSON data to an API, you might need to stringify your parameters to send them:
loginReq.send(JSON.stringify(params));
Edit:
According to the docs this is done automatically (see comments). In some of my network clients I had to do that explicitly, though...
Moreover, you might also want to implement the onerror callback, so in case your call fails for any reason you will know why:
var loginReq = Titanium.Network.createHTTPClient({
onload: function(e){
// just displays the response
var webview = Titanium.UI.createWebView({html:this.responseText});
win.add(webview);
},
onerror: function(e) {
Ti.API.debug('Status: ' + this.status);
Ti.API.debug('Response: ' + this.responseText);
}
});
What I am looking to do is:
1) From an MVC View, Start a long running Process. In my case, this process is a seperate Console Application being executed. The Console Application runs for potentially 30 minutes and regurlarily Console.Write's its current actions.
2) Back on the MVC View, periodically poll the server to retrieve the latest Standard Out which I have redirected to a Stream (or anywhere I can get access to it for that matter). I'll append newly retieved standard output to a log textbox or something equivalent.
Sounds relativly easy. My client side programming is a bit rusty though and I'm having issues with the actual streaming. I would assume this is not an uncommon task. Anyone got a decent solution for it in ASP.NET MVC?
Biggest issue seems to be that I cant get the StandardOutput until the end of execution, but I was able to get it with an event handler. Of course, using the event handler seems to lose focus of my output.
This is what I was working with so far...
public ActionResult ProcessImport()
{
// Get the file path of your Application (exe)
var importApplicationFilePath = ConfigurationManager.AppSettings["ImportApplicationFilePath"];
var info = new ProcessStartInfo
{
FileName = importApplicationFilePath,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false
};
_process = Process.Start(info);
_process.BeginOutputReadLine();
_process.OutputDataReceived += new DataReceivedEventHandler(_process_OutputDataReceived);
_process.WaitForExit(1);
Session["pid"] = _process.Id;
return Json(new { success = true }, JsonRequestBehavior.AllowGet);
}
void _process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
_importStandardOutputBuilder.Insert(0, e.Data);
}
public ActionResult Update()
{
//var pid = (int)Session["pid"];
//_process = Process.GetProcessById(pid);
var newOutput = _importStandardOutputBuilder.ToString();
_importStandardOutputBuilder.Clear();
//return View("Index", new { Text = _process.StandardOutput.ReadToEnd() });
return Json(new { output = newOutput }, "text/html");
}
I haven't written the client code yet as I am just hitting the URL to test the Actions, but I'm also interested how you would approach polling for this text. If you could provide the actual code for this too, it would be great. I would assume you'd have a js loop running after kicking off the process that would use ajax calls to the server which returns JSON results... but again, its not my forte so would love to see how its done.
Thanks!
Right, so from the couple of suggestions I received and a lot of trial and error I have come up with a work in progress solution and thought I should share with you all. There are definitely potential issues with it at the moment, as it relies on static variables shared across the website, but for my requirement it does the job well. Here goes!
Let's start off with my view. We start off by binding the click event of my button with some jquery which does a post to /Upload/ProcessImport (Upload being my MVC Controller and ProcessImport being my MVC Action). Process Import kicks off my process which I will detail below. The js then waits a short time (using setTimeout) before calling the js function getMessages.
So getMessages gets called after the button is clicked and it does a post to /Upload/Update (my Update action). The Update action basically retrieves the status of the Process and returns it as well as the StandardOutput since last time Update was called. getMessages will then parse the JSON result and append the StandardOutput to a list in my view. I also try to scroll to the bottom of the list, but that doesn't work perfectly. Finally, getMessages checks whether the process has finished, and if it hasn't it will recursivly call itself every second until it has.
<script type="text/javascript">
function getMessages() {
$.post("/Upload/Update", null, function (data, s) {
if (data) {
var obj = jQuery.parseJSON(data);
$("#processOutputList").append('<li>' + obj.message + '</li>');
$('#processOutputList').animate({
scrollTop: $('#processOutputList').get(0).scrollHeight
}, 500);
}
// Recurivly call itself until process finishes
if (!obj.processExited) {
setTimeout(function () {
getMessages();
}, 1000)
}
});
}
$(document).ready(function () {
// bind importButton click to run import and then poll for messages
$('#importButton').bind('click', function () {
// Call ProcessImport
$.post("/Upload/ProcessImport", {}, function () { });
// TODO: disable inputs
// Run's getMessages after waiting the specified time
setTimeout(function () {
getMessages();
}, 500)
});
});
</script>
<h2>Upload</h2>
<p style="padding: 20px;">
Description of the upload process and any warnings or important information here.
</p>
<div style="padding: 20px;">
<div id="importButton" class="qq-upload-button">Process files</div>
<div id="processOutput">
<ul id="processOutputList"
style="list-style-type: none; margin: 20px 0px 10px 0px; max-height: 500px; min-height: 500px; overflow: auto;">
</ul>
</div>
</div>
The Controller. I chose not to go with an AsyncController, mainly because I found I didn't need to. My original issue was piping the StdOut of my Console application to the view. I found couldn't ReadToEnd of the standard out, so instead hooked the event handler ProcessOutputDataReceived up which gets fired when standard out data is recieved and then using a StringBuilder, append the output to previously received output. The issue with this approach was that the Controller gets reinstantiated every post and to overcome this I decided to make the Process and the StringBuilder static for the application. This allows me to then receive a call to the Update Action, grab the static StringBuilder and effectivly flush its contents back to my view. I also send back to the view a boolean indicating whether the process has exited or not, so that the view can stop polling when it knows this. Also, being static I tried to ensure that if an import in in progress, don't allow other's to begin.
public class UploadController : Controller
{
private static Process _process;
private static StringBuilder _importStandardOutputBuilder;
public UploadController()
{
if(_importStandardOutputBuilder == null)
_importStandardOutputBuilder = new StringBuilder();
}
public ActionResult Index()
{
ViewData["Title"] = "Upload";
return View("UploadView");
}
//[HttpPost]
public ActionResult ProcessImport()
{
// Validate that process is not running
if (_process != null && !_process.HasExited)
return Json(new { success = false, message = "An Import Process is already in progress. Only one Import can occur at any one time." }, "text/html");
// Get the file path of your Application (exe)
var importApplicationFilePath = ConfigurationManager.AppSettings["ImportApplicationFilePath"];
var info = new ProcessStartInfo
{
FileName = importApplicationFilePath,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false
};
_process = Process.Start(info);
_process.BeginOutputReadLine();
_process.OutputDataReceived += ProcessOutputDataReceived;
_process.WaitForExit(1);
return Json(new { success = true }, JsonRequestBehavior.AllowGet);
}
static void ProcessOutputDataReceived(object sender, DataReceivedEventArgs e)
{
_importStandardOutputBuilder.Append(String.Format("{0}{1}", e.Data, "</br>"));
}
public ActionResult Update()
{
var newOutput = _importStandardOutputBuilder.ToString();
_importStandardOutputBuilder.Clear();
return Json(new { message = newOutput, processExited = _process.HasExited }, "text/html");
}
}
Well, that's it so far. It works. It still needs work, so hopefully I'll update this solution when I perfect mine. What are your thoughts on the static approach (assuming the business rule is that only one import can occur at any one time)?
Look into long poll. Basically you can open an ajax request and then hold onto it inside the controller.
Sample of long poll
This is something that you will want to do Async or you will possibly have issues with thread starvation.
Consider writing a service that runs on a server somewhere and pipes its output to a file/db accessible by your web server. Then you can just load the generated data in your website and returning them to your caller.
Understand that tying up your web server's threads for extended periods of time can result in thread starvation and make it look like your website has crashed (even though it's acutally just busy waiting for your console app to run).
i am trying to accomplish a two way communication request response in my firefox sidebar extension, i have a file named event.js this resides on the content side, i have another file called sidebar.js file which is residing in the xul. I am able to communicate from event.js to sidebar.js file using the dispatchEvent method. my event in turn raises a XMLHttpRequest in sidebar.js file which hits the server and sends back the response. Now, here i am unable to pass the response to the event.js file. I want the response to be accessed in the event.js file. Till now i have achieved only one way communication. Please help me in getting the two way communication.
Code is as follows:
// event.js file
// This event occurs on blur of the text box where i need to save the text into the server
function saveEvent() {
var element = document.getElementById("fetchData");
element.setAttribute("urlPath", "http://localhost:8080/event?Id=12");
element.setAttribute("jsonObj", convertToList);
element.setAttribute("methodType", "POST");
document.documentElement.appendChild(element);
var evt = document.createEvent("Events");
evt.initEvent("saveEvent", true, true);
element.dispatchEvent(evt);
//Fetching the response over here by adding the listener
document.addEventListener("dispatchedResponse", function (e) { MyExtension.responseListener(e); }, false, true);
}
var MyExtension = {
responseListener: function (evt) {
receivedResponse(evt.target.getAttribute("responseObject"));
}
}
function receivedResponse(event) {
alert('response: ' + event);
}
// sidebar.js file
window.addEventListener("load", function (event) {
var saveAjaxRequest = function (urlPath, jsonObj, methodType, evtTarget) {
var url = urlPath;
var request = Components.classes["#mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Components.interfaces.nsIXMLHttpRequest);
request.onload = function (aEvent) {
window.alert("Response Text: " + aEvent.target.responseText);
saveResponse = aEvent.target.responseText;
//here i am again trying to dispatch the response i got from the server back to the origin, but unable to pass it...
evtTarget.setAttribute("responseObject", saveResponse);
document.documentElement.appendChild(evtTarget);
var evt = document.createEvent("dispatchedRes"); // Error line "Operation is not supported" code: "9"
evt.initEvent("dispatchedResponse", true, false);
evtTarget.dispatchEvent(evt);
};
request.onerror = function (aEvent) {
window.alert("Error Status: " + aEvent.target.status);
};
//window.alert(methodType + " " + url);
request.open(methodType, url, true);
request.send(jsonObj);
};
this.onLoad = function () {
document.addEventListener("saveEvent", function (e) { MyExtension.saveListener(e); }, false, true);
}
var MyExtension =
{
saveListener: function (evt) {
saveAjaxRequest(evt.target.getAttribute("urlPath"), evt.target.getAttribute("jsonObj"), evt.target.getAttribute("methodType"), evt.originalTarget);
}
};
});
Why are you moving your fetchData element into the sidebar document? You should leave it where it is, otherwise your content code won't be able to receive the event. Also, use the content document to create the event. Finally, document.createEvent() parameter for custom events should be "Events". So the code after your //here i am again trying comment should look like:
evtTarget.setAttribute("responseObject", saveResponse);
var evt = evtTarget.ownerDocument.createEvent("Events");
evt.initEvent("dispatchedResponse", true, false);
evtTarget.dispatchEvent(evt);
Please note however that your code as you show it here is a huge security vulnerability - it allows any website to make any HTTP requests and get the result back, so it essentially disables same-origin policy. At the very least you need to check that the website talking to you is allowed to do it (e.g. it belongs to your server). But even then it stays a security risk because server response could be altered (e.g. by an attacker on a public WLAN) or your server could be hacked - and you would be giving an attacker access to sensitive data (for example he could trigger a request to mail.google.com and if the victim happens to be logged in he will be able to read all email data). So please make this less generic, only allow requests to some websites.