Javascript code to shorten long text needs optimization - javascript

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.

Related

How to remove generated node on page

I'm creating a node manually using vanilla JS. The idea is this alert appears after an input field if the maxlength limit has been reached and is removed if it returns below. This is being used on a CMS that users can use to create forms dynamically, so I won't know if the field will have a maxlength or not, or if it will have anything else after it. I'm using the following code:
document.querySelectorAll('input, textarea').forEach(element => {
if (element.hasAttribute('maxlength')) {
let maxChars = element.getAttribute('maxlength');
let elID = element.getAttribute('id');
let charWarning = document.querySelectorAll('#' + elID + ' + .char-limit');
element.addEventListener('input', () => {
let inputLength = element.value.length;
console.log(inputLength);
if (inputLength >= maxChars) {
if (charWarning.length == 0) {
let divAlert = document.createElement('div');
let divAlertText = document.createTextNode(maxChars + ' character limit reached on input');
divAlert.classList.add('text-danger', 'char-limit');
divAlert.setAttribute('aria-live', 'polite');
divAlert.setAttribute('aria-labelledby', elID);
divAlert.appendChild(divAlertText);
element.insertAdjacentElement('afterend', divAlert);
charWarning = document.querySelectorAll('#' + elID + ' + .char-limit');
}
} else {
// console.log(charWarning.length);
if (charWarning.length > 0) {
charWarning.remove(); // This is not working and I have no idea why.
}
}
});
}
});
For whatever reason, the .remove() function isn't working. It's throwing an error:
charWarning.remove is not a function at HTMLTextAreaElement
I don't really understand this. I thought it might be down to the fact that the initial setting of the charWarning prior to the listener was static, so I've added it again at the end of the function creating the warning element. It all works fine, but it's not removing the warning when below the maxlength and throwing that error.
For info, the commented out console.log:
// console.log(charWarning.length);
When uncommented does return 1, when the node has been added.
Can anyone point to what I'm doing wrong?
Ok, so after 2 days of no real responses, I took a late night stab at this again and somehow figured it out through trial and error. I thought the issue was to do with scoping but I couldn't figure out WHAT scope. In the end, I tried changing the "let" to "var" on the warning and that didn't work. So then I changed the selector from document.querySelectorAll to document.querySelector, cleaned up a little of the code and removed the else by using conditional chaining and voilĂ ... result! Much cleaner, much better (and most importantly) functioning code...
However, on researching the properties of aria-live="polite" I discovered that creating and removing the node is the incorrect process. I can create it but when I want to update it for assistive technologies, it's the content I need to change. This kinda goes back to #ZainWilson-WCHStudent's first comment about showing and hiding the content. While I'm still not doing this as I believe it is incorrect for accessibility, it does kind of lean on that idea. I believe that this solution is much more elegant, efficient and (most importantly) accessible:
document.querySelectorAll("input, textarea").forEach((e) => {
if (e.hasAttribute("maxlength")) {
let maxChars = e.getAttribute("maxlength");
let elID = e.getAttribute("id");
let divAlert = document.createElement("div");
let divAlertText = document.createTextNode(
maxChars + " character limit reached on input"
);
divAlert.classList.add("text-danger", "char-limit");
divAlert.setAttribute("aria-live", "polite");
e.insertAdjacentElement("afterend", divAlert);
e.addEventListener("input", () => {
let charWarning = document.querySelector("#" + elID + " + .char-limit");
let inputLength = e.value.length;
if (inputLength >= maxChars) {
charWarning.appendChild(divAlertText);
} else {
if(charWarning.firstChild) {
charWarning.removeChild(charWarning.firstChild);
}
}
});
}
});
Here's a Codepen to test: https://codepen.io/tadywankenobi/pen/OJQXwwR

.length on array crashing when length is 1 (maybe issue with split)

I'm having trouble with this code. I've tried to troubleshoot it many times and seem to have isolated the issue, but can't figure out the cause.
If the variable called string is set to something in the form of "text v. text," the code runs fine and the first if-statement triggers the sentence. If the string contains text but no "v." i.e. nothing that meets the search separator value, the function fails and does not execute the second if-statement.
Link to Fiddle: http://jsfiddle.net/qsq4we99/
Snippet of code, there also would need to be a html div with ID "outputtext."
function brokenCode()
{
//Setting Relevant Variables
var string = "red";
var array = string.split("v.");
var number = array.length;
// Checking location of things
var findText1 = array[0].search("search text");
var findText2 = array[1].search("search text");
//Running Conditional Stuff
if(number > 1)
{
document.getElementById('outputtext').innerHTML = "2+ listed";
}
else if(number < 2)
{
document.getElementById('outputtext').innerHTML = "1 listed";
}
}
brokenCode();
In this simplified example there is no clear explanation why the search operations need to occur (they are there because in the real code they are needed... but something about them seems to be causing the problem (even in this simple example). If the two searches are removed, the code runs smoothly.
You can't start setting variables from the array without checking for length. Before setting findText1 & findText2, check to make sure the length of the array is greater than zero.
function brokenCode() {
//Setting Relevant Variables
var string = "red";
var array = string.split("v.");
var number = array.length;
if (number > 0) {
// Checking location of things
var findText1 = array[0].search("search text");
var findText2 = array[1].search("search text");
//Running Conditional Stuff
if(number > 1)
{
document.getElementById('outputtext').innerHTML = "2+ listed";
}
else if(number < 2)
{
document.getElementById('outputtext').innerHTML = "1 listed";
}
}
}
brokenCode();

javascript - download several files

I'm new in javascript and i need to maintain a site.
The actual fonctionnality is to download several card into a file, one card per tab. As users can have plenty of cards, treatment can not always succeed (too many tabs), i wanted create a file evey 20 tabs for instance.
var printTerm = function(grid){
var rows = grid.selected;
if(rows==null) return;
var ids = rows.map(function(val){return grid.getDataByRow(val).num_terme;});
var nbMax = 20;
var nbFic;
var idsPartiel;
var posDebut;
var posFin;
var a;
if(ids.length > nbMax)
{
idsPartiel = ids;
if(ids.length % nbMax == 0) nbFic = ids.length / nbMax;
else nbFic = ((ids.length - (ids.length % nbMax)) / nbMax) + 1;
for (i=0 ; i< nbFic ; i++)
{
posDebut = (nbMax * i);
if(i == nbFic - 1) posFin = idsPartiel.length + 1;
else posFin = posDebut + nbMax;
ids = idsPartiel.slice(posDebut,posFin);
a = new Element('a', 'id':'download','href':'php/utils/export2pdf.php?ids='+ids.join(',')})
.addEvent('click',function(){location.href=this.href;}).inject(document.body);
a.fireEvent('click');
a.dispose();
}
}
else
{
a = new Element('a',{'id':'download','href':'php/utils/export2pdf.php?ids='+ids.join(',')})
.addEvent('click',function(){location.href=this.href;}).inject(document.body);
a.fireEvent('click');
a.dispose();
}
};
When the number of cards is less or equal to nbMax, it works well, bu when there must be several files, not: only the last passage of the loop is creating the file.
When i try to see what happens with firebug, i see my lines of treatment, but only the last is ended.
Thanks for helping me.
If I read your code correctly, you are trying to make the browser download a ton of files into separate tabs. Most browsers won't really appreciate you trying to launch a loop of downloads that way, nor would a user really want that many save as dialogs potentially popping off.
You would be way better off packaging them into a zip file server side and sending ONE file. It would be more efficient and way more user friendly.

Javascript alert box shows up before executing previous statement

I am having a strange issue, but it is not surprising as I am a bit of a JavaScript newbie. Basically I am creating a simple high-low card game. (Draw two cards, highest card wins). Anyways, the code is below.
The basic flow of the program is pretty simple. I choose 2 random numbers (1-52). These numbers are mapped to a corresponding card. (i.e. number 1 is the ace of spades, number 37 is the jack of clubs, etc.). Anyways, after drawing the cards, the program is to display the corresponding card and determine the winner. At the end of all of this, i have an alert that comes up and and tells the winner of the draw and asks if the user wants to play again.
The problem I am having is this: Even though the program should have already displayed the image of the card and output the results to a text area, the alert box shows up before any of that actually occurs and never displays the cards or the results. Any ideas? I am posting all of the code so far and any help would be appreciated. Thanks in advance.
function drawCards() {
var oppCard = randNumber();
var customerCard = randNumber();
while (oppCard == customerCard) {
customerCard = randNumber();
}
var oppCardName = displayCard(oppCard, "oppImage");
var customerCardName = displayCard(customerCard, "custImage");
var result2 = "Your card was: " + customerCardName;
var result1 = "The opponent's card was: " + oppCardName;
var result3 = determineWinner(oppCard, customerCard);
var result4 = result3 + '\n' + result1 + '\n' + result2;
$("#textareaRes").text(result4);
playAgain(result3);
}
function determineWinner(oppsCard, customersCard) {
var oppValue = oppsCard % 13;
var customerValue = oppsCard % 13;
var winnerString = "";
if (oppValue == 0) {
oppValue = 13;
}
if (customerValue == 0) {
customerValue = 13;
}
if (oppValue == customerValue) {
winnerString = "You Tied.";
}
else if (oppValue > customerValue) {
winnerString = "You Lose.";
}
else if (oppValue < customerValue) {
winnerString = "You Win!!";
}
return winnerString;
}
function randNumber() {
var min = 1;
var max = 52;
var random = Math.floor(Math.random() * (max - min + 1)) + min;
return random;
}
function playAgain(resultString) {
if (resultString == "You Lose." || resultString == "You Win!!") {
alert(resultString);
var conf = confirm("Play Again?");
if (conf == true) {
$("#textareaRes").text("");
document.getElementById("custImage").src="./cardImages/default.png";
document.getElementById("oppImage").src="./cardImages/default.png";
}
else {
window.location = "#mainMenuPage";
}
}
else {
alert(resultString);
alert("Try Again.");
$("#textareaRes").text("");
document.getElementById("custImage").src="./cardImages/default.png";
document.getElementById("oppImage").src="./cardImages/default.png";
}
}
So I did not place the code in here for the display card function, just because for testing it is exceptionally long. It is just a giant switch case for all 52 random numbers. The finished product will actually be pulling from an XML file, but I used this just for testing purposes. (If, for some reason, you need to see the display cards function, let me know and I can post it.) Anyway, to recap, the last call made in the drawCards() function is the playAgain function. Upon running this code the results nor the card images are displayed. It just jumps straight to the alert that is called for by the playAgain function. This is probably a pretty noobish question, but I am a little perplexed by it. So any help you guys can offer would be GREATLY appreciated. Thanks.
EDIT: It actually performs correctly in a computer's browser. However, the problem happens on a mobile device like a phone or tablet. So this is probably something that I am doing incorrectly here. Any help is greatly appreciated.
Changes in the browser doesn't show up as long as your Javascript code is running.
The browser is event driven, so changing an element in the DOM doesn't show the change immediately, instead an event is triggered to redraw the element. When your function has finished running, the browser will handle any pending events and show the changes.
So, when building an application, you have to use the same approach so that the browser has a chance to show the changes.
For anyone who finds this looking for the solution to the problem, the solution can be found in this answer: https://stackoverflow.com/a/13338585/870729
Here is a working fiddle of a simple example:
jQuery(function($) {
$.when($('#empty-me').html('')).done(function() {
alert('I did it!');
});
});
"./cardImages/default.png"
im not sure ... but try "../cardImages/default.png" ... i always use 2 dots for come to a higher level

better way to code recursive if statements

I find myself in this position occasionally, and I'm sure there is a better way to do it than I am currently.
In this example, I'm trying to sort a group of times where I have conflicting items. I need to know what times are high-priority,can't be moved, vs low priority, can be moved.
But I'm pretty sure this code is very inefficient.
var holdLowPriorities = [];
for (conflict = 0; conflict < sortedTimes.length - 1; conflict++) {
var firstConflictTimeStart = sortedTimes[conflict][0];
var firstConflictTimeEnd = sortedTimes[conflict][1];
var secondConflictTimeStart = sortedTimes[conflict + 1][0];
var secondConflictTimeEnd = sortedTimes[conflict + 1][1];
if (firstConflictTimeStart < secondConflictTimeEnd &&
firstConflictTimeEnd > secondConflictTimeStart) {
// are either of the conflicts a high priority
var firstContactPriority = sortedTimes[conflict][2];
var secondContactPriority = ortedTimes[conflict + 1][2]
if (firstConflictPriority == 2) {
//this is high priority, can't move
}
if (secondConflictPriority == 2) {
// this is also a priority, but has to move
}
// are either of the conflicts a low priority?
if (firstConflictPriority == 0) {
// this is a low priority so I can adjust the time
} else if (secondConflictPriority == 0) {
// this is a low priority so I can adjust the time
}
}
}
Unfortunately, I don't even know what to call this type of a problem, and therefore don't know what to look for, though I'm sure the answer isn't overly complicated (I hope not anyway).
A switch statement might help clean up your code a bit.
Edit: you've altered the question so most of this becomes irrelevant.
Here is a simple clarification of it.
var holdLowPriorities = [];
for(conflict=0; conflict<sortedTimes.length-1; conflict++){
var conflictTime = sortedTimes[conflict],
nextConflictTime = sortedTimes[conflict + 1];
if (conflictTime[0] >= nextConflictTime[1] || conflictTime[1] <= nextConflictTime[0]) {
continue;
}
// are either of the conflicts a high priority
if (data[conflictTime[2]].stepsArr[conflictTime[3]].priority==2) {
alert(data[conflictTime[2]].stepsArr[conflictTime[3]].
}
if (data[nextConflictTime[2]].stepsArr[nextConflictTime[3]].priority == 2) {
alert(data[nextConflictTime[2]].stepsArr[nextConflictTime[3]].
}
// are either of the conflicts a low priority?
if (data[conflictTime[2]].stepsArr[conflictTime[3]].priority==0) {
holdLowPriorities.push([conflictTime[2], conflictTime[3], conflict]);
} else if (data[nextConflictTime[2]].stepsArr[nextConflictTime[3]].priority == 0) {
holdLowPriorities.push([nextConflictTime[2], nextConflictTime[3], conflict+1])
}
//alert(data[nextConflictTime[2]].stepsArr[nextConflictTime[3]].prerequisite+' '+conflictTime[0]+' '+conflictTime[1]+' '+nextConflictTime[0]+' '+nextConflictTime[1]+' '+data[nextConflictTime[2]].stepsArr[nextConflictTime[3]].taskid+' / '+data[conflictTime[2]].stepsArr[conflictTime[3]].taskid);
}
Then this can be made more obvious by using a helper method and a couple more variables.
function conflictData(conflict) {
return data[conflict[2]].stepsArr[conflict[3];
}
var holdLowPriorities = [];
for(conflict=0; conflict<sortedTimes.length-1; conflict++){
var conflictTime = sortedTimes[conflict],
nextConflictTime = sortedTimes[conflict + 1];
if (conflictTime[0] >= nextConflictTime[1] || conflictTime[1] <= nextConflictTime[0]) {
continue;
}
var thisConflictData = conflictData(conflictTime),
nextConflictData = conflictData(nextConflictTime);
// are either of the conflicts a high priority
if (thisConflictData.priority == 2) {
alert(thisConflictData);
}
if (nextConflictData.priority == 2) {
alert(nextConflictData);
}
// are either of the conflicts a low priority?
if (thisConflictData.priority == 0) {
holdLowPriorities.push([conflictTime[2], conflictTime[3], conflict]);
} else if (nextConflictData.priority == 0) {
holdLowPriorities.push([nextConflictTime[2], nextConflictTime[3], conflict+1])
}
//alert(nextConflictData.prerequisite + ' ' + conflictTime[0] + ' ' + conflictTime[1] + ' ' + nextConflictTime[0] + ' ' + nextConflictTime[1] + ' ' + nextConflictData.taskid + ' / ' + thisConflictData.taskid);
}
When expressed like this, I think you can start to see probable bugs; high priority has conflictTime and nextConflictTime checks as if, if, whereas low priority has if, else if. Is this what is desired? You may be able to then put this/next conflict priority in a switch or reorganise the code more. But I think it's now at the stage where it can be understood more readily.
In terms of data structures, there is nothing inherently inefficiently with if..else blocks. Even nested if..else are not a problem. In terms of readability it's another matter altogether. As a rule of thumb, large and deeply nested if..else blocks are hard to follow (for a human, computers of course have no problems with them).
First of all, I'd go with Mitch's suggestion to use intermediate variables to reduce code verbosity. In terms of efficiency it is definitely less memory efficient (though for javascript, it could potentially speed up access depending on the browser). But efficiency is not the point, code clarity is.
Secondly, use array literal syntax instead of new Array() : holdLowPriorities.push([...]). Again, less noise on the screen, the easier it is to see what's going on.
Third, there are several places where all you're doing is checking the priority of something. Use either a helper function or a method to simplify the code here: checkProirity(sortedTimes,conflict,2) or sortedTimes.checkProirity(conflict,2). Again, a function/method call is inherently less efficient but the point is to improve code clarity for good readability.

Categories

Resources