Helpful Information
 
 
Category: Coding tips & tutorials threads
Emulating a terminal-like caret with javascript and css.

How to ... emulate a terminal-like caret with plain css and javascript(optionally unobtrusive)?

NOTES: This is just a guide which shows you steps on creating a terminal-like caret, therefore I do not have any plug-and-play library for creating this. This is nothing than just a proof-of-concept that this effect can be acquired without any advanced techniques(which includes disabling keyboard actions to global{browser based} actions, et cetera)

WARNING: I do not recommend using this technique in any real-world-applications as this could be really annoying and confusing to the user, therefore this might only be used where it is absolutely needed like demonstrations, et cetera.

Enough warnings and notes, let's begin.;)

What we are making?

I hope you've seen those fancy web-apps that have a [customized] caret(the thin thing that enables you to type, or "trace" where you are)? I first came across such a thing at http://masswerk.at/jsuix/index.html I was fascinated when I saw that and I wanted to know how it was made. After trying for ages I finally gave up. After some months I came across another application which had the same effect at: http://tryruby.hobix.com/ this time I decided to find out how it was made but still failed :( when some days ago I had this brilliant idea which showed a beam of light on how it could be done, so this morning I sat in-front of my computer and began my journey and it seems I succeeded. So here I am to share the technique I used.

BTW if you haven't seen any of those already, check this out: http://shachi.prophp.org/demo.html
Click on the red caret to activate and start typing.

That's what we are going to make today.

How is it going to work?

Sshhh ... this is a secret. :D

We will have a plain textarea somewhere in the screen out of the view of the viewer and when the user clicks on our "fake terminal" we will focus into the textarea and when the user starts typing we will simply append the data typed into the textarea to our "terminal" and that's that.

The code

Let me take this part step-by-step, the full source will be available at the end.

The HTML

Lets start with the HTML part, here's the basic skeleton:



<html>
<head>
<script type="text/javascript">
// js(will be described later)
</script>

<style type="text/css">
// css(will be described later)
</style>
</head>
<body>
<div id="terminal" onclick="$('setter').focus();">
<textarea type="text" id="setter" onkeydown="writeit(this, event);moveIt(this.value.length, event)" onkeyup="writeit(this, event)" onkeypress="writeit(this, event);"></textarea>
<div id="getter">
<span id="writer"></span><b class="cursor" id="cursor">B</b>
</div>
</div>
</body>
</html>


That's seems pretty self explanatory. Let me just go through the main things you need to know here:


<div id="terminal" ...>...</div>

This is the main container where all your elements go.


<textarea type="text" id="setter" ...></textarea>

This is the "out-of-sight" textarea.


<div id="getter">
<span id="writer"></span><b class="cursor" id="cursor">B</b>
</div>

This is the place where the "fake caret" and the content goes.

The Css

The css is also very self explanatory and I don't think I need to describe it as it's your own choice to customise it.



body {
margin: 0px;
padding: 0px;
height: 99&#37;;
}

textarea#setter { /* explorer doesn't support [att=val] selector :( */
left: -1000px;
position: absolute;
}

.cursor {
font-size: 12px;
background-color: red;
color: red;
position: relative;
opacity: 0.5;
}

#terminal {
margin: 8px;
cursor: text;
height: 500px;
}

#writer {
font-family: cursor, courier;
font-weight: bold;
}

#getter {
margin: 5px;
}


The javascript

The next thing is the javascript:



function $(elid){ /* shortcut for d.gEBI */
return document.getElementById(elid);
}

var cursor; /* global variable */
window.onload = init;

function init(){
cursor = $("cursor"); /* defining the global var */
cursor.style.left = "0px"; /* setting it's position for future use */
}

function nl2br(txt){ /* helper, textarea return \n not <br /> */
return txt.replace(/\n/g, "<br />");
}

function writeit(from, e){ /* the magic starts here, this function requires the element from which the value is extracted and an event object */
e = e || window.event; /* window.event fix for browser compatibility */
var w = $("writer"); /* get the place to write */
var tw = from.value; /* get the value of the textarea */
w.innerHTML = nl2br(tw); /* convert newlines to breaks and append the returned value to the content area */
}

function moveIt(count, e){ /* function to move the "fake caret" according to the keypress movement */
e = e || window.event; /* window.event fix again */
var keycode = e.keyCode || e.which; /* keycode fix */
// alert(count); /* for debugging purposes */
if(keycode == 37 && parseInt(cursor.style.left) >= (0-((count-1)*10))){ // if the key pressed by the user is left and the position of the cursor is greater than or equal to 0 - the number of words in the textarea - 1 * 10 then ...
cursor.style.left = parseInt(cursor.style.left) - 10 + "px"; // move the cursor to the left
} else if(keycode == 39 && (parseInt(cursor.style.left) + 10) <= 0){ // otherwise, if the key pressed by the user if right then check if the position of the cursor + 10 is smaller than or equal to zero if it is then ...
cursor.style.left = parseInt(cursor.style.left) + 10 + "px"; // move the "fake caret" to the right
}

}

function alert(txt){ // for debugging
console.log(txt); // works only with firebug
}


And now the ....

Final Code(comments ripped):



<html>
<head>
<script type="text/javascript">
function $(elid){
return document.getElementById(elid);
}

var cursor;
window.onload = init;

function init(){
cursor = $("cursor");
cursor.style.left = "0px";
}

function nl2br(txt){
return txt.replace(/\n/g, "<br />");
}

function writeit(from, e){
e = e || window.event;
var w = $("writer");
var tw = from.value;
w.innerHTML = nl2br(tw);
}

function moveIt(count, e){
e = e || window.event;
var keycode = e.keyCode || e.which;
// alert(count);
if(keycode == 37 && parseInt(cursor.style.left) >= (0-((count-1)*10))){
cursor.style.left = parseInt(cursor.style.left) - 10 + "px";
} else if(keycode == 39 && (parseInt(cursor.style.left) + 10) <= 0){
cursor.style.left = parseInt(cursor.style.left) + 10 + "px";
}

}

function alert(txt){
console.log(txt);
}

</script>

<style type="text/css">
body {
margin: 0px;
padding: 0px;
height: 99%;
}

textarea#setter {
left: -1000px;
position: absolute;
}

.cursor {
font-size: 12px;
background-color: red;
color: red;
position: relative;
opacity: 0.5;
}

#terminal {
margin: 8px;
cursor: text;
height: 500px;
overflow: auto;
}

#writer {
font-family: cursor, courier;
font-weight: bold;
}
#getter {
margin: 5px;
}
</style>
</head>
<body>
<div id="terminal" onclick="$('setter').focus();">
<textarea type="text" id="setter" onkeydown="writeit(this, event);moveIt(this.value.length, event)" onkeyup="writeit(this, event)" onkeypress="writeit(this, event);"></textarea>
<div id="getter">
<span id="writer"></span><b class="cursor" id="cursor">B</b>
</div>
</div>
</body>
</html>



Browser Compatibility:

As far as I know it works in FF 2.0.0.1 and IE4Lin 6.0. Haven't tested on any other browser yet.

What about the Optionally Unobtrusive part?

To make it unobtrusive, things you can do:

i) Create the main div(id="terminal"), and the getter div(id="getter" and all it's contents) dynamically through javascript
ii) move the textarea to the far left(-1000px) with javascript again.

PS.

I have no idea how http://masswerk.at/jsuix/index.html or http://tryruby.hobix.com/ created their carets so this is not the exact same technique they used, as far as I know, the way they did it, it cancels (almost)all global browser shortcuts.

Hope you like it, bug reports or problems are welcome. :)

EDIT:BUG FOUND:When you type a few things and hit enter and then try to go back to the first line, the cursor simultaneously goes to the left and not to the first line. I am searching for a fix for this but cannot find anything(yet).

53 views and no comments? Must be something wrong.:(

seems like a good idea, but what practical uses could it employ?

it would have to be secured with a password for sending SQL requests, php scripts, etc. if you could define a set of commands, send via ajax on enter, this would make an excellent environment for trying out new code.

boxxertrumps: sure you can send ajax calls when hit enter. It's really easy, just to detect if the enter key is pressed and if it is, do the ajax call and return false.

JS noob... Remember? i was kindof asking you to create a class/function that returns the last line in an easy to transfer manner...

works well, good job. But instead of the solid block, having it animated would look better. i have the perfect pic. once I get home, ill play with the styles to see if i can give it more of a DOS feel...

I guess you didn't read the notice:



This is just a guide which shows you steps on creating a terminal-like caret, therefore I do not have any plug-and-play library for creating this


The blinking can be easily done.

Here's the function(to make it blinkable):



function blink(){
var div = document.getElementById("id_of_the_cursor");
if(div.style.display == "none"){
div.style.display = "block";
} else {
div.style.display = "none";
}
}

setInterval("blink()", 500);


May be I can help you with what you are trying to achieve?

Block elements are displayed on a new line, so i changed this:
div.style.display = "inline";
only minor changes, but i have them HERE (http://www.freewebs.com/boxxertrumps/carret.html).
but to the best of my knowlege, seperating lines could be done in php with the explode function, explode("\n\r",$full) then delete the previosly used lines.
ill write something up...
Would you be able to write a function that gets all of the content written by the carret?

Would you be able to write a function that gets all of the content written by the carret?


Sure I can, but you need to provide some more information. Do you want the caret-written data to just remain there or disappear? Where(and how) will you send the data?

i want to send the data on the last line via ajax POST then load the contents of the ajaxed document into a side/bottom div. it doesn't really matter if the previous commands stay or not. i just want to turn this into a fun testing enviroment.

im going to have php files commands tested as something like this..
$encname = $testdir . md5($final) .".php";
$handle = fopen($encname,"a");
$data = strstr("<?php",$final);
fwrite($handle,$data);
include "$encname";
unlink($encname);
fclose($handle);

function $(elid){
return document.getElementById(elid);
}$ is a terrible name for a function. Prototype was evidently written by a deranged Perl scripter.
w.innerHTML = nl2br(tw);It would be nice to avoid innerHTML, especially since writing directly to innerHTML in this case would break the script in KHTML (deleting any spaces as soon as they're written).

On a purely stylistic note, eight spaces per block is far too much indentation for practical use.

Of course, the easy way to do this is:
<style type="text/css">
input.stdin, span.cursor {
font-family: monospace;
}

input.stdin {
width: 0;
}
</style>
</head>
<body>
<p>
<input type="text" id="stdin">
<span id="cursor" style="display: none;">_</span>
<script type="text/javascript">
var e = document.getElementById("cursor");
e.className = "cursor";
e.onfocus = e.onclick = function() {
setTimeout(
function() {
document.getElementById("stdin").focus();
},
0
);
};
e = document.getElementById("stdin");
e.onkeyup = function() {
this.style.width = this.value.length + ".1em";
};
e.className = "stdin";
e = null;
</script>
</p>There are some improvements that could be made, but that's the gist of it.

made some more changes... used to scroll to the top when you type. now it stays with the cursor.
same URL.

but twey, your code outputs some odd behavior. for some reason the last character is outputted when a new one is typed...

Hmm? Output? What output? :)

That was just an example of caret code using an expanding textbox, there's no output involved.

Oh i thought it was an addon for sachi's code. NVM then...

$ is a terrible name for a function. Prototype was evidently written by a deranged Perl scripter.

Sorry Twey, but I've got this habit of using the $ function because of firebug. I will try to avoid it as much as possible.


It would be nice to avoid innerHTML, especially since writing directly to innerHTML in this case would break the script in KHTML (deleting any spaces as soon as they're written).

What should I use then? I guess I should use <element>.firstChild.firstChild.textValue but I don't usually use that because of the extra #text nodes netscape browsers insert instead of a newline.

I was about to use the same technique but unfortunately, I couldn't make the default caret disappear so ... I had to think of a new technique.

EDIT: boxxertrumps: Here's the function to send a request when the user hits enter:

Add the following code after the



if(e.ctrlKey && e.keyCode == 65){

$("writer").style.backgroundColor = "#99ccff";

} else {

$("writer").style.backgroundColor = "";

}


part:



if(e.keyCode == 13){
sendAjaxRequest(document.getElementById("your_textarea_id").value);
document.getElementById("your_textarea_id").value = "";
return false;
}


That should work.

What should I use then? I guess I should use <element>.firstChild.firstChild.textValue but I don't usually use that because of the extra #text nodes netscape browsers insert instead of a newline.It would be appropriate to use separate <code> elements for each line, or replace linebreaks with <br> elements as you have been doing.

Twey: I am sorry but, I didn't get what you meant by:



It would be appropriate to use separate <code> elements for each line.


How would using code elements help me write the data into the div?

php doc i have so far... no error support, ill have to add that later.
http://www.freewebs.com/boxxertrumps/carret.phps

EDIT: the code at the top is assuming there are multiple lines being sent and can be replaced with
$final = $_POST["com"];

How would using code elements help me write the data into the div?You don't. You write the data into <code> elements inside the <div>, which are styled to be block-level, thus solving the line-break problem.

Twey: Ah ha!! My bad, sorry. :( I'll try to implement that right away. Thanks Twey!!

By the way I've found a bug and is listed in the original post. You can check it out if you want.










privacy (GDPR)