Related
World!
I'm trying to create a program in Javascript that takes the log of a number typed into an HTML input. Unfortunately i've encountered a problem where it wont accept the string with the .replace().
Its Function:
I.E: When log(10) is calculated, the function should first remove the first 4 char's "log(" next remove the last parenthesis ")" and then take the log of the no. between.
HTML includes style elements, button and input form and an output < DIV >.
//Function
function calculate()
{
var inputString = document.getElementById("inpstr");
var output = document.getElementById("output");
//TESTING CODE
/*
if (inputString.value.startsWith("log(").endsWith(")"))
{
console.log(output.innerHTML = inputString.value.substring(4, 20).replace(")", ""));
}
else
{
output.innerHTML = "false";
}
*/
//Math.log() calc *****DOESNT WORK*****
if (inputString.value.startsWith("log(").endsWith(")"))
{
output.innerHTML = Math.log(inputString.value.replace(")", "").substring(4, 20));
}
else
{
output.innerHTML = inputString.value;
}
event.preventDefault();
}
If someone can give me an effective solution that would be much appreciated.
Thanks,
Syntax
Since Math.log() accepts only number values and you're trying to pass a string to it, you should first parse this value into a float number and then pass it to the log function:
let val = parseFloat(inputString.value.replace(")", "").substring(4, 20));
output.innerHTML = Math.log(val);
I'm guessing I got downvoted for being lazy, so here is the quick info. Gonras got it right relating to what you want to extract, but he forgot to check that what's being input is actually a log.
That's where the regex below comes in handy! I'm matching the field to:
^ start of word, since we want to match the entire field.
log(
([-.\d])) any consecutive sequence () of numbers (\d), -, and '.', represented by the []. The \(...\) makes sure to save this inner part for later.
$ is end of word, see 1.
res will be null if there is no match. Otherwise, res[0] is the entire match (so the entire input field) and res[1] is the first 'capture group', at point 3 - which is presumably the number.
This of course fails for multiple "-" inside, or "." etc... so think it over.
//Function
function calculate()
{
var inputString = document.getElementById("inpstr");
var output = document.getElementById("output");
var res = /^log\(([-.\d]*)\)$/.exec(inputString.value);
if (res)
output.innerHTML = Math.log(res[1]);
else
output.innerHTML = res;
}
document.getElementById("output").innerHTML='start';
calculate()
<div id='output'></div>
<input id='inpstr' value='log(2.71828)'></input>
If I wanted to fix your if to supplement Gonras's solution:
if (inputString.value.startsWith("log(") && inputString.value.endsWith(")"))
Yours fails since startsWith() returns a boolean, which obviously doesn't have a endsWith function.
(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…
I have an array with incidents that has happened, that are written in free text and therefore aren't following a pattern except for some keywords, eg. "robbery", "murderer", "housebreaking", "car accident" etc. Those keywords can be anywhere in the text, and I want to find those keywords and add those to categories, eg. "Robberies".
In the end, when I have checked all the incidents I want to have a list of categories like this:
Robberies: 14
Murder attempts: 2
Car accidents: 5
...
The array elements can look like this:
incidents[0] = "There was a robbery on Amest Ave last night...";
incidents[1] = "There has been a report of a murder attempt...";
incidents[2] = "Last night there was a housebreaking in...";
...
I guess the best here is to use regular expressions to find the keywords in the texts, but I really suck at regexp and therefore need some help here.
The regular expressions is not correct below, but I guess this structure would work?
Is there a better way of doing this to avoid DRY?
var trafficAccidents = 0,
robberies = 0,
...
function FindIncident(incident) {
if (incident.match(/car accident/g)) {
trafficAccidents += 1;
}
else if (incident.match(/robbery/g)) {
robberies += 1;
}
...
}
Thanks a lot in advance!
The following code shows an approach you can take. You can test it here
var INCIDENT_MATCHES = {
trafficAccidents: /(traffic|car) accident(?:s){0,1}/ig,
robberies: /robbery|robberies/ig,
murder: /murder(?:s){0,1}/ig
};
function FindIncidents(incidentReports) {
var incidentCounts = {};
var incidentTypes = Object.keys(INCIDENT_MATCHES);
incidentReports.forEach(function(incident) {
incidentTypes.forEach(function(type) {
if(typeof incidentCounts[type] === 'undefined') {
incidentCounts[type] = 0;
}
var matchFound = incident.match(INCIDENT_MATCHES[type]);
if(matchFound){
incidentCounts[type] += matchFound.length;
};
});
});
return incidentCounts;
}
Regular expressions make sense, since you'll have a number of strings that meet your 'match' criteria, even if you only consider the differences in plural and singular forms of 'robbery'. You also want to ensure that your matching is case-insensitive.
You need to use the 'global' modifier on your regexes so that you match strings like "Murder, Murder, murder" and increment your count by 3 instead of just 1.
This allows you to keep the relationship between your match criteria and incident counters together. It also avoids the need for global counters (granted INCIDENT_MATCHES is a global variable here, but you can readily put that elsewhere and take it out of the global scope.
Actually, I would kind of disagree with you here . . . I think string functions like indexOf will work perfectly fine.
I would use JavaScript's indexOf method which takes 2 inputs:
string.indexOf(value,startPos);
So one thing you can do is define a simple temporary variable as your cursor as such . . .
function FindIncident(phrase, word) {
var cursor = 0;
var wordCount = 0;
while(phrase.indexOf(word,cursor) > -1){
cursor = incident.indexOf(word,cursor);
++wordCount;
}
return wordCount;
}
I have not tested the code but hopefully you get the idea . . .
Be particularly careful of the starting position if you do use it.
RegEx makes my head hurt too. ;) If you're looking for exact matches and aren't worried about typos and misspellings, I'd search the incident strings for substrings containing the keywords you're looking for.
incident = incident.toLowerCase();
if incident.search("car accident") > 0 {
trafficAccidents += 1;
}
else if incident.search("robbery") > 0 {
robberies += 1;
}
...
Use an array of objects to store all the many different categories you're searching for, complete with an appropiate regular expression and a count member, and you can write the whole thing in four lines.
var categories = [
{
regexp: /\brobbery\b/i
, display: "Robberies"
, count: 0
}
, {
regexp: /\bcar accidents?\b/i
, display: "Car Accidents"
, count: 0
}
, {
regexp: /\bmurder\b/i
, display: "Murders"
, count: 0
}
];
var incidents = [
"There was a robbery on Amest Ave last night..."
, "There has been a report of an murder attempt..."
, "Last night there was a housebreaking in..."
];
for(var x = 0; x<incidents.length; x++)
for(var y = 0; y<categories.length; y++)
if (incidents[x].match(categories[y].regexp))
categories[y].count++;
Now, no matter what you need, you can simply edit one section of code, and it will propagate through your code.
This code has the potential to categorize each incident in multiple categories. To prevent that, just add a 'break' statement to the if block.
You could do something like this which will grab all words found on each item in the array and it will return an object with the count:
var words = ['robbery', 'murderer', 'housebreaking', 'car accident'];
function getAllIncidents( incidents ) {
var re = new RegExp('('+ words.join('|') +')', 'i')
, result = {};
incidents.forEach(function( txt ) {
var match = ( re.exec( txt ) || [,0] )[1];
match && (result[ match ] = ++result[ match ] || 1);
});
return result;
}
console.log( getAllIncidents( incidents ) );
//^= { housebreaking: 1, car accident: 2, robbery: 1, murderer: 2 }
This is more a a quick prototype but it could be improved with plurals and multiple keywords.
Demo: http://jsbin.com/idesoc/1/edit
Use an object to store your data.
events = [
{ exp : /\brobbery|robberies\b/i,
// \b word boundary
// robbery singular
// | or
// robberies plural
// \b word boundary
// /i case insensitive
name : "robbery",
count: 0
},
// other objects here
]
var i = events.length;
while( i-- ) {
var j = incidents.length;
while( j-- ) {
// only checks a particular event exists in incident rather than no. of occurrences
if( events[i].exp.test( incidents[j] ) {
events[i].count++;
}
}
}
Yes, that's one way to do it, although matching plain-words with regex is a bit of overkill — in which case, you should be using indexOf as rbtLong suggested.
You can further sophisticate it by:
appending the i flag (match lowercase and uppercase characters).
adding possible word variations to your expression. robbery could be translated into robber(y|ies), thus matching both singular and plural variations of the word. car accident could be (car|truck|vehicle|traffic) accident.
Word boundaries \b
Don't use this. It'll require having non-alphanumeric characters surrounding your matching word and will prevent matching typos. You should make your queries as abrangent as possible.
if (incident.match(/(car|truck|vehicle|traffic) accident/i)) {
trafficAccidents += 1;
}
else if (incident.match(/robber(y|ies)/i)) {
robberies += 1;
}
Notice how I discarded the g flag; it stands for "global match" and makes the parser continue searching the string after the first match. This seems unnecessary as just one confirmed occurrence is enough for your needs.
This website offers an excellent introduction to regular expressions
http://www.regular-expressions.info/tutorial.html
I'm struggling with a ExtJS 4.1.1 grid that has editable cells (CellEditing plugin).
A person should be able to type a mathematic formula into the cell and it should generate the result into the field's value. For example: If a user types (320*10)/4 the return should be 800. Or similar if the user types (320m*10cm)/4 the function should strip the non-mathematical characters from the formula and then calculate it.
I was looking to replace (or match) with a RegExp, but I cannot seem to get it to work. It keeps returning NaN and when I do console.log(e.value); it returns only the originalValue and not the value that I need.
I don't have much code to attach:
onGridValidateEdit : function(editor,e,opts) {
var str = e.value.toString();
console.log(str);
var strCalc = str.match(/0-9+-*\/()/g);
console.log(strCalc);
var numCalc = Number(eval(strCalc));
console.log(numCalc);
return numCalc;
},
Which returns: str=321 strCalc=null numCalc=0 when I type 321*2.
Any help appreciated,
GR.
Update:
Based on input by Paul Schroeder, I created this:
onGridValidateEdit : function(editor,e,opts) {
var str = e.record.get(e.field).toString();
var strCalc = str.replace(/[^0-9+*-/()]/g, "");
var numCalc = Number(eval(strCalc));
console.log(typeof numCalc);
console.log(numCalc);
return numCalc;
},
Which calculates the number, but I am unable to print it back to the grid itself. It shows up as "NaN" even though in console it shows typeof=number and value=800.
Final code:
Here's the final code that worked:
onGridValidateEdit : function(editor,e,opts) {
var fldName = e.field;
var str = e.record.get(fldName).toString();
var strCalc = str.replace(/[^0-9+*-/()]/g, "");
var numCalc = Number(eval(strCalc));
e.record.set(fldName,numCalc);
},
Lets break this code down.
onGridValidateEdit : function(editor,e,opts) {
var str = e.value.toString();
What listener is this code being used in? This is very important for us to know, here's how I set up my listeners in the plugin:
listeners: {
edit: function(editor, e){
var record = e.record;
var str = record.get("your data_index of the value");
}
}
Setting it up this way works for me, So lets move on to:
var strCalc = str.match(/0-9+-*\/()/g);
console.log(strCalc);
at which point strCalc=null, this is also correct. str.match returns null because your regex does not match anything in the string. What I think you want to do instead is this:
var strCalc = str.replace(/[^0-9+*-]/g, "");
console.log(strCalc);
This changes it to replace all characters in the string that aren't your equation operators and numbers. After that I think it should work for whole numbers. I think that you may actually want decimal numbers too, but I can't think of the regex for that off the top of my head (the . needs to be escaped somehow), but it should be simple enough to find in a google search.
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.