scrollTop = scrollHeight not working in chatbot - javascript

As part of Technigo bootcamp, my team has created a chatbot and got it to work well! (see it live here) However, when the content is bigger than the chat container a scroll appear and it wont scroll to the last message.
We have tried using (on both function showMessage and showFinalMessage)
chat.scrollTop = chat.scrollHeight
But it does not seem to work as seen on below pic
enter image description here
See JS code here
// All the DOM selectors stored as short variables
const chat = document.getElementById('chat')
const inputWrapper = document.getElementById('input-wrapper')
// Global variables, if you need any, declared here
let stepNumber = 1
// Functions declared here
const botAnswer = (message) => {
showMessage (message, 'bot')
}
const userAnswer = (message) => {
showMessage (message, 'user')
}
// This function will add a chat bubble in the correct place based on who the sender is
const showMessage = (message, sender) => {
console.log(sender)
if (sender === 'user') {
chat.innerHTML += `
<section class="user-msg">
<div class="bubble user-bubble">
<p>${message}</p>
</div>
<img class="profile-pic" src="assets/user-img.jpeg" alt="User" />
</section>
`
} else if (sender === 'bot') {
chat.innerHTML += `
<section class="bot-msg">
<img class="profile-pic" src="assets/bot-img.jpeg" alt="Bot" />
<div class="bubble bot-bubble">
<p>${message}</p>
</div>
</section>
`
}
// This little thing makes the chat scroll to the last message when there are too many to be shown in the chat box
chat.scrollTop = chat.scrollHeight
}
const nextStep = (message) => {
console.log( 'stepNumber', stepNumber)
if (answerNumber === 1) {
userAnswer (message)
setTimeout (() => showPlace(message),1000)
} else if (answerNumber === 2) {
userAnswer (message)
setTimeout (() => showVibe(message),1000)
} else if (answerNumber === 3) {
userAnswer (message)
setTimeout (() => showOutfit(message),1000)
}
}
// Starts here
const showMood = () => {
answerNumber = 1
botAnswer(`Welcome! How's the party mood?`)
inputWrapper.innerHTML = `
<div class="slider-container">
<div class="emoji-container">
<p class="emoji">🍺</p>
<p class="emoji">🍻</p>
<p class="emoji">🍹</p>
<p class="emoji">🍸</p>
<p class="emoji">🍷</p>
<p class="emoji">🍾</p>
</div>
<input id="sliderinput" type="range" min="1" max="100" value="50" class="slider">
</div>
`
document.getElementById('sliderinput')
.addEventListener('mouseup', () => nextStep ('This is my mood!'))
}
const showPlace = () => {
answerNumber++
botAnswer(`Good to know! But where's the party at?`)
inputWrapper.innerHTML = `
<button id="nightclubBtn">Nightclub</button>
<button id="cocktailBtn">Cocktail bar</button>
`
document.getElementById('nightclubBtn')
.addEventListener('click', () => nextStep('Nightclub'))
document.getElementById('cocktailBtn')
.addEventListener('click', () => nextStep('Cocktail bar'))
}
const showVibe = (place) => {
answerNumber++
if (place === 'Nightclub') {
botAnswer(`Are we talking Berghain or Studio54?`)
inputWrapper.innerHTML = `
<button id="berghainBtn">Berghain</button>
<button id="studio54Btn">Studio 54</button>
`
document.getElementById('berghainBtn')
.addEventListener('click', () => nextStep('Berghain'))
document.getElementById('studio54Btn')
.addEventListener('click', () => nextStep('Studio 54'))
} else {
botAnswer(`In the mood for Cosmopolitan or Old Fashioned?`)
inputWrapper.innerHTML = `
<button id="cosmoBtn">Cosmopolitan</button>
<button id="oldfashionBtn">Old Fashioned</button>
`
document.getElementById('cosmoBtn')
.addEventListener('click', () => nextStep('Cosmopolitan'))
document.getElementById('oldfashionBtn')
.addEventListener('click', () => nextStep('Old fashioned'))
}
}
const showOutfit = (outfit) => {
answerNumber++
botAnswer(`I got the perfect outfit for you! Party on!`)
const showFinalMessage = () => {
chat.innerHTML += `
<section class="bot-msg">
<img class="profile-pic" src="assets/bot-img.jpeg" alt="Bot" />
<div class="bubble bot-bubble final">
<img class="outfit-gif" id="outfitGif" src=""/>
</div>
</section>
`
chat.scrollTop = chat.scrollHeight
}
if (outfit === 'Berghain') {
showFinalMessage()
document.getElementById("outfitGif").src = "assets/berghain.gif"
}
else if (outfit === "Studio 54") {
showFinalMessage()
document.getElementById("outfitGif").src = "assets/studio54.gif"
}
else if (outfit === "Cosmopolitan") {
showFinalMessage()
document.getElementById("outfitGif").src = "assets/cocktail.gif"
}
else if (outfit === "Old fashioned") {
showFinalMessage()
document.getElementById("outfitGif").src = "assets/oldfashioned.gif"
}
inputWrapper.innerHTML = ""
}
setTimeout(showMood, 1000)
For reference, also see CSS code for main och chat here.

Related

Svelte variable not updating from localStorage without using setInterval and forcing it

I have an app that stores items into locations in localStorage and then displays the items in HTML.
One of the reasons i wanted to use Svelte was for reactive variables, but whenever I attempt to use a reactive variable that changes whenever localStorage.current_items changes, the ItemList variable doesn't change.
The only way I could get it to work is by using setInterval but that is not a great way to do it. How can I make it so that ItemList changes properly when the localStorage.current_items string changes.
<script lang="ts">
import {
getData,
createItem,
createLocation,
closeItem,
} from './lib/database.js';
import LocationSelector from './lib/LocationSelector.svelte';
import { flip } from 'svelte/animate';
import { writable } from 'svelte/store';
let DB = getData();
// load items from localstorage.items
let ItemList = [];
let Location;
setInterval(() => {
Location = localStorage.current_items;
ItemList = JSON.parse(localStorage.current_items).items;
}, 500);
console.log(ItemList);
let newItem = '';
let filter_showClosed = false;
function addNewItem(e) {
e.preventDefault();
console.log(newItem);
const newItemInput = document.querySelector(
'#newItemInput'
) as HTMLInputElement;
createItem(JSON.parse(Location).id, newItem);
newItem = '';
}
function newItemKeyDown(e) {
if (e.keyCode === 13) {
addNewItem(e);
}
}
</script>
<LocationSelector />
<div class="app">
<input
type="text"
id="newItemInput"
bind:value={newItem}
placeholder="Add a new item"
on:keydown={newItemKeyDown}
/>
<button
id="filter_showClosed"
data-active="false"
on:click={function () {
filter_showClosed = !filter_showClosed;
let el = document.getElementById('filter_showClosed');
if (filter_showClosed) {
el.innerHTML = 'Hide closed';
el.dataset.active = 'true';
} else {
el.innerHTML = 'Show closed';
el.dataset.active = 'false';
}
}}>Show closed</button
>
<!-- <button
id="deleteClosed"
on:click={function () {
let it = items;
for (let i = 0; i < it.length; i++) {
if (it[i].closed == true) {
it.splice(i, 1);
}
}
items = it;
sort_items(items);
}}>Delete all closed</button
> -->
<div class="list">
{#each ItemList as item, index (item.id)}
<div class="item {item.closed}" animate:flip={{ duration: 100 }}>
{#if item.closed == false || (filter_showClosed == true && item.closed == true)}
<div>
<img
src="/up.svg"
class="item-icon"
class:closed={item.closed == true}
alt="move item up in priority"
on:click={function () {
// increaseLevel({ item });
}}
/>
{item.name} ({index})
</div>
<div>
{#if item.closed == false}
<img
src="/close.svg"
class="item-icon"
alt="close item"
on:click={function () {
console.log(Location.id);
closeItem(JSON.parse(Location).id, item.id);
}}
/>
{/if}
</div>
{/if}
</div>
{/each}
</div>
</div>
<style>
</style>
I tried using this writeable method, but that didn't work either as the variable still didn't change.
import { writable } from 'svelte/store';
const ItemList = writable([]);
let Location = {};
let newItem = '';
let filter_showClosed = false;
function addNewItem(e) {
e.preventDefault();
console.log(newItem);
const newItemInput = document.querySelector(
'#newItemInput'
) as HTMLInputElement;
createItem(Location.id, newItem);
newItem = '';
}
function newItemKeyDown(e) {
if (e.keyCode === 13) {
addNewItem(e);
}
}
// Update the Location object with the current value of localStorage.current_items as an object
Location = JSON.parse(localStorage.current_items);
// Update the ItemList store with the new location's items
ItemList.set(Location.items);
You should use a store that fully wraps the access to localStorage.
Something like:
function localStorageStore(key, initial) {
const value = localStorage.getItem(key)
const store = writable(value == null ? initial : JSON.parse(value));
store.subscribe(v => localStorage.setItem(key, JSON.stringify(v)));
return store;
}
Reading and writing is just a regular store, but on initial load the value comes from the storage and on setting the value, it is also written to storage.

jinja2.exceptions.UndefinedError: 'participant' is undefined

I'm trying to build a video chat webapp using Twilio following https://www.twilio.com/blog/build-video-chat-application-python-javascript-twilio-programmable-video, but I keep getting the error listed in the title. From what I've gathered, I'm trying to call upon the attributes of an object (sid, name) that was never really defined (participant), but I'm not sure where in my code to define it.
<body>
<h1>join existing jam</h1>
<form>
<label for="username">Name: </label>
<input type="text" name="username" id="username">
<button id="join_leave">join</button>
</form>
<p id="count"></p>
<div id="container" class="container">
<div id="local" class="participant"><div></div><div>Me</div></div>
<div id="{{ participant.sid }}" class="participant">
<div></div> <!-- the video and audio tracks will be attached to this div -->
<div>{{participant.name}}</div>
</div>
</div>
<script src="//media.twiliocdn.com/sdk/js/video/releases/2.3.0/twilio-video.min.js"></script>
<script>
let connected=false;
const usernameInput = document.getElementById('username');
const button = document.getElementById('join_leave');
const container = document.getElementById('container');
const count = document.getElementById('count');
let room;
function addLocalVideo() {
Twilio.Video.createLocalVideoTrack().then(track => {
let video = document.getElementById('local').firstChild;
video.appendChild(track.attach());
});
};
function connectButtonHandler(event) {
event.preventDefault();
if (!connected) {
let username = usernameInput.value;
if (!username) {
alert('Enter your name before connecting');
return;
}
button.disabled = true;
button.innerHTML = 'connecting...';
connect(username).then(() => {
button.innerHTML = 'leave';
button.disabled = false;
}).catch(() => {
alert('Connection failed. Is the backend running?');
button.innerHTML = 'join';
button.disabled = false;
});
}
else {
disconnect();
button.innerHTML = 'join';
connected = false;
}
};
function connect(username) {
let promise = new Promise((resolve, reject) => {
// get a token from the back end
fetch('/login', {
method: 'POST',
body: JSON.stringify({'username': username})
}).then(res => res.json()).then(data => {
// join video call
return Twilio.Video.connect(data.token);
}).then(_room => {
room = _room;
room.participants.forEach(participantConnected);
room.on('participantConnected', participantConnected);
room.on('participantDisconnected', participantDisconnected);
connected = true;
updateParticipantCount();
resolve();
}).catch(() => {
reject();
});
});
return promise;
};
function updateParticipantCount() {
if (!connected)
count.innerHTML = 'Disconnected.';
else
count.innerHTML = (room.participants.size + 1) + ' participants online.';
};
function participantConnected(participant) {
let participantDiv = document.createElement('div');
participantDiv.setAttribute('id', participant.sid);
participantDiv.setAttribute('class', 'participant');
let tracksDiv = document.createElement('div');
participantDiv.appendChild(tracksDiv);
let labelDiv = document.createElement('div');
labelDiv.innerHTML = participant.identity;
participantDiv.appendChild(labelDiv);
container.appendChild(participantDiv);
participant.tracks.forEach(publication => {
if (publication.isSubscribed)
trackSubscribed(tracksDiv, publication.track);
});
participant.on('trackSubscribed', track => trackSubscribed(tracksDiv, track));
participant.on('trackUnsubscribed', trackUnsubscribed);
updateParticipantCount();
};
function participantDisconnected(participant) {
document.getElementById(participant.sid).remove();
updateParticipantCount();
};
function trackSubscribed(div, track) {
div.appendChild(track.attach());
};
function trackUnsubscribed(track) {
track.detach().forEach(element => element.remove());
};
function disconnect() {
room.disconnect();
while (container.lastChild.id != 'local')
container.removeChild(container.lastChild);
button.innerHTML = 'Join call';
connected = false;
updateParticipantCount();
};
addLocalVideo();
button.addEventListener('click', connectButtonHandler);
</script>
</body>
Also, if it helps, this is the app.py that I'm calling from terminal:
import os
from dotenv import load_dotenv
from flask import Flask, render_template, request, abort
from twilio.jwt.access_token.grants import VideoGrant
load_dotenv()
twilio_account_sid=os.environ.get("TWILIO_ACCOUNT_SID")
twilio_api_key_sid = os.environ.get('TWILIO_API_KEY_SID')
twilio_api_key_secret = os.environ.get('TWILIO_API_KEY_SECRET')
app=Flask(__name__)
#app.route('/')
def index():
return render_template('joinJam.html')
#app.route('/login',methods=['POST'])
def login():
username=request.get_json(force=True).get('username')
if not username:
abort(401)
token=AccessToken(twilio_account_sid, twilio_api_key_sid, twilio_api_key_secret, identity=username)
token.add_grant(VideoGrant(room='My Room'))
return {'token': token.to_jwt().decode()}
Twilio developer evangelist here.
Your issue is in the HTML here:
<div id="container" class="container">
<div id="local" class="participant"><div></div><div>Me</div></div>
<div id="{{ participant.sid }}" class="participant">
<div></div> <!-- the video and audio tracks will be attached to this div -->
<div>{{participant.name}}</div>
</div>
</div>
You are trying to refer to a participant object that does not exist.
In this case you are trying to render the participant information for the local participant. Instead of doing so directly in the HTML, you need to do this in the JavaScript once you have successfully requested the media of your local participant.
Your HTML should be:
<div id="container" class="container">
<div id="local" class="participant"><div></div><div>Me</div></div>
</div>
Then the showing of your media will be handled by the addLocalVideo method.

why check is undefined in my code? 'pure js'

** I want when to click on the active button if the checkbox is checked to add filtered class in HTML element but it doesn't work and give me an undefined error in this line check.parentElement.classList.add("filtered"); **
<ul class="ul-list"></ul>
</section>
</main>
<footer class="footer">
<button class="all footer-btn">All</button>
<button class="active footer-btn">Active</button>
<button class="complete footer-btn">Complete</button>
</footer>
let check = document.querySelectorAll(".complete-txt");
let complete_btn = document.querySelector(".complete");
let active_btn = document.querySelector(".active");
let all_btn = document.querySelector(".all");
let edit_list = document.querySelector(".edit-list");
let main_text = document.querySelector(".main-text");
let list_item = document.querySelector(".list-item");
let footer = document.querySelector(".footer");
const generateTemplate = (todo) => {
const html = `
<li class="list-item">
<input type="checkbox" class="complete-txt" name="" id="check"><span class="main-text">${todo}</span><div class="edit-list"></div><div class="delete-list"></div>
</li>
`;
list.innerHTML += html;
};
// add todos event
addForm.addEventListener("submit", (e) => {
e.preventDefault();
const todo = addForm.add.value.trim();
if (todo.length) {
generateTemplate(todo);
addForm.reset();
}
});
active_btn.addEventListener("click", function () {
let check_id = document.querySelector(".complete-txt");
// check.forEach(function () {
debugger;
if (check.checked !== "true") {
check.parentElement.classList.add("filtered");
console.log("hi");
}
// });
// console.log("hi");
console.log("hi");
// console.log(check.checked.value);
});
if the larger document fixes all other inconcistencies you should be able to change the eventlistener to
active_btn.addEventListener("click", function () {
let check_id = document.querySelector(".complete-txt");
if (check_id.checked !== "true") {
check_id.parentElement.classList.add("filtered");
}
});
BUT!!! this will not "fix" all of your errors, like defining let check before the checkbox is created with generateTemplate

How can I compare scores using if statement and .innerText?

I'm trying to make a Ping Pong scoreKeeper. Everything is done except the part where the scores are compared and a winner is declared. I'm trying to use the if statement to compare the innerText of two variables and whether their scores match or not. But it's not working.
Here's the Javascript and HTML code I've written.
const p1Score = document.querySelector("#p1Score")
const p2Score = document.querySelector("#p2Score")
const increaseP1Score = document.querySelector("#increaseP1Score")
const increaseP2Score = document.querySelector("#increaseP2Score")
const resetScore = document.querySelector("#resetScore")
const scoreKeeper = document.querySelector("#scoreKeeper")
increaseP1Score.addEventListener('click', function(event) {
p1Score.innerText++
// if (p1Score.innerText == 5 && p1Score.innerText > p2Score.innerText) {
// console.log("Here it works!")
})
increaseP2Score.addEventListener('click', function() {
p2Score.innerText++
})
resetScore.addEventListener('click', function() {
p1Score.innerText = 0;
p2Score.innerText = 0;
})
if (p1Score.innerText == 5 && p1Score.innerText > p2Score.innerText) {
console.log("Working!")
}
<div id="container">
<header id="header">
<h1 id="scoreKeeper">Current Score: <span id="p1Score">0</span> to <span id="p2Score">1</span></h1>
</header>
<footer id="footer">
<button id="increaseP1Score">+1 Player One</button>
<button id="increaseP2Score">+1 Player Two</button>
<button id="resetScore">Reset</button>
</footer>
</div>
You'll see a comment in my JS code. When I try to compare the values there, it somehow works. But I don't know why it doesn't work outside the event listener.
const p1Score = document.querySelector("#p1Score")
const p2Score = document.querySelector("#p2Score")
const increaseP1Score = document.querySelector("#increaseP1Score")
const increaseP2Score = document.querySelector("#increaseP2Score")
const resetScore = document.querySelector("#resetScore")
const scoreKeeper = document.querySelector("#scoreKeeper")
increaseP1Score.addEventListener('click', function(event) {
p1Score.innerText++
checkScore();
// if (p1Score.innerText == 5 && p1Score.innerText > p2Score.innerText) {
// console.log("Here it works!")
})
increaseP2Score.addEventListener('click', function() {
p2Score.innerText++
checkScore();
})
resetScore.addEventListener('click', function() {
p1Score.innerText = 0;
p2Score.innerText = 0;
})
function checkScore(){
if (p1Score.innerText == 5 && p1Score.innerText > p2Score.innerText) {
//console.log("Working!")
alert("working!");
}
}
<div id="container">
<header id="header">
<h1 id="scoreKeeper">Current Score: <span id="p1Score">0</span> to <span id="p2Score">1</span></h1>
</header>
<footer id="footer">
<button id="increaseP1Score">+1 Player One</button>
<button id="increaseP2Score">+1 Player Two</button>
<button id="resetScore">Reset</button>
</footer>
</div>
Your if statement is just running once when the page loads. You could put the functionality... in a function like checkScore() above and call it when you increment the scores. This is more re-usable and a better solution to hard-coding it in each incrementer.

Passing onclick event in template literal

I'm trying to pass url through onclick event, its not working.
there is <body onload="displayBookmarks()"> to initialise displayBookmarks function as soon as the page gets loaded
function deleteBookmark(url){
alert(url);
};
function displayBookmarks(){
bookmarksResults.innerHTML = "";
for (let a in bookmarks){
let name = bookmarks[a].name;
let url = bookmarks[a].url;
bookmarksResults.innerHTML += `<div class="well"> <h3> ${name} <a class="btn btn-default" target="_blank" href=${url} >Visit</a> <a onclick=${deleteBookmark(url)} class="btn btn-danger" >Delete</a></h3></div>`
}
}
The main problem is onclick=${deleteBookmark(url)}
As soon as the page loads it starts displaying the url but I want to to be shown only when delete button is pressed.
I've found that there is another way to do this with encapsulation. I don't know if I would recommend doing it like this at all but since you've asked the question.
const app = document.getElementById("app");
const button = ((app) => {
let _url;
const _log = (data) => {
console.log(data);
}
let _content = `<button onclick="(${_log})('${_url}')">test</button>`;
const _setContent = () => {
_content = `<button onclick="(${_log})('${_url}')">test</button>`;
}
const _setUrl = (url) => {
_url = url;
}
return {
setUrl: (url) => {
_setUrl(url);
_setContent();
},
render: () => {
app.innerHTML = _content;
}
}
})(app)
const url = 'www.something.com';
button.setUrl(url);
button.render();
<section id="app">...</section>
const markUp = `
<button onclick="myFunction()">Click me</button>
`;
document.body.innerHTML = markUp;
window.myFunction = () => {
console.log('Button clicked');
};

Categories

Resources