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.
Related
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
I´m stuck with a problem... given an input number I´m trying to output a string of length 4. The string has to be divided into 2 parameter sections "On"/"Off".
For example:
-If the input number is 16, then the string should get combined as follows:
"On" section = "On" * Math.floor(16/5) = 3 --> "On On On".
"Off" section should be: length-On-section = 4-3 = 1 --> "Off".
Hence the string should look like "On On On Off".
I´m currently trying to narrow my solution to a nicer approach than using a for loop. I have to repeat this process various times in my function to create strings following the same approach but in various lengths and "On"/"Off" section ratios. but I´m not sure how to set it up properly..
this is one example:
function hoursTop(hour) {
var lights = [], on = Math.floor(hour/5), off = 4 - topLightsOn;
for(var i=1; i<=on; i++){
lights.push('On');
}
for(var j=1; j<=Off; j++){
lights.push('Off');
}
return lights.join("");
}
This produces way too much code overall.. Thanks for helping me out!
You can use String#repeat to create the strings, then concat them, and trim the extra spaces:
function hoursTop(hour) {
var on = Math.floor(hour/5), off = 4 - on;
return 'on '.repeat(on) + 'off '.repeat(off).trim();
}
console.log(hoursTop(16));
Or you can use Array#fill to create the array, then concat them, and join the array to a string:
function hoursTop(hour) {
var on = Math.floor(hour/5), off = 4 - on;
return Array(on).fill('on').concat(Array(off).fill('off')).join(' ');
}
console.log(hoursTop(16));
I have an HTML document which contains this text somewhere in it
function deleteFolder() {
var mailbox = "CN=John Urban,OU=Sect-1,DC=TestServer ,DC=acme,DC=com";
var path = "/Inbox/";
//string of interest: "CN=John Urban,OU=Sect-1,DC=TestServer ,DC=acme,DC=com"
I just want to extract this text and store it in a variable in C#. My problem is that string of interest will slightly change each time the page is loaded, something like this:
"CN=John Urban,OU=Sect-1,DC=TestServer ,DC=acme,DC=com"
"CN=Jane Doe,OU=Sect-1,DC=TestServer ,DC=acme,DC=com"
etc....
How do I extract that ever changing string, without regular expression?
Is it always a function deleteFolder() which has its first line as var mailbox = "somestring"? And you are interested in somestring?
Based on the requirements you told us, could just search your string containing the HTML for var mailbox =" and then the next " and take all text between these two occurrences.
var htmlstring= "..."; //
var i1 = htmlstring.IndexOf("var mailbox = \"");
var i2 = i1 >= 0 ? htmlstring.IndexOf("\"", i1+15) : -1;
var result = i2 >= 0 ? htmlstring.Substring(i1+15, i2-(i1+15)): "not found";
VERY, VERY ugly, not maintainable, but without more information, I can't do any better. However Regex would be much nicer!
Hello I´m having a bit of trouble to fix my carousel dots (the ones that show your current position, for example if you´re on image three then it could look like this: ●●◦●●●●●●●●).
p.s I´m not interested in a jQuery solution.
Today I´m doing this:
I have a variable that check the activePage (to mark out the white dot) and then I do a for loop to see where I shall put a white dot and where to put the other black dots, but this does not work. I´ll post my code below:
// I will later assign this variable to a view that I have declared that will show the dots (I´m working on a Titanium project)
var x = '';
for (var i = 0; i < mySlideViews.length; i++){
if (activeSlideNbr == 0){
// This is the first image and I want to set the first dot to a white dot
x = '◦' + x;
}
// This is the last image and I want to set the last dot to a white dot
else if (activeSlideNbr == mySlideViews.length -1){
x ='◦' + x;
}
// if the active page is neither in the start or end
else if (i == activeSlideNbr){
x = '◦' + x;
}
// to set out the black dots
else{
x = x + '●';
}
}
I would really appreciate some help, if you need more information please let me know.
I'm not sure if I'm missing something, but I think this is what you are looking for:
var activeSlideNbr = 3;
var mySlideViews = [1,2,3,4,5,6,7];
var t = [];
while (t.length < mySlideViews.length) {
t.push('●');
}
t[activeSlideNbr] = '◦';
console.log(t.join("")); //●●●◦●●●
There is no need for a loop to check where to put the white circle. You can just create an Array with all black circles and then put the white circle on the right index. To create a string from the array, you can use the join method.
FIDDLE
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.