BackboneJS Flash Messages - backbone.js

Is there an extension to backbone for Flash Messages? It seems to be a common feature in Web Frameworks (server side at least). There appears to be none, and I attempted to make my own:
class FlashMessenger extends Backbone.Model
constructor: ->
#messages = []
# add a message to the messages array
add: (type, message) ->
#messages.push
type: type
message: message
# returns all existing messages and clearing all messages
getMessages: ->
ret = #messages.slice(0)
#messages = []
return ret
Now, I was wondering how can I inject them into my views automatically. I will like my messages to show when I use Backbone.Router.navigate() eg:
app.flashMessages.add("success", "Successfully logged in")
appRouter.navigate("dashboard")
# flash messages should show when I render the view

My 5 cents -- it seems to be a bit of an overkill to use Backbone for flash messages. If you have only 1 instance of flash message on the page, you're better off not using a separate model for it.
Instead I would use a view for Flash message and a global dispatcher:
Dispatcher = _.extend({}, Backbone.Events);
Create view:
var FlashMessage = Backbone.View.extend({
initialize: function() {
Dispatcher.bind('show_flash_message', this.render);
},
render: function(msg) {
// do something with the message
}
});
And from the part of the app where you have to show the flash message, do
Dispatcher.trigger('show_flash_message', 'Some message');

Related

Action cable: How to broadcast the chat messages only the specific chat room in Rails 5?

I currently have a Rails 5 application acting as my back-end,we can call this the "Core." I also have another Angular 1.6.4 application acting as my front-end, which is serving up Angular client side,And integrate with backed-end application through angular-actionable we can call this the "Front". These are two completely separate applications with completely different domains.
Basically, I am trying to integrate Action Cable through the Core and have it talk to the Front. I'm using this service here for the Front: enter link description here. As far as the Core, that's just basic Action Cable set up.
I have a list of chat rooms on admin side.
Problem: I sent message from client side but it broadcast message to all the chat rooms in admin side.I try to give the specific path of chat room in stream but still it broadcast message to all chat rooms.
I want to broadcast the message to specific chat room
Core
chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from stream_name
end
def unsubscribed
stop_all_streams
end
def receive(data)
ActionCable.server.broadcast stream_name, data.fetch('message')
end
private
def stream_name
"chat_channel_#{chat_id}"
end
def chat_id
params.fetch('data').fetch('chat')
end
end
Fornt
chatCtrl.js
app.run(function (ActionCableConfig){
ActionCableConfig.debug = true;
ActionCableConfig.wsUri= "ws://localhost:3000/cable";
});
app.controller('chatCtrl', ['$scope', 'ActionCableChannel',
function($scope, ActionCableChannel) {
var consumer = new ActionCableChannel("ChatChannel", { chat: 'localhost:3000/#!/chat/1'});
var callback = function(message) {
$scope.myData.push(message);
};
consumer.subscribe(callback).then(function(){
$scope.sendToMyChannel = function(message){
consumer.send(message);
};
$scope.$on("$destroy", function(){
consumer.unsubscribe().then(function(){ $scope.sendToMyChannel = undefined; });
});
});
}
]);
this is the solution for my problem, very similar to yours, but I did not use cofeescript, just javascript.. so I attach also my code if you need it.. I just check that the attribute data-chat-room-id from the DOM is equal to the one I am passing with the message:
The message controller create the message then with ActionCable.server.broadcast calls with those parameters (message, user, chatroom_id, lastuser) my js code in messages.js
class MessagesController < ApplicationController
def create
message = Message.new(message_params)
message.user = current_user
chatroom = message.chatroom
if message.save
ActionCable.server.broadcast 'messages',
message: message.content,
user: message.user.name,
chatroom_id: message.chatroom_id,
lastuser: chatroom.messages.last(2)[0].user.name
head :ok
end
end
end
Inside app/assets/javascrips/channels/messages.js I do a check that data.chatroom_id (the element from the DOM) equals chat_room_id (the element sent from the controller, of the object message just saved)
App.messages = App.cable.subscriptions.create('MessagesChannel', {
received: function(data) {
messages = $('#chatroom_id');
chat_room_id = messages.data('chat-room-id');
if (data.chatroom_id == chat_room_id) {
$('#messages').append(this.renderMessage(data));
};
},
renderMessage: function(data) {
return "<br><p> <strong>" + data.user + ": </strong>" + data.message + "</p>";
}
});
This is the article that gave me this answer
Action Cable chat Ilya Bodrov-Krukowski
Ilya Bodrov-Krukowski on Github
Another problem to tackle here is providing our script with the room’s id. Let’s solve it with the help of HTML data- attribute:
views/chat_rooms/show.html.erb
<div id="messages" data-chat-room-id="<%= #chat_room.id %>">
<%= render #chat_room.messages %>
</div>
Having this in place, we can use room’s id in the script:
javascripts/channels/rooms.coffee
jQuery(document).on 'turbolinks:load', ->
messages = $('#messages')
if $('#messages').length > 0
App.global_chat = App.cable.subscriptions.create {
channel: "ChatRoomsChannel"
chat_room_id: messages.data('chat-room-id')
},
connected: ->
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
# Data received
send_message: (message, chat_room_id) ->
#perform 'send_message', message: message, chat_room_id: chat_room_id

Send Notification properly using Socket.io Redis server

OBJECTIVE: When post is added to perticular channel send notification to all connected subscribed users for that channel.
Relational Database tables over view: Post, Channels, Users, Channels_Users
Post: id, title, content, channel_id(Indicate post is related to what channel)
Channels: id, name (List of Channels)
Users: id, username (User Table)
Channels_Users: id, channel_id, user_id (This table indicate which channels user is subscribed to.)
Goal: When post is created, send notification to perticular user group.
I have tried serveral ways and it is working as aspected. But I am looking for the correct way of doing this.
Server Side: socket.js
Naming conventions:
Channel name:
channel-1, channel-2, channel-3, ....
Namespace name('namespace-'+[channelName]) :
namespace-channel-1, namespace-channel-2, ...
var app = require('express')();
var request = require('request');
var http = require('http').Server(app);
var io = require('socket.io').listen(http);
var Redis = require('ioredis');
var redis = new Redis(####, 'localhost');
// Get all channels from Database and loop through it
// subscribe to redis channel
// initialize namespace inorder to send message to it
for(var i=1; i=<50; i++) {
redis.subscribe('channel-'+i);
io.of('/namespace-channel-'+i).on('connection', function(socket){
console.log('user connected');
});
}
// We have been subscribed to redis channel so
// this block will hear if there is any new message from redis server
redis.on('message', function(channel, message) {
message = JSON.parse(message);
// send message to specific namespace, with event = newPost
io.of('/namespace-'+channel).emit('newPost', message.data);
});
//Listen
http.listen(3000, function(){
console.log('Listening on Port 3000');
});
Client side: Angularjs Socket factory code: (Mentioning main socket code)
// userChannels: user subscribed channels array
// Loop through all channel and hear for new Post and
// display notification if there is newPost event
userChannels.forEach(function (c) {
let socket = {};
console.info('Attempting to connect to namespace'+c);
socket = io.connect(SOCKET_URL + ':3000/namespace-'+c, {query: "Authorization=token"});
socket.on('connect',function(){
if(socket.connected){
console.info("namespace-" + c + " Connected!");
// On New post event for this name space
socket.on('newPost', function(data) {
/* DISPLAY DESKTOP NOTIFICATION */
});
}
});
});
Publish data to Redis server, at the time of new post created
$data = [
'event' => 'newPost',
'data' => [
'title' => $postTitle,
'content' => $postContent
]
];
$redisChannel = "channel-" . $channelId; // As per our naming conventions
Redis::publish($redisChannel, json_encode($data));
The user is getting notification correctly for the channels they have been subscribed.
Problem 1: I am not sure this is the best solution to implement this notification thing.
Problem 2: When a user opens the same application in more than one browser tabs, it gets the notification for all of them. It should send only one notification. This is something related to user management at server side on redis end. I am not sure about this part too.
I really appreciate your suggestion/help on this. Thank you in advance.

Backbone collection export issues

I have a collection of users (model user)
model has a boolean value: isExport
i have a button that on click supposed to post to the server all the users that isExport=true
Can anyone suggest a suitable solution for this problem?
I know it's possible to wrap the collection as a model and overwrite the toJSON function
but couldn't manage it so far (can someone please give a code example?)
App.User = Backbone.Model.extend({ defaults: {isExport: false}...});
App.Users = Backbone.Collections.extend({model: App.User...});
Thanks!
Roy
Backbone collections by default don't have any write operations to the server, so you'll need to add a new method to your Collection subclass such as doExport, use .where to get the models with isExport=true and their toJSON() to get an array of objects which you can then send to the server with Backbone.sync or $.post.
Backbone comes with RESTful support.
So, if you give each collection a url pointing to the collection rest service, then with a few functions (create,save) you can handle server requests.
App.Models.User = Backbone.Model.extend();
App.Collections.Users = Backbone.Collection.extend({
url: 'users',
model: App.Models.User
});
So, this way, you can:
var users = new App.Collections.Users();
users.fetch(); // This will call yoursite.com/users
// Expecting a json request
And then you can:
users.create({ name: 'John', isExport: true });
This will send a post request to the server, in order to create a new record.
And you can check on server side if it has the flag you want.
App.Views.ExportAll = Backbone.View.extend({
el: '#exportAll',
events: {
'click': 'exportAll'
},
exportAll: function(e){
e.preventDefault();
console.log('exporting all');
console.log(this.collection.toJSON());
var exportModel = new App.Models.Export;
exportModel.set("data", this.collection.toJSON());
console.log(exportModel.toJSON());
exportModel.save();
}
});
I think this is the best solution for the problem

How to get the attributes created by the server when creating a model

For creating a new model I need to pass just one attribute:
model.save({
name: 'bar'
});
Then the server will add extra attributes like the id for this model.
Actually the response of the server when I create the new model is
this request has no response data available.
What is the best way to get the extra attributes created by the server?
Should I fix the sever part?
Here's my code:
var ListView = Marionette.CompositeView.extend({
// some code
events: {
'click #create-user': 'createUser'
},
createUser: function (e) {
this.collection.create(this.newAttributes(e));
},
newAttributes: function (e) {
return {
name: $(e.currentTarget).val(),
};
}
appendHtml: function (collectionView, itemView) {
collectionView.$el.find('ul.list-users').append(itemView.el);
}
});
This question is not related to Marionette, which only extends views, not models.
Backbone will automatically incorporate any new attributes sent back from the server - there is nothing special that needs to be done on the client side. But for this to work, your server needs to return a JSON object of attribute-value pairs that you want to be set.

Run function after Backbone.Router.navigate()

I want to run a function (to render flash messages) after a backbone.navigate call. Is it possible? I currently hooked into the all event and check if eventname starts with "route:". But that triggers too early, before my messages are added... so I was thinking of running after the route completes
# intercept all router navigate and render flash messages if any
renderFlashMessengesOnNavigate = (evt) =>
if evt.indexOf("route:") is 0
console.log "rendering messages after route ..."
#flashMessenger.render()
window.appRouter.bind "all", renderFlashMessengesOnNavigate
window.authRouter.bind "all", renderFlashMessengesOnNavigate
window.userRouter.bind "all", renderFlashMessengesOnNavigate
Try this:
var originalNavigate = Backbone.history.navigate;
Backbone.history.navigate = function(fragment, options){
originalNavigate.apply(this, arguments);
//your code here
}

Resources