This is article number 3 of a series of article, which simply documents my refactoring of some code I originally wrote (pretty poorly) last year. To get the gist of what’s going on. Check the original post - Refactoring Code in to an Object-Oriented Paradigm.
Moving Code to Object-Oriented Code Patterns
So now we have our group of variables under the name autoScrobbler
, I said earlier that we could also include functions under this group. This is quite a good way of going, because at the moment, we’re taking up the function name stop()
globally, any function that calls stop when this code is loaded will run our function.
This can cause problems for other code and ours. For other programs, it may be trying to stop something else from running, when it calls stop()
, it won’t have stopped what it expects to. For our programs, that means that the other program has just stopped the auto-scrobbler when the user hasn’t asked it to!
The other nice thing about putting our functions under the same group, is that we don’t need to rely on a name for the group. If we decide that autoScrobbler is a bad name for the group later down the line, that’s fine we can change the name and everything will still work.
function AutoScrobbler() {
var userControls = "<div id=\"autoScrobbler\" style=\"background: #FFFFFF; border-top: 1px solid #000000; border-left: 1px solid #000000; position: fixed; bottom: 0; height: 50px; width: inherit;\">"+
"<input id=\"autoScrobblerStart\" type=\"button\" value=\"Start auto-scrobbling\" onclick=\"autoScrobbler.start();\" /> | <input id=\"autoScrobblerStop\" type=\"button\" value=\"Stop auto-scrobbling\" onclick=\"autoScrobbler.stop();\" />"+
"<p><span id=\"autoScrobblerScrobbleCount\">0</span> tracks scrobbled</p>"+
"</div>";
document.querySelector("#disclaimersContainer").innerHTML += userControls;
this.startElm = document.getElementById("autoScrobblerStart");
this.stopElm = document.getElementById("autoScrobblerStop");
this.loopUID = -1;
this.lastTrackUID = undefined;
this.scrobbled = 0;
this.countReport = document.getElementById("autoScrobblerTracksScrobbled");
this.start();
}
AutoScrobbler.prototype.addLatest = function() {
var tracks = document.querySelectorAll(".userSong");
this.lastTrackUID = (typeof this.lastTrackUID == "undefined") ? tracks[1].querySelector("input").value : this.lastTrackUID;
for (var i = 0; i < tracks.length; i++) {
var item = tracks[i];
if (item.querySelector("input").value == this.lastTrackUID) {
i = tracks.length;
this.lastTrackUID = tracks[0].querySelector("input").value;
} else {
item.querySelector("input").checked = true;
this.scrobbled++;
}
}
doUserScrobble();
this.countReport.innerHTML = this.scrobbled;
}
AutoScrobbler.prototype.loadThenAdd = function() {
doRadioSearch();
setTimeout(this.addLatest, 30000);
}
AutoScrobbler.prototype.start = function() {
this.loadThenAdd();
autoScrobbler.loopUID = setInterval(this.loadThenAdd, 300000);
autoScrobbler.start.disabled = true;
autoScrobbler.stop.disabled = false;
}
AutoScrobbler.prototype.stop = function() {
clearInterval(this.loopUID);
this.lastTrackUID = undefined;
this.loopUID = -1;
this.stop.disabled = true;
this.start.disabled = false;
}
autoScrobbler = new AutoScrobbler();
This time, we’ve changed quite a lot. I’ll go through each of the changes.
Firstly, you’ll notice that each of the function headers (originally function addLatest() {
and now AutoScrobbler.prototype.addLatest = function() {
), have been changed. This is the best way of defining functions under the group that we grouped our variables under in the previous stage. There are other ways of doing this, but this generally has performance advantages. I’ve spoken about using prototype before, so if you’re interested in that, you should have a look at my post on using prototype.
Note (07/02/2013)
On reviewing the code for later articles, it has become clear that I have fallen into a classic “gotcha” or moving these functions into the same group as our variables.
Until I edited the code today, we had both a variable called start and a function called start(), when this code was run, the function (declared before the constructor function is called) is overwritten by the variable. So there is no start() function to call!
I have now corrected this, renaming the variables startElm and stopElm respectively. This is definitely something to check for if you are getting TypeErrors on running your code!
Secondly, you’ll notice that all references to autoScrobbler have been removed (except for one at the end). This is because everything is now in the same group of variables and functions, and we can use that third scope that we created in the previous stage without having to reference the name of the group explicitly, functions that are within the same group as variables can access these variables with the this
keyword.
Next, you’ll notice that I’ve put all the code that was at the bottom at the top, and wrapped it in a function definition. This function header hasn’t been changed unlike the rest of the functions. This is part of the Object-Oriented style of coding. All this code was used to set up the variables, creating the user controls and essentially getting the program going. In Object-Oriented coding, this code goes in a constructor function (functions are often referred to as methods in Object-Oriented coding). Moving it to the top does nothing to the way in which the program runs, but is a more logical to have the code, because this is the code that runs to set up this group of variables and functions.
Finally, we’ve added a last line at the end, if you look at our previous stage you’ll see we have a similar line.
autoScrobbler = new Object();
In this stage we exchange the word Object
for AutoScrobbler
. In our previous stage, new Object()
(as far as we need to know) set up our group to add variables, data and functions to. Using new AutoScrobbler()
does the same setting up of the group, plus running any code in the constructor function.
A side effect of this is that we can create several of these groups, all of which can run at the same time and not interfere with each other. I said earlier that this probably isn’t something we want with our example, but it means that we could now, if we wanted, auto-scrobble two radio stations at a time.*
That’s it for this article, next we’ll be looking at extending, improving readability and describing code.
* Okay, not quite, if we tried this, the user controls would go a bit screwy, but the Javascript would not, the two would run independently of each other without an issue!
