How to get JavaScript running properly on mobile phones? - javascript
(http://www.learnwithjesse.com/white-hmong-to-green-hmong-converter/).
I have JavaScript that utilizes two HTML <textarea> elements, one for input values and one for output values. Input values are converted a different word and is outputted to the output box. For example, if I type in the word 'daj' in the input box and click on the convert button, it should output the converted word 'dlaaj' to the output box. It works fine in Chrome, Firefox, and Internet Explorer, but not on my Galaxy S5 Active; it outputs the same word I put in the input box. 'daj' still outputs 'daj'.
What I've tried so far: I've tried running on different phones, iPhone5, it doesn't output correctly. I've tried running the script on http://mobiletest.me which makes my computer act like mobile phone using Galaxy S5 and it runs properly.
How can I get it to output properly on mobile phones?
<p class="welcome" id="greeting">White Hmong to Green Hmong Converter</p>
<p class="content">
</p>
<form>
<input type="button" value="Convert White Hmong to Green Hmong" onClick="clicked(0)" /><input type="button" value="Converter Green Hmong to White Hmong" onClick="clicked(1)" />
<br>
<br>
<textarea rows="7" cols="68" id="whiteHmongInput" >
Input</textarea>
<br>
<br>
<textarea rows="7" cols="68" id="greenHmongInput" >
Output</textarea>
</form>
<script>
function clicked(number) {
var n = -1;
var list = [];
var list2 = [];
var NUMBERWORDS = 90;
list[0] ="cab";
list[1] ="cia";
list[2] ="dab";
list[3] ="daj";
list[4] ="dej";
list[5] ="dev";
list[6] ="dib";
list[7] ="duab";
list[8] ="fiav";
list[9] ="hais";
list[10] ="hla";
list[11] ="hlab";
list[12] ="hlad";
list[13] ="hmaim";
list[14] ="hmaiv";
list[15] ="hmaj";
list[16] ="hmaiv";
list[17] ="hmob";
list[18] ="hmog";
list[19] ="hmoo";
list[20] ="hmoob";
list[21] ="hmood";
list[22] ="hmoog";
list[23] ="hmoov";
list[24] ="hmos";
list[25] ="hmov";
list[26] ="hnas";
list[27] ="hnais";
list[28] ="hneev";
list[29] ="hnyab";
list[30] ="hnov";
list[31] ="iav";
list[32] ="kos";
list[33] ="liab";
list[34] ="liaj";
list[35] ="liam";
list[36] ="loos";
list[37] ="los";
list[38] ="mloos";
list[39] ="mus";
list[40] ="npib";
list[41] ="nqhaiv";
list[42] ="nyaiv";
list[43] ="pa";
list[44] ="pab";
list[45] ="pad";
list[46] ="pag";
list[47] ="pam";
list[48] ="piam";
list[49] ="piav";
list[50] ="qaib";
list[51] ="rhiam";
list[52] ="siab";
list[53] ="siav";
list[54] ="thab";
list[55] ="tiab";
list[56] ="tias";
list[57] ="tiav";
list[58] ="tsam";
list[59] ="tshaj";
list[60] ="txiav";
list[61] ="txiab";
list[62] ="vaj";
list[63] ="xa";
list[64] ="xaj";
list[65] ="xya";
list[66] ="yiag";
list[67] ="zaj";
list[68] = "txoj";
list[69] = "nco";
list[70] = "dua";
list[71] = "tus";
list[72] = "txog";
list[73] = "cas";
list[74] = "tos";
list[75] = "qab";
list[76] = "yaj";
list[77] = "pov";
list[78] = "niaj";
list[79] = "hmo";
list[80] = "hnub";
list[81] = "iab";
list[82] = "pom";
list[83] = "niaj";
list[84] = "Ziag";
list[85] = "ya";
list[86]= "tas";
list[87]= "nws";
list[88] = "rau";
list[89] = "li";
list2[0] ="caab";
list2[1] ="ca";
list2[2] ="dlaab";
list2[3] ="dlaaj";
list2[4] ="dlej";
list2[5] ="dlev";
list2[6] ="dlib";
list2[7] ="dluab";
list2[8] ="fav";
list2[9] ="has";
list2[10] ="hlaa";
list2[11] ="hlaab";
list2[12] ="hlaad";
list2[13] ="maim";
list2[14] ="maiv";
list2[15] ="maaj";
list2[16] ="maiv";
list2[17] ="mob";
list2[18] ="mog";
list2[19] ="moo";
list2[20] ="moob";
list2[21] ="mood";
list2[22] ="moog";
list2[23] ="moov";
list2[24] ="mog";
list2[25] ="mov";
list2[26] ="naag";
list2[27] ="nais";
list2[28] ="neev";
list2[29] ="nyab";
list2[30] ="nov";
list2[31] ="av";
list2[32] ="kaus";
list2[33] ="lab";
list2[34] ="laj";
list2[35] ="lam";
list2[36] ="loog";
list2[37] ="lug";
list2[38] ="noog";
list2[39] ="moog";
list2[40] ="pib";
list2[41] ="qhav";
list2[42] ="yav";
list2[43] ="paa";
list2[44] ="paab";
list2[45] ="paad";
list2[46] ="paag";
list2[47] ="choj";
list2[48] ="puag";
list2[49] ="pav";
list2[50] ="qab";
list2[51] ="rag";
list2[52] ="sab";
list2[53] ="sav";
list2[54] ="hab";
list2[55] ="tab";
list2[56] ="tag";
list2[57] ="tav";
list2[58] ="tsaam";
list2[59] ="tshaaj";
list2[60] ="txav";
list2[61] ="txab";
list2[62] ="vaaj";
list2[63] ="xaa";
list2[64] ="xaaj";
list2[65] ="xyaa";
list2[66] ="yag";
list2[67] ="zaaj";
list2[68] = "txuj";
list2[69] = "ncu";
list2[70] = "dlua";
list2[71] = "tug";
list2[72] = "txug";
list2[73] = "caag";
list2[74] = "tog";
list2[75] = "qaab";
list2[76] = "yaaj";
list2[77] = "puv";
list2[78] = "naj";
list2[79] = "mo";
list2[80] = "nub";
list2[81] = "ab";
list2[82] = "pum";
list2[83] = "naj";
list2[84] = "Zag";
list2[85] = "yaa";
list2[86]= "tag";
list2[87]= "nwg";
list2[88] = "rua";
list2[89] = "le";
var s = document.getElementById("whiteHmongInput").value;
var choppedIntoLines = s.split(/\r\n|\r|\n/g);
var choppedIntoWords;
//Splits Lines Into Words
document.getElementById("greenHmongInput").value ="";
for(var i = 0; i < choppedIntoLines.length; i++) {
choppedIntoWords = choppedIntoLines[i].split(" ");
//Splits each Line to words, then match words to see if white hmong if so convert to green word
for(var o = 0; o < choppedIntoWords.length; o++) {
choppedIntoWords[o].toLowerCase();
if (number == 0){
n = list.indexOf(choppedIntoWords[o].valueOf()); //tries to find the index of a word if i exist, returns -1 if it doesn't
if ( n != -1){
choppedIntoWords[o] = list2[n];
}
n = -1; //Basically if n = -1 it means the white hmong word coulnd't be found.
}
if (number == 1 ){
n = list2.indexOf(choppedIntoWords[o].valueOf()); //tries to find the index of a word if i exist, returns -1 if it doesn't
if ( n != -1){
choppedIntoWords[o] = list[n];
}
n = -1; //Basically if n = -1 it means the white hmong word couldn't be found.
}
}
//Recombines words to line of Words.
choppedIntoLines[i] = "";
for (var p = 0; p < choppedIntoWords.length; p++) {
choppedIntoLines[i] += choppedIntoWords[p] + " ";
}
//Recombines lines and Output to Green Hmong Section
document.getElementById("greenHmongInput").value += choppedIntoLines[i]+"\n";
}
}
</script>
I suspect the reason your code is not working on mobile is not because of the mobile browser, but the keyboard on your mobile device. Most mobile keyboards will automatically capitalize the first letter you type, so when you type daj in it automatically comes out Daj.
There is a bug in the code that prevents it form working with words that have any capital letters.
Fixing the current code
The line choppedIntoWords[o].toLowerCase(); does nothing. In JavaScript strings are immutable, toLowerCase does not alter the string, it returns a new string. Since you never assigned the result of toLowerCase to a variable, the result was immediately discarded.
The quick fix would be to just assign the result of toLowerCase to the array element you are calling it on: choppedIntoWords[o] = choppedIntoWords[o].toLowerCase();.
Better yet, move the toLowerCase call into the comparison, leaving the original value untouched, this will preserve the capitalization of any words that are not being replaced.
list.indexOf(choppedIntoWords[o].toLowerCase()) // .valueOf() is not needed here
(Another subtle bug, word 84 in the arrays (Ziag/Zag), both words are capitalized which will screw up the comparisons even after the toLowerCase bug is fixed.)
That said, I would rewrite it to work a little differently to make it more maintainable.
A better way of doing it
Instead of trying to keep two lists of words in sync, it would be much easier to store the word pairs as two-element arrays inside of another array. This way when adding/removing/editing any of the words you can do it all in one place.
At run-time, you could then generate two objects to act as look-up tables for the white-to-green and green-to-white conversions. Using these objects to look up the conversions should also be much faster than doing .indexOf on an array. In this particular situation, efficiency probably does not matter that much but it is a bonus that the conversion will happen (ever so slightly) faster.
<p class="welcome" id="greeting">White Hmong to Green Hmong Converter</p>
<p class="content">
</p>
<form>
<button type="button" id="white-to-green">Convert White Hmong to Green Hmong</button>
<button type="button" id="green-to-white">Convert Green Hmong to White Hmong</button>
<br>
<br>
<textarea rows="7" cols="68" id="input" placeholder="Type in Hmong words here"></textarea>
<br>
<br>
<textarea rows="7" cols="68" id="output" placeholder="Converted words will appear here"></textarea>
</form>
<script>
// Storing the white and green words in a single array that made up of
// two-element arrays containing each word pair will be much more
// maintainable than trying to keep two different lists in sync.
var hmongWords = [
// white first, green second
["cab", "caab"],
["cia", "ca"],
["dab", "dlaab"],
["daj", "dlaaj"],
// ...
["tas", "tag"],
["nws", "nwg"],
["rau", "rua"],
["le", "li"]
],
// these objects will act as look-up tables for the conversions
whiteToGreen = {},
greenToWhite = {},
elInput = document.getElementById("input"),
elOuput = document.getElementById("output"),
elWhiteToGreen = document.getElementById("white-to-green"),
elGreenToWhite = document.getElementById("green-to-white"),
convert = function (text, lookupTable) {
var lines = text.split(/\r\n|\r|\n/),
processWord = function (word) {
// Look for the word in the look up object.
// If the object has a property that is the word we are looking for,
// that value will be returned. If the word is not in the look-up
// object, undefined is returned. Since undefined is falsey the
// second half of the or statement will return the original word
return lookupTable[word.toLowerCase()] || word;
},
processLine = function (line) {
// Split the line up based on whitespace and process them,
// join the resulting array with spaces and return the converted line
return line.split(/\s/).map(processWord).join(' ');
};
// Map will return a new array of lines that have been processed by
// processLine. Join the new lines with a newline and return the string
return lines.map(processLine).join('\n');
},
convertWhiteToGreen = function (text) {
// just calls convert with the whiteToGreen look-up object
return convert(text, whiteToGreen);
},
convertGreenToWhite = function (text) {
// just calls convert with the greenToWhite look-up object
return convert(text, greenToWhite);
},
makeListener = function (converter) {
// this returns a function that will be used as an event listener
return function () {
// grab text from the input box, runs it through the
// converter function that was provided when makeListener was called
// and puts the output into the output box
elOuput.value = converter(elInput.value);
};
};
// build the look-up tables
hmongWords.forEach(function (wordPair) {
whiteToGreen[wordPair[0]] = wordPair[1];
greenToWhite[wordPair[1]] = wordPair[0];
});
// Attach the event listeners to the buttons.
// makeListener returns a function that uses the function you pass to it
// to convert the text.
elWhiteToGreen.addEventListener('click', makeListener(convertWhiteToGreen), false);
elGreenToWhite.addEventListener('click', makeListener(convertGreenToWhite), false);
</script>
You can see my version in action here
My code uses the forEach and map methods to loop over the arrays that split creates instead of for loops. This avoids the need for a counter variable and instead allows us to provide each item in the array a meaningful name (word, line, etc) instead of referring to an item in the array by its index.
Something else you might notice is that the convert function uses the logical or (||) operator. The logical or statement short-circuits if the first operand is truthy. So if a value is found in the look-up object, it is returned. If the value is not found in the object the second operand, the original word is returned. You have to be careful when using this technique in some situations, for instance when a valid option might be falsy such as 0 or an empty string. But in this situation lookupTable[word.toLowerCase()] will either return a non-empty string, which is always truthy or undefined which is always falsy.
You might have noticed that I used the words "truthy" and "falsy" instead of true and false this has to do with how implicit type conversion is handled in JavaScript. If something is "truthy" it will be converted to true when in a context that a Boolean value is needed. Likewise "falsy" values are values that will be converted to false in a context where a Boolean value is needed.
In the HTML instead of putting the placeholder text as a value in the textareas, I used the placeholder attribute.
Here are a few articles that might help understanding some of the techniques I've used if they are new to you:
Truthy and Falsy: When All is Not Equal in JavaScript
JavaScript quirk 1: implicit conversion of values
Functions are first class objects in javascript
Tidying Up a JavaScript Application with Higher-Order Functions
Exploring JavaScript’s Logical OR Operator
Another thing I used in my code but didn't discus is closures. They are a kind of big and important topic in JavaScript here is some stuff to help with them:
Closing The Book On Javascript Closures
(videos) Stuart Langridge: Secrets of JavaScript Closures part 1, part 2
Advanced JavaScript: Namespaces, Closures, Self-Invoking Functions, and much, much more…
Related
Return or highlight only new values in textarea
I am stuck in one of the tricky problem. I have a textarea having some strings already in it. What I am trying is, when the user put new values in the textarea, they should be displayed highlighted (green), only the new ones, the old ones should remain colorless. And if the user removes the strings from the textarea, the removed values should be highlighted as red. I am using plain JS and HTML. What I have tried is below. function f() { var checkBox = document.getElementById('checkbox'); var scopeResult = document.getElementById('scopeResult'); var scopes = document.getElementById('scopes').value; if (checkBox.checked === true) { scopeResult.style.display = 'block'; addOrRemoveScope(scopes); } else { scopeResult.style.display = 'none'; } } function addOrRemoveScope(val) { document.getElementById("scopeResult").innerHTML = val.replace(/(?:\\[rn]|[\r\n])/g, ",<br/>"); } <textarea id="scopes" class="form-control" field="*{scope}" rows="10" maxlength="4096" onchange="addOrRemoveScope(this.value)"> </textarea> <input id="checkbox" type="checkbox" required onclick="f()" /> <label for="checkbox"> Are you sure, you want to modify the scopes?*</label><br> <p id="scopeResult" style="display: none; background-color: #5cb85c"></p> Thanks!
replace(/(?:\\[rn]|[\r\n])/g, ",<br/>") is not going to help solve the problem. Really you should look into a robust lib that does text diff, but to answer. Based entirely on the position of the value compared to the original value. When the user put new values in the textarea, they should be displayed highlighted (green), only the new ones. New values can replace old ones, so I would opt in to making them red, and new new values would be green, essentially any new value longer then the original string length is green everything else is treated as changed, which would be red The old ones should remain colourless. And if the user removes the strings from the textarea, the removed values should be highlighted as red. Easy enough if is all based on position. As said if you want something more robust look into using a lib. Or use something like the following: Split the original string and new value into arrays, then loop over original string char by char and if is the same, then dont apply styling, if it's not the same then treat as changed (red), if it does not exist in new array then its deleted, so use the original char as the value. Then use slice to pick only the new chars in the new value as they will be all new, then join both together. let original = document.getElementById('input').value; function process() { let letters_original = [...original] let letters_current = [...document.getElementById('input').value] let b = letters_original.map( (v, i) => letters_current[i] === v ? v : `<span style="color:red">${letters_current[i] || v}</span>` ) let c = letters_current.slice(b.length).map( v => `<span style="color:green">${v}</span>` ) document.getElementById("output").innerHTML = [...b, ...c].join('') } <textarea id="input" rows="10" maxlength="4096" oninput="process()">123</textarea> <p id="output"></p>
trying to find words that begin with a
I am writing some code to find words in paragraphs that begin with the letter "a". I was wondering if there was a shortcut that I could put inside of a variable. I do know about the startsWith() function but that does not work for what i'm trying to do. Here's what I have so far. I'm trying to use the match method and .innerText to read the paragraphs. function processText() { var totalNumberOfWords = document.getElementById('p') var wordsBegginingWithA = 0; var wordsEndingWithW = 0; var wordsFourLettersLong = 0; var hyphenatedWords = 0; } <p><button onClick="processText();">Process</button></p> <p id="data"></p> <p>The thousand injuries of Fortunato I had borne as I best could; but when he ventured upon insult, I vowed revenge. You, who so well know the nature of my soul, will not suppose, however, that I gave utterance to a threat. <span style='font-style:italic;'>At length</span> I would be avenged; this was a point definitely settled--but the very definitiveness with which it was resolved precluded the idea of risk. I must not only punish, but punish with impunity. A wrong is unredressed when retribution overtakes its redresser. It is equally unredressed when the avenger fails to make himself felt as such to him who has done the wrong.</p>
You can get the inner text of the p element - split it at the spaces to get the words - pass the words through a function to see if the first letter is "a" and if so, increment a count. processText(); function processText() { var p = document.querySelector('p').innerText; var totalWords = p.split(' '); var wordsBegginingWithA = 0; totalWords.forEach(function(word){ if ( beginsWithA(word) ) { wordsBegginingWithA++ }; }) console.log(wordsBegginingWithA); // gives 5 } function beginsWithA(word){ return word.toLowerCase().charAt(0) == 'a'; } <p>Apples and oranges are fruit while red and blue are colors</p>
You can use: [variablename].match(/(?<!\w)a\w*/ig)!=null? a.match(/(?<!\w)a\w*/ig).length:0; to detect what words starting with what letter (in example it was a). And: [variablename].match(/\S+/g)!=null? a.match(/\S+/g).length:0; to detect word count. function processText() { var a = document.getElementById('p').innerText; var b = a.match(/(?<!\w)a\w*/ig)!=null? a.match(/(?<!\w)a\w*/ig).length:0; var word= a.match(/\S+/g)!=null? a.match(/\S+/g).length:0; console.log('Text: ',a,'\nA starting word: ', b, '\nWord count: ',word); } processText(); <span id="p">Apple is super delicious. An ant is as good as my cat which favors a pear than fish. I'm going to test them all at once.</span> Explanation: .match would return all value which matches the expression given. Notice that I also used conditional (ternary) operator to detect whether or not the Regex will return a null value if no match were returned. If it's returning null then it would result in 0 (:0) if it's returning another value than null then it would return the count (.length). More info related to Regular expression: https://www.rexegg.com/regex-quickstart.html
function processText() { let pp = document.getElementById('root') console.log(pp.innerHTML.match(/(?<!\w)a\w*/g)) return pp.innerHTML.match(/(?<!\w)a\w*/g); } processText() <p id='root'>this is a apple</p>
Using the result of indexOf, 0 is the equivalent to startsWith var str = document.getElementById("myTextarea").value; var keyword = document.getElementById("myInput").value; var n = str.indexOf(keyword);` Working sample in this fiddle. HTH
Unable to Get Output From While Loop in Javascript
I'm working on my final project of the Winter 2017 quarter to demonstrate how to use Regular Expressions in both C# and JavaScript code behind pages. I've got the C# version of my demonstration program done, but the JavaScript version is making me pull what little hair I have left on my head out (no small achievement since I got a fresh buzz cut this morning!). The problem involves not getting any output after applying a Regular Expression in a While loop to get each instance of the expression and printing it out. On my HTML page I have an input textarea, seven radio buttons, an output textarea, and two buttons underneath (one button is to move the output text to the input area to perform multiple iterations of applying expressions, and the other button to clear all textareas for starting from scratch). Each radio button links to a function that applies a regular expression to the text in the input area. Five of my seven functions work; the sixth is the one I can't figure out, and the seventh is essentially the same but with a slightly different RegEx pattern, so if I fix the sixth function, the seventh function will be a snap. (I tried to insert/upload a JPG of the front end, but the photo upload doesn't seem to be working. Hopefully you get the drift of what I've set up.) Here are my problem children from my JS code behind: // RegEx_Demo_JS.js - code behind for RegEx_Demo_JS var inputString; // Global variable for the input from the input text box. var pattern; // Global variable for the regular expression. var result; // Global variable for the result of applying the regular expression to the user input. // Initializes a new instance of the StringBuilder class // and appends the given value if supplied function StringBuilder() { var strings = []; this.append = function (string) { string = verify(string); if (string.length > 0) strings[strings.length] = string; } this.appendLine = function (string) { string = verify(string); if (this.isEmpty()) { if (string.length > 0) strings[strings.length] = string; else return; } else strings[strings.length] = string.length > 0 ? "\r\n" + string : "\r\n"; } this.clear = function () { strings = []; }; this.isEmpty = function () { return strings.length == 0; }; this.toString = function () { return strings.join(""); }; var verify = function (string) { if (!defined(string)) return ""; if (getType(string) != getType(new String())) return String(string); return string; } var defined = function (el) { // Changed per Ryan O'Hara's comment: return el != null && typeof(el) != "undefined"; } var getType = function (instance) { if (!defined(instance.constructor)) throw Error("Unexpected object type"); var type = String(instance.constructor).match(/function\s+(\w+)/); return defined(type) ? type[1] : "undefined"; } } Within the code of the second radio button (which will be the seventh and last function to complete), I tested the ScriptBuilder with data in a local variable, and it ran successfully and produced output into the output textarea. But I get no output from this next function that invokes a While loop: function RegEx_Match_TheOnly_AllInstances() { inputString = document.getElementById("txtUserInput").value; pattern = /(\s+the\s+)/ig; // Using an Flag (/i) to select either lowercase or uppercase version. Finds first occurrence either as a standalone word or inside a word. //result = pattern.exec(inputString); // Finds the first index location var arrResult; // Array for the results of the search. var sb = getStringBuilder(); // Variable to hold iterations of the result and the text while ((arrResult = pattern.exec(inputString)) !==null) { sb.appendLine = "Match: " + arrResult[0] ; } document.getElementById("txtRegExOutput").value = sb.toString(); /* Original code from C# version: // string pattern = #"\s+(?i)the\s+"; // Same as above, but using Option construct for case insensitive search. string pattern = #"(^|\s+)(?i)the(\W|\s+)"; MatchCollection matches = Regex.Matches(userTextInput, pattern); StringBuilder outputString = new StringBuilder(); foreach (Match match in matches) { string outputRegExs = "Match: " + "\"" + match.Value + "\"" + " at index [" + match.Index + "," + (match.Index + match.Length) + "]" + "\n"; outputString.Append(outputRegExs); } txtRegExOutput.Text = outputString.ToString(); */ } // End RegEx_Match_The_AllInstances I left the commented code in to show what I had used in the C# code behind version to illustrate what I'm trying to accomplish. The test input/string I used for this function is: Don’t go there. If you want to be the Man, you have to beat The Man. That should return two hits. Ideally, I want it to show the word that it found and the index where it found the word, but at this point I'd be happy to just get some output showing every instance it found, and then build on that with the index and possibly the lastIndex. So, is my problem in my While loop, the way I'm applying the StringBuilder, or a combination of the two? I know the StringBuilder code works, at least when not being used in a loop and using some test data from the site I found that code. And the code for simply finding the first instance of "the" as a standalone or inside another word does work and returns output, but that doesn't use a loop. I've looked through Stack Overflow and several other JavaScript websites for inspiration, but nothing I've tried so far has worked. I appreciate any help anyone can provide! (If you need me to post any other code, please advise and I'll be happy to oblige.)
Javascript Regex Subgroups
First off, don't link to the "Don't parse HTML with Regex" post :) I've got the following HTML, which is used to display prices in various currencies, inc and ex tax: <span id="price_break_12345" name="1"> <span class="price"> <span class="inc" > <span class="GBP">£25.00</span> <span class="USD" style="display:none;">$34.31</span> <span class="EUR" style="display:none;">27.92 €</span> </span> <span class="ex" style="display:none;"> <span class="GBP">£20.83</span> <span class="USD" style="display:none;">$34.31</span> <span class="EUR" style="display:none;">23.27 €</span> </span> </span> <span style="display:none" class="raw_price">25.000</span> </span> An AJAX call returns a single string of HTML, containing multiple copies of the above HTML, with the prices varying. What I'm trying to match with regex is: Each block of the above HTML (as mentioned, it occurs multiple times in the return string) The value of the name attribute on the outermost span What I have so far is this: var price_regex = new RegExp(/(<span([\s\S]*?)><span([\s\S]*?)>([\s\S]*?)<\/span><\/span\>)/gm); console && console.log(price_regex.exec(product_price)); It matches the first price break once for each price break that occurs (so if there's name=1, name=5 and name=15 it matches name=1 3 times. Whereabouts am I going wrong?
So, if you can count on the format of that first span in each block like this: <span id="price_break_12345" name="1"> Then, how about you use code like this to cycle through all the matches. This code identifies the price_break_xxxx id value in that first span and then picks out the following name attribute: var re = /id="price_break_\d+"\s+name="([^"]+)"/gm; var match; while (match = re.exec(str)) { console.log(match[1]); } You can see it work here: http://jsfiddle.net/jfriend00/G39ne/. I used a converter to make three of your blocks of HTML into a single javascript string (to simulate what you get back from your ajax call) so I could run the code on it. A more robust way to do this is to just use the browser's HTML parser to do all the work for you. Assuming you have the HTML in a string variable named `str', you can use the browser's parser like this: function getElementChildren(parent) { var elements = []; var children = parent.childNodes; for (var i = 0, len = children.length; i < len; i++) { // collect element nodes only if (children[i].nodeType == 1) { elements.push(children[i]); } } return(elements); } var div = document.createElement("div"); div.innerHTML = str; var priceBlocks = getElementChildren(div); for (i = 0; i < priceBlocks.length; i++) { console.log(priceBlocks[i].id + ", " + priceBlocks[i].getAttribute("name") + "<br>"); } Demo here: http://jsfiddle.net/jfriend00/F6D8d/ This will leave you with all the DOM traversal functions for these elements rather than using (the somewhat brittle) regular expressions on HTML.
Thanks in large part to jfriend for making me realise why my regex was matching in a strange way (while (price_break = regex.exec(string)) instead of just exec'ing it once), I've got it working: var price_regex = new RegExp(/<span[\s\S]*?name="([0-9]+)"[\s\S]*?><span[\s\S]*?>[\s\S]*?<\/span><\/span\>/gm); var price_break; while (price_break = price_regex.exec(strProductPrice)) { console && console.log(price_break); } I had a ton of useless () which were just clogging up the result set, so stripping them out made things a lot simpler. The other thing, as mentioned above was that originally I was just doing price_break = price_regex.exec(strProductPrice) which runs the regex once, and returns the first match only (which I mistook for returning 3 copies of the first match, due to the ()s). By looping over them, it keeps evaluating the regex until all the matches have been exhausted, which I assumed it did normally, similar to PHP's preg_match.
Transposing music using javascript or jquery
I have a chord chart application that I wrote and I would like to allow users to transpose the key of the chart using an onClick handler. My chart looks like this {C}My name is Blanket, {F}And I can run fast The chords inside the brackets appear above the letter it preceeds. I would like to use javascript or jquery in order to do this. How would I go about creating this transpose button? Any help is appreciated. Thank you in advance. EDIT So here's what I came up with... $('.transposeUp').click(function(){ $('.chord').each(function(){ var currentChord = $(this).text(); // gathers the chord being used if(currentChord == $(this).text()){ var chord = $(this).text().replace("F#", "G") } //... the if statements continue though every chord //but I didn't place them here to save space }); }); So here is the problem... I have a slash chord in the mix (G/B), It changes the be on transpose but because it changes the "B" the chord is now (G/C) which is not the same as the "currentChord" so it doesn't change the G when it gets to its respective if condition. Until I have transposed enough where the Chord is eventualy (G/G) then the first "G" starts transposing leaving the last "G" the same. Any ideas? Again your knowledge and help is greatly appreciated. Thanks in advance.
You need to match the chords sequentially so that you can update them one at a time. If you try to match everything at once you'll run into problems like you described, because you keep matching the same chord over and over again. A good way to accomplish this would be with regular expressions to parse and split the chord. Once you have the matching chord values, use an array of chords to find the next/previous chord to transpose. Here is some sample code I have developed as a demo: <p><span class="chord">{C}</span>My name is Blanket,</p> <p><span class="chord">{G / B}</span>And I can run fast</p> <p> <input id="transposeDown" type="button" value="Down" /> | <input id="transposeUp" type="button" value="Up" /> </p> var match; var chords = ['C','C#','D','Eb','E','F','F#','G','Ab','A','Bb','B','C', 'Db','D','D#','E','F','Gb','G','G#','A','A#','C']; var chordRegex = /C#|D#|F#|G#|A#|Db|Eb|Gb|Ab|Bb|C|D|E|F|G|A|B/g; $('#transposeUp').click(function() { $('.chord').each(function() { var currentChord = $(this).text(); var output = ""; var parts = currentChord.split(chordRegex); var index = 0; while (match = chordRegex.exec(currentChord)) { var chordIndex = chords.indexOf(match[0]); output += parts[index++] + chords[chordIndex+1]; } output += parts[index]; $(this).text(output); }); }); $('#transposeDown').click(function() { $('.chord').each(function() { var currentChord = $(this).text(); var output = ""; var parts = currentChord.split(chordRegex); var index = 0; while (match = chordRegex.exec(currentChord)) { var chordIndex = chords.indexOf(match[0],1); output += parts[index++] + chords[chordIndex-1]; } output += parts[index]; $(this).text(output); }); }); Sample demo: http://jsfiddle.net/4kYQZ/2/ A couple of things to notice: I took #gregp's idea of having multiple copies of the key list so that I can handle both sharps and flats. The first scale includes the preferred #/b which will be used during transposing. The second scale includes the other formats so that if the music includes them, they will transpose correctly. For instance, since Eb is in the first scale, it will be used instead of D# as you are transposing up and down; however, if there is a D# in the music to begin with, it will correctly move to D/E (with the caveat that if you move back, it will become an Eb again). Feel free to modify the preferred scale - I used my educated judgment based on personal music experience. The regular expression has to have the sharped/flatted keys first, otherwise a C# would be just as easily matched as a C when that's not correct. I have C at the beginning and the end of the array so that I can start at any location and move both directions without passing the end of the array. In order for this to work, the transposeDown code has an extra parameter in the call to chords.indexOf to start at position 1 so it matches the last C in the array instead of the first C. Then when it attempts to move to the previous element, it doesn't pass the beginning of the array. I'm splitting the chord as well using the regular expression, which is redundant, but it makes it super-easy to recompose the final string back together - by interleaving the original string parts with the updated chord keys (don't forget the last piece at the end!). I'm using elements instead of classes for your buttons. Hope this helps! Update 1: Per comment by OP, the use of indexOf on arrays is not supported by pre-ie9. This can be solved by using a helper function that does the same thing: function arrayIndexOf(arr, match) { for (var i = 0; i < arr.length; i++) if (arr[i] == match) return i; return -1; } And this line var chordIndex = chords.indexOf(match[0]); would be replaced with: var chordIndex = arrayIndexOf(chords, match[0]); See updated sample demo: http://jsfiddle.net/4kYQZ/11/
Using jQuery, you're spoiled for choice for binding click handlers. There's .click(), .delegate(), .live(), and many others. It wouldn't make sense to reproduce what the APIs already tell you, but I can say this: learning how to bind the click is the smallest part of the overall problem, and even capturing the chord name with .text() is going to be trivial once you've had a look at the jQuery API. The tricky part is going to be the logic of transposing itself. You'll need to take the knowledge you already have (for example, going from E to F is only a half-step; there's no E# or Fb) and make it work as code. My apologies for general advice rather than code samples, but my advice is to have two arrays, one containing all the chords in terms of sharps, one with all the chords in terms of flats. You could do a bit of cheating, too: instead of logic to wrap around to the beginning (say, C in position 0), just have your arrays duplicated: ["C","C#","D","D#","E","F","F#","G","G#","A","B","C","C#","D","D#","E","F","F#","G","G#","A","B"] Then find the first occurence of the chord name, move ahead the desired number of stops, and you'll still have the right string.
Call function transpose for up: text = transpose(text, 1); Call function transpose for down: text = transpose(text, -1); Function: function transpose(text, amount){ var lines = new Array(); var chord = new Array(); var scale = ["C","Cb","C#","D","Db","D#","E","Eb","E#","F","Fb","F#","G","Gb","G#", "A","Ab","A#","B","Bb","B#"]; var transp = ["Cb","C","C#","Bb","Cb","C","C","C#","D","Db","D","D#","C","Db","D", "D","D#","E","Eb","E","F","D","Eb","E","E","E#","F#","E","F","F#", "Eb","Fb","F","F","F#","G","Gb","G","G#","F","Gb","G","G","G#","A", "Ab","A","A#", "G","Ab","A","A","A#","B","Bb","B","C","A","Bb","B", "B","B#","C#"]; var inter = ''; var mat = ''; lines = text.split("\n"); for(var i in lines){ if(i%2===0){ chord = lines[i].split(" "); for(var x in chord){ if(chord[x]!==""){ inter = chord[x]; var subst = inter.match(/[^b#][#b]?/g); for(var ax in subst){ if(scale.indexOf(subst[ax])!==-1){ if(amount>0){ for(ix=0;ix<amount;ix++){ var pos = scale.indexOf(subst[ax]); var transpos = 3*pos-2+3; subst[ax] = transp[transpos+1]; } } if(amount<0){ for(ix=0;ix>amount;ix--){ var pos = scale.indexOf(subst[ax]); var transpos = 3*pos-2+3; subst[ax] = transp[transpos-1]; } } } } chord[x]=subst.join(""); } } lines[i] = chord.join(" "); } } return lines.join("\n"); } The first line is transpose and the second does not, and sequentially.