SignalR changing hub subscription - javascript

I have a simple app, which displays a list of available signalR hubs. A user selects a hub and it connects to it, this subscribes an event to add messages to a table on the page. The user can then send messaged to that hub which will also fire the subscription adding that message to the table. This all works great.
Now if the user selects another hub, the app connects and sets up a new subscription, however the original subscription still fires causing duplicate messages to be added to the table. Each time the hub is changed further subscriptions get added causing one send to result in many messages in the table.
I have tried disconnecting the hub, disposing the hub and trying to remove the subscription with hubProxy.off(eventName), but nothing seems to work, other than a page reload.
Here is the code I have just added the onHub changed function as this is where everything is happening.
Any ideas appreciated. :)
function HubViewModel() {
var self = this;
self.hubConnection = '';
self.hub = '';
$.getScript("../signalR/hubs");
self.hubs = ko.observableArray();
self.selectedHub = ko.observable();
self.messageText = ko.observable();
self.messageCollection = ko.observableArray();
self.hubChanged = function () {
// Setup hub connection.
$.connection.hub.url = "../signalR";
self.hubConnection = $.hubConnection();
// Get the selected hub name.
var selectedHubName;
_.each(self.hubs(), function(item) {
if (item.hubId == self.selectedHub()) {
selectedHubName = item.hubName;
}
});
// Check for a selected connection
if (self.selectedHub()) {
// Create proxy.
self.hub = self.hubConnection.createHubProxy(selectedHubName);
// Remove any existing listener(s).
self.hub.off('addNewMessageToPage');
// Setup listener.
self.hub.On('addNewMessageToPage', function (sender, message) {
self.messageCollection().push({ hubName: selectedHubName, name: selectedHubName, message: message, dateTime: new Date().toLocaleString() });
$('#hubMessageGrid').dxDataGrid('instance').refresh();
});
// start connection.
self.hubConnection.start()
.done(function() {
toastr.success('hub connected');
$('#sendMessageButton').click(function() {
self.hub.invoke('sendAll', 'hub management page', self.messageText());
self.messageText('');
});
})
.fail(function(error) {
toastr.error('hub connection ' + error);
});
}
};

You can to disconnect the hub first by calling the self.hub.stop(); function

You need to pass the exact same handler instance when unsubscribing. Passing a different instance (even if the function body is the same) will not remove the handler.
See https://learn.microsoft.com/en-us/javascript/api/#microsoft/signalr/hubconnection?view=signalr-js-latest#off-string---args--any-------void-

Related

Does BroadcastChannel work on the same page?

My web page has
var bc = new BroadcastChannel('Consumer');
bc.onmessage = function(event) {
alert("a");
}
bc.postMessage("hello");
It broadcasts a message, and the page is also required to receive the same message.
However it doesn't work. Did I miss anything?
You can create two instances of BroadcastChannel on your page. One can act as a broadcaster for messages, the other one for receiving messages.
var broadcaster = new BroadcastChannel('Consumer');
var messageReceiver= new BroadcastChannel('Consumer');
messageReceiver.onmessage = function(event) {
alert(event.data);
}
broadcaster.postMessage("hello");
See this in action: https://jsfiddle.net/h56d3y27/
Or wrapped in a reusable class:
(note: class is not supported by all browsers. See : https://caniuse.com/#search=class for browser compatibility)
class AllInclusiveBroadcaster {
constructor(listener, channelName) {
if (!channelName) channelName = "channel";
this.broadcaster = new BroadcastChannel(channelName);
this.messageReceiver = new BroadcastChannel(channelName);
this.messageReceiver.onmessage = (event) => {
listener(event.data);
}
}
postmessage(data) {
this.broadcaster.postMessage(data);
}
}
var broadcaster = new AllInclusiveBroadcaster((data) => alert(data));
broadcaster.postmessage("Hello BroadcastChannel");
See this also in action a JSFiddle
You could dispatch an event (call it what you like) to, say, document, with the same data ... then have a single handler that listens for BroadcastChannel messages and to the event name you created above
in the following, the code creates and listens for fakeBroadcastMessage
created a function to send both the bc message and the "local" message
var bc = new BroadcastChannel('Consumer');
function handleBroadcastMessage(event) {
// do things here
}
bc.addEventHandler('message', handleBroadcastMessage);
document.addEventListener('fakeBroadcastMessage', handleBroadcastMessage);
function sendMessage(data) {
bc.postMessage(data);
var ev = new Event('fakeBroadcastMessage');
ev.data = data;
document.dispatchEvent(ev);
}
sendMessage('hello');

SAPUI5: How to show a loading dialog box while pushing data to OData Service?

I am building a new SAPUI5 application without pursuing any template approach. What I'm building is just a little form with two fields and a button. AHHhh... "the button".
What about the button? The button has the following code:
<Button text="OK" width="100%" id="__button1" press="insertIntoOData"/>
With that, I expect that when I press the button, the insertIntoOData function is called. And guess what!? It is!
Fantastic!
But the problem is that in the insertIntoOData I want it to show a dialog (which is built with a fragment - check this link) while the OData model handles the insertion of a record. Unfortunately, I haven't managed to get the dialog to be shown. It looks like the insertIntoOData function is called synchronously, and won't show the dialog until the function is done.
When the OData Model finish handling the insertion, a response is processed and the dialog is shown only for a moment because, as you may notice in the following code of insertIntoOData, the navigation is redirected to the master (main) page.
insertIntoOData: function(evt) {
/*
* to prevent namespace issues, reserve 'this' into 'that',
* so the ajax will know who to call inside its scope
*/
var that = this;
//declare the dialog
if (!that._dialog) {
that._dialog = sap.ui.xmlfragment("valac.view.requestloading", null);
that.getView().addDependent(that._dialog);
}
// open dialog
jQuery.sap.syncStyleClass("sapUiSizeCompact", that.getView(), that._dialog);
that._dialog.open();
// get the csrf token from the service url
var csrfToken = this.getCSRFToken("/valacDestination/sap/c4c/odata/v1/c4codata/ValacObjectCollection");
// get the values from the 'form'
var name_var = this.byId("tSubjectInput").getValue();
var description_var = this.byId("tDescriptionArea").getValue();
// create the entry that will be sent with the request
var oEntry = {};
// the description list
oEntry.requestDescription = [];
// the description item that goes inside the list
var entryOfRequestDescription = {};
entryOfRequestDescription.Text = description_var;
oEntry.requestDescription.push(entryOfRequestDescription);
// name is a complex object that needs to be built. Content and language.
oEntry.Name = {};
oEntry.Name.content = name_var;
oEntry.Name.languageCode = "EN";
// fetch the model schema
var oModel = new sap.ui.model.odata.ODataModel("/valacDestination/sap/c4c/odata/v1/c4codata/");
sap.ui.getCore().setModel(oModel);
/* create the entry into the model schema via ajax
* return to the master page if there's a success response
* put a message on the master page.
*/
oModel.create('/ValacObjectCollection', oEntry, null, function(response){
that._dialog.close();
sap.ui.core.UIComponent.getRouterFor(that).navTo("master");
sap.m.MessageToast.show("Object Persisted!", {
duration: 30000000
});
},function(){
that._dialog.close();
sap.m.MessageToast.show("ERROR!", {
duration: 30000000
});
});
}
My question is: How can I show the dialog before the insertIntoOData ends or calls the oModel.create function?
when you enter the insertIntoOData method.
before calling the service set
that._dialog.setBusy(true);
after getting service responce (sucess or error don't matter )set as
that._dialog.setBusy(false);
You can do global busy indicator or component busy indicator, show before oModel.create and hide into the success or error function:
sap.ui.core.BusyIndicator.show(0); <- Parameter is delay time.
sap.ui.core.BusyIndicator.hide(); <- hide
link docs: https://sapui5.hana.ondemand.com/1.44.18/explored.html#/sample/sap.ui.core.sample.BusyIndicator/preview
Only the Dialog show busy.
that._dialog.setBusy(true); <- Show
that._dialog.setBusy(false); <- hide
I've managed to show the busyIndicator.
I rebuilt the insertIntoOData function to be as the following:
insertServiceRequestIntoOData: function(evt) {
var that = this;
var token = null;
var serviceUrl = "URL";
var name_var = this.byId("tSubjectInput").getValue();
var description_var = this.byId("tDescriptionArea").getValue();
var oEntry = {};
/*
* oEntry building process omitted
*/
this.oModel = new sap.ui.model.odata.ODataModel(serviceUrl);
sap.ui.getCore().setModel(this.oModel);
/*
* This is where the magic happens:
* 1) ajax async request to get the token and to show the busy indicator on the screen
* 2) when it's over, make a post to the oData service with the data.
* 3) when it's over, hide the busy indicator and go to the correct page (success or error).
*/
$.ajax({
url: serviceUrl + "/MyCollection";
type: "GET",
async: true,
beforeSend: function(xhr) {
sap.ui.core.BusyIndicator.show(0);
xhr.setRequestHeader("X-CSRF-Token", "Fetch");
},
complete: function(xhr) {
token = xhr.getResponseHeader("X-CSRF-Token");
// begin of odata send
that.oModel.create("/MyCollection", oEntry, null, function(response){
sap.ui.core.BusyIndicator.hide();
sap.ui.core.UIComponent.getRouterFor(that).navTo("insertConfirmation");
that.clearInputs();
},function(){
sap.ui.core.BusyIndicator.hide();
sap.ui.core.UIComponent.getRouterFor(that).navTo("insertErrorConfirmation");
that.clearInputs();
});
}
});
}

Socket.io client side emits too many times to server side

Please help me figure out why my socket.io app fires too many emit messages.
Yes, I have spent weeks on the socket.io docs and relevant stackO questions, experimented with the nesting of my functions and everything but I am just totally stuck.
This is supposed to allow people in a 'room' to vote on an issue. Each client gets one vote (yea, nay, or abstain). The problem is when someone votes, it cannot just add one vote it adds a vote for the number of people in the socket. For example, if three people are in a room and the first vote is cast for 'yea' it registers 3x 'yea' votes. I cant get a good count ever!
client site:
var socket = io.connect('http://localhost');
const url = window.location.href;
let voteName = url.substring(url.lastIndexOf("/") + 1);
let notVoted = true;
$('.voteName').html(voteName.replace(/_/g, ' '));
function getRoom(){
return voteName;
}
socket.on('connect', () => {
// Connected, let's sign-up for to receive messages for this room
socket.emit('room', getRoom());
console.dir(socket.id);
});
function yea () {
if(notVoted){
socket.emit('yea',voteName);
console.log(voteName)
notVoted = false;
}
}
function nay () {
if(notVoted){
io.emit('nay',voteName);
notVoted = false;
}
}
function abs () {
if(notVoted){
io.emit('abs',voteName);
notVoted = false;
}
}
Server Side:
app.post('/votePick', (req, res) => {
let voteName = req.body.votePick;
res.send(voteName);
app.get('/'+voteName,(req,res)=>{
res.sendFile(__dirname + '/canvas.html');
});
var nsp = io.of('/');
io.sockets.on('connection', (socket) => {
socket.on('room', (voteName) => {
//TODO check if room exists
socket.join(voteName);
if(!db[voteName]) {
db[voteName]={'yea':0,'nay':0,'abs':0,'cnctCount':1};
io.emit('update',db[voteName]);
} else {
io.emit('update',db[voteName]);
}
});
socket.on('yea', (voteName)=>{
db[voteName].yea++;
io.emit('update',db[voteName]);
console.log(db[voteName].cnctCount);
});
socket.on('nay', (voteName)=>{
db[voteName].nay++;
io.emit('update',db[voteName]);
console.log(db[voteName]);
});
socket.on('abs', (voteName)=>{
db[voteName].abs++;
io.emit('update',db[voteName]);
console.log(db[voteName]);
});
});
});
my full repo is here:
https://github.com/Bokeefe/sockerPractice
You should do user validation. No one can prevent code on the clientside to be changed, so you always need to check on the serverside. For example you could store the socket ids that have already voted:
const voted = new Set;
then inside the socket event handlers:
if(voted.has(socket)) return;
voted.add(socket);
//vote
this should also solve the problem od votes counted twice.

SOCKJS reply upon SOCKET_CREATED event

I am connection through Vertx eventbus (SockJS to my Java based backend. Everything work fine, However, I cannot find a way to send an initial message.
Is there a way to send back data when SockJS bridge receives SOCKET_CREATED to the sockjs browser side?
Thank you.
Taken from their documentation:
if (event.type() == SOCKET_CREATED || event.type() == SOCKET_CLOSED)
{
//...
vertx.eventBus().publish("fromServer", jmsg.toJSONString());
}
Your event instantiation may be different, but that would be how you check for the specific event and run code after it has occurred
You can check this code , where I'm using EventBus.
Here is the Reference code
this.eventBus = new EventBus(this.URL);
this.eventBus.onopen = (e) => {
this._opened = true;
console.log("open connection");
this.callHandlers('open', e);
this.eventBus.publish("http://localhost:8082", "USER LOGIN INFO");
this.eventBus.registerHandler("http://localhost:8081/pushNotification", function (error, message) {
console.log(message.body);
//$("<div title='Basic dialog'>Test message</div>").dialog();
});
}
this.eventBus.onclose = (e) => {
this.callHandlers('close', e);
}
}

Call one app's view from another in Marionette

I have an IdentificationView that has to call a method of another view in different app:
var NameAndIdentificationView = Application.Views.ItemView.extend({
loadMailingAddressView: function() {
//call to mailing address view method setAddress() from here
}
});
Now my MailingAddressView is something like this:
var MailingAddressView= Application.Views.ItemView.extend({
setAddress: function(address) {
if (address != null) {
// this.autoCompleteBox.data('inputData') = address;
ProWeb.EXTRACTED_ADDRESS = address;
this.model.set('mailingAddress', address);
}
}
});
I would like to know what is the way to call a method of one view from another. Please provide suggestions.
EDIT:
This is how my application has been setup:
First App is called.
Then the Controller is called.
From within the Controller, View is called and intialized.
So from within my NameIdentificationView, I'm calling the MailingAddress app that in turn controls all the process of calling the controller and it's view.
You can do it many ways using event channel,triggering and listening events on shared model, the simpler way of doing this is using Bsckbone events.
Trigger a event in caller
loadMailingAddressView: function () {
//call to mailing address view method setAddress() from here
Backbonw.trigger('setAddress')
})
and listen in callee's initalize
var MailingAddressView = Application.Views.ItemView.extend({
initialize: function() {
Backbone.on('setAddress', this.setAddress)
},
setAddress: function(address) {
if (address != null) {
// this.autoCompleteBox.data('inputData') = address;
ProWeb.EXTRACTED_ADDRESS = address;
this.model.set('mailingAddress', address);
}
},
});
make sure callee View is initialized by the time you trigger event

Categories

Resources