angular and socket io class manipulation - javascript

I am bulding a chat app with angular on the front end and socket io for the chat platform.
I want to have a diffenet css class for the sender in the chat (the one who sends the message in the chat) and for the side that recieves the message.
to do that I've created 2 classes in my css: "sender" and "reciever".
I want that when my socket gets a message from the "sender" (aka "the speaker side") - the li class will be "sender". when the socket gets a message from outside (some other member in the chat the is not me) the class will be "reciever".
like the green and white here: http://www.androidpolice.com/wp-content/uploads/2015/07/nexus2cee_whatsapp-middle-finger-2.png
I know that I can change the class in angluar using the ng-class directive.
The problem is that when I do the class manipulate all the messages in my chat became a part of that class (I am using ng-repeat directive).
and what I want is that 1 message will be class A and other message will be from class B and 3rd message will be class A.... so on...
I know that I should somehow use the ng-class propertie like:
<li ng-class={'sender' : message.sender, 'btn-off' : !message.sender} ng-repeat="message in messages track by $index">{{message}}</li>
but how can I get the message object (which is a string to include a boolean as well?
can you please help me to figure out how I can write the code for that?
this is my controler
mymodule.controller("cntrlChat", ['$scope', 'myService','$q','$timeout',
function($scope, myService,$q,$timeout){
var socket = io();
$scope.messages = [];
$scope.message_type="sender";
$scope.room= myService.get().room;
$scope.name= myService.get().name;
socket.emit('room', $scope.room);
$scope.submit=function(){
socket.emit('chat_message',{ room: $scope.room, msg: $scope.name+": "+$scope.insertedText });
$scope.message_type="sender";
$scope.messages.push($scope.name+": "+$scope.insertedText);
$scope.insertedText='';
return false;
}
socket.on('chat_message', function(msg){
$scope.$apply(function() {
$scope.message_type="receiver";
$scope.messages.push(msg);
});
});
socket.on('info_message', function(msg){
$scope.$apply(function() {
$scope.info=msg;
});
});
this is the server.js file:
var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var path = require('path');
app.use(express.static(path.join(__dirname, '/')));
app.get('/', function(req, res){
res.sendFile(__dirname + '/Index.html');
});
io.on('connection', function(socket){
io.emit('chat_message', "welcome");
socket.on('room', function(room) {
socket.join(room);
});
socket.on('chat_message', function(data){
socket.broadcast.to(data.room).emit('chat_message',data.msg);
});
socket.on('info_message', function(data){
socket.broadcast.to(data.room).emit('info_message',data.msg);
});
socket.on('disconnect', function(){
io.emit('chat message', "Bye");
});
});
http.listen((process.env.PORT || 3000), function(){
console.log('listening on *:3000 '+ __dirname);
});
this is the html for my chat:
<head>
<link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
<div class="chat">
<div class="members">
{{users}}
</div>
<div class="messages">
<ul>
<li ng-class="message_type" ng-repeat="message in messages track by $index">{{message}}</li>
</ul>
</div>
<form ng-submit="submit()">
{{info}}
<br>
<input autocomplete="off" ng-model="insertedText" ng-change="isUserTyping()" type="text" />
<button type="button" ng-click="submit()">
Send
</button>
</form>
</div>
</body>

Ok let's take a crack at this.
On a high level description, we want the socket to pass messages between users.
The best way to ensure we know who sent what is to make sure the data we pass has the right details.
With that being said, you just need to turn the msg from a string to an object and pass that around with a flag (I used sender/receiver, you can use sender: true/false):
$scope.submit=function(){
//
// change msg to an object (I don't know io well so it may need to be json encoded decoded?)
//
socket.emit('chat_message', {
room: $scope.room,
msg: {
text: $scope.name+": "+$scope.insertedText,
status: 'sender'
}
});
//
// push the data obj to your messages array
//
$scope.messages.push(msg)
$scope.insertedText='';
return false;
}
socket.on('chat_message', function(data){
$scope.$apply(function() {
//
// we expect data to look like the data above except we'll change the flag when it's retrieved
//
data.status = 'received'
$scope.messages.push(data);
});
});
socket.on('info_message', function(msg){
$scope.$apply(function() {
$scope.info=msg;
});
});
We're now passing objects with flags to and from the node server without modification on the server side so that code doesn't need to change at all.
Finally, the angular part:
<div class="chat">
<div class="members">
{{users}}
</div>
<div class="messages">
<ul>
//
// ng class stuff
// (ternary) ng-class="message.status === 'received' ? 'class1' : 'class2'"
//
<li ng-class="{'class1': message.status === 'sender', 'class2': message.status === 'received'}" ng-repeat="message in messages track by $index">{{message.text}}</li>
</ul>
</div>
<form ng-submit="submit()">
{{info}}
<br>
<input autocomplete="off" ng-model="insertedText" ng-change="isUserTyping()" type="text" />
<button type="button" ng-click="submit()">
Send
</button>
</form>
</div>

Related

How do I use data from the server side on the cliente side?

This is my app.js
var products = [
{description: "Hi"},
{description: "Hi2"}
]
app.get('/', function (req, res){
res.render("home.ejs");
})
app.get('/checkout', function (req, res){
res.render('checkout.ejs', {products:products});
});
app.post('/checkout', function(req, res){
var description = req.body.description;
var newProduct = {description};
products.push(newProduct);
res.redirect('/');
});
This is my home.ejs
<form action="/checkout" method = "POST">
<p class = "item-description">LP Snake Pocket Tee</p>
<input type="hidden" name = "description" class = "item-description-post">
<div class="btn-group btn-add mr-2" role="group" aria-label="Second group">
<button class="btn btn-success btn-add-cart">ADD TO CART</button>
</div>
</form>
And this is my hoje.js file, which just basically fills my input value with what I want, so I can send that as POST input
var item = $(".item-description")[0].innerHTML;
var postInput = $(".item-description-post").val(item);
Now I'm my checkout.ejs file I can do something like this, and print all my products (descriptions only so far)
<% products.forEach(function(product){ %>
<p> <%= product.description %> </p>
<% }); %>
My question is, how can I do the same thing, but in a separate .js file?
I do have a javascript file connected with my checkout.ejs
<!-- JAVASCRIPT -->
<script type = "text/javascript" src="./checkout.js"></script>
But that file can not access my array 'products'. Which makes sense, because my back end is only sending that data to my ejs file, so basically, how do I send that data 'products' from my app.js file to my checkout.js file? :)))
You can't.
Note: You can only pass data from .ejs file to .js file but not the other way. It won't work because .ejs is rendered on the server side while .js runs on the client side. I am assuming you are using EJS on server side
Why:
The EJS template will be rendered on the server before the js is started execution(it will start on browser), so there is no way going back to server and ask for some previous changes on the page which is already sent to the browser.

button should delete specific item on server side

I'm working on an app to track my expenses with javascript, nodejs , express and as templating engine handelbars.
So I have a div "list" which contains all expenses. ( i have an add button next to the div list, not visible on the pic)
Everytime a I add an expense , I add the div "obj" with a delete button ,a "B" button and some information about the expense.
here is the code in my html:
<div class="list">
{{#each expArr}}
<div id="obj" class="obj">
<form action="/removetodo" method="POST" >
<button class="btn2">X</button>
</form>
<button onclick="openNav()" class="btn">B</button>
<a>{{date}}</a> <n>{{name}}</n> <a>{{value}} </a>
{{description}}
</div>
{{/each}}
Now, my backend is runnning on a NodeJS server with express.
here is my index.js file :
var express = require('express');
var router = express.Router();
var expArr = [];
router.get('/', function(req, res, next) {
res.render('index' , {expArr: expArr} );
});
router.post('/addtodo', function (req, res, next) {
var exp = new Object();
exp.name = req.body.name;
exp.value = req.body.val;
exp.date = req.body.date;
exp.description = req.body.descr;
expArr.push(exp);
res.redirect('/');
});
router.post('/removetodo', function (req, res, next) {
expArr.pop();
res.redirect("/");
});
module.exports = router;
In addtodo I simply adding all the informtion to an array on the server (later I will add a database).
Now my question:
The delete button on every expense should delete the right expense.
In other words, I want ,by clicking the delete button , that the right entry in the array on the server deletes.
How do I do that?
Thanks!
you're storing everything in memory, taking that for granted, you can start by using a plain object rather then an array to store your data
expArr = {}
and then add a unique identifier like a hash or a date in ms for each instance
var exp = new Object();
exp.id = new Date().getUTCMilliseconds();
exp.name = req.body.name;
exp.value = req.body.val;
exp.date = req.body.date;
exp.description = req.body.descr;
expArr[exp.id] = exp;
now be sure to pass from the client the right id when you want to remove an expense
router.post('/removetodo', function (req, res, next) {
if(expArr[req.body.id]) {
delete expArr[req.body.id];
}
res.redirect("/");
});

mongoose syntax with user.id connecting to a form

Hi I am new to mongoose and with angular through ionic. I am trying to build an app and having problem with it. I have no Idea if this syntax mongoose is right because it still is not working in my front end where the user picks and answer and it saves it to the database
Heres my mongoose question.js
var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var Question = require('../models/Question.js');
var app = express();
// GET questions listing. questions/yourroute
router.get('/', function(req, res, next) {
Question.find(function(err, questions){
if(err) return next(err);
res.json(questions);
});
});
router.post('/saved-questions', function(req, res){
var now = Date()
User.findById(user.id, function(err, user){
var newanswer1 = {
question: '58ccb991caf9756f8e1d8a6c',
answer_text: req.body.Q1,
postdate: now
}
});
user.users_answers.push(newanswer1);
//repeat for Q 2 and 3, push each time
User.save()
});
module.exports = router;
And this is the form in ionic.. I don't know why its not working, maybe its how im putting it in the ionic form. There suppose to be slides of questions in ionic and each possible answer is in the ion check box
<div id="questions" ng-controller="QCntrl">
<ion-content>
<ion-slide-box>
<ion-slide>
<div class="intro" ng-controller="PopupCtrl">
<h1 class="hidden">Intro Question</h1>
<h3>Just need to ask some questions to make this app personalized for you</h3>
<button class="button" value="next" ng-click="nextSlide()">OKAY</button>
<button class="button2 button-positive" ng-click="PopAlert()">
Why?
</button>
</div>
</ion-slide>
<ion-slide>
<div id="Q1box">
<h2>{{questions[0].question_text}}</h2>
<ul>
<li data-ng-repeat="answer in answers1">
<ion-checkbox id="Q1" ng-model="filter.users_answers">{{answer.label}}</ion- checkbox>
</li>
</ul>
Next Question
</div>
</ion-slide>
Where you have User.save(), try changing it to user.save and move the }) that's before User.save and put it after where you change User.save to user.save
You are editing the user you just found with User.findById(user.id, function(err, user){
and it seems you then want to save the push you made to user.users_answers which user.save should do for you

jScrollPane not working with meteor.js

I'm trying to use jScrollPane with meteor.js, but it's not behaving as expected. First off, if I give the '.scroll-pane' class to a div, it will work without being initialized explicitly by me. But when I try to initialize it explicitly, it does not work. Is this some sort of meteor magic? or I am missing something obvious.
Second, if I don't initialize it, but I try to access it...it's data is empty, so I can't use the api on it. I've included some sample code below, please let me know if I am doing something wrong.
html
...
<div class="rectangle">
<div class="chat scroll-pane" id="chatWindow">
{{#each Messages}}
{{#if Compare uId UID}}
<div class="bubble me">{{Text}}</div>
{{else}}
<div class="bubble you">{{Text}}</div>
{{/if}}
{{/each}}
</div>
<input class="textinput" type="text" placeholder="Insert Message" id="textToSubmit">
<button class="btn btn-success" id="submit" autofocus="autofocus">Send
<br>
<br>
</button>
</div>
js
if (Meteor.isClient) {
...
var Message = new Meteor.Collection("Message");
Template.Message.rendered = function(){
if(!this._rendered) {
this._rendered = true;
var scroll = $(this.find("#chatWindow"));
var api = scroll.data('jsp');
console.log(api);
}
};
...
}
If you need any more info, please let me know.
Thanks
There are a couple things going on:
(1) you need to wire up the reactivity to your template to ensure that the timing of the incoming Messages is correct. This is achieved by using a Session variable to set the load, and by setting Template.scroll.Messages equal to a function that returns a collection cursor. Typically, you would not need to set a Session this way if your Template.scroll.Messages returns a query that uses a Session object (i.e., a roomId). If that were the case, you could forgo the callback on the Meteor.subscribe call and the "loaded" Session altogether.
(2) you'll want to turn off autopublish (meteor remove autopublish) and explicitly subscribe to the data so you know when your collection loads;
(3) you must declare your messages Collection outside the isClient block to ensure the server knows about it.
HTML:
<template name="scroll">
<div class="rectangle">
<div style="height:100px;overflow:auto;" id="chatWindow">
{{#each Messages}}
{{#if isMe this}}
<div class="bubble me">{{text}}</div>
{{else}}
<div class="bubble you">{{text}}</div>
{{/if}}
{{/each}}
</div>
</div>
</template>
JS:
Messages = new Meteor.Collection("messages");
if (Meteor.isClient) {
Meteor.subscribe("messages", function () {
Session.set("loaded", true);
});
Template.scroll.rendered = function () {
if (Session.get("loaded")) {
$(this.find("#chatWindow")).jScrollPane();
var api = $(this.find("#chatWindow")).data("jsp");
...
}
};
Template.scroll.helpers({
isMe: function (msg) {
return msg.userName === 'me';
}
});
Template.scroll.Messages = function () {
return Messages.find({});
};
}
if (Meteor.isServer) {
Meteor.startup(function () {
if (Messages.find().count() === 0) {
Messages.insert({ text: 'Message 1', userName: 'me' });
Messages.insert({ text: 'Message 2', userName: 'you' });
Messages.insert({ text: 'Message 3', userName: 'me' });
}
});
Meteor.publish("messages", function () { return Messages.find({}); });
}

What's a good way to handle flash notifications in meteor (with meteor-router)?

I am using meteor along with meteor-router for client and server side routing. I'm wondering what a good way to handle site notifications, specifically "flash" type ones.
In the global layout.html I can have a handlebars output a message if a "message" session variable is set, but the message shouldn't stick around once the app is routed to a new url with Meteor.Router.to().
What's a good solution to having "flash" notifications? Or, how can I automatically clear a session variable after routing to a new URL.
layout.html:
<head>
<title>Meteor App</title>
</head>
<body>
{{> global-layout}}
</body>
<template name="global-layout">
{{#if message}}
<div class="message">{{message}}</div>
{{/if}}
{{renderPage}}
</template>
then in layout.js
Template['global-layout'].message = function () {
return Session.get('message');
};
I'm using a Meteor.Router.filter for this. This filter will be applied to all routes, therefore all flashes will be cleared on all url changes.
routes.js
Meteor.Router.filters({
// clearSeenMessages filter clears all seen messages.
// This filters is applied to all pages
clearSeenMessages: function (page) {
flash.clear();
return page;
},
});
// applies to all pages
Meteor.Router.filter('clearSeenMessages');
Here's the rest of the implementaion, aspects were borrowed from telesc.pe
client/views/flashes/flash_item.html
<template name="flashItem">
{{#if show}}
<div class="alert-box {{type}}">
{{message}}
<a class="close" href="">×</a>
</div>
{{/if}}
</template>
client/views/flashes/flash_item.js
// When the template is first created
Template.flashItem.created = function () {
// Get the ID of the messsage
var id = this.data._id;
Meteor.setTimeout(function () {
// mark the flash as "seen" after 100 milliseconds
flash.Flashes.update(id, {$set: {seen: true}});
}, 100);
}
client/views/flashes/flashes.html
<template name="flashes">
{{#each flashes}}
{{> flashItem}}
{{/each}}
</template>
client/views/flashes/flashes.js
Template.flashes.flashes = function () {
return flash.Flashes.find();
}
client/views/app.html
<body>
<!-- add the flashes somewhere in the body -->
{{> flashes}}
</body>
client/lib/flashes.js
// flashes provides an api for temporary flash messages stored in a
// client only collecion
var flash = flash || {};
(function (argument) {
// Client only collection
flash.Flashes = new Meteor.Collection(null);
// create given a message and optional type creates a Flash message.
flash.create = function (message, type) {
type = (typeof type === 'undefined') ? 'error' : type;
// Store errors in the 'Errors' local collection
flash.Flashes.insert({message: message, type: type, seen: false, show: true});
};
// error is a helper function for creating error messages
flash.error = function (message) {
return flash.create(message, 'error');
};
// success is a helper function for creating success messages
flash.success = function (message) {
return flash.create(message, 'success');
};
// info is a helper function for creating info messages
flash.info = function (message) {
return flash.create(message, 'info');
};
// clear hides viewed message
flash.clear = function () {
flash.Flashes.update({seen: true}, {$set: {show: false}}, {multi: true});
};
})();
Usage
flash.success('This is a success message');
flash.error('This is a error message');
flash.info('This is a info message');
You can now use the router-with-flash package available on atmosphere to handle flash notifications. If you use meteorite (which you should), you can do mrt add router-with-flash in the root directory of your project. Then, to display an alert you need to -
Meteor.Router.to("/", { alert: "Some alert..." });
Meteor.Router.notification("alert");
This will display the alert until the next call to Meteor.Router.to().

Categories