I'm trying to build a system for like & dislike posts in my project but I'm facing some issue using this on the client side , I have this form
but the problem there is no communication between server and client I don't know what I'm missing, even I click on like button I see in the client side value 1 but nothing happen on server side
the front is build with EJS view engine and this my code
<div class="row">
<button onclick="actOnPost(event);"
data-post-id="<%= user.posts[x].id %>">Like
</button>
<span id="likes-count-<%= user.posts[x].id %>"><%= user.posts[x].likes %></span>
</div>
I use this script in the index.ejs file :
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
var updatePostStats = {
Like: function (postId) {
document.querySelector('#likes-count-' + postId).textContent++;
},
Unlike: function(postId) {
document.querySelector('#likes-count-' + postId).textContent--;
}
};
var toggleButtonText = {
Like: function(button) {
button.textContent = "Unlike";
},
Unlike: function(button) {
button.textContent = "Like";
}
};
var actOnPost = function (event) {
var postId = event.target.dataset.postId;
var action = event.target.textContent.trim();
toggleButtonText[action](event.target);
updatePostStats[action](postId);
axios.post('/posts/' + postId + '/act', { action: action });
};
</script>
<script src="https://js.pusher.com/4.1/pusher.min.js"></script>
<script>
var pusher = new Pusher('your-app-id', {
cluster: 'your-app-cluster'
});
var socketId;
// retrieve the socket ID on successful connection
pusher.connection.bind('connected', function() {
socketId = pusher.connection.socket_id;
});
var channel = pusher.subscribe('post-events');
channel.bind('postAction', function(data) {
// log message data to console - for debugging purposes
console.log(data);
var action = data.action;
updatePostStats[action](data.postId);
});
</script>
for the server side I have this code located in file.js:
router.post('/posts/:id/act', (req, res, next) => {
console.log('im here')
const action = req.body.action;
const counter = action === 'Like' ? 1 : -1;
Post.update({_id: req.params.id}, {$inc: {likes_count: counter}}, {}, (err, numberAffected) => {
pusher.trigger('post-events', 'postAction', { action: action, postId: req.params.id }, req.body.socketId);
res.send('');
});
});
I added console.log('Im here') to verify if there something is launching on my server side but I don't get anything, this router is not even launched
my mongodb image for posts and like button is below :
could it be possible to help on solving this or provide me a better exemple to follow ?
Best Regards,
Instead of making string or number in like, make an array, push req.user.id- user-id of the particular logged in user, and get the array length. This will give you total number of likes. In order to get toggle functionality, search that user id using findOne method and if no user found, push the id of the user who clicked the like button else pull the user id of the same user. Always print the length of the array so that while performing push or pull operations, you will get real time length of the like array.
Related
Let me preface this by saying there are endless threads that describe the issue WITH a button involved. Usually it's solved by just calling event.preventDefault() on the event you pass in. But what if the post request is called after something happens that isn't within the user's control, for instance, after a certain amount of frame?
makeScore (userId, gameId, time) {
fetch('http://localhost:3000/scores', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
time: time,
user_id: userId,
maze_id: gameId
})
}).catch(error => console.log('ERROR'))
};
loadNextLevel(option) {
console.log("Load Next");
// Making a score
clearInterval(this.interval);
this.time = document.getElementById('timer_text').innerHTML;
this.makeScore(this.userId, this.gameId, this.time);
console.log("Score made");
if (this.GAMESTATE != GAMESTATE.GAMEOVER) {
console.log(this.gameObjects);
document.getElementById('timer_text').innerHTML = "Time"
this.gameObjects = []
this.gameIndex += 1;
console.log(this.gameIndex, this.maxMazes);
if (option == "new") {
this.gameIndex = 0;
}
}
if (this.gameIndex >= this.maxMazes) {
this.gameEnd();
return;
}
With this code, I want to call makeScore only after loadNextLevel is called, and that is only getting called when a level is completed. But, currently the score is saved, and the page is refreshed. How can I prevent this refresh?
Edit: added scores controller below
class ScoresController < ApplicationController
def index
scores = Score.all
render json: scores
end
def show
score = Score.find(params[:id])
render json: score
end
def create
if (params[:user_id] && params[:maze_id])
user = User.find_by(id: params[:user_id])
maze = Maze.find_by(id: params[:maze_id])
score = user.scores.build(time: params[:time], username: user.username)
maze.scores << score
end
end
end
Edit #2 - Adding the function that it apparently gets stuck on after the fetch is completed.
function setLoggedOutUI() {
console.log("Log out UI set");
document.getElementById("timer_text").style.display = "none";
document.getElementById("logInButton").innerHTML = "Log in";
document.getElementById("userInfo").innerHTML = "Please Log in to play";
document.getElementById("logInField").style.display = "inline-block";
document.getElementById("play").innerHTML = "Log in";
document.getElementById("scores").style.display = "none";
document.getElementById("usernameText").style.display = "inline";
uiSwitch = false;
}
I had this same issue, in my case the problem was caused by the live server. I had in the same folder the client (HTML,CSS, JS, etc.) and the database (I was using the json-server extension). The problem was that the live server was detecting the changes in the db.json file. When fetch post or delete data, it triggers the live reload in the live server extension. So I solved it by dividing the folders and opening them separately in vs code, then, inside the client folder, run live server and it worked perfectly. More details here
If this is a vague question, please let me know, so that I can specify. I really want to get around this stump.
I have read several cheat sheets regarding how to properly broadcast a message to a client room. My reference is: https://github.com/socketio/socket.io/blob/master/docs/emit.md.
To describe my problem:
The chat page comes up with the send button correctly, however as soon as I click send, nothing is ever sent.
The interesting thing, is that whenever I am not using rooms, and just use the default namespace, I can get messages.
Any idea on what is going on? Thank you!!
server.js
io.on('connection', function(socket) {
global.rooms = 'room1';
socket.on('subscribe', function(room) {
socket.join(rooms);
console.log(socket.id + 'joining room', rooms)
})
socket.on('chat', function(data) {
io.to(rooms).emit('chat', data);
})
})
chat.jade
extends layout
block content
h2.page-header Chat Page
div(id='the-chat')
div(id='chat-window')
div(id='output')
input(id='handle', type='text', value = user.name, style= 'width: 0px; visibility: hidden;')
input(id='message', type='text', placeholder='message')
button(id='send' value='Send') Send
//imports the socket.io functionality on the client side for the chat.jade application
script(src="/socket.io/socket.io.js")
script.
//variable created that mirrors connection made in the backend
//matches the connection made in the server side
var socket = io.connect('http://localhost:3000')
//Query dom
var message = document.getElementById('message')
var handle = document.getElementById('handle')
var btn = document.getElementById('send')
var output = document.getElementById('output')
//emit events
btn.addEventListener('click', function() {
socket.emit('chat', {
message: message.value,
handle: handle.value
})
})
socket.on('chat', function(data) {
output.innerHTML += '<p><strong>' + data.handle + ': </strong>' + data.message + '</p>';
document.getElementById('message').value = "";
})
Home.jade
extends layout
block content
h2.page-header(style = "text-align: center;").
Home Page
if (user.isTutor)
b(style = "text-align: center;")
form(method='post', action = '/home/available')
input.btn.btn-primary(type = 'submit',name='isAvailable', value = 'Available', id = 'button4')
form(method='post', action = '/home/unavailable')
input.btn.btn-primary(type = 'submit',name='isUnavailable', value = 'Unavailable', id = 'button5')
script(src="/socket.io/socket.io.js")
script.
var btn = document.getElementById('button4')
//var space = '#{user.room}'
var socket = io.connect()
btn.addEventListener('click', function() {
socket.emit('subscribe', 'room1')
})
div(id = 'magic')
form(method='get')
if (user.hasApplied)
input.btn.btn-primary(type = 'submit', onclick = "javascript: form.action = '/findatutor';" name='find', value = 'Find a Tutor', class = 'middle', id = 'button7')
else if (user.hasApplied == false)
input.btn.btn-primary(type = 'submit', onclick = "javascript: form.action = '/findatutor';" name='find', value = 'Find a Tutor', id = 'button1')
input.btn.btn-primary(type = 'submit', onclick = "javascript: form.action = '/apply';" name='become', value = 'Become a Tutor', id = 'button2')
Home.js
router.post('/available', ensureAuthenticated, (req,res,next) => {
var io = res.locals['socketio']
db.collection('DefaultUser').update({_id: req.user._id}, {$set: {isAvailable: true}});
res.redirect('../chat')
})
The problem is you did not specify where to render the messages. As I understood, you have no problem creating rooms, so I will explain step by step how to handle after that point.
According to your code this is the communication between server and client for sending messages
//server.js
socket.on('chat', function(data) {
io.to(rooms).emit('chat', data);
})
//chat.jade
btn.addEventListener('click', function() {
socket.emit('chat', {
message: message.value,
handle: handle.value
})
})
First client is sending the message (typing the input box) to the server and when server receives it, it has to send the same message to all other clients. once clients receive this message, clients have to figure out how to display it. But when server sends the received message, then you have to initiate a new event. let's call it display. so your code should be like this:
//server.js
//i always use arrow functions, but I will follow along your code
socket.on('chat', function(data) {
io.to(rooms).emit('display', data);
})
Now your client should be listening for this event and should be handling where to display it:
//chat.jade
socket.on('display', (data) => {
const displayedMessage = pug.render(messageTemplate, {
message: data.message,
})
$messages.insertAdjacentHTML('beforeend', displayedMessage)
})
Since Jade has been renamed to pug, i used pug here. So i will render {message: data.message} into html in a script tag then placed it into DOM ELEMENT $message.
I am not sure where you want to render handle:handle.value i will just show how to display message. similarly, you can handle it.
Now what are messageTemplate, $messages and insertAdjacentHTML()?
create a div tag and a script tag in your html
//this is where you are gonna display the message
<div id="messages" class="chat__messages"></div>
<!-- template messages -->
<script id="message-template" type="text/html">
<div>
<p>{{message}}</p>
</div>
</script>
//chat.jade
const $messages = document.getElementById("messages");
const messageTemplate = document.getElementById("message-template").innerHTML;
The insertAdjacentHTML() method inserts a text as HTML, into a specified position. you can get more explanation and examples here:
https://www.w3schools.com/jsref/met_node_insertadjacenthtml.asp
socket io code looks long and complicated but if you know the logic and move step by step it will easy to implement it.
Try
io.in(rooms).emit('chat', data);
Missing this under global.rooms = 'room1';
var rooms = global.rooms
Longer Answer: At least what I can see, you've added "rooms" as a property on the global object (idk where 'global' itself is defined, but if it's not throwing an error, I'm assuming you defined it above). While it's a property, the variable 'rooms' that you're using as a namespace isn't defined at the time you're calling it, so it doesn't know where to emit the message.
Even more answer: Also, if you're intending to add additional rooms to global.rooms, I think you might want to use a hashlist to store them, so that you can easily access them as global.rooms[room], as well as easily add new rooms to the list ie global.rooms[room.name] = room
Here's the rundown. Users can view a razor page both anonymously and logged in. If they are logged in, they get certain features. In my controller, I have a boolean isAnonymous which I set to true or false depending on if there's a signed in user or not. I pass isAnonymous to my view model which gets sent to the razor page.
In the razor page, I have a javascript script tag which needs to retrieve that boolean value and, if isAnonymous is false (meaning someone is signed in), fire off one of two ajax calls to the server.
The first thing I do in my script tag is get the isAnonymous value and convert it to a JavaScript boolean with this:
var isAnonymous = #Json.Encode(Model.IsAnonymous);
after console logging, this appears to return correctly.
Then i put in my if statement. The summary here is if the user is not logged in, none of these functions nested inside the if statement should fire, because they take an ApplicationUser as part of the model. If there is no signed in user, Model.User is null and throws a Null Reference Exception. I thought putting my ajax calls inside the if statement would guard against the exception, but the the logic seems to be blowing right through the if (isAnonymous == false) and hitting those functions despite the logic. Any thoughts as to why this is happening? When isAnonymous is true, I can't have the functions fire.
if (isAnonymous == false) {
if ($('.bookmark-btn').hasClass('bookmark-story-btn')) {
addBookmark();
} else {
removeBookmark();
}
function addBookmark() {
//bookmark a story btn click event
$('.bookmark-story-btn').on('click', function () {
var storyid;
//the storyid should come as a string -
//try to parse it as an int for the controller
if (!isNaN($(this).attr('storyid'))) {
storyid = parseInt($(this).attr('storyid'))
//verify successful conversion from string to int before ajax call
if (typeof (storyid) == 'number') {
var userid = $(this).attr('userId')
var newBookmark = {
UserId: userid,
StoryId: storyid,
};
$.ajax({
url: "/api/bookmark/new",
method: "POST",
data: newBookmark,
success: function (data) {
//remove the save bookmark btn and dynamically add
//the remove bookmark btn so
//the page doesn't require a refresh
$('.bookmark-story-btn').remove();
$('.bookmark-btn-group').append("<button bookmarkId='"
+ data.Id
+ "' userId=#Model.User.Id storyId=#Model.StoryId"
+" class='btn remove-bookmark-btn bookmark-btn'>"
+"<i class='fas fa-2x fa-bookmark'></i></button>")
removeBookmark();
},
error: function (error) {
$('.page-alert').css('visibility', 'visible')
.html("Whoops. Something went wrong."
+" Adding the bookmark failed.")
//automatically close the alert-danger div
//after 2 seconds
setTimeout(function () {
$('.page-alert').css('visibility', 'hidden')
}, 3000);
}
});
}
}
});
}
function removeBookmark() {
//remove a bookmark click event
$('.remove-bookmark-btn').on('click', function () {
if (!isNaN($(this).attr('bookmarkid'))) {
bookmarkid = parseInt($(this).attr('bookmarkid'))
//verify successful conversion from string to int before ajax call
if (typeof (bookmarkid) == 'number') {
//call the ajax
$.ajax({
url: "/api/bookmark/" + bookmarkid,
method: "DELETE",
success: function (data) {
//show-hide the appropriate icons
$('.remove-bookmark-btn').remove();
$('.bookmark-btn-group').append("<button userId=#Model.User.Id"
+" storyId=#Model.StoryId class='btn bookmark-story-btn"
+" bookmark-btn'><i class='far fa-2x fa-bookmark'>"
+"</i></button>")
addBookmark();
},
error: function (error) {
$('.page-alert').css('visibility', 'visible')
.html("Whoops. Something went wrong here."
+" Removing the bookmark didn't work.")
//automatically close the alert-danger div
//after 2 seconds
setTimeout(function () {
$('.page-alert').css('visibility', 'hidden')
}, 3000);
}
})
}
}
})
}
}
You can use Request.IsAuthenticated in both the Razor view:
#if(Request.IsAuthenticated)
{
<script>
' your authenticated client side script here
</script>
}
And then check again server side when posting in your controller for example:
public ActionResult Index()
{
if(Request.IsAuthenticated)
{
//server logic here
}
}
Better still if you decorate the method with the AuthoriseAttribute the user will get an 403 Unauthorized.
You can then do something similar server side for the UserId:
[Authorize]
public ActionResult Index()
{
var userId = User.Identity.Name;
}
Then you don't even need to pass the UserId about. This is all based on using the common Identity practices:
https://learn.microsoft.com/en-us/aspnet/identity/overview/getting-started/introduction-to-aspnet-identity
I am trying to do something seemingly very simple but I'm having trouble working it out. Users can submit some text using a HTML form with POST method. This is then sent off to an API for processing, and returns with a JSON object. I then just want the app.js file to send this JSON object back so I can play around with it using JQuery.
Here is the .post method in my app.js
app.post('/', function(req, res){
console.log("starting app.post");
// See User Modeling API docs. Path to profile analysis is /api/v2/profile
// remove the last / from service_url if exist
var parts = url.parse(service_url.replace(/\/$/,''));
var profile_options = { host: parts.hostname,
port: parts.port,
path: parts.pathname + "/api/v2/profile",
method: 'POST',
headers: {
'Content-Type' :'application/json',
'Authorization' : auth }
};
// create a profile request with the text and the https options and call it
create_profile_request(profile_options,req.body.content)(function(error,profile_string) {
if (error) {res.render('home.html',{'error': error.message});
console.log("errormessage: "+error.message);
}
else {
// parse the profile and format it
var profile_json = JSON.parse(profile_string);
var flat_traits = flatten.flat(profile_json.tree);
// Extend the profile options and change the request path to get the visualization
var fileName="file 1"; //this will eventually be imported automatically
//console.log(flat_traits);
var scoreObject={"title":fileName, "percentage":functions.matchPercentage(flat_traits)}
res.send(scoreObject); //this is what I assume should send this back client-side
});
}
});
});
// creates a request function using the https options and the text in content
// the function that return receives a callback
var create_profile_request = function(options,content) {
return function (/*function*/ callback) {
// create the post data to send to the User Modeling service
var post_data = {
'contentItems' : [{
'userid' : 'dummy',
'id' : 'dummyUuid',
'sourceid' : 'freetext',
'contenttype' : 'text/plain',
'language' : 'en',
'content': content
}]
};
// Create a request to POST to the User Modeling service
var profile_req = https.request(options, function(result) {
result.setEncoding('utf-8');
var response_string = '';
result.on('data', function(chunk) {
response_string += chunk;
});
result.on('end', function() {
if (result.statusCode != 200) {
var error = JSON.parse(response_string);
console.log("status: "+result.statusCode);
callback({'message': error.user_message}, null);
console.log(error.user_message);
} else
callback(null,response_string);
});
});
profile_req.on('error', function(e) {
callback(e,null);
});
profile_req.write(JSON.stringify(post_data));
profile_req.end();
}
};
So I presume res.send is what passes the data across to the client-side, but then how do I receive the data on the client-side? This is my attempt at the JScript:
$.getJSON('/').done(function(data){
$('#resultsList').append('<li data-icon="arrow-r" data-iconpos="right" id="'+
data.title+'"> <a href="#breakdownDialog"> <div id="cvResults"><h3>'+
data.title+'</h3> <span>'+data.percentage+
'%</span></div></a><div id="output"></div></li>');
console.log(data.title+data.percentage);
}
});
I want to take some of the values from the JSON object and put them in a list on the existing HTML page. At the moment this just takes me to a different blank page that says Undefined.
How should I grab the JSON data from the server?
EDIT: Here's the HTML form I am submitting the data with:
<form method="POST" id="submitForm">
<fieldset>
<textarea id="textArea" required="true" rows="5" name="content"></textarea>
<button class="btn btn-block" type="submit">
Analyse
</button>
</fieldset>
</form>
Are you sure that you are sending json with res.send()? Try to set header
res.set('Content-Type', 'application/json') or use this res.json() instead of res.send()
I have this ajax call here in a script tag at the bottom of my page. Everything works fine! I can set a breakpoint inside the 'updatestatus' action method in my controller. My server gets posted too and the method gets called great! But when I put the javascript inside a js file the ajax call doesn't hit my server. All other code inside runs though, just not the ajax post call to the studentcontroller updatestatus method.
<script>
$(document).ready(function () {
console.log("ready!");
alert("entered student profile page");
});
var statusdropdown = document.getElementById("enumstatus");
statusdropdown.addEventListener("change", function (event) {
var id = "#Model.StudentId";
var url = '#Url.Action("UpdateStatus", "Student")';
var status = $(this).val();
$.post(url, { ID: id, Status: status }, function (data) {
// do something with the returned value e.g. display a message?
// for example - if(data) { // OK } else { // Oops }
});
var e = document.getElementById("enumstatus");
if (e.selectedIndex == 0) {
document.getElementById("statusbubble").style.backgroundColor = "#3fb34f";
} else {
document.getElementById("statusbubble").style.backgroundColor = "#b23f42";
}
}, false);
</script>
Now I put this at the bottom of my page now.
#section Scripts {
#Scripts.Render("~/bundles/studentprofile")
}
and inside my bundle.config file it looks like this
bundles.Add(new ScriptBundle("~/bundles/studentprofile").Include(
"~/Scripts/submitstatus.js"));
and submitstatus.js looks like this. I know it enters and runs this code because it I see the alert message and the background color changes. So the code is running. Its just not posting back to my server.
$(document).ready(function () {
console.log("ready!");
alert("submit status entered");
var statusdropdown = document.getElementById('enumstatus');
statusdropdown.addEventListener("change", function (event) {
var id = "#Model.StudentId";
var url = '#Url.Action("UpdateStatus", "Student")';
var status = $(this).val();
$.post(url, { ID: id, Status: status }, function (data) {
// do something with the returned value e.g. display a message?
// for example - if(data) { // OK } else { // Oops }
});
var e = document.getElementById('enumstatus');
if (e.selectedIndex == 0) {
document.getElementById("statusbubble").style.backgroundColor = "#3fb34f";
} else {
document.getElementById("statusbubble").style.backgroundColor = "#b23f42";
}
}, false);
});
In the console window I'm getting this error message.
POST https://localhost:44301/Student/#Url.Action(%22UpdateStatus%22,%20%22Student%22) 404 (Not Found)
Razor code is not parsed in external files so using var id = "#Model.StudentId"; in the main view will result in (say) var id = 236;, in the external script file it will result in var id = '#Model.StudentId'; (the value is not parsed)
You can either declare the variables in the main view
var id = "#Model.StudentId";
var url = '#Url.Action("UpdateStatus", "Student")';
and the external file will be able to access the values (remove the above 2 lines fro the external script file), or add them as data- attributes of the element, for example (I'm assuming enumstatus is a dropdownlist?)
#Html.DropDownListFor(m => m.enumStatus, yourSelectList, "Please select", new { data_id = Model.StudentId, data_url = Url.Action("UpdateStatus", "Student") })
which will render something like
<select id="enumStatus" name="enumStatus" data-id="236" data-url="/Student/UpdateStatus">
Then in the external file script you can access the values
var statusbubble = $('#statusbubble'); // cache this element
$('#enumStatus').change(function() {
var id = $(this).data('id');
var url = $(this).data('url');
var status = $(this).val();
$.post(url, { ID: id, Status: status }, function (data) {
....
});
// suggest you add/remove class names instead, but if you want inline styles then
if (status == someValue) { // the value of the first option?
statusbubble.css('backgroundColor', '#3fb34f');
} else {
statusbubble.css('backgroundColor', '#b23f42');
};
});