for example when i join room-1 then room-2 then room-3 and send message in room-3 that message will be emitted 3 times, when it should get sent just one time. i'm using vanilla JavaScript in the client side
server side
namespaces.forEach(namespace => {
// join namespace
io.of(namespace.endpoint).on('connection', socket => {
console.log(`${socket.id} has joined the ${namespace.endpoint}`)
socket.emit('nsRooms', {data: namespace.rooms})
// Join room
socket.on('joinRoom', async (payload, cb) => {
const room = Array.from(socket.rooms)[1]
room && socket.leave(room)
socket.join(payload.data.roomName)
const numOfMem = await io.of(payload.data.nsp).in(payload.data.roomName).allSockets()
cb(Array.from(numOfMem).length)
})
socket.on('sendMessage', payload => {
const room = Array.from(socket.rooms)[1]
const nsp = socket.nsp.name
io.of(nsp).to(room).emit('updateMessage', payload)
})
})
})
client side \
Here is when i join rooms and send messages
function joinRoom(roomName) {
form.removeEventListener('submit', e => submitMsg(e))
nsSocket.emit('joinRoom', {data: {nsp: nsSocket.nsp, roomName}}, numberOfMember => {
document.getElementById('current-room').innerHTML = `<span class="curr-room-text">${roomName}</span> <span class="curr-room-num-users">Users: ${numberOfMember}<span class="glyphicon glyphicon-user"></span></span>`
})
messages.innerHTML = ''
nsSocket.on('updateMessage', payload => {
messages.innerHTML +=
`
<li>
<div class="user-image">
<img src="https://via.placeholder.com/30" />
</div>
<div class="user-message">
<div class="user-name-time">rbunch <span>${new Date(Date.now()).toDateString()}</span></div>
<div class="message-text">${payload.data}</div>
</div>
</li>
`
})
}
form.addEventListener('submit', e => submitMsg(e))
function submitMsg(e) {
e.preventDefault()
const msg = userMessage.value
msg.length > 0 && nsSocket.emit('sendMessage', {data: msg})
userMessage.value = ''
}
This happens because removeEventListener needs to work with the exact same function reference that was registered, and (e) => submitMsg(e) creates a new lambda all the time. Which means that each time you join a room, a new event handler will be added, without removing the old one.
I created a quick sample app here with the following code that would fix your issue. If you click 'Join some room' three times and then click 'Send message', only one console.log will appear (expand the console on the right hand side to see the result).
const testBtn = document.getElementById('joinRoom');
const form = document.getElementById('chatForm');
testBtn.addEventListener('click', () => {
form.removeEventListener('submit', submitMsg);
// ... some other code
form.addEventListener('submit', submitMsg);
});
submitMsg = (e) => {
e.preventDefault();
console.log('submitMsg() called!');
return false;
}
Related
I have a WebSocket that adds people to a messaged list when they receive a message. I use a fetch to get information from the server to build the messaged list, but I cannot find an easy way to get the code to pause until the fetch completes. I cannot add await because the funciton is inside a websocket that isn't async. Does anyone have any suggestions on this?
socket.on('receive_message', function(data) {
let messaged_user_li = document.getElementById('messaged_user_li'+data['from_user']);
console.log('messaged_user_li: '+messaged_user_li)
if (messaged_user_li == null) {
console.log('if fired')
//THIS is the fetch I want to pause the function until complete. Note that the await here does not work since the funciton isn't async.
await fetch('/get_messaged_user/'+from_user).then((response) => {
response.json().then((data2) => {
loadMessagedUser(data2[0].id, data2[0].avatar, data2[0].username, data2[0].last_seen);
});
});
messaged_user_li = document.getElementById('messaged_user_li'+data['from_user']);
}
console.log('messaged_user_li: '+messaged_user_li)
let message_user = document.getElementById('message_user'+data['from_user']);
let message_target = document.getElementById("message_target"+data['from_user']);
if (messaged_user_li.classList.contains('active') == false) {
messaged_user_li.classList.add('flash-message');
}
if (message_user != null) {
data = `
<li class="clearfix">
<div class="message-data text-right">
<span class="message-data-time">just now</span>
</div>
<div class="message other-message float-right">`+data['message']+`</div>
</li>`;
message_target.innerHTML += data;
//Move scroller to bottom when message received
myDiv = message_user.querySelector(".chat-history");
myDiv.scrollTop = myDiv.scrollHeight;
}
});
Actually You can add async function in socket.on
socket.on('receive_message',async function(data) {
I'm using an API to get information for a database sort of thing. I want the images to be displayed to the right of the text but the images aren't showing up at all. I tried multiple different keys and still nothing. Here is what it currently looks like:
The images are not showing up as you can see.
Here is the JS (its pulling the data from here https://api.tvmaze.com/shows/347/episodes):
// DATABASE const sunnyList = document.getElementById('sunnyList'); let sunnyInfo = [];
searchBar.addEventListener('keyup', (e) => { const searchTarget = e.target.value.toLowerCase(); const filteredSunny = sunnyInfo.filter(sunny => {
return sunny.name.toLowerCase().includes(searchTarget) || sunny.airdate.toLowerCase().includes(searchTarget) || sunny.airtime.includes(searchTarget) });
displayInfo(filteredSunny); });
const loadLayout = async () => {
try {
const res = await fetch('https://api.tvmaze.com/shows/347/episodes');
sunnyInfo = await res.json();
displayInfo(sunnyInfo);
} catch (err) {
console.error(err);
} };
const displayInfo = (sunny) => {
const htmlString = sunny
.map((sunny) => {
return `
<li class="character">
<div class="detail">
<h2>${sunny.name}</h2>
<p>Season ${sunny.season} Episode ${sunny.number}</p>
<p>${sunny.airdate}</p>
<p>${sunny.airtime}</p>
<p>${sunny.rating.average}</p>
</div>
<img src="${sunny.image}"></img>
</li>
`;
})
.join('');
sunnyList.innerHTML = htmlString; };
loadLayout();
I've tried sunny.image.medium and sunny.image.original but it still doesn't show up.
Any help is appreciated :)
The image is not a url string, but an object with the following shape:
{
medium: string,
original: string
}
where both strings contain the actual image URLs.
For your use case medium probably makes more sense, so you can do this:
<img src="${sunny.image?.medium}"></img>
Edit
Added optional chaining because some items do not have image property.
The problem your are facing is that not all objects have images.
Please try this code:
const displayInfo = (sunny) => {
const htmlString = sunny
.map((sunny) => {
const img = sunny.image ? sunny.image.medium : "https://picsum.photos/200/300"
return `
<li class="character">
<div class="detail">
<h2>${sunny.name}</h2>
<p>Season ${sunny.season} Episode ${sunny.number}</p>
<p>${sunny.airdate}</p>
<p>${sunny.airtime}</p>
<p>${sunny.rating.average}</p>
</div>
<img src=${img} />
</li>
`;
})
.join('');
sunnyList.innerHTML = htmlString; };
I am pulling data with fetch api. but I could not retrieve the data in the todosApi section of the last data I pulled. how can i pull data?
const usersApi = () =>{
fetch("https://jsonplaceholder.typicode.com/users").
then(response=>response.json()).
then(girilenVeri).
catch(e=>console.log(e));
}
const todosApi = (element) =>{
fetch(`https://jsonplaceholder.typicode.com/todos/?userId=${element.id}`).
then(response=>veriOlusturucu(response.json(), element)).//I can't get the data in response.json
catch(e=>console.log(e));
}
const girilenVeri = (data) => {
let cumle = [];
document.getElementById('arama').addEventListener('keydown',function(e){
if(e.keyCode == 8){
cumle.pop();
veriEslestir(data, cumle);
}
});
document.getElementById('arama').addEventListener('keypress',function(e){
cumle.push(String.fromCharCode(e.keyCode));
veriEslestir(data, cumle);
});
}
const veriEslestir = (data,cumle) =>{
veri = cumle.toString().replace(/,/g,"");
data.forEach(element => {
if(element.username.toLowerCase() == veri.toLowerCase()){
todosApi(element);
}
});
}
const veriOlusturucu = (todo,element) => {
console.log(todo);
console.log(element);
let html = "";
html =`
<h5 class="card-title">İletişim</h5>
<ul class="list-group">
<li class="list-group-item">Kullanıcı Adı: ${element.username}</li>
<li class="list-group-item">E-Mail: ${element.email}</li>
<li class="list-group-item">Web Site: ${element.website}</li>
<li class="list-group-item">Şirket: ${element.company.name}</li>
<li class="list-group-item">Telefon No: ${element.phone}</li>
<li class="list-group-item">Adres: ${element.address.street} ${element.address.suite} ${element.address.city} ${element.address.zipcode}</li>
</ul>
<h5 class="card-title">Yapılacaklar Listesi</h5>
<ul class="list-group">
`;
todo.forEach(element=>{//I need to access the data here with loop
html+=`
<li class="list-group-item">Kullanıcı Adı: ${element.title}</li>
`;
});
html +=`</ul>`;
document.getElementById('veriListele').innerHTML=html;
}
document.addEventListener('DOMContentLoaded',usersApi());
How do I return the "response.json" part with a foreach?
There is no problem with user information. but there is a problem with todo information. sends it to me as a promise. I can't access the promise result
If I can get into the "PromiseResult" the problem will be solved. but i can't reach
You're not quite using the fetch api correctly with the todo list. If you notice, on your userApi method, you include an extra .then which is necessary to return the json data rather than the promise:
const usersApi = () =>{
fetch("https://jsonplaceholder.typicode.com/users").
then(response=>response.json()).
then(girilenVeri).
catch(e=>console.log(e));
}
const todosApi = (element) =>{
fetch(`https://jsonplaceholder.typicode.com/todos/?userId=${element.id}`)
.then(response=>response.json())
.then(data => veriOlusturucu(data, element))
catch(e=>console.log(e));
}
Try this out.
Issue:
I want to return the button Element within my document that matches the specified selector, in this case ".comment-body__interaction--delete" but keep getting a return of null every time I console.log the variable that contains the return element.
Background Info
The HTML element I'm Trying to target has been inserted into the document via innerHTML.
All my scripts are at the bottom of the index.html page
I'm using querySelector at the bottom of the js document.
I know my class name is correct because I can style it via CSS.
my code
// LOCATION VARIABLES ***
const conversation = document.querySelector('.conversation-container-posted');
const form = document.querySelector('form');
console.log(form);
// Array THAT HOLDS ALL MY COMMENT OBJECTS
let objectsArray;
// VARIABLE THAT HOLDS MY HTML TEMPLATE
const template = (singleCommentObj) => {
return `
<article class="comment-container">
<figure class="comment-container__picture">
<img class="comment-container__picture-img" src="${singleCommentObj.image}" alt="profile picture" />
</figure>
<div class="comment-body">
<h3 class="comment-body__name">${singleCommentObj.name}</h3>
<div class="comment-body__date">${singleCommentObj.date}</div>
<article class="comment-body__comment"><p>${singleCommentObj.comment}</p></article>
<div class="comment-body__interaction">
<div class="comment-body__interaction--likes">Likes</div>
<button id="${singleCommentObj.id}" class="comment-body__interaction--delete">Delete</button>
</div>
</div>
</article>
<hr class="comment-container__divider"/>
`;
};
const displayComment = (object) => {
let staticComments = object
.sort((a, b) => b.timestamp - a.timestamp)
.map((values) => {
values.image = 'https://loremflickr.com/48/48';
values.date = moment.unix(values.timestamp / 1000).fromNow();
return template(values);
})
.join('');
conversation.innerHTML = staticComments;
};
// Gets AN ARRAY OF OBJECTS FROM THE api AND ASSIGNS IT TO objectsArray
// CALLS displayComment WITH objectsArray AS A PARAMETER TO INSERT ITS CONTENT INTO THE DOM
axios
.get('https://project-1-api.herokuapp.com/comments?api_key=7d8d085e-486e-42dc-b836-58009cbfa68f')
.then((response) => {
objectsArray = response.data;
displayComment(objectsArray);
})
.catch((error) => {
console.log(error);
});
form.addEventListener('submit', (e) => {
e.preventDefault();
let fluidObject = new FormData(e.target);
fluidObject = Object.fromEntries(fluidObject);
axios
.post('https://project-1-api.herokuapp.com/comments?api_key=7d8d085e-486e-42dc-b836-58009cbfa68f&content-type=application/json', {
name: fluidObject.name,
comment: fluidObject.comment,
})
.then((response) => {
objectsArray.push(response.data);
displayComment(objectsArray);
})
.catch((error) => {
console.log(error);
});
});
// DELETE
const a = document.querySelector('.comment-body__interaction--delete');
console.log(a);
This console.log(a) returns NULL
The code that creates the said element, displayComment is in an asynchronous actions callback.
You have to wait for the action to complete before you try to access the element.
In other words const a = document.querySelector('.comment-body__interaction--delete'); executes before your request was successful and the elements were created.
I have a few buttons with different categories. When the user clicks on the button, the correct category should be displayed. In every category, there are a few products, each with their own "add to cart"-button.
So, the user clicks "beds" and then adds item #3 to the cart (which updates and so on).
I have managed to do this with classes IF the user can't choose a category. It also works without classes if I add the buttons dynamically in js. But again, without allowing the user to choose a category.
I also want the user to be able to search for an item, get the item/ items displayed, and add it to the cart.
Get Products
class Products {
async getProducts() {
try {
const result = await fetch("/data/products.json");
const data = await result.json();
let products = data.items;
products = products.map((item) => {
const { category, title, price } = item;
const { id } = item.sys;
const image = item.image.url;
return { category, title, price, id, image };
});
return products;
} catch (error) {
console.log(error);
}
}
}
Display Products
class UI {
async displayProducts(products, searchText) {
let matches = products.filter(item => {
const regex = new RegExp(`^${searchText}`,'gi');
return item.category.match(regex);
})
let result = "";
matches.forEach((product) => {
result += `
<!-- single product -->
<article class="product">
<div class="img-container">
<img
src=${product.image}
alt="product"
class="product-img"
/>
<button class="bag-btn" data-id=${product.id}>
<i class="fas fa-shopping-cart">add to cart</i>
</button>
</div>
<h3>${product.title}</h3>
<h4>$${product.price}</h4>
</article>
<!-- end single product -->
`;
});
productDOM.innerHTML = result;
}
getBagButtons() {
const buttons = [...document.querySelectorAll(".bag-btn")];
In HTML I used onclick="displayProducts('bed')"
This will not work tho, since displayProducts is in a class.
I have also tried to add an id to each button and add an eventlistener in DOMContentLoaded, but that wrecks the rest of my DOMContentLoaded stuff
DOMContentLoaded
document.addEventListener("DOMContentLoaded", () => {
const ui = new UI();
const products = new Products();
// setup app
ui.setupAPP();
products
.getProducts()
.then((products) => {
ui.displayProducts(products);
Storage.saveProducts(products);
})
.then(() => {
ui.getBagButtons();
ui.cartLogic();
});
});
These are just a few of the things I've tried, but for each try, one issue is fixed but one or more issues are added, so I could really use some help here. Thanks!
These are the changes we made:
All category buttons gets this event listener
onclick="searchNdisplay(new UI, new Products, 'category text');"
This initializes the ui and products and they get displayed, so we changed it to use the searchNdisplay function
document.addEventListener("DOMContentLoaded", () => {
const ui = new UI();
const products = new Products();
// setup app
ui.setupAPP();
searchNdisplay(ui, products, "");
Storage.saveProducts(products);
});
This function repopulates the page with products that met the search criteria.
function searchNdisplay(ui, products, search)
{
products
.getProducts()
.then((products) => {
if (search == "")
{
ui.displayProducts(products);
}
else
{
ui.displayProducts(products, search);
}
})
.then(() => {
ui.getBagButtons();
ui.cartLogic();
});
}