How to integrate websocket with emberjs? - javascript

I'm learning and building emberjs app with rails.
In this app, I want the data to be pushed rather than polled to the client app.
For.e.g. the following snippet at http://awardwinningfjords.com/2011/12/27/emberjs-collections.html
// Setup a global namespace for our code.
Twitter = Em.Application.create({
// When everything is loaded.
ready: function() {
// Start polling Twitter
setInterval(function() {
Twitter.searchResults.refresh();
}, 2000);
// The default search is empty, let's find some cats.
Twitter.searchResults.set("query", "cats");
// Call the superclass's `ready` method.
this._super();
}
});
It polls twitter API, but my question is how to make an EmberJS app that uses a WebSocket connection to update its state?

You have to implement a DS.Adapter that understands how to handle WebSockets. Here is an simple example:
var SOCKET = 'ws://localhost:9090/some-websocket';
var ID = 'uuid';
var FIND = 'find';
var FIND_MANY = 'findMany';
var FIND_QUERY = 'findQuery';
var FIND_ALL = 'findAll';
/**
* Implementation of WebSocket for DS.Store
*/
App.Store = DS.Store.extend({
revision: 4,
adapter: DS.Adapter.create({
socket: undefined,
requests: undefined,
send: function(action, type, data, result) {
/* Specific to your web socket server side implementation */
var request = {
"uuid": generateUuid(),
"action": action,
"type": type.toString().substr(1),
"data": data
};
this.socket.send(JSON.stringify(request));
/* So I have access to the original request upon a response from the server */
this.get('requests')[request.uuid] = request;
return request;
},
find: function (store, type, id) {
this.send(FIND, type, id);
},
findMany: function (store, type, ids, query) {
this.send(FIND_MANY, type, ids);
},
findQuery: function (store, type, query, modelArray) {
this.send(FIND_QUERY, type, query, modelArray).modelArray = modelArray;
},
findAll: function (store, type) {
this.send(FIND_ALL, type);
},
/* Also implement:
* createRecord & createRecords
* updateRecord & updateRecords
* deleteRecord & deleteRecords
* commit & rollback
*/
init: function () {
var context = this;
this.set('requests', {});
var ws = new WebSocket(SOCKET);
ws.onopen = function () {
};
ws.onmessage = function(event) {
var response = JSON.parse(event.data);
var request = context.get('requests')[response.uuid];
switch (request.action) {
case FIND:
App.store.load(type, response.data[0]);
break;
case FIND_MANY:
App.store.loadMany(type, response.data);
break;
case FIND_QUERY:
request.modelArray.load(response.data);
break;
case FIND_ALL:
App.store.loadMany(type, response.data);
break;
default:
throw('Unknown Request: ' + request.action);
}
/* Cleanup */
context.get('requests')[response.uuid] = undefined;
};
ws.onclose = function () {
};
this.set('socket', ws);
}
});
});

I actually was playing around with the code from that article a few days ago. Keep the handle bar template the same, and use the following code. Obviously, this all depends on what JSON you're passing through the socket. The following code is tried and tested with ntwitter for node.
Twitter = Em.Application.create({
ready: function() {
var socket = io.connect();
socket.on('message', function(json) {
Twitter.searchResults.addTweet(Twitter.Tweet.create(JSON.parse(json)));
});
this._super();
}
});
//Model
Twitter.Tweet = Em.Object.extend();
//Collection
Twitter.searchResults = Em.ArrayController.create({
content: [],
_idCache: {},
addTweet: function(tweet) {
var id = tweet.get("id");
if (typeof this._idCache[id] === "undefined") {
this.pushObject(tweet);
this._idCache[id] = tweet.id;
}
}
});

With websockets you are observing for socket events. When an event is triggered you handle that event (if appropriate) and then set your values.
Looking at your code you would you would observe Socket.onmessage. If the message contains what you are looking for then call refresh.

Related

Managing multiple SignalR connections in a single page

I'm experiencing intermittent signalr connection problems
sometimes it fails, sometimes it doesn't...
Here is the setup...
I have a list of orders, each order has a unique signalr connection. Currently there are 230 orders on a single page. The purpose of having a signalr connection is so users can see any real time updates on each order (who is viewing, editing, etc). I've decided to have a separate connection for each order so that I don't have to manage the order that is currently being viewed, edited, etc. So far, for the orders that have successfully connected, the updates are correct and smooth.
Here is my list with a sample of another user viewing an order (a photo of that user is being shown)
Here is my code that connects to the signalr hubs
crimeassure.factory('hubProxy', ['$rootScope', function ($rootScope) {
function hubProxyFactory(hubName) {
var _hubConnection = $.hubConnection();
_hubConnection.logging = true;
var _hubProxy = _hubConnection.createHubProxy(hubName);
return {
on: function (eventName, callback, failCallback) {
_hubProxy.on(eventName, function (result) {
$rootScope.$apply(function () {
if (callback) {
callback(result);
}
});
})
},
invoke: function (methodName, data) {
_hubProxy.invoke(methodName, data)
.done(function (result) {
//$rootScope.$apply(function () {
// if (callback) {
// callback(result);
// }
//});
});
},
start: function (successCallback, failCallback) {
_hubConnection.start({ transport: 'webSockets' }).done(successCallback).fail(failCallback);
},
hubConnection: _hubConnection,
};
};
return hubProxyFactory;
}]);
crimeassure.directive('componentLiveUpdates', function () {
return {
restrict: 'E',
scope: {
componentId: '=',
},
templateUrl: '/scripts/templates/directive-templates/component-live-updates.html',
controllerAs: 'vm',
bindToController: true,
controller: ["$scope", "$rootScope", "appData", "hubProxy",
function componentLiveUpdates($scope, $rootScope, appData, hubProxy) {
var vm = (this);
var user = appData.getCurrentUser();
vm.componentActivity = [];
var reQueueHub = hubProxy('researcherExpressQueueHub');
var componentActivityChanged = function (component) {
if (component.ActivityValue === 'ComponentModalClose') {
var idx = vm.componentActivity.indexOf(component);
vm.componentActivity.splice(idx, 1);
}
if (component.ActivityValue === 'ComponentModalOpen') {
vm.componentActivity.push(component);
}
}
var successCallback = function () {
console.log('connected to signalR, connection ID =' + reQueueHub.hubConnection.id + '--' + vm.componentId);
reQueueHub.invoke('joinGroup', vm.componentId);
$rootScope.reQueueHubs.push({
ComponentId: vm.componentId,
Hub: reQueueHub
});
}
var failCallback = function (e) {
console.log('Error connecting to signalR = ' + vm.componentId);
console.log(e);
//startHubConnection();
}
var startHubConnection = function () {
reQueueHub.start(successCallback, failCallback);
}
var initialize = function () {
reQueueHub.on('updateComponentActivity', componentActivityChanged);
startHubConnection();
}
initialize();
}],
}
});
and here is my hub class
public class ResearcherExpressQueueHub : Hub
{
public void UpdateComponentActivity(ComponentItem item)
{
Clients.Group(item.ComponentId.ToString()).updateComponentActivity(item);
}
public void ComponentModalOpen(ComponentItem item)
{
item.Activity = ComponentActivity.ComponentModalOpen;
Clients.Group(item.ComponentId.ToString()).updateComponentActivity(item);
}
public void ComponentModalClose(ComponentItem item)
{
item.Activity = ComponentActivity.ComponentModalClose;
Clients.Group(item.ComponentId.ToString()).updateComponentActivity(item);
}
public Task JoinGroup(string componentId)
{
return Groups.Add(Context.ConnectionId, componentId);
}
public Task LeaveGroup(string componentId)
{
return Groups.Remove(Context.ConnectionId, componentId);
}
}
so my questions are,
Why am i experiencing a disconnect "WebSocket is closed before the connection is established"
Is my approach the best way to approach this type of requirement?
Use grouping mechanisme of signalr and NOT create multiple connections for your usecase!
There are limitations from IIS and also from browsers. Some browser have a limit of 4 or 5 paralell connections. You can test it by yourself by opening multiple different browsers.
Details about grouping:
Working with groups in signalr is really simple. Details you will find here: https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/working-with-groups

Laravel Redis display sock data with vue

I built a simple event class with laravel, which I fire if one sents a notifications the event class looks like this:
class InquirySent extends Event implements ShouldBroadcast
{
use SerializesModels;
public $inquiries;
public function __construct($inquiries)
{
$this->inquiries = $inquiries;
}
public function broadcastOn()
{
return ['inquiry-sent-channel'];
}
}
I fire the event (create a new instance) like this:
# fire inquiry notification event
event(new InquirySent(
$user->notifications()->where('type', InvoiceInquiry::class)->count()
));
I set up my node server with this script:
var server = require('http').Server();
var io = require('socket.io')(server);
// class declaration
var Redis = require('ioredis');
/**
* Redis UserSignedUp Channel
*/
var redisSignedUp = new Redis();
redisSignedUp.subscribe('signed-up-channel');
/**
* Redis InquirySent Channel
*/
var redisInquirySent = new Redis();
redisInquirySent.subscribe('inquiry-sent-channel');
redisInquirySent.on('message', function(channel, message) {
message = JSON.parse(message);
console.log(message.data.inquiries);
io.emit(channel + ':' + message.event, message.data.inquiries);
});
// run server on port X
server.listen(3000);
And in my event.js I bind it like this:
var socket = io('http://192.168.3.115:3000');
new Vue({
el: '#app',
data: {
inquiries: [],
},
ready: function() {
// InquirySent event
socket.on('inquiry-sent-channel:App\\Events\\InquirySent', function(data) {
console.log(data);
this.inquiries.push(data);
}.bind(this));
}
});
The console (command line) returns the correct value in my case: 69
but if I try to put it in my view with vue.js it does not display anything, nor do I get any errors in the browser console:
<li v-for="inquiry in inquiries">#{{ inquiry }}</li>

Get access to closure dojo object when callback scope was entered

I have a dojo object, I want to do a retry connection to a web socket. However, the connection to the web socket is triggered by a callback function. I tried subscribing to a topic to allow reconnect without using this. However, if the class has two or more instance, it gets all the subscribed message on all instance of MyClass. Is there a way to only let the original instance that fail to connect to get the subscribed message?
// Dojo class
dojo.declare("MyClass", null, {
constructor: function() {
dojo.subscribe("WebSocketConnect", this, function() {
this.DoConnect();
});
},
DoConnect: function() {
this.myWebSocket = new WebSocket('ws://192.0.0.1');
// ウェブソケットは閉じたイベント
this.myWebSocket.onclose = function () {
// The this in this clousure is "myWebSocket"
setTimeout(function() {
dojo.publish("WebSocketConnect", [ ] );
}, 5000);
};
}
}
Note: The project I am working on uses dojo 1.4. Quite old but I have no permission to upgrade it.
Any particular reason you dont want to connect to this?
When you publish or subscribe, it is dependent on the string id used to identify the "event", If you could make it unique for each instance then you could prevent the function execute on all instance.
// Dojo class
dojo.declare("MyClass", null, {
uniqueID:"",
constructor: function() {
this.uniqueID = <generate unique id>;
dojo.subscribe("WebSocketConnect" + this.uniqueID, this, function() {
this.DoConnect();
});
},
DoConnect: function() {
var self = this;
this.myWebSocket = new WebSocket('ws://192.0.0.1');
// ウェブソケットは閉じたイベント
this.myWebSocket.onclose = function () {
// The this in this clousure is "myWebSocket"
setTimeout(function() {
dojo.publish("WebSocketConnect" + self.uniqueID, [ ] );
}, 5000);
};
}
}
How you generate the uniqueID is upto you, it could be as simple as a global counter or use some logic to create a GUID. Anything will work as long as it is unique.
Use a dynamic topic name:
// Dojo class
define(['dijit/registry', 'dojo/_base/declare', 'dojo/topic'], function(registry, declare, topic) {
declare("MyClass", null, {
constructor: function() {
var uniqId = registry.getUniqueId('WebSocketConnect'),
doConnect = this._DoConnect;
//for external use
this.DoConnect = function() {
doConnect(uniqId);
}
//from internal fail
topic.subscribe("WebSocketConnect" + uniqId, this.DoConnect());
},
_DoConnect: function(uniqId) {
this.myWebSocket = new WebSocket('ws://192.0.0.1');
// ウェブソケットは閉じたイベント
this.myWebSocket.onclose = function() {
// The this in this clousure is "myWebSocket"
setTimeout(function() {
topic.publish("WebSocketConnect" + uniqId, []);
}, 5000);
};
}
}
});
});
but best is to use hitch:
// Dojo class
define(['dojo/_base/declare'], function(declare) {
declare("MyClass", null, {
DoConnect: function() {
this.myWebSocket = new WebSocket('ws://192.0.0.1');
// ウェブソケットは閉じたイベント
this.myWebSocket.onclose = lang.hitch(this, function() {
setTimeout(lang.hitch(this, 'DoConnect'), 5000);
});
}
}
});
});

ember.js Uncaught TypeError: Object data-size has no method 'transitionTo'

I am very new to ember and trying to implement authentication via facebook
I am using ember-facebook.js library to connect with facebook. Once the authentication is successful, I want to transition to some other route e.g. '/index'. This library creates a App.FBUser object in mixin which is populated from the facebook response. The blog say following:
Whenever the user changes (login, logout, app authorization, etc) the method updateFBUser is called, updating the App.FBUser object on your application. You can do whatever you want with this binding, observe it, put it in the DOM, whatever.
Ember.Facebook = Ember.Mixin.create({
FBUser: void 0,
appId: void 0,
fetchPicture: true,
init: function() {
this._super();
return window.FBApp = this;
},
appIdChanged: (function() {
var _this = this;
this.removeObserver('appId');
window.fbAsyncInit = function() {
return _this.fbAsyncInit();
};
return $(function() {
var js;
js = document.createElement('script');
$(js).attr({
id: 'facebook-jssdk',
async: true,
src: "//connect.facebook.net/en_US/all.js"
});
return $('head').append(js);
});
}).observes('appId'),
fbAsyncInit: function() {
var _this = this;
FB.init({
appId: this.get('appId'),
status: true,
cookie: true,
xfbml: true
});
this.set('FBloading', true);
FB.Event.subscribe('auth.authResponseChange', function(response) {
return _this.updateFBUser(response);
});
return FB.getLoginStatus(function(response) {
return _this.updateFBUser(response);
});
},
updateFBUser: function(response) {
console.log("Facebook.updateFBUser: Start");
var _this = this;
if (response.status === 'connected') {
//console.log(_this);
return FB.api('/me', function(user) {
var FBUser;
FBUser = user;
FBUser.accessToken = response.authResponse.accessToken;
if (_this.get('fetchPicture')) {
return FB.api('/me/picture', function(path) {
FBUser.picture = path;
_this.set('FBUser', FBUser);
return _this.set('FBloading', false);
});
} else {
_this.set('FBUser', FBUser);
return _this.set('FBloading', false);
}
});
} else {
this.set('FBUser', false);
return this.set('FBloading', false);
}
}//updateFBUser
});
Update :
Adding following observer in my LoginController, I am able to capture the App.FBUser update event(it is update after getting response from FB; as indicated by the blog).
From this observer method, when I try to 'transitionTo' my index route I get following error
Uncaught TypeError: Object data-size has no method 'transitionTo'. Following is the code
App.LoginController = Ember.Controller.extend({
onSuccess: (function(){
var self = this;
/*
//tried all these method to redirect but error is the same
var attemptedTransition = this.get('attemptedTransition');
attemptedTransition.retry();
*/
/*
//tried all these method to redirect but error is the same
var router = this.get('target.router');
router.transitionTo('index');
*/
//tried all these method to redirect but error is the same
this.transitionToRoute('index');
}).observes('App.FBUser')
});
Index Route
App.AuthenticatedRoute = Ember.Route.extend({
beforeModel: function(transition){
var self = this;
if(!App.FBUser){
self.redirectToLogin(transition);
}
},
redirectToLogin: function(transition){
var loginController = this.controllerFor('login');
loginController.set('attemptedTransition', transition);
this.transitionTo('login');
}
});
I am not able to get my head around it.
Any help is greatly appreciated. Thanks
How can I access this object in my Route.beforeModel() hook.
Depending on what route's beforModel hook you are talking about, this is how you could do it:
App.SomeRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (!Ember.isNone(App.FBUser)) {
// calling 'transitionTo' aborts the transition, redirects to 'index'
this.transitionTo('index');
}
}
});
Update in response to your last comment
The addon you are using is slightly outdated and the proposed implementation method for the mixin in your application will not work with the current version of ember:
App = Ember.Application.create(Ember.Facebook)
App.set('appId', 'yourfacebookappid');
starting from version 1.0.0-rc3 of ember you should rather do it like this:
App = Ember.Application.creatWithMixins(Ember.Facebook);
App.set('appId', 'yourfacebookappid');
After that you should be able to have access to the App.FBUser object as mentioned above.
Update 2
If you want to be able to be notified when some events happend, like login, logout etc. you should (as the Author of the addon states on it's blog post) override the updateFBUser method and do in there your transitions.
Since the addon is trough the mixin available in our App namespace you should be able to do the following:
App = Ember.Application.creatWithMixins(Ember.Facebook, {
updateFBUser: function() {
this._super();
// we are calling super to let the addon
// do it's work but at the same time we get
// notified that something happened, so do at this
// point your transition
}
});
Hope it helps.
As per Issue 1 adding
attributeBindings: [],
to:
return Ember.FacebookView = Ember.View.extend({
solved the issue.

Fetch collection only one time

i've a userlist made by a fetch from parse.com and rendered by view.Once people click on item list i've insert in url the objectid. In router i've made a function "home" that make fetch from collection and call view to render.The function "userdetails" catch objectid previous insert by view in url and use it to make a get from collection. The problem is:how can i pass the collection to this function userdetails?I don't want make another fetch.
home: function() {
var self=this;
console.log("inrouterhome");
var utenti = new Usercollection();
utenti.fetch({
success: function(object) {
var page=new Homelistuser({model:object});
self.changePage(page);
},
error: function(amici, error) {
// The collection could not be retrieved.
}
});
},
userDetails: function (objectId) {
HERE I WANNA USE OBJECTID TO MAKE A GET FROM COLLECTION FETCHED IN HOME
},
It looks like it is probably a scoping issue. Try this
var Models = {};
var AppRouter = Backbone.Router.extend({
home: function() {
var self=this;
console.log("inrouterhome");
Models.utenti = new Usercollection();
Models.utenti.fetch({
success: function(object) {
var page=new Homelistuser({model:object});
self.changePage(page);
},
error: function(amici, error) {
// The collection could not be retrieved.
}
});
},
userDetails: function (objectId) {
//Models.utenti should exist as long as home came first,
// may want to write a condition that check to see if it exists and if not do fetch.
}
});
As #abritez mentioned this is probably a scoping problem i.e. the userDetails method doesn't have access to the instantiated collection. #abritez's solution resolves this but if the user refreshes the page or accesses the route directly the collection will not be loaded.
If the collection is used between both routes consider fetching it at run time and using a listener for when it's ready:
var Models = {};
Models.utenti = new Usercollection();
Models.utenti.fetch();
var AppRouter = Backbone.Router.extend({
home: function() {
var utentiLoaded = function(object) {
var page = new Homelistuser({model:object});
this.changePage(page);
}
this.listenTo(Models.utenti, 'reset', utentiLoaded);
this.listenTo(Models.utenti, 'error', function(amici, error) {
// The collection could not be retrieved.
});
if (Models.utenti.any()) {
utentiLoaded(Models.utenti);
}
},
userDetails: function(objectId) {
var utentiLoaded = function(object) {
}
this.listenTo(Models.utenti, 'reset', utentiLoaded);
this.listenTo(Models.utenti, 'error', function(amici, error) {
// The collection could not be retrieved.
});
if (Models.utenti.any()) {
utentiLoaded(Models.utenti);
}
}
});

Categories

Resources