I have created a unique web chat using websockets and python for the server. It's very basic at the moment however there's a feature I'd like to implement but I'm not sure as to how I'd go about it.
I made a div which is basically a small red square which is located beside the chat input, when a user types a message and clicks enter/submit I'd like for that red square to be at the end of the message which is displayed in the chat log.
The only clue I can think of is that it would involve an appendTo in js after an event has occured (the sending of the message) using a listener but I'm unsure as to how it would detect the end of a message. Any ideas? Hopefully it's a possible task.
HTML
<input type="text" id="send">
<div id="box"></div>
<!-- Message appears here -->
<fieldset id="chatlog"></fieldset>
CSS
div#box {
background:url(http://i.imgur.com/iJipdb6.png) no-repeat;
width: 10px;
height: 20px;
position: absolute;
top: 9px;
left: 110px;
SERVER
var messages;
var form;
var inputBox;
function log_msg(msg) {
var p = document.createElement("p");
p.innerHTML = msg;
messages.appendChild(p);
}
function doInit() {
inputBox = document.getElementById("message");
messages = document.getElementById("messages");
form = document.getElementById("message-form");
var s;
try {
var host = "ws://localhost:4545/";
if(window.location.hostname) {
host = "ws://" + window.location.hostname + ":4545/";
}
s = new WebSocket(host);
s.onopen = function (e) { log_msg("connected..."); };
s.onclose = function (e) { log_msg("connection closed."); };
s.onerror = function (e) { log_msg("connection error."); };
s.onmessage = function (e) { log_msg("message: " + e.data); };
} catch (ex) {
log_msg("connection exception:" + ex);
}
form.addEventListener("submit", function (e) {
e.preventDefault();
s.send(inputBox.value);
inputBox.value = "";
}, false);
}
IMAGE for further clarification: http://i.imgur.com/RkBpAMI.png
beTo my knowledge, it is not possible. A textarea only accepts raw text. To be able to do that, you will need to format your chat log in HTML.
On the top of my head and just for the sake of scratching it a little, you could imagine calculating the grid of your textarea (chars on x axis * chars on y axis), then calculate where on this grid the square would end up based on the length of its value, and absolutely position the box to simulate it being in the textarea. It could manageable since textarea are by default in monospace fonts, but this is a very bad idea. I would just make it HTML, it will be easier.
Related
I want to find the simplest barebones (that is, no libraries if possible; this is a learning exercise) way to draw a simple line between components. The elements are divs representing cards always stacked vertically potentially forever. Cards can be different heights. The line will exit the left hand side of any given element (card a), turn 90 degrees and go up, turning 90 degrees back into another (card b).
I've tried a few things. I haven't got any fully working yet and they're looking like they all need some serious time dedicated to figuring them out. What I want to know is what's the right/preferred way to do this so that I spend time on the right thing and it's future proof with the view:
I can add as many connecting lines as I need between any two boxes, not just consecutive ones
These lines obey resizing and scrolling down and up the cards
Some cards may not have an end point and will instead terminate top left of page, waiting for their card to scroll into view or be created.
Attempts
My first thought was a <canvas> in a full column component on the left but aligning canvas' and the drawings in them to my divs was a pain, as well as having an infinite scrolling canvas. Couldn't make it work.
Next I tried <div>s. Like McBrackets has done here. Colouring the top, bottom and outer edge of the div and aligning it with the two cards in question but while I can position it relative to card a, I can't figure out how to then stop it at card b.
Lastly I tried <SVG>s. Just .getElementById() then add an SVG path that follows the instructions above. i.e.
const connectingPath =
"M " + aRect.left + " " + aRect.top + " " +
"H " + (aRect.left - 50) +
"V " + (bRect.top) +
"H " + (bRect.left);
Nothing seems to line up, it's proving pretty difficult to debug and it's looking like a much more complex solution as I need to take into account resizing and whatnot.
You might be able to apply something like this by taking a few measurements from the boxes you want to connect; offsetTop and clientHeight.
Update Added some logic for undrawn cards requirement.
While this doesn't fully simulate dynamic populating of cards, I made an update to show how to handle a scenario where only one card is drawn.
Click connect using the default values (1 and 5). This will show an open connector starting from box 1.
Click "Add box 5". This will add the missing box and update the connector.
The remaining work here is to create an event listener on scroll to check the list of connectors. From there you can check if both boxes appear or not in the DOM (see checkConnectors function). If they appear, then pass values to addConnector which will connect them fully.
class Container {
constructor(element) {
this.connectors = new Map();
this.element = element;
}
addConnector(topBox, bottomBox, displayHalf = false) {
if (!topBox && !bottomBox) throw new Error("Invalid params");
const connector = new Connector(topBox, bottomBox, displayHalf);
const connectorId = `${topBox.id}:${bottomBox.id}`;
this.element.appendChild(connector.element);
if (this.connectors.has(connectorId)) {
connector.element.style.borderColor = this.connectors.get(connectorId).element.style.borderColor;
} else {
connector.element.style.borderColor = "#" + Math.floor(Math.random() * 16777215).toString(16);
}
this.connectors.set(connectorId, connector);
}
checkConnectors() {
this.connectors.forEach((connector) => {
if (connector.displayHalf) {
connector.firstBox.updateElement();
connector.secondBox.updateElement();
if (connector.firstBox.element && connector.secondBox.element) {
this.addConnector(connector.firstBox, connector.secondBox);
}
}
});
}
}
class Box {
constructor(id) {
this.id = id;
this.updateElement();
}
getMidpoint() {
return this.element.offsetTop + this.element.clientHeight / 2;
}
updateElement() {
this.element ??= document.getElementById(`box${this.id}`);
}
static sortTopDown(firstBox, secondBox) {
return [firstBox, secondBox].sort((a,b) => a.element.offsetTop - b.element.offsetTop);
}
}
class Connector {
constructor(firstBox, secondBox, displayHalf) {
this.firstBox = firstBox;
this.secondBox = secondBox;
this.displayHalf = displayHalf;
const firstBoxHeight = this.firstBox.getMidpoint();
this.element = document.createElement("div");
this.element.classList.add("connector");
this.element.style.top = firstBoxHeight + "px";
let secondBoxHeight;
if (this.displayHalf) {
secondBoxHeight = this.firstBox.element.parentElement.clientHeight;
this.element.style.borderBottom = "unset";
} else {
secondBoxHeight = this.secondBox.getMidpoint();
}
this.element.style.height = Math.abs(secondBoxHeight - firstBoxHeight) + "px";
}
}
const connectButton = document.getElementById("connect");
const error = document.getElementById("error");
const addBoxButton = document.getElementById("addBox");
const container = new Container(document.getElementById("container"));
connectButton.addEventListener("click", () => {
const firstBoxId = document.getElementById("selectFirstBox").value;
const secondBoxId = document.getElementById("selectSecondBox").value;
if (firstBoxId === "" || secondBoxId === "") return;
error.style.display = firstBoxId === secondBoxId ? "block" : "none";
const firstBox = new Box(firstBoxId);
const secondBox = new Box(secondBoxId);
// Check for undrawn cards
if (!!firstBox.element ^ !!secondBox.element) {
return container.addConnector(firstBox, secondBox, true);
}
const [topBox, bottomBox] = Box.sortTopDown(firstBox, secondBox);
container.addConnector(topBox, bottomBox);
});
window.addEventListener("resize", () => container.checkConnectors());
addBoxButton.addEventListener("click", () => {
const box = document.createElement("div");
box.innerText = 5;
box.id = "box5";
box.classList.add("box");
container.element.appendChild(box);
addBoxButton.style.display = 'none';
container.checkConnectors();
});
.box {
border: solid 1px;
width: 60px;
margin-left: 30px;
margin-bottom: 5px;
text-align: center;
}
#inputs {
margin-top: 20px;
}
#inputs input {
width: 150px;
}
.connector {
position: absolute;
border-top: solid 1px;
border-left: solid 1px;
border-bottom: solid 1px;
width: 29px;
}
#error {
display: none;
color: red;
}
<div id="container">
<div id="box1" class="box">1</div>
<div id="box2" class="box">2</div>
<div id="box3" class="box">3</div>
<div id="box4" class="box">4</div>
</div>
<div id="inputs">
<input id="selectFirstBox" type="number" placeholder="Provide first box id" min="1" value="1" max="5" />
<input id="selectSecondBox" type="number" placeholder="Provide second box id" min="1" value="5" max="5" />
<div id="error">Please select different boxes to connect.</div>
</div>
<button id="connect">Connect</button>
<button id="addBox">Add box 5</button>
I have the following HTML:
<td class="pn">
<span class="copyable">410-555-1234</span>
<span title="Click to copy" class="clipboard">📋</span>
<form class="pn-copy-form">
<input class="pn-copy" type="text" value="+14105551234" />
</form>
</td>
When the user clicks on the clipboard icon, I would like to populate the contents of the user's system clipboard with "+14105551234". However, I notice that nothing populates. Here's the Javascript I'm using, placed at the bottom of the same page:
var evHandler = function(clipboardElem) {
return function() {
var pnCopy = clipboardElem.parentNode.querySelector('.pn-copy');
if (pnCopy === null) {
return;
}
pnCopy.select();
try {
result = document.execCommand('copy');
if (result === false) {
throw new Error("Could not copy value: " + pnCopy.value);
}
} catch (e) {
console.error(e);
alert("Couldn't copy text, sorry. Here it is: " + pnCopy.value);
}
console.log("Copied "+ pnCopy.value + " to the clipboard");
pnCopy.blur();
};
}
var clipboards = document.querySelectorAll('.clipboard');
for (var i = 0; i < clipboards.length; i++) {
var clipboard = clipboards[i];
clipboard.addEventListener('click', evHandler(clipboard));
}
I'm hiding the form, since I don't want it to appear on the page.
.pn-copy {
display: none;
}
Why isn't the clipboard populating? Does click-to-copy not work on localhost, or not work on unencrypted HTTP?
The form needs to be "visible," though that definition is loose, and I think is roughly "not display:none". Without that in place, .select() selects no text. I had success with the following CSS, which leaves the input "visible" but placed 3000 pixels off screen.
/* the input has to be "visible", if you use display: none copy won't work. */
.pn-copy-form {
position: absolute;
height: 1px;
left: -3000px;
}
I'm writing a text-based game in javascript, and one of the main "features" is a input box which accepts a user input, and submits the input by a button tag. In my main game loop a call the button's onclick:
var game_u = new game_util();
function Game_Main(){
while(active){
input = game_u.getText();
//p = new player();
active = false;
}
}
function game_util(){
this.getText = function(){
//The Submit button
confirm_plr.onclick = function(){
//Input value
return player_in.value;
}
}
}
The problem with this way, though is that the while loop does not "wait" for the submit button to be clicked to get the input from the `game_u.getText(); function and continues on with the loop.
Is there a better way for me to do this, it is my first wack at a text-based game? I don't want to use the prompt method, because it breaks the immersion of the gameplay.
I'm also coming from Java, an object-oriented programming language, so that's why I use a while loop.
Any help is appreciated.
If you want to suspend with user input, a simple prompt() box will do.
Try this:
var input = prompt("Enter data here", "");
This will wait for input and store it into variable input.
See working example on JSFiddle.net.
AFAIK, synchronous JS is not possible, as according to this SO post:
JavaScript is asynchronous, you cannot "pauses" execution. Moreover, while javascript is running the entire user interface freezes, so the user cannot click the button.
As to your question,
If I shouldn't be using a while loop, what would replace it?
because JS is event driven, and you're trying to run code every time that button is clicked (and input is entered), just use a onclick handler.
So, rather than this:
while(active) {
input = game_u.getText();
p = new player();
active = false;
}
You can do:
document.getElementById("button").addEventListener("click", function() {
input = game_u.getText();
p = new player();
active = false;
});
This will run the code every time the button is clicked, essentially the same as what you're trying to do.
One approach is to break the different stages of your game into functions corresponding to the different stages (rooms, levels etc.) which are called depending on the user's input; you will also need a variable that saves the game's current state (room in the example below).
(function Game() {
var room = 1;
document.getElementById('playerSubmit').addEventListener('click', function() {
var playerInput = document.getElementById('playerInput').value;
if (playerInput == "Go to room 2") {
room = 2;
}
if (playerInput == "Go to room 1") {
room = 1;
}
if (room == 1) {
room1(playerInput);
} else if (room == 2) {
room2(playerInput);
}
document.getElementById('playerInput').value = '';
});
function room1(playerInput) {
output('You are in the first room and entered the command ' + playerInput);
}
function room2(playerInput) {
output("Now you're in room 2. Your command was " + playerInput);
}
function output(text) {
document.getElementById('output').innerHTML += '<p>' + text + '</p>';
}
})();
#output {
border: solid 1px blue;
width: 500px;
height: 400px;
overflow: scroll;
}
label {
display: block; margin-top: 1em;
}
<div id="output"></div>
<label for="playerInput">Player input</label>
<input id="playerInput" type="text" size=50 />
<input id="playerSubmit" type="button" value="Submit" />
http://jsfiddle.net/spjs8spp/2/
You've gotten some great info in the commentary. An evented model might be ideal, but you can do persistent loops. I'm not as familiar with it, but HTML5 Canvas is just that. Maybe look into that some, since nobody else mentioned it yet.
I'm trying to save more than one entry of contenteditable content into my localstorage for a Chrome extension. My current code saves just one contenteditable section fine, but when I try to add another Id of a seperate contenteditable section it either deletes all the saved information or doesn't do anything at all. I'm pretty novice in JS, so I hope I'm just making a simple mistake. My html looks like this:
<div id = "content">
<div id= "tcontent" contenteditable="true" data-ph=" Make a note . . . "
style= "height: 300px; overflow: auto"></div>
<div id = "content2">
<div id= "tcontent2" contenteditable="true" data-ph= " Make a note . . . "
style= "height: 300px; overflow: auto"></div>
</div>
And this is my Javascript:
window.addEventListener('load', onLoad); function onLoad() {
checkEdits();
}
function checkEdits() {
if(localStorage.userEdits!=null) {
document.getElementById("tcontent", "tcontent2").innerHTML += localStorage.userEdits;
}
};
document.onkeyup = function (e) {
e = e || window.event;
console.log(e.keyCode);
saveEdits();
};
function saveEdits() {
var editElem = document.getElementById("tcontent", "tcontent2");
var userVersion = editElem.innerHTML;
localStorage.userEdits = userVersion;
};
Basically this code will only save one (the content I place first into the getElementbyId). Isn't there a way to save both of the 'content's?
I've been playing around with all my little knowledge of javascript I have but can't seem to see what I'm doing wrong or what I should be doing here.
Much thanks for any and all help.
document.getElementById is a method that only takes one element's id. You are currently trying to pass two strings to the method. That will not work.
Please refer to the documentation here: https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById
Also, you must assign the innerHTML of each element individually to each piece of saved content in localStorage.
Granted that you are fairly new to the Language I do not want to overcomplicate the answer for you. With that said, please find below your code with a few modifications to be able to save both pieces in localStorage respectively:
window.addEventListener('load', onLoad); function onLoad() {
checkEdits();
}
function checkEdits() {
if(localStorage.userEdits1!=null) {
document.getElementById("tcontent").innerHTML = localStorage.userEdits1;
}
if(localStorage.userEdits2!=null) {
document.getElementById("tcontent2").innerHTML = localStorage.userEdits2;
}
};
document.onkeyup = function (e) {
e = e || window.event;
console.log(e.keyCode);
saveEdits();
};
function saveEdits() {
var editElem1 = document.getElementById("tcontent");
var editElem2 = document.getElementById("tcontent2");
localStorage.userEdits1 = editElem1.innerHTML;
localStorage.userEdits2 = editElem2.innerHTML;
};
Basically I have this header that slides out of the way when people don't want it anymore and can be slid down again when they want it. I want it to be down by default however I don't want people to have to constantly click the header toggle every-time a new page loads. I have read about using cookies but my skill in javascript is rather limited. Here is the code I currently use that works well:
<div id="headtoggle" onclick="headertoggle()"></div>
<script>
function headertoggle() {
var top;
var pagetop;
if (document.getElementById("page-header").style.top === "-210px") {
top = "-11px";
pagetop = "275px";
} else {
top = "-210px";
pagetop = "80px";
}
document.getElementById("page-header").style.top = top;
document.getElementById("page-body").style.top = pagetop;
}
</script>
How do I change the code so that it "remembers" what the last setting was for each person? I am willing to also use jquery. Any help for this very novice coder would be more than appreciated.
Thanks,
Dylan
EDIT2: I changed the code given to me a bit and am having a new problem.
<div id="headtoggle" onclick="headertoggle()" onload="topposition()"></div>
<script>
function topposition() {
var top;
var pagetop;
if (localStorage.getItem("headerDown") == "false") {
top = "-11px";
pagetop = "275px";
} else {
top = "-210px";
pagetop = "80px";
}
document.getElementById("page-header").style.top = top;
document.getElementById("page-body").style.top = pagetop;
}
</script>
<script>
function headertoggle() {
var top;
var pagetop;
if (document.getElementById("page-header").style.top === "-210px") {
localStorage.setItem("headerDown", false);
top = "-11px";
pagetop = "275px";
} else {
localStorage.setItem("headerDown", true);
top = "-210px";
pagetop = "80px";
}
document.getElementById("page-header").style.top = top;
document.getElementById("page-body").style.top = pagetop;
}
</script>
The toggle works as intended however whenever I fire "function topposition ()" in the firefox console I get the following error:
SyntaxError: expected expression, got end of script data:,/*
EXPRESSION EVALUATED USING THE FIREBUG COMMAND LINE:
*/%0A%09function%20topposition() Line 2
You can create a cookie by using
document.cookie="headerDown=true";
Then when the header is moved up overwrite the cookie with the same code changed to false.
If you then add some code that reads the cookies on page load you will be able to determine what is need.
You can read cookies by accessing document.cookie which will be a string of any cookies available in the following format.
cookie1=value; cookie2=value; cookie3=value;
Look for your cookie and you should be able to set whether the header is up or down from there.
i would say local storage would be a good option for this. Just log an event or variable or whatever and then check for it each time you load the page.
https://developer.mozilla.org/en-US/docs/Web/API/Window.localStorage
Using local storage will allow you to save variables between page loads.
add localStorage.setItem(name,value) in to your if statements as below.
If you are only using the headerToggle function to position these elements then the if statement below the function should suffice. It requires two calls to the headertoggle function in the even that you want the header to be down.
You will need to make sure the if statement is placed after the html for the divs, most likely best place is the very bottom of the page.
function headertoggle() {
var top;
var pagetop;
if (document.getElementById("page-header").style.top === "-210px") {
localStorage.setItem("headerDown", true);
top = "-11px";
pagetop = "275px";
} else {
localStorage.setItem("headerDown", false);
top = "-210px";
pagetop = "80px";
}
document.getElementById("page-header").style.top = top;
document.getElementById("page-body").style.top = pagetop;
}
if (localStorage.getItem("headerDown") == "true") {
headertoggle();
}
headertoggle();