Find and select next string in textarea - javascript

I'm trying to implement a find and replace functionality for a textarea in a javascript app.
I've got this pretty much working, but for an incredibly weird bug that I've no idea how to fix.
Essentially, 50% of the time this code works as expected. Every time I run the function, it selects the next instance of my string. However, the other 50% it just re-selects the same bit of text over and over.
This inconsistency is most confusing. I can relaunch the app and it works different each time. Here's my code. I'm guessing this might be performance related? Any ideas?
function selectNext() {
// collect variables
var findBox = document.getElementById('findBox');
var editable = document.getElementById('editable');
var txt = editable.innerText;
var strSearchTerm = findBox.value;
// find next index of searchterm, starting from current cursor position
var cursorPos = editable.selectionEnd;
var termPos = txt.indexOf(strSearchTerm, cursorPos);
// if found, select it
if (termPos != -1) {
editable.setSelectionRange(termPos, termPos + strSearchTerm.length)
} else {
// not found from cursor pos, so start from beginning
termPos = txt.indexOf(strSearchTerm);
if (termPos != -1) {
editable.setSelectionRange(termPos, termPos + strSearchTerm.length)
}
}
};

Related

javascript indexOf with millions of matches

I'm trying to extract a few lines representing some XML elements from a file.
The user provides a file using a simple <input type="file"> tag, and than this file is read as text with FileReader, and given as the parameter to this function:
var relevantDelimiters = [{"begin":"<header>","end":"</header>"}
,{"begin":" <someElement>","end":"</someElement>"}];
function dealWithString(invalidXML) {
var validXML = "";
for (var i=0; i<relevantDelimiters.length; i++) {
delimiter = relevantDelimiters[i];
while (invalidXML.indexOf(delimiter.begin) != -1) {
//while there are relevant elements of this kind left:
startPos = invalidXML.indexOf(delimiter.begin);
endPos = invalidXML.indexOf(delimiter.end);
//append to end result:
validXML+=invalidXML.substring(startPos,endPos+delimiter.end.length)+"\n";
//take this item out of the input to process next item
invalidXML = invalidXML.replace(invalidXML.substring(startPos,endPos+delimiter.end.length),"");
}
}
//return fixed data
return validXML;
}
This approach seems to work just fine with a small amount of matches in the input text file, but given a file of 1.5MB, script is stuck (Running with Google Chrome, making it's tab non-responsive). This file contains about a million "relevant elements", meaning matches from relevantDelimiters.
How can I optimize this?
Instead of repeatedly "taking the item out of the input" by calling replace on it, you should use the second argument to indexOf: fromIndex. That way, it'll search the next occurence after the given index, and you can loop through the very large input without needing to touch it.
function dealWithString(invalidXML) {
var validXML = "";
for (var i=0; i<relevantDelimiters.length; i++) {
var delimiter = relevantDelimiters[i],
pos = 0,
startPos;
while ((startPos = invalidXML.indexOf(delimiter.begin, pos)) != -1) {
//while there are relevant elements of this kind left:
var endPos = invalidXML.indexOf(delimiter.end, startPos);
// assert(endPos != -1) - otherwise this could go horribly wrong
pos = endPos+delimiter.end.length;
//append to end result:
validXML += invalidXML.slice(startPos, pos) + "\n";
}
}
return validXML;
}
Where's the time being spent? I assume you could break up this big synchronous action into a couple of async hopes. (Every couple of while-iterations, you could store your index and set-timeout before resuming. This way you don't lock the UI thread.

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

Have text flow around an object that can be moved by the user

I have a div that floats to the left, and some text that wraps around it.
I want to be able to move the div up and down with javascript. As I do so, I want the text to flow around it (above it and below it as needed).
I hope it is clear what I am trying to achieve. Something like how text might behave if you position an object in a word document.
I have already looked into it a bit, and the conclusion I am coming to is that it is only possible if the div is contained within the same parent element as the text. If you want to move the div up and down, you would have to move its position within the text itself (e.g. take a sentence from behind and move it infront).
Doing something like giving it a top margin simply extends the block which text has to flow around, and making it position relative means that the text flows around its original position, and the relatively positioned div overlaps the text. It all makes perfect sense, but makes it very difficult to achieve what I want.
Is there anything in html/css that might allow for what I want, or any plugin that does what I have described.
Thanks
Marcels link is v interesting actually.
I might be tempted to fudge it and get a close approximation, since its simple and gets you half way there.
http://jsfiddle.net/tromm/e3YHb/
var img = $('#whale');
var nextP = img.next();
var previousP = img.prev();
$('#move-down').click(function() {
nextP = img.next();
img.detach();
img.insertAfter(nextP)
});
$('#move-up').click(function() {
previousP = img.prev();
img.detach();
img.insertBefore(previousP)
});
Essentially moving the through the s. in the fiddle i also floated the image so it almost-kinda-sorta looks like its doing what you expect. :)
This is what I was working on (if it is of any use to anyone). It attempts to move the element though the text, checking offsetTop to see when it has actually moved to a new line (I only did the down motion). I would then have moved on to figure out how to call this multiple times when dragging an element. Too much effort for what I need (I would have to take into account elements within elements), and it looks like it will be in html one day anyway, so I will just accept Tom's solution.
function down(el) {
var parentDiv = el.parentNode;
var next = el.nextSibling;
var prev = el.previousSibling;
var match, word;
var offset = el.offsetTop;
while ((match = /^\s*\S+\s*/.exec(next.nodeValue)) !== null && el.offsetTop === offset) {
word = match[0];
if (prev === null) {
prev = parentDiv.insertBefore(document.createTextNode(''), el);
}
prev.nodeValue += word;
next.nodeValue = next.nodeValue.substring(word.length);
}
if (el.offsetTop !== offset) {
return;
}
var nextDiv = findNextDiv(parentDiv);
if (nextDiv === null) {
return;
}
parentDiv.removeChild(el);
if (nextDiv.firstChild === null) {
nextDiv.appendChild(el);
}
else {
nextDiv.insertBefore(el, nextDiv.firstChild);
if (el.offsetTop === offset) {
down(el);
}
}
}
function findNextDiv(el) {
var next;
while ((next = el.nextSibling) !== null) {
if (next.tagName === 'DIV') {
return next;
}
el = next;
}
return null;
}

detect the end of asynchronous recursion

var checkduplicates = new Array();
drawOne(i);
//console.log(checkduplicates)
function drawOne(i)
{
//randomly select one photo
var picinfo = photos[Math.floor(Math.random()*photos.length)];
//check duplicates pic, if duplicates exist, get another one
while(checkduplicates.indexOf(picinfo)!=-1||picinfo.title.length>10)
{
picinfo = photos[Math.floor(Math.random()*photos.length)];
}
checkduplicates.push(picinfo);
var ctx = document.getElementsByClassName("canvas")[i].getContext('2d');
var img = new Image();
//get the pic URL
img.src = "http://farm" + picinfo.farm + ".static.flickr.com/"
+ picinfo.server + "/" + picinfo.id + "_" + picinfo.secret + "_m.jpg";
img.onload = function()
{
// Draw pieces
ctx.drawImage(img,0,0,132,150);
ctx.drawImage(frame,0,0,133,152);
if(picinfo.title=="")
$("#"+i).append("Untitled");
else
$("#"+i).append(picinfo.title);
i++;
if (i != canvaslength)
{
drawOne(i);
}
}
What I am doing here is that I am dynamically generate pictures to fill out 16 canvas and some people said that I am using asynchronous recursion which I dont even notice. I have tried to use loop instead of recursion but somehow ended it up getting exception that i dont know how to fix. So I stick to recursion. However, my problem is that how I can detect the end of the recursion like the commented line shows there is only one item in the array.
//console.log(checkduplicates)
and the explanation I got is that as I understand, the commented console.log is executed before a bunch of recursion of drawOne function finished But what I wanted was that I wanted the full 16 images to be fully loaded and then select them so that I can do something with them. Therefore, the question is how I can detect the end of the recursion. Thank you. You are welcomed to ignore most of my codes and just look at the recursion part.
This is not 'asynchronous recursion'. That would imply that at least two of these loops are running at the same time, and they return asynchronously. Which is simply not the case.
Basically the only time you STOP recursion is when i == canvaslength.
So, just take that if statement.
if (i != canvaslength)
{
drawOne(i);
}else{
console.log('recursion is done') // do what you want here.
}

Javascript code to shorten long text needs optimization

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.

Categories

Resources