I'm trying to add and remove paragraph elements that are 1em (16px) wide both initially and when the window is resized. When the script first loads, it adds too many paragraph elements about 2 - 6 in most cases, and when I resize the window it either adds too many or removes to many I'm not sure what is causing it to go over or under the difference. I'm trying to accomplish this with vanilla javascript.
Edit: The paragraphs are meant to be vertical, and a single character wide 16px. I will then have characters randomly and continuously generate and fall down the screen.
(function(window, undefined){
var parentContainer = document.getElementsByClassName('stringFall_Container'),
paras = document.getElementsByClassName('para'),
containerWidth,
paraWidth,
difference,
paraAmount;
function checkContainerWidth () {
console.log('Running checkContainerWidth();')
containerWidth = parentContainer[0].offsetWidth;
console.log('The containers size is:' + containerWidth)
return true;
}
function checkParaWidth () {
console.log('Running checkParaWidth();')
paraWidth = paras[0].offsetWidth;
console.log('The Paragraphs size is:' + paraWidth)
return true;
}
function checkParaAmount () {
console.log('Running checkParaAmount();');
paraAmount = paras.length;
console.log(paraAmount);
return true;
}
function checkDifference () {
console.log('Running checkDifference();');
difference = containerWidth / paraWidth;
return true;
}
function addPara (number) {
console.log('Running addPara();');
number = number === undefined ? 1 : number;
console.log(number);
for (var i = 0; i < number; i++) {
var create = document.createElement('p');
parentContainer[0].appendChild(create).setAttribute('class', 'para');
}
return true;
}
function removePara (number) {
console.log('Running removePara()');
var lastElement = paras[paras.length - 1];
checkParaAmount();
number = number === undefined ? 1 : number;
for (var i = 0; i < number; i++) {
parentContainer[0].removeChild(lastElement);
}
return true;
}
function executeOnResize () {
checkDifference();
console.log('Running executeOnResize();');
checkContainerWidth();
if (difference < paraAmount) {
addPara(paraAmount - difference);
} else {
removePara(difference - paraAmount)
}
}
addPara();
checkContainerWidth();
checkParaWidth();
checkParaAmount();
checkDifference();
console.log(difference);
addPara(difference);
window.addEventListener('resize', executeOnResize, false);
})(this);
In such scenarios it is highly recommended to post a complete example, because it matters what styles are applied to the paragraphs and their container. An example will also help people understand what you are trying to do faster and more easily.
There are some issues in the algorithm, which standard debugging should reveal:
the initial adding of paragraphs should take into account that you already have one rendered: addPara(difference - 1);
before adding or removing paragraphs in executeOnResize, you should update the paraAmountValue value, as it is still 1;
when calculating the difference, you may want to disregard the decimal part and obtain an integer value, otherwise you will add or remove more paragraphs than necessary
the conditional statement in executeOnResize should allow a case when you neither have to add paragraphs, nor remove any
are you sure you want to add paragraphs when difference is less than paraAmount? Shouldn't it be the other way around?
Here is my test page, please review it. I hope it will help you go on.
http://dojo.telerik.com/AVoKU
You will notice that at some points, there is one paragraph falling on the second line - this is something that still needs to be fixed.
Related
my div slider ignores the borders that i've made, can't find the mistakes.
P.S. right/left functions are called in tag attribute onclick:'slider.right()'. I'm just learning, I know that the elegance of the code is far from being ideal.
let elArr = [];
let pusher = elArr.push(document.querySelectorAll('#scr>div'));
let elements = Array.from(elArr[0]);
let slider = {
frame: 0,
set: function(element){
var container = document.getElementById('scr');
container = element.style.visibility='visible';
},
init: function(){
this.set(elements[this.frame]);
},
left: function(){
elements[this.frame].style.visibility='hidden';
this.frame--;
if(this.frame<0)
this.frame = elements.length - 1;
this.set(elements[this.frame]);
},
right: function(){
elements[this.frame].style.visibility='hidden';
this.frame++;
if(this.frame>elements.length)
this.frame = 0;
this.set(elements[this.frame]);
}
};
window.onload = function(){
slider.init();
}
One great guy just helped me, so there is an incorrect condition
if(this.frame>elements.length)
it drops 'frame' value, when current value is bigger then the number of elements in the array, but taking in consideration the fact, that indexation of the elements starts with 0, when frame is equal to elements.length - value becomes undefined, so frame value doesn't drop.
So, we need to change condition to a comparison == or to make it >=;
thnx to user:186999 for the decision
This problem has really reminded me where I am regarding JS skills... :(
I feel I'm right on the cusp but I'm struggling to understand what to do conceptually, it's not so much a syntax issue as I feel I almost cracked it.
What I need
I'm trying to console.log a series of strings, one letter at a time.
There needs to be a delay between each LETTER outputted, say 300ms.
There must then be a delay between each STRING outputted, say 2000ms.
There are 2 strings in example array, but solution must support a dynamic number of strings.
My Current Code (can be pasted into a console)
var stringList = ['first test','second test'],
stringListLen = stringList.length;
for(var i = 0; i < stringListLen; i++){
// begin with first string and read it's length for iterations
var stringNumber = 0;
var currentString = stringList[stringNumber];
var currentStringLen = currentString.length;
// just see the word for clarification at this point in code
console.log(currentString);
(function (i) {
setTimeout(function () {
for (var j = 0; j < currentStringLen; j++) {
(function (j) {
setTimeout(function () {
// THE MAGIC HAPPENS HERE
console.log(j, currentString.charAt(j));
// End of string, so read next string, reset letter count
if(j === currentStringLen - 1){
stringNumber++;
currentString = stringList[stringNumber];
j = 0;
}
}, 300 * j); // each letter * specified delay
})(j);
};
}, currentStringLen * 300 * i); // letter * delay * letters in word
})(i);
}
The Issue
THE GOOD: I am successfully getting the short delay between letters outputted, and my check to switch to a new word and reset the letter counter when we get to the end of the first word is working...
THE BAD: I can't get the wait between the two words to work. I have tried a few ideas and have just got myself so confused I don't know if my approach is correct now.
THE UGLY: The final letter of the last term is also not outputting, and that is just totally unexpected.
What I've tried.
Okay, I've tried simply changing the "currentStringLen * 300 * i" elements to various combinations that seemed logical but had no effect better or worse. Ultimately I think I am trying to calculate "wait the number of letters in current string times 300 (the letter delay) * " <---- STRIKETHROUGH...
I actually don't know what I'm calculating and that's the issue.
I now think I want to split this into TWO functions, not two nested ones. One to READ AND PASS IN a string to another function that JUST outputs the letters with a short delay, then once it gets to the last letter it calls the first function asking for the next word. BUT then I'm still going to need to recurse for the number of strings in the array which creates the same issue...
Am I missing anything fundamental here people?
Is this roughly what you had in mind?
function printLetters(stringList) {
var wordIndex = 0,
letterIndex = 0;
printNext();
function printNext() {
var word = stringList[wordIndex];
var letter = word.charAt(letterIndex);
console.log(letter);
++letterIndex;
if (letterIndex === word.length) {
letterIndex = 0;
++wordIndex;
if (wordIndex < stringList.length) {
setTimeout(printNext, 2000);
}
return;
}
setTimeout(printNext, 300);
}
}
printLetters(['first test', 'second test']);
Here there's only ever one setTimeout running at once and a new one is being set as required with the appropriate time.
While I don't recommend having multiple timers running at once, it can be done. Something like this:
function printLetters(stringList) {
var letterCount = 0,
startTime = Date.now();
stringList.forEach(function(word, wordCount) {
word.split('').forEach(function(letter) {
setTimeout(function() {
console.log(letter, Date.now() - startTime);
}, wordCount * 1700 + (letterCount * 300));
++letterCount;
});
});
}
printLetters(['first test', 'second test']);
Here I've included the time delta in the logging to give a better sense of what is going on when. The gap between strings is 2000 but the constant in the code is 1700 because there's already 300 being added.
I would do a quite different approach. Rather than doing a bunch of precalculated timeouts and their associated closures, i would do just one timeout at a time, using recursion to then move on to the next timeout:
function delayShow(words) {
if (!words || words.length === 0) {
return;
} else if (words[0].length === 0) {
words.shift()
setTimeout(() => delayShow(words), 2000);
} else {
console.log(words[0].charAt(0));
words[0] = words[0].substr(1);
setTimeout(() => delayShow(words), 300);
}
}
delayShow(['first test','second test']);
You can use a condition inside the loop, and when you're on the last iteration of the strings characters, you call the recursive function again, inside a timeout, to iterate over the next string in the array etc.
var stringList = ['first test', 'second test'];
(function rec(j) {
var chars = stringList[j].split('');
chars.forEach(function(char, i) {
setTimeout(function() {
console.log(char);
// if it's the last iteration && there are more strings in the array
if ((i == (chars.length - 1)) && (j < stringList.length - 1)) {
setTimeout( function() {
rec(++j); // play it again
}, 2000);
}
}, i * 300);
});
})(0);
Even something as trivial as setTimeout's, is a good example of using async / await. So I've included an example below.
As you will see, the code is much easier to follow. With the added advantage of not creating multiple setTimeout's running concurrently,.
It also really helps make things much easier to change things, eg. If I asked you to alter the code below so that SPACE takes less time than other letters, to make it flow more naturally, it wouldn't take a lot of thought to work out what to change.
var
stringList = ['first test','second test'];
async function delay(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms);
});
}
async function run() {
let wordpos = 0;
let wordElem = document.querySelector('.words');
while (true) {
let word = stringList[wordpos];
let text = '';
for (var letterpos = 0; letterpos < word.length; letterpos ++ ) {
let letter = word[letterpos];
text = text + letter;
wordElem.innerText = text;
await delay(300);
}
await delay(2000);
wordpos = (wordpos + 1) % stringList.length;
}
}
run();
<h1 class="words">?</h1>
Code:
http://jsfiddle.net/s4UQP/
^ Here is the best way to see the code and how it works with the divs
But here is the code anyway:
function move(from, to) {
document.getElementById('progress').innerHTML = '...';
from = parseInt(from,10);
to = parseInt(to,10);
tbc = document.getElementById(from);
before = document.getElementById(to);
containr = document.getElementById('alldivs');
neworder = 'Order: <select><option onclick="move(' + to + ',1)">1</option><option onclick="move(' + to + ',2)">2</option><option onclick="move(' + to + ',3)">3</option></select> <br>Send up | Send down<br>Bring to front (#1) | Send to back (#4)';
document.getElementById(from).getElementsByClassName('order')[0].innerHTML = neworder;
document.getElementById(from).getElementsByClassName('number')[0].innerHTML = to;
tempdiv = document.createElement('div');
tmphtml = document.getElementById(from).innerHTML;
tempdiv.className = 'holder';
tempdiv.innerHTML = tmphtml;
n = 0;
npieces = 4;
if (from < to) {
nochanges = to - from;
fromone = from + 1;
//alert(n+' '+to+' '+fromone);
for (n = fromone; n <= to; n++) {
//alert('down');
idnum = parseInt(document.getElementById(n).id,10);
//alert(idnum);
document.getElementById(n).getElementsByClassName('number')[0].innerHTML = (idnum - 1);
alert(document.getElementById(n).id);
document.getElementById(n).id = (idnum - 1);
//alert('down '+idnum+' to '+(idnum-1));
}
}
if (from > to) {
nochanges = from - to;
totone = to + 1;
for (n = to; n < from; n++) {
//alert('n is '+n+' going to '+to+' ends at '+totone);
//alert('up');
idnum = parseInt(document.getElementById(n).id,10);
//alert(idnum);
document.getElementById(n).getElementsByClassName('number')[0].innerHTML = (idnum + 1);
alert(document.getElementById(n).id);
document.getElementById(n).id = (idnum + 1);
//alert('up '+idnum+' to '+(idnum+1));
}
}
//tempdiv.id = 'span'+to;
if (from > to) {
containr.insertBefore(tempdiv, before);
}
if (from < to) {
before = to + 1;
containr.insertBefore(tempdiv, document.getElementById(before));
}
tbc.parentNode.removeChild(tbc);
tempdiv.id = to;
document.getElementById('progress').innerHTML = 'done';
}
The script works as you move a block (or div) up or down, but when you try to move a different block (e.g. the one at the top), it just switches around the first two blocks beneath it.
Could anyone give me any advice?
I don't know whether it's because of the order that the script was done in, or if it's something else. It's been confusing me for some time, and I'd really appreciate it if someone could look through it and give me some advice.
(I don't want to code it in jQuery, this is really just me trying to learn more JavaScript by coding something. If it's not the most efficient, secure, whatever, it's still just something with which I'm trying to teach myself JavaScript.)
Thank you for reading. (Please don't edit the JS Fiddle itself, but rather post any edits/improvements here. Thank you.)
[Edit: I'm not really writing a cliche sci-fi, they're just example divs because I couldn't think of anything better]
In the statement neworder =... you change the values of the onclick functions, but you only do this for the block that is about to be moved. The problem is that the other blocks also change positions. For instance, if you click on 'Send up' for block 2, then block 2 moves up to position 1 and block 1 moves down to position 2. But only the event handlers on block 2 are updated accordingly. So the next time you click on (what was originally) block 1, it will not behave correctly.
One solution would be to update the event handlers on all of the blocks that are affected every time one of them is moved. For instance, make a function called updateEventHandlers(blockNumber) and call it for all of the affected blocks.
However relying on IDs to indicate the position of a block and then fiddling with the IDs after they are moved can lead to all sorts of confusion. It is better either to keep an array or dictionary recording the positions of the blocks, or loop through them to determine their positions in the DOM each time you want to move them.
For instance the following code provides moveup, movedown and moveto functions using the latter method (it finds where the element is in the DOM and swaps it with the holder before or after). (JSFIDDLE)
function E(id) { return document.getElementById(id);}
var holders = document.getElementsByClassName('holder');
function moveup(id) {
for (var i = 0; i < holders.length - 1; i++) {
// Find the holder before the one we're interested in
if (holders[i + 1] == E(id)) {
// Swap their positions
E('alldivs').insertBefore(E(id), holders[i]);
break;
}
}
resetNumbers();
}
function movedown(id) {
for (var i = 1; i < holders.length; i++) {
// Find the holder after the one we're interested in
if (holders[i - 1] == E(id)) {
// Swap their positions
E('alldivs').insertBefore(holders[i], E(id));
break;
}
}
resetNumbers();
}
function moveto(id, position) {
if (position == holders.length) { // move to end
E('alldivs').appendChild(E(id));
}
else { // move before another holder
E('alldivs').insertBefore(E(id), holders[position - 1]);
}
resetNumbers();
}
function resetNumbers() {
// Reset all the numbers to reflect their current position
var numbers = document.getElementsByClassName('number');
for (var i = 0; i < numbers.length; i++) {
numbers[i].innerHTML = i + 1;
}
}
A few other points:
clicking on the selects in your original code won't do anything initially, because no event handler is assigned to it until after one of the elements has been moved
there is a missing </div> from the end of the html
it is good practice to declare variables using var somewhere in your code
appendChild and insertBefore remove a node from its current position in the DOM before appending/inserting it in its new position, so there is no need to remove the element explicitly.
having moveup and movedown functions is better than only having moveto, which requires you to insert the current, preceding and following positions into the html and refresh them every time a block is moved.
I've made a counter with javascript that shows a user how characters are remaining (from a set limit) for some text input or text area. Here's the code:
<script type="text/javascript">
function CountRemaining()
{
var limit = 1000;
var count = document.getElementById('press-form-body').value.length;
document.getElementById('counter').innerHTML = ((limit-count) + " characters left");
var timer = setTimeout("CountRemaining()",50);
}
</script>
My abomination above works fine but my problem is that I need to use this multiple times and making a separate function for every time I need it would be impractical to say the least.
I tried this and it didn't work:
<script type="text/javascript">
function CountRemaining(string, targetcounter, limit)
{
var count = document.getElementById(string).value.length;
document.getElementById(targetcounter).innerHTML = ((limit-count) + " characters left");
var timer = setTimeout("CountRemaining()",50);
}
I then figured I put the wrong statement for the timer so I changed it to this but still didn't work:
var timer = setTimeout("CountRemaining(string, targetcounter, limit)",50);
I'm lost. Any help would be highly appreciated. Thank you!
I think a better idea would be to use the "onchange" event for those types of elements.
Basically as soon as the text area / text input loses focus and is changed, you can bind a function to count how many characters are left.
document.getElementById('press-form-body').onchange = function() {
// your stuff (double check this to make sure the "this" value is right
// use this as an example
document.getElementById(targetcounter).innerHTML = this.value.length - 1000
}
Another solution would be to use the "key" events to listen to any keypress in the inputs.
document.getElementById('press-form-body').onkeypress = function() {
// your stuff (double check this to make sure the "this" value is right
// use this as an example
document.getElementById(targetcounter).innerHTML = this.value.length - 1000
}
function limittxt()
{
var tval = document.getElementById('press-form-body').value;
tlength = tval.length;
set = 100;
remain = parseInt(set - tlength);
document.getElementById('counter').innerHTML = remain + " characters left";
if (remain <= 0) {
document.getElementById('press-form-body').value = tval.substring(0, tlength - Math.abs(remain)))
}
}
An call this function in the input element like the following :
<input type='text' onkeypress='limittxt()' onkeyup='limittxt()' onkeydown='limittxt()'>
I suppose the error is with following line:
var timer = setTimeout("CountRemaining(string, targetcounter, limit)",50);
Here i think it should come like:
var str = "CountRemaining(" + string + "," + targetcounter + "," + limit + ")";
var timer = setTimeout(str,50);
If you want to proceed along the lines of using the timer to run the function at regular intervals, then you would need code similar to the following (hat-tip to #Sameera Thilakasiri for the inspiration):
function CountRemaining(string, targetcounter, limit){
var count = document.getElementById(string).value.length;
document.getElementById(targetcounter).innerHTML = ((limit-count) + " characters left");
}
setInterval(function() {
// call the function for each of the inputs on the page you need a counter for
CountRemaining('press-form-body', 'counter', 1000);
// etc
}, 50);
However, I believe #amchang87's approach is better overall, so I recommend you go with that if possible.
Tracking the number of characters left is always a little difficult. A good event to use is keyup or keypress, but that doesn't cover text that is dragged and dropped into the element, so people end up using a timer.
If you have many elements to monitor, consider putting them into an array, then call the timer at each interval and check all of the elements. Be careful with performance though, running the function every 50 ms may sap quite a bit of browser performance so try to keep the processing to an absolute minimum.
That means caching whatever you can and keep the logic simple.
Edit
The run and stop methods below could be used to start the timer when particular elements get focus, then stop it when they lose focus. That way you aren't hogging resources when not required.
/Edit
var keyCountCheck = (function() {
var elementArray, timerRef;
return {
// Initialise once
init: function() {
var input, inputs;
// Initialise elementArray if hasn't been done already
// If adding and removing elements, create new aray
// instead each time.
if (!elementArray) {
elementArray = [];
inputs = document.getElementsByTagName('input');
for (var i=0, iLen=inputs.length; i<iLen; i++) {
input = inputs[i];
if (input.type == 'text') {
elementArray.push(input);
}
}
}
timerRef = window.setInterval(keyCountCheck.run, 50);
},
// Run timer
run: function() {
// If setInterval not running, start it
if (!timerRef) {
keyCountCheck.init();
}
var el;
for (var i=0, iLen=elementArray.length; i<iLen; i++) {
checkLength(elementArray[i]);
}
},
// In case there is a reason to stop this thing.
stop: function() {
if (timerRef) {
window.clearTimeout(timerRef);
timerRef = null;
}
}
};
}());
window.onload = keyCountCheck.init;
function checkLength(el) {
// Character limit can be set as a data- attribute or
// class or various other ways. This is the simple way
var limit = 10;
var msgEl = document.getElementById(el.id + '_limitMsg');
if (msgEl) {
msgEl.innerHTML = (limit - el.value.length) + ' characters left. ' + (new Date());
}
}
Some supporting HTML to play with:
<input id="i0" value="1"><span id="i0_limitMsg"></span>
<br>
<input id="i1" value="2"><span id="i1_limitMsg"></span>
<br>
<button onclick="keyCountCheck.stop()">stop</button>
<button onclick="keyCountCheck.run()">run</button>
setInterval(
function CountRemaining(string, targetcounter, limit){
var count = document.getElementById(string).value.length;
document.getElementById(targetcounter).innerHTML = ((limit-count) + " characters left");
},50
);
Tryout this way.
Basic concept of this solution,
var f = function() {function_name(arg1); };
setTimeout(f, msec);
NOTE: Originally had this listed as a memory leak. After looking into this deeper, I discovered that it's not a memory issue. It's just a very slow script. Any suggestions to speed this up would be greatly appreciated.
ANOTHER NOTE: After looking into this even further, I see that FF does not support any type of CSS that formats text in overflow. There is a hack and a workaround for that hack...but that will not be a suitable solution.
I have voted for and joined the e-mail list on this particular bug at mozilla. It's almost six years old so I resolve that users will just have to deal with it for now. At least it's not a common scenario for our product.
Original post:
The script truncates the value of an element and appends '...' while its scrollWidth is greater than it's offsetWidth. (e.g. A value of "LastName, VeryLongFirstName"will change to something like "LastName, Ver...", depending on the width of the column)
var eTable = document.getElementById(this._eDiv.id + "_tbl");
//...lots of code here...
//function called that gets all cells in a table, loops through them and clips the text
addEventListenerEx(window, "load", function() {
var aCells = eTable.getElementsByTagName("DIV");
window.alert(aCells.length);
//When aCells is length of 100, we're ok...but when it's big (like 3,000) I have problems
for (var i = 0; i < aCells.length; i++){
Grid.clipText(aCells[i]);
}
}, false);
//...lots of code here...
//This is the function doing the actual clipping
Grid.clipText = function (oDiv) {
//for tooltip
var oCurDiv;
var oTagA;
var sToolTip;
if (oDiv.firstChild) {
if (oDiv.firstChild.firstChild){
oCurDiv = oDiv.firstChild;
while (oCurDiv) {
if (is.ie) {
oTagA = oCurDiv;
} else {
// there are some different between IE & FireFox.
oTagA = oCurDiv.firstChild.parentNode;
}
if (oTagA.tagName == "A") {
sToolTip = oTagA.innerHTML;
if (sToolTip.indexOf('<b>') > 0) {
sToolTip = sToolTip.replace('<b>',"");
sToolTip = sToolTip.replace('</b>',"");
}
if (sToolTip.indexOf('<B>') > 0) {
sToolTip = sToolTip.replace('<B>',"");
sToolTip = sToolTip.replace('</B>',"");
}
oTagA.parentNode.title = convertHTMLToText(sToolTip);
}
oCurDiv = oCurDiv.nextSibling;
}
} else {
oDiv.title = convertHTMLToText(oDiv.innerHTML);
}
}
//NOTE: Additional steps to take for non-IE browsers
if (!is.ie) {
var oText = oDiv;
while (oText.nodeType != 3) {
oText = oText.firstChild;
}
var sDisplayText = oText.nodeValue;
if (sDisplayText.length < 3) return;
var lastThree;
sDisplayText = sDisplayText.slice(0, parseInt(oDiv.offsetWidth / 5));
oText.nodeValue = sDisplayText + "...";
//NOTE: Bad things happen here because of this loop
while (oDiv.scrollWidth > oDiv.offsetWidth && sDisplayText != "") {
lastThree = sDisplayText.slice(-3);
sDisplayText = sDisplayText.slice(0, sDisplayText.length - 3);
oText.nodeValue = sDisplayText + "...";
}
oText.nodeValue = sDisplayText + lastThree.slice(0, 1) + "...";
while (oDiv.scrollWidth > oDiv.offsetWidth && sDisplayText != "") {
oText.nodeValue = sDisplayText + "...";
}
}
The code works. However, the problem is that it's called over and over again after a table is loaded on the page. When the table is huge (>1,500 cells), that's when the issue starts.
So, I'm really looking for a way to make this sample (particularly the WHILE loop) more efficient.
Nothing in that is going to leak by itself. You're probably leaking oText in the closure, can you show the surrounding code?
Btw, here is a vastly more efficient way of doing this:
http://jsfiddle.net/cwolves/hZqyj/
If you really want to keep doing it the way you are, you can estimate the cutoff point by taking the length of the string and multiplying it by the proportional width it needs to be...
e.g. if the string is 100 characters and it's 2x as long as it should be, cut it to 50 chars and re-check. Or you could implement a binary 'search' algorithm to get the correct length.
The work-around, and best answer to my problem came from basic arithmetic: cross multiplication
I posted my answer in a more popular stackoverflow thread discussing the topic in better detail.