Pushing complete data from Spring Boot server through Web Socket to Client - html

I have a spring boot server and i am able to generate a streaming table on client side by sending json one after the another. The problem is if a user logs in say after 10 minutes, he is only able to access data starting from 10th minute i.e he is not able to access data from 0 to 10th minute. What i want is to push the data from 0th to 10th minute first and at the same time continue the streaming process. How can this be done? I am using jquery datatable to generate the table.
I am attaching the controller and client side html for reference
1) Controller
#Controller
public class ScheduledUpdatesOnTopic {
#Autowired
private SimpMessagingTemplate template;
int count=0;
#Scheduled(fixedDelay=500)
public void trigger() {
DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Date date = new Date();
String str[] = {"name"+count,""+Math.round(Math.random()*100),"India"+Math.round(Math.random()*100),df.format(date)};
this.template.convertAndSend("/topic/message",str);
++count;
}
}
2) Client HTML
var _self = this;
$(document).ready(function() {
var message ;
$('#tbl').DataTable( {
data: message,
"aLengthMenu" : [[25,50,75,-1],[25,50,75,"All"]],
"pageLength" :25,
columns: [
{ title: "Name" },
{ title: "Age" },
{ title: "Country" },
{ title: "Date"}
]
});
subscribeSocket();
});
function addRow(message){
var table = $('#tbl').DataTable();
if(table && message ){
table.row.add(message).draw();
}
}
function subscribeSocket(){
var socket = new SockJS('/gs-guide-websocket');
var stompClient = Stomp.over(socket);
stompClient.connect({ }, function(frame) {
stompClient.subscribe("/topic/message", function(data) {
message = JSON.parse(data.body);
_self.addRow(message);
});
});
};

If you don't save previous sent datas, you can't send them back to new customers.
On the front side, you have to subscribe to an "history" resource and make a call to get it.
Front:
function subscribeSocket() {
var socket = new SockJS('/gs-guide-websocket');
stompClient = Stomp.over(socket);
var firstCounterReceived = null;
stompClient.connect({}, function (frame) {
setConnected(true);
stompClient.subscribe('/topic/history', function (response) {
console.log(JSON.parse(response.body));
});
stompClient.subscribe('/topic/message', function (response) {
var message = JSON.parse(response.body);
if (firstCounterReceived == null) {
firstCounterReceived = message[0];
console.log(`Calling history endpoint with ${firstCounterReceived}`);
stompClient.send("/app/topic/history", {}, message[0]);
}
console.log(message);
});
});
}
Back:
#Controller
#EnableScheduling
public class ScheduledUpdatesOnTopic {
Map<Integer, String[]> history = new LinkedHashMap<>();
#Autowired
private SimpMessagingTemplate template;
private Integer count = 0;
#MessageMapping("/topic/history")
#SendTo("/topic/history")
public List<String[]> history(Integer to) {
return history.keySet()
.stream()
.filter(counter -> counter < to)
.map(counter -> history.get(counter))
.collect(Collectors.toList());
}
#Scheduled(fixedRate = 500)
public void sendMessage() {
DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Date date = new Date();
String[] str = {count.toString(), "name"+count,""+Math.round(Math.random()*100),"India"+Math.round(Math.random()*100),df.format(date)};
history.put(count, str);
this.template.convertAndSend("/topic/message",str);
++count;
}
}
In this sample, saved datas are stored in a map, be aware that it will consume some memory at some point.

Related

need signalR polling for asp.net mvc

Any kind soul can guide me how to use signalR on an existing mvc project to poll data in real time i'd be greatly appreciate.
example code:
[controller]
private ApplicationDbContext db = new ApplicationDbContext();
public PartialViewResult Chat(string people) // <---need to send real time data to partial
{
var model = new MessageVM()
{
sender = User.Identity.Name;,
messageList = db.messages.Where(x => x.receiver == people).ToList().Take(30)
};
return PartialView("_chat", model);
}
[view]
#Ajax.ActionLink(item.name, "Chat", new { people = item.name }, new AjaxOptions()
{ HttpMethod = "GET", UpdateTargetId = "divChat", InsertionMode = InsertionMode.Replace })
<div id="divChat"></div> // <---this area need real-time messages data from controller.
First create your signalr connection in js in client side. something like:
function signalrconnection() {
$.connection.hub.url = "http://localhost:54321/signalr";
chat = $.connection.myHub;
if (chat != undefined) {
$.connection.hub.start()
.done(function () {
chat.server.send("client", "Status\tasking for status");
chat = $.connection.myHub;
})
.fail(function () { NoLTConnectionAlert(); });
}
else {
///do something.
}
}
return chat;
}
Then add signalr call to your $(document).ready(function ()) in your js something like:
$(document).ready(function () {
chat = signalrconnection();
intervalstatus = setInterval(checkstatus, 1000);
// Create a function that the hub can call to broadcast messages.
chat.client.addMessage = function (name, message) {}
}
In your controller you should have a class for hub and method inside like:
public class MyHub : Hub
{
public void Send(string name, string message)
{
Clients.Caller.addMessage("parameter", reply);
}
}
Then again you should handle Clients.Caller.addMessage in you js to update <div id="divChat"></div>

Format JSON string before displaying it in MVC? (with datatables)

Got a nice JSON problem over here;
I don't know how to go about formatting date, timespan, decimals etc before sending it to the view in MVC. I'm using the datatables jQuery plugin, and my 'DataHandler' method returns a JSON object as source for the datatable.
When I was processing the data and filtering client-side it was pretty straightforward, but now I'm processing the data on the server-side.
Controller:
public JsonResult DataHandler(DTParameters param)
{
try
{
var dtsource = new List<spRegistrations_Result>();
using (entities dc = new entities())
{
dtsource = dc.spRegistrations().ToList();
}
List<String> columnSearch = new List<string>();
foreach (var col in param.Columns)
{
columnSearch.Add(col.Search.Value);
}
List<spRegistrations_Result> data = new ResultSet().GetResult(param.Search.Value, param.SortOrder, param.Start, param.Length, dtsource, columnSearch);
int count = new ResultSet().Count(param.Search.Value, dtsource, columnSearch);
DTResult<spRegistrations_Result> result = new DTResult<spRegistrations_Result>
{
draw = param.Draw,
data = data,
recordsFiltered = count,
recordsTotal = count
};
return Json(result);
}
catch (Exception ex)
{
return Json(new { error = ex.Message });
}
}
Table initialization:
var table = $('#myTable').DataTable({
responsive: true,
"serverSide": true,
"ajax": {
"type": "POST",
"url": '/Table/DataHandler',
"contentType": 'application/json; charset=utf-8',
'data': function (data) { return data = JSON.stringify(data); }
},
"drawCallback": function(settings){
$('.card').hide();
},
"paging": true,
"deferRender": true,
"columns": [
{ "data": "RegId" },
{ "data": "PresenceDate" }, etc...
Model:
public int RegId { get; set; }
public System.TimeSpan StartTime { get; set; }
public System.TimeSpan EndTime { get; set; }
public System.DateTime PresenceDate { get; set; }
This is how it looks when the table is displayed
As you can see, the date is not very nicely formatted, and is the reason that I want to format the data before displaying it. Same goes for a couple of TimeSpan objects etc that I eventually want to show in the table.
I'm still pretty new to ajax, and don't know how to go about this the easiest way. Thanks for any input !
You could use the columns.render property to define the content of the table cell, using a custom js function to format the date. Something like:
...
"render": function ( data, type, full, meta ) {
var date = new Date(parseInt(data.substr(6), 0));
return ISODateString(date);
}
The function to format the date dd/mmm/yyyy:
function ISODateString(d) {
function pad(n) { return n < 10 ? '0' + n : n }
return pad(d.getDate()) + '/' + pad(d.getMonth() + 1) + '/' + d.getFullYear();
}

Setting data in viewModel knockoutjs from html5 websocket

I am trying to create knockout.js component that is getting data from HTML5 Websocket. Websocket code is in separate script e.g. util.js. I am able to connect and get data from socket, but dont know how correctly to set corresponding property in component`s ViewModel.
Websocket - util.js:
var options = {
server: '127.0.0.1',
port: '12345'
};
var socket, loadedFlag;
var timeout = 2000;
var clearTimer = -1;
var data = {};
function handleErrors(sError, sURL, iLine)
{
return true;
};
function getSocketState()
{
return (socket != null) ? socket.readyState : 0;
}
function onMessage(e)
{
data=$.parseJSON(e.data);
// ???? Is it possible to have here something like
// ???? viewModel.getDataWS1(data);
}
function onError()
{
clearInterval(clearTimer);
socket.onclose = function () {
loadedFlag = false;
};
clearTimer = setInterval("connectWebSocket()", timeout);
}
function onClose()
{
loadedFlag = false;
clearInterval(clearTimer);
clearTimer = setInterval("connectWebSocket()", timeout);
}
function onOpen()
{
clearInterval(clearTimer);
console.log("open" + getSocketState());
}
function connectWebSocket()
{
if ("WebSocket" in window)
{
if (getSocketState() === 1)
{
socket.onopen = onOpen;
clearInterval(clearTimer);
console.log(getSocketState());
}
else
{
try
{
host = "ws://" + options.server + ":" + options.port;
socket = new WebSocket(host);
socket.onopen = onOpen;
socket.onmessage = function (e) {
onMessage(e);
};
socket.onerror = onError;
socket.onclose = onClose;
}
catch (exeption)
{
console.log(exeption);
}
}
}
}
Component (productDisplay.js) - creating so that is can be used on multiple pages:
define([
'jquery',
'app/models/productDisplayModel',
'knockout',
'mapping',
'socket'
],
function ($, model, ko, mapping) {
ko.components.register('product', {
viewModel: {require: 'app/models/productModel'},
template: {require: 'text!app/views/product.html'}
});
});
Product ViewModel (productModel.js) - where I struggle to set viewModel property to data from websocket:
var viewModel = {};
define(['knockout', 'mapping', 'jquery'], function (ko, mapping, $) {
function Product(name, rating) {
this.name = name;
this.userRating = ko.observable(rating || null);
}
function MyViewModel() {
this.products = ko.observableArray(); // Start empty
}
MyViewModel.prototype.getDataWS1 = function () {
//Websocket has not connected and returned data yet, so data object is empty
// ???? Is there anyway I can add something like promise so that the value is set once socket is connected?
this.products(data);
};
// apply binding on page load
$(document).ready(function () {
connectToServer1();
viewModel = new MyViewModel();
ko.applyBindings(viewModel);
viewModel.getDataWS1();
});
});
Thank you for any ideas.
You can update an observable when you get a message in the following manner:
util.js
function onMessage(e) {
var productData = $.parseJSON(e.data);
viewModel.addNewProduct(productData);
}
productModel.js
function Product(name, rating) {
this.name = name;
this.userRating = ko.observable(rating || null);
}
function MyViewModel() {
this.products = ko.observableArray(); // Start empty
}
MyViewModel.prototype.addNewProduct(product) {
var newProduct = new Product(product.name, product.rating);
this.products.push(newProduct);
}
Basically the idea is that when you get a message (in onMessage function), you will parse the data and call a function in your viewmodel to add the message data to the viewmodel properties (observables, observableArrays, etc.)

MultipartMemoryStreamProvider and reading user data from MultiPart/Form Data

I have a file and user data that is being posted from Multipart/form data to a post method in my apicontroller class.
I am able to read the file without any problems but unable to read user data.
I tried couple of things like using model binding, passing the individual fields as a method parameter in the post method but i get: No MediaTypeFormatter is available to read an object of type 'FormDataCollection' from content with media type 'multipart/form-data'.
var provider = await Request.Content.ReadAsMultipartAsync(new MultipartMemoryStreamProvider());
foreach (var item in provider.Contents)
{
var fieldName = item.Headers.ContentDisposition.Name.Trim('"');
if (item.Headers.ContentDisposition.FileName == null)
{
var data = await item.ReadAsStringAsync();
if (fieldname == "name")
{
Name = data;
}
else
{
fileContents = await item.ReadAsByteArrayAsync();
}
}
}
Thanks.
It seems to me the OP, was really close. This is some code that tries to clearly show how to get the form variables, as well as the file upload data.
First the ApiController:
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http;
namespace WebApplication1.Controllers
{
public class FormAndFileDataController : ApiController
{
private class FormItem
{
public FormItem() { }
public string name { get; set; }
public byte[] data { get; set; }
public string fileName { get; set; }
public string mediaType { get; set; }
public string value { get { return Encoding.Default.GetString(data); } }
public bool isAFileUpload { get { return !String.IsNullOrEmpty(fileName); } }
}
/// <summary>
/// An ApiController to access an AJAX form post.
/// </summary>
/// <remarks>
///
/// </remarks>
/// <returns></returns>
public async Task<HttpResponseMessage> Post()
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
var provider = new MultipartMemoryStreamProvider();
await Request.Content.ReadAsMultipartAsync(provider);
var formItems = new List<FormItem>();
// Scan the Multiple Parts
foreach (HttpContent contentPart in provider.Contents)
{
var formItem = new FormItem();
var contentDisposition = contentPart.Headers.ContentDisposition;
formItem.name = contentDisposition.Name.Trim('"');
formItem.data = await contentPart.ReadAsByteArrayAsync();
formItem.fileName = String.IsNullOrEmpty(contentDisposition.FileName) ? "" : contentDisposition.FileName.Trim('"');
formItem.mediaType = contentPart.Headers.ContentType == null ? "" : String.IsNullOrEmpty(contentPart.Headers.ContentType.MediaType) ? "" : contentPart.Headers.ContentType.MediaType;
formItems.Add(formItem);
}
// We now have a list of all the distinct items from the *form post*.
// We can now decide to do something with the items.
foreach (FormItem formItemToProcess in formItems)
{
if (formItemToProcess.isAFileUpload)
{
// This is a file. Do something with the file. Write it to disk, store in a database. Whatever you want to do.
// The name the client used to identify the *file* input element of the *form post* is stored in formItem.name.
// The *suggested* file name from the client is stored in formItemToProcess.fileName
// The media type (MimeType) of file (as far as the client knew) if available, is stored in formItemToProcess.mediaType
// The file data is stored in the byte[] formItemToProcess.data
}
else
{
// This is a form variable. Do something with the form variable. Update a DB table, whatever you want to do.
// The name the client used to identify the input element of the *form post* is stored in formItem.name.
// The value the client input element is stored in formItem.value.
}
}
return Request.CreateResponse(HttpStatusCode.OK);
}
}
}
and the MVC View to test it:
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
<script type="text/javascript">
var hiddenForm, hiddenFile;
function initialize() {
// Use a hidden file element so we can control the UI
// of the file selection interface. The built in browser
// UI is not localizable to different languages.
hiddenFile = document.createElement("input");
hiddenFile.setAttribute("type", "file");
hiddenFile.setAttribute("style", "display: none;");
// We don't need the form really, but it makes it easy to
// reset the selection.
hiddenForm = document.createElement("form");
hiddenForm.appendChild(hiddenFile);
hiddenFile.onchange = function () {
var elementToUpdate = document.getElementById("fileNameToUpload");
var filesToUpload = hiddenFile.files;
var fileToUpload = filesToUpload[0];
elementToUpdate.value = fileToUpload.name;
}
document.body.appendChild(hiddenForm);
}
function chooseFile() {
hiddenFile.click();
}
function clearFile() {
var elementToUpdate = document.getElementById("fileNameToUpload");
elementToUpdate.value = "";
hiddenForm.reset();
}
function testAJAXUpload() {
// We are going to use the FormData object and jQuery
// to do our post test.
var formToPost = new FormData();
var formVariableNameElement = document.getElementById("variableNameToUpload");
var formVariableValueElement = document.getElementById("variableValueToUpload");
var formVariableName = formVariableNameElement.value || "formVar1";
var formVariableValue = formVariableValueElement.value || "Form Value 1";
var filesToUpload = hiddenFile.files;
var fileToUpload = filesToUpload[0];
formToPost.append(formVariableName,formVariableValue)
formToPost.append("fileUpload", fileToUpload);
// Call the Server.
$.ajax({
url: '#Url.HttpRouteUrl("DefaultApi", new { controller = "FormAndFileData" })',
type: 'POST',
contentType: false,
processData: false,
data: formToPost,
error: function (jqXHR, textStatus, errorThrown) {
alert("Failed: [" + textStatus + "]");
},
success: function (data, textStatus, jqXHR) {
alert("Success.");
}
});
}
</script>
</head>
<body>
<input id="variableNameToUpload" type="text" placeholder="Form Variable: Name" />
<br />
<input id="variableValueToUpload" type="text" placeholder="Form Variable: Value" />
<br />
<input id="fileNameToUpload" type="text" placeholder="Select A File..." /><button onclick="chooseFile()">Select File</button><button onclick="clearFile()">Reset</button>
<br />
<button onclick="testAJAXUpload()">Test AJAX Upload</button>
<script type="text/javascript">
initialize();
</script>
</body>
</html>
I had considered adding this to your other post per your comment, but (as you also decided), it is a separate question.
public async Task<HttpResponseMessage> Post()
{
if (!Request.Content.IsMimeMultipartContent())
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
try
{
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = await Request.Content.ReadAsMultipartAsync(new MultipartFormDataStreamProvider(root));
// file data
foreach (MultipartFileData file in provider.FileData)
{
using (var ms = new MemoryStream())
{
var diskFile = new FileStream(file.LocalFileName, FileMode.Open);
await diskFile.CopyToAsync(ms);
var byteArray = ms.ToArray();
}
}
// form data
foreach (var key in provider.FormData.AllKeys)
{
var values = provider.FormData.GetValues(key);
if (values != null)
{
foreach (var value in values)
{
Console.WriteLine(value);
}
}
}
return Request.CreateResponse(HttpStatusCode.Created);
}
catch (Exception ex)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
}
}

Is there a free lib accessing to html5 database SQLite?

This lib should be easy to use to access to html5 local storage (sqlite). Like define tables, add/update/delete entity, query entities from db.
Likely with the lib I can write code like below:
//code
var db=new MyDataBase();
var users = db.Users.Tolist();
var admin = db.Users.FirstOrDefault(u=>u.Name=="admin");
admin.Password="new password";
db.UpdateUser(admin);
I am using a phonegap lib that contains the DAL part for html5 sqlite. It's developed by novasoftware. Take a look at it's sample code here, I also paste part of it below:
/// <reference path="scripts/novas/data/ArrayExtensions.js" />
/// <reference path="scripts/novas/data/nova.data.Repository.js" />
/// <reference path="scripts/novas/data/nova.data.DbContext.js" />
/// <reference path="scripts/novas/data/nova.data.Entity.js" />
/// <reference path="scripts/novas/data/nova.data.Queryable.js" />
// define dbContext & entities------------------------------------
var DemoDataContext = function () {
nova.data.DbContext.call(this, "Demo", "1.0", "Demo DB", 1000000);
this.users = new nova.data.Repository(this, User, "users");
this.roles = new nova.data.Repository(this, Role, "roles");
};
DemoDataContext.prototype = new nova.data.DbContext();
DemoDataContext.constructor = DemoDataContext;
var User = function () {
nova.data.Entity.call(this);
this.name = "";
this.password = "";
this.birthYear = 1980;
this.createdDate = new Date();
this.deleted = false;
};
User.prototype = new nova.data.Entity();
User.constructor = User;
var Role = function () {
nova.data.Entity.call(this);
this.name = "";
this.createdDate = new Date();
};
Role.prototype = new nova.data.Entity();
Role.constructor = Role;
// end define dbContext & entities------------------------------------
// service methods----------------------------------------------------
function getAllUsers(callback) {
new DemoDataContext().users.toArray(function (users) {
alert(users.length);
callback(users);
});
}
function getUserByName(name, callback) {
new DemoDataContext().users.where("name='" + name + "'").toArray(function (users) {
callback(users.firstOrDefault());
});
}
function addRole(roleName, callback) {
var role = new Role();
role.name = roleName;
var db = new DemoDataContext();
db.roles.add(role);
db.saveChanges(callback);
}
function updateUserPassword(username, password, callback) {
getUserByName(username, function (user) {
if (user == null) {
throw "no user found.";
}
user.password = password;
var db = new DemoDataContext();
db.users.update(user);
db.saveChanges(callback);
});
}
function deleteUserByName(name, callback) {
getUserByName(name, function (user) {
if (user == null) {
throw "no user found.";
}
var db = new DemoDataContext();
db.users.remove(user);
db.saveChanges(callback);
});
}
// end service methods----------------------------------------------------