Reputation: 57
This is my code:
<script>
function TransposeDown() {
$("body").html($("body").html().replace(/C/g, 'B'));
$("body").html($("body").html().replace(/B/g, 'Bb'));
}
</script>
<button onclick="TransposeDown();">Transpose Down</button>
<span class="hcrd">C</span>
<span class="hcrd">B</span>
<span class="hcrd">C</span>
The problem is that once the button
is clicked, the text inside all the <span>
tags change to "Bb". But what I want is the first and the last span
's text to change to "B" and the second one's text to change to "Bb" only.
How do I do this? Is there any way to do this in such a way that the function is done only once? Any help is appreciated.
Upvotes: 0
Views: 150
Reputation: 11339
Since you are obviously trying to transpose musical chords, I'd go for a solution using data-original
to store the original note, and keep track of how many times the user has transposed the notes. This way you can easily reset the whole thing.
Working example: https://jsfiddle.net/dannyjolie/b5ghxLoL/1/
Update: Out of the box, this doesn't work with any 6th, 7th, maj, min etc, but this can also be stored as a data attribute, and be appended to the output.
var notes = ["A", "Bb", "B", "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab"];
var transposed = 0;
function transpose() {
var chords = document.querySelectorAll('.hcrd');
var chord = '';
for (var i = 0; i < chords.length; i++) {
chord = chords[i].dataset['original'];
chords[i].innerHTML = getNewChord(chord);
}
}
function getNewChord(chord) {
var origIndex = notes.indexOf(chord);
if (origIndex === -1) {
// Invalid chord
return chord;
}
if (origIndex + transposed < 0) {
return notes[notes.length + transposed + origIndex];
}
return notes[origIndex + transposed];
}
function transposeDown() {
transposed -= 1;
if(transposed === -12){
transposed = 0;
}
transpose();
}
function reset() {
transposed = 0;
transpose();
}
document.querySelector('#transposedown').addEventListener('click', transposeDown, false);
document.querySelector('#reset').addEventListener('click', reset, false)
<span class="hcrd" data-original="C">C</span>
<span class="hcrd" data-original="B">B</span>
<span class="hcrd" data-original="C">C</span>
<span class="hcrd" data-original="G">G</span>
<span class="hcrd" data-original="E">E</span>
<button id="transposedown">Transpose down</button>
<button id="reset">Reset</button>
Upvotes: 2
Reputation: 5621
You should change them in sequence by selecting them all with a common selector, then skip the change if the element has changed.
var rules = [{
regEx: /^C$/g,
str: 'B'
}, {
regEx: /^B$/g,
str: 'Bb'
}];
function TransposeDown() {
$(".hcrd").each(function(index, el) {
var $el = $(el);
var prevVal = $(el).html();
var newVal = '';
for (var i = 0; i < rules.length; i++) {
newVal = $el.html().replace(rules[i].regEx, rules[i].str);
if (newVal != prevVal) {
$el.html(newVal);
break;
}
}
});
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button onclick="TransposeDown();">Transpose Down</button>
<span class="hcrd">C</span>
<span class="hcrd">B</span>
<span class="hcrd">C</span>
Upvotes: 0
Reputation: 254
I would do it differently..
<script>
function TransposeDown(){
$("#C").html(text.replace("C", "B"));
$("#B").html(text.replace("B", "Bb"));
}
</script>
<button onclick="TransposeDown();">Transpose Down</button>
<span class="hcrd" id="C">C</span>
<span class="hcrd" id="B">B</span>
<span class="hcrd" id="C">C</span>
Upvotes: 0
Reputation: 10685
As a more general solution, you could consider writing a regex that matches both and using a function to determine the replacement. This would work for arbitrary search/replace as it does it all in one pass.
$("body").html($("body").html().replace(/(B|C)/g, function(match) {
switch (match) {
case 'B':
return 'Bb';
case 'C':
return 'B';
}
));
Upvotes: 1
Reputation: 64657
You can either invert, which works in simple instances, or you can do it like this, which works even in complex instances:
var string = "A B C D";
string = string.replace(/(C)|(B)/g,function(str,p1,p2) {
if(p1) return 'B';
if(p2) return 'Bb';
});
So in your case, it would be:
$("body").html($("body").html().replace(/(C)|(B)/g,function(str,p1,p2) {
if(p1) return 'B';
if(p2) return 'Bb';
}));
Upvotes: 2
Reputation: 14962
Simply invert your two methods, starting with the B replacement then with the C one:
function TransposeDown() {
$("body").html($("body").html().replace(/B/g, 'Bb'));
$("body").html($("body").html().replace(/C/g, 'B'));
}
You could also transpose to a special pattern (for example prefixing all transpositions by a special tag) then make sure to exclude this tag from the regexes. Finally, remove the special pattern from the whole document. This would take longer, but would allow you to not take order into account
Upvotes: 1