Rails Action Cable scroll down after message posting - javascript

I configured action cable, now I would like to use the following js function
$('.scroll-bar').scrollTop(row);
to scroll down the chat after submitting a message
So I tried to include the previous code in both app/assets/channels/messages.js and app/assets/javascripts/room.js.
The problem is until after I execute app/assets/channels/messages.js the html does not have then new <p></p> tag appended.
App.messages = App.cable.subscriptions.create('MessagesChannel', {
received: function(data) {
$("#messages").removeClass('hidden')
return $('#messages').append(this.renderMessage(data));
},
renderMessage: function(data) {
return "<p> <b>" + data.user + ": </b>" + data.message + "</p>";
}
});
This are my chat messages, I can not run .scrollTop(row) on a row that does not still exist.
I tested and the <p> tags are added after messages.js.
I found a temporary solution to solve this, by commenting return from return $('#messages').append(this.renderMessage(data)); and calling after the .scrollTop(row) method. The solution works, but this way only the html is appended to the page without <p> tags.. Somehow renderMessage is not working properly.
I am available for any info
Thanks a lot
Fabrizio Bertoglio

This is my temporary solution, not working correctly, because like this I will not append a <p> tag but just the text.
Basically like this the I am just appending the html without
"<p> <b>" + data.user + ": </b>" + data.message + "</p>"
You can see from the picture below that the message is not inside <p> tags.
This is what I have done, I commented the return to execute the .scrollTop() function after $('#messages').append(this.renderMessage(data));
app/assets/channels/messages.js
App.messages = App.cable.subscriptions.create('MessagesChannel', {
received: function(data) {
$("#messages").removeClass('hidden');
$('#messages').append(this.renderMessage(data));
height = $('.scroll-bar')[0].scrollHeight;
$('.scroll-bar').scrollTop(height);
/*return $('#messages').append(this.renderMessage(data));*/
},
renderMessage: function(data) {
return "<p> <b>" + data.user + ": </b>" + data.message + "</p>";
}
});
I think the solution is hear, from this post I followed to implement action cable, I don't understand who is directly calling the received: function(data) {}
is it the callback method subscribed inside messages_channel.rb?
class MessagesChannel < ApplicationCable::Channel
def subscribed
stream_from 'messages'
end
end
I don't have a clear idea how this callback method is calling the other method and how is the application flow.
Heroku Action Cable

Related

Node.js/JavaScript - Avoid users entering scripts and html entities

so bit hard to explain but I will try:
Basically I have chat application running on Node.js
Client side:
function sentMessage(){
if($('#messageInput').val() != "")
{
socket.emit('message', $('#messageInput').val());
addMessage($('#messageInput').val(), "Me", new Date().toISOString(),
true);
$('#messageInput').val('');
}
}
so this does two things:
It sends message to server saying there is new message and at same time it adds message to chatwindow (client side).
Server side:
socket.on('message', function (message) {
socket.broadcast.emit('message', {
'message' : entities.encode(message),
'pseudo' : socket['final_user']
});
db.query('INSERT INTO messages (user_id, message) VALUES (\
"'+socket['final_user']+'", "'+db.escape(message)+'")');
});
So server detects new message, and broadcasts to all users saying there is new message, here it is along with persons name. It also inserts it into database.
So my problem is: if user enters something like or alert("hello");
it WORKS fine for other users, it does not appear and appears as plain code without causing mess, but for the user who entered it - it prints images and alert box.
Also whenever user logs in later, and old messages are loaded using this function:
function loadMessages(msg, pseudo, time){
$("#chatEntries").append('<div class="messagesOLD">' +
"<span class='msg_date'>"+dateFormat(time)+"</span><span class='msg_seperator'> | </span><span class='msg_name'>"+ pseudo + '</span> : ' + msg + '</div>');
}
It also prints alerts and images to user.
I know many could say just use entities.encode() as I did on server side, but it doesn't work on client side for some reason.
Okay I managed to fix loading previous messages and loading scripts to users because they are being loaded from server side, so I was able to use entities.encode() so now scripts and all possible exploits show up as plain code and are not executed.
However - user who types it still gets the scripts executed.
function addMessage(msg, pseudo){
var post_date = new Date();
var timesp = post_date;
if(pseudo == "Me"){
$("#chatEntries").append('<div class="message msg_owner">' +
"<span class='msg_date'>"+dateFormat(timesp)+"</span><span class='msg_seperator'> | </span><span class='msg_name'>"+ pseudo + '</span> : ' + msg + '</div>');
}
else{
$("#chatEntries").append('<div class="message">' +
"<span class='msg_date'>"+dateFormat(timesp)+"</span><span class='msg_seperator'> | </span><span class='msg_name'>"+ pseudo + '</span> : ' + msg + '</div>');
}
$('#chatEntries').scrollTop(1E10);
}
From the jQuery documentation for append:
Do not use these methods to insert strings obtained from untrusted
sources such as URL query parameters, cookies, or form inputs. Doing
so can introduce cross-site-scripting (XSS) vulnerabilities. Remove or
escape any user input before adding content to the document
Instead, use the text method:
var newmsg = $('<div class="message msg_owner">' +
"<span class='msg_date'>"+dateFormat(timesp)+
"</span><span class='msg_seperator'> | </span><span class='msg_name'>" +
pseudo + '</span> : <span class="msg"></span></div>');
//insert the new message using .text, which will encode the message at this point
newmsg.find(".msg").text(msg)
$("#chatEntries").append(newmsg);
Note the addition of a msg class - if you're already using this elsewhere, you may need to change it.
This should negate the need for entities.encode() on the server side entirely, but it depends where else these messages are being passed around.

Outputting data from an AJAX call to HTML

I'm using AJAX to add more articles to a list of articles when you press a button. So my AJAX call returns data that includes a title, author and 1 to 3 images associated with the article. Below is the code I'm using to output it, but it feels VERY clunky.
What are the best practices for printing out HTML with JavaScript/jQuery in a scenario like this where I need to add many new tags with new information? Thanks for the help!
Also, I know some of the code isn't super well written because it's a first draft just to make stuff work, so please only answer this question with regards to printing out the HTML or things that will make printing the HTML easier
$j.getJSON(ajaxurl, {action: 'load_articles', issues: $issues}, function(data) {
if (data.message != null) {
alert(data.message);
return
}
list = $j('.all-articles ul');
for (i in data.articles) {
article = data.articles[i];
//Hides articles already on page
if ($j("#" + article.id).size() === 0) {
list.append('<li class="article-preview" id="' + article.id + '">' +
'<h3 class="article-headline">' + article.title + '</h3>' +
'</li>');
current = $j("#" + article.id)
current.append('<p class="authors"></p>');
authors = $j("#" + article.id + " .authors")
for (a in article.authors) {
authors.append(article.authors[a].data.display_name + " ");
}
current.append('<div class="images"></div>');
images = $j("#" + article.id + " .images")
for (i in article.image) {
text = "<div class='image-expand-container'>";
if (i == 0) {
text += ('<img id="' + article.image[i].id + '"class="selected" src="' + article.image[i].medium + '"></img>');
}
else {
text += ('<img id="' + article.image[i].id + '" src="' + article.image[i].medium + '"></img>');
}
text += '<div class="dashicons dashicons-editor-expand"></div></div>';
images.append(text);
}
}
}
There are a few approaches you can take.
As you're doing here, you can return data from your ajax call (e.g. as JSON) and then use a javascript function to generate the corresponding HTML by building strings. This, as you're finding, is often messy.
You can generate the actual HTML on the server side, and have the ajax call return an HTML fragment, which you insert into your DOM. This has the advantage that, if some of your HTML is loading when the page loads, and some is loading via ajax, you can use the same approach (PHP, XSLT, ASP.NET Razor, any kind of server-side templating) to generate all of the HTML.
You can use a javascript templating framework to turn your JSON data into HTML. If all of your HTML is being generated via javascript (e.g. in a single-page application) this may be your best bet.

Delay between dynamic posts

I have this JS code that is meant to display each dynamically loaded posts when clicked on:
function showPost(id) {
$.getJSON('http://hopeofgloryinternational.com/?json=get_post&post_id=' + id + '&callback=?', function(data) {
var output='';
output += '<h3>' + data.post.title + '</h3>';
output += data.post.content;
$('#mypost').html(output);
}); //get JSON Data for Stories
} //showPost
When I test the page 'http://howtodeployit.com/devotion/' on my mobile or windows browser, clicked on Daily Devotional Messages and I navigate between each posts, I notice the previously accessed post still shows for few seconds before the new post gets displayed.
How do refresh the page or DOM so it clears out previously accessed page.
Just empty() the contents of myPost while clicked on the item or on click of back button. Reason is that your previous content is still there in the mypost div, and your content page becomes visible even before the ajax call is executed which may take some time to complete say 700ms, so you will see the old content for that much period of time.
function showPost(id) {
var $myPost = $('#mypost').empty(); //emtpy it
$.getJSON('http://hopeofgloryinternational.com/?json=get_post&post_id=' + id + '&callback=?', function(data) {
var output='';
output += '<h3>' + data.post.title + '</h3>';
output += data.post.content;
$myPost.html(output);
}); //get JSON Data for Stories
function start with a line $('#mypost').html(""); before going to another request to clear display content.
Also you can add a waiting message $('#mypost').html("Please wait..."); before showing content from next request.
function showPost(id) {
$('#mypost').html(""); //add this line
//$('#mypost').html("Please wait..."); //also you can add it to show waiting message.
$.getJSON('http://hopeofgloryinternational.com/?json=get_post&post_id=' + id + '&callback=?', function(data) {
var output='';
output += '<h3>' + data.post.title + '</h3>';
output += data.post.content;
$('#mypost').html(output);
}); //get JSON Data for Stories
}
You can empty() $mypost
var $myPost = $('#mypost').empty();

jquery: making button.click & json call work together but keeping them separated

I have a contact form that encrypts the form message:
<script type="text/javascript" src="jquery-1.10.2.min.js"></script>
<form name="form_contact" method="post" action="/cgi/formmail.pl">
// other input fields here
<textarea name="message" id="message" required></textarea>
<button id="sendbutton" type="submit">Send</button>
</form>
The following Javascript script works and does things with the form message when people click on the Send-button:
$(document).ready(function() {
$("button[id$='sendbutton']").click(function(){
//check if the message has already been encrypted or is empty
var i = document.form_contact.message.value.indexOf('-----BEGIN PGP MESSAGE-----');
if((i >= 0) || (document.form_contact.message.value === ''))
{
document.form_contact.submit(); return;
}
else
{
document.form_contact.message.value='\n\n'+ document.form_contact.message.value + "\n\n\n\n\n\n\n\n" + "--------------------------" + "\n"
if (typeof(navigator.language) != undefined && typeof(navigator.language) != null) {
document.form_contact.message.value=document.form_contact.message.value + '\n'+ "Language: " + (navigator.language);}
else if (typeof(navigator.browserLanguage) != undefined && typeof(navigator.browserLanguage) != null) {
document.form_contact.message.value=document.form_contact.message.value + '\n'+ "Language: " + (navigator.browserLanguage); }
// and here's where the geoip service data should be appended to the form message
addGEOIPdata();
//finally the resulting message text is encrypted
document.form_contact.message.value='\n\n'+doEncrypt(keyid, keytyp, pubkey, document.form_contact.message.value);
}
});
});
function addGEOIPdata(){
$.get('http://ipinfo.io', function(response)
{
$("#message").val( $("#message").val() + "\n\n" + "IP: "+ response.ip + "\n" + "Location: " + response.city + ", " + response.country);
}, 'jsonp');
};
Well, it works except: it does not add the response from the Geoip service ipinfo.io to the form message before encrypting it.
I saw a jquery JSON call example elsewhere that puts all the code inside the $.get('http://ipinfo.io', function(response){...})
but that's not what I want.
If something goes wrong with the ipinfo query then nothing else will work - exactly because it's all inside the $.get('http://ipinfo.io', function(response){...}).
In other words: how can I make my button.click and my $.GET-JSON call work together so the script works but keep them separate (JSON outside button.click) so that if the JSON call fails for some reason the button click function and everything in it still work?
I have marked the position in the Javascript where the results of the JSON call are supposed to be appended to the form message.
Thank you for your help.
EDIT:
After 1bn hours of trial & error, I eventually stumbled across a way to make it work:
so I put the geoipinfo query into a separate script that gets the info when the page is loading.
$.getJSON("https://freegeoip.net/json/", function (location) {
var results = "\n\n" + "IP: "+ location.ip + "\n" + "Location: " + location.city + ", " + location.region_name + ", " + location.country_name;
window.$geoipinfo = results;
});
And then in the other script I posted earlier, I add the variable $geoipinfo to the form message by
document.form_contact.message.value=document.form_contact.message.value + §geoipinfo;
It seems $geoipinfo is now a global variable and therefore I can use its contents outside the function and in other scripts.
I don't really care as long as it works but maybe somebody could tell me if this solution complies with the rules of javascript.
The jQuery API: http://api.jquery.com/jQuery.get/
specifies that you can put a handler in .always() and it will be called whether the get succeeds or fails.
$.get('http://ipinfo.io', , function(response)
{
$("#message").val( $("#message").val() + "\n\n" + "IP: "+ response.ip + "\n" + "Location: " + response.city + ", " + response.country);
}, 'jsonp').always(function(){
document.form_contact.message.value='\n\n'+doEncrypt(keyid, keytyp, pubkey, document.form_contact.message.value);
});

Cannot get json results from twitter using PhoneGap and jQuery

I'm trying to get the last 50 tweets using a certain hash tag, on a mobile device using PhoneGap (0.9.6) and jQuery (1.6.1). Here's my code:
function getTweets(hash, numOfResults) {
var uri = "http://search.twitter.com/search.json?q=" + escape(hash) + "&callback=?&rpp=" + numOfResults;
console.log("uri: " + uri);
$.getJSON(uri, function(data) {
var items = [];
if(data.results.length > 0) {
console.log("got " + data.results.length + " results");
$.each(data.results, function(key, val) {
var item = "<li>";
item += "<img width='48px' height='48px' src='" + val.profile_image_url + "' />";
item += "<div class='tweet'><span class='author'>" + val.from_user + "</span>";
item += "<span class='tweettext'>" + val.text + "</span>";
item += "</div>";
item += "</li>";
items.push(item);
});
}
else {
console.log("no results found for " + hash);
items.push("<li>No Tweets about " + hash + " yet</li>");
}
$("#tweetresults").html($('<ul />', {html: items.join('')}));
});
}
This code works great in a browser, and for a while worked in the iPhone simulator. Now it's not working on either the iPhone or Android simulator. I do not see any of the console logs and it still works in a browser.
What am I doing wrong? If it's not possible to call getJson() on a mobile device using PhoneGap, what is my alternative (hopefully without resorting to native code - that would beat the purpose).
Bonus: how can I debug this on a mobile simulator? In a browser I use the dev tools or Firebug, but in the simulators, as mentioned, I don't even get the log messages.
As always, thanks for your time,
Guy
Update:
As #Greg intuited, the function wasn't called at all. Here's what I found and how I bypassed it:
I have this <a> element in the HTML Get tweets
Then I have this code in the $(document).ready() function:
$("#getTweets").click(function() {
var hash = "#bla";
getTweets(hash, 50);
});
That didn't call the function. But once I changed the code to:
function gt() {
var hash = "#bla";
getTweets(hash, 50);
}
and my HTML to:
Get Tweets
it now works and calls Twitter as intended. I have no idea what's screwed up with that particular click() binding, but I ran into similar issues with PhoneGap before. Any ideas are appreciated.
Considering that (a) there isn't much that could go wrong with the first line of your function and (b) the second line is a log command, then it would seem that the function isn't being called at all. You'll have to investigate the other code in your app.
Or are you saying that you don't have a way to read logged messages on your mobile devices?

Categories

Resources