So, I discovered today, as is proved by a screenshot below from digg's RSS feed, that C++ is actually 21 times harder than C!
Monday, November 20, 2006
Tuesday, October 31, 2006
Ubuntu Edgy
Let me start off by saying that Ubuntu Edgy is awesome. Throw on a bit of Automatix after that, and it's a heaven-sent operating system - unless you upgrade. If you upgrade, everything just blows up. Your eclipse stops working, and that experimental fix that you pushed to the website? Well, it's going to stay there for a while as you fight your install and fight it and fight it, and then finally save *everything* off to another disk and re-install from scratch.
This is palantar signing out, saying, "Don't Upgrade Your Linux"
This is palantar signing out, saying, "Don't Upgrade Your Linux"
Wednesday, July 19, 2006
CSS Code Blocks
Ok, this is just silly, but I hate having to experiment and look things up, so I'm thinking you aren't so different. Here, I've layed out in CSS a basic little css block to create those nifty little blocks of code that you see everywhere with their fancy formatting. The following CSS block defines the look of the code. Add that to your CSS file, or template file, or wherever :)
.code {
font-style: bold;
margin: 0 0 1.5em 0;
padding: 0 0 1.5em 14px;
background-color: #EEEEEE;
font-family: monospace; font-size: 14px;
border: 1px solid black;
}
Now, in your post/document/etc just surround your post with the following: <div class="code"> and </div>
.code {
font-style: bold;
margin: 0 0 1.5em 0;
padding: 0 0 1.5em 14px;
background-color: #EEEEEE;
font-family: monospace; font-size: 14px;
border: 1px solid black;
}
Now, in your post/document/etc just surround your post with the following: <div class="code"> and </div>
Wednesday, June 21, 2006
AGAD!! (Tutorial-ish sort of post)
Please note this blog has now moved Here.
Asynchronous Google (Web Toolkit) and Django
Personally, I think that the acronymn is way more fun to say than "Ajax". Picture this, you're sneaking up behind someone at a party... you wait until the perfect moment when the music has just paused slightly between songs and you yell one of two things, you yell "AJAX!" or "AGAD!" Also, it should be noted, that the person you chose to sneak up behind has been horribly disfigured by a childhood accident involving large quantities of a brand-name cleaner. NOW, which are you going to yell? At this point, the superiority of AGAD should be evident, but just in case not, I will proceed to actually talk about it.
The Google Web Toolkit (GWT) is designed to work primarily with a Java backend, but a lot of people, myself included, don't really want to mess with the monolithic terror that is a Java backend, and so Google provides a JSON interface as well. In my most recent application, I decided to use JSON to communicate between the GWT front end and Django backend. The results are midly thrilling, so it is not advised that you not follow the link unless you have a strong heart, a good back, and at least one friend named Bubbles for support.
www.lunchtimevoter.com
So, how do you integrate the Django and the GWT? The key part is found in the here. It's a Python-JSON package that will help Django to speak GWT's language. It does a pretty good job of converting standard Pythonisms into JSON. I actually use a modified version, which can be found here. It takes the Python-JSON package and adds the function of interpreting any Python object into JSON as well. Feel free to download and use to your hearts content...
Now, once you have got that, you need to create the object to send, say an object looking something like this, which is the code for a single choice in the lunchtime voter :
As you can see, this code is nothing more than a shell for containing a few essentials that need to be communicated to the frontend. It is then written using the JSON write function with a call such as:
Since you'll be updating the AJAX frequently (that's kinda the point of the AJAX stuff isn't it?) you will want to the cache control lines to make sure you get a new copy each time you ask for it. Additionally, you can add some random garbage GET variable to your request from GWT to force the browser to recognize the response as new. After that, you have to decode the thing in your GWT application. This is actually really easy, and for the most part, you can just follow along with the included JSON example. However, it can be a bit tricky to get the JSON decoder working in *your* application. After copying the JSON decoder into your own project structure, it is probably easiest to leave the decoder in its own package and import it using the XML files. Firstly, you want to remove the entry-point from the JSON xml file, just rip the whole line out of there. Secondly, you want to inherit the json classes. As an example, here is what the core xml file for the lunchtime voter looks like:
And that's about it. After that, it's all standard django and or gwt stuff, so experiment and have fun :)
Asynchronous Google (Web Toolkit) and Django
Personally, I think that the acronymn is way more fun to say than "Ajax". Picture this, you're sneaking up behind someone at a party... you wait until the perfect moment when the music has just paused slightly between songs and you yell one of two things, you yell "AJAX!" or "AGAD!" Also, it should be noted, that the person you chose to sneak up behind has been horribly disfigured by a childhood accident involving large quantities of a brand-name cleaner. NOW, which are you going to yell? At this point, the superiority of AGAD should be evident, but just in case not, I will proceed to actually talk about it.
The Google Web Toolkit (GWT) is designed to work primarily with a Java backend, but a lot of people, myself included, don't really want to mess with the monolithic terror that is a Java backend, and so Google provides a JSON interface as well. In my most recent application, I decided to use JSON to communicate between the GWT front end and Django backend. The results are midly thrilling, so it is not advised that you not follow the link unless you have a strong heart, a good back, and at least one friend named Bubbles for support.
www.lunchtimevoter.com
So, how do you integrate the Django and the GWT? The key part is found in the here. It's a Python-JSON package that will help Django to speak GWT's language. It does a pretty good job of converting standard Pythonisms into JSON. I actually use a modified version, which can be found here. It takes the Python-JSON package and adds the function of interpreting any Python object into JSON as well. Feel free to download and use to your hearts content...
Now, once you have got that, you need to create the object to send, say an object looking something like this, which is the code for a single choice in the lunchtime voter :
class DisplayChoice(object):
__name__ = "DisplayChoice"
def __init__(self,choice):
votes = LunchVote.objects.filter( choice__choiceName__exact = choice.choiceName )
blackballs = LunchBlackball.objects.filter( choice__choiceName__exact = choice.choiceName )
self.choiceName = choice.choiceName;
self.voteList = list()
self.blackballList = list()
for vote in votes:
self.voteList.append(vote.user.displayName)
for blackball in blackballs:
self.blackballList.append(blackball.user.displayName)
__name__ = "DisplayChoice"
def __init__(self,choice):
votes = LunchVote.objects.filter( choice__choiceName__exact = choice.choiceName )
blackballs = LunchBlackball.objects.filter( choice__choiceName__exact = choice.choiceName )
self.choiceName = choice.choiceName;
self.voteList = list()
self.blackballList = list()
for vote in votes:
self.voteList.append(vote.user.displayName)
for blackball in blackballs:
self.blackballList.append(blackball.user.displayName)
As you can see, this code is nothing more than a shell for containing a few essentials that need to be communicated to the frontend. It is then written using the JSON write function with a call such as:
response = HttpResponse(write(displayChoice))
response['Pragma'] = "no cache"
response['Cache-Control'] = "no-cache, must-revalidate"
return response;
response['Pragma'] = "no cache"
response['Cache-Control'] = "no-cache, must-revalidate"
return response;
Since you'll be updating the AJAX frequently (that's kinda the point of the AJAX stuff isn't it?) you will want to the cache control lines to make sure you get a new copy each time you ask for it. Additionally, you can add some random garbage GET variable to your request from GWT to force the browser to recognize the response as new. After that, you have to decode the thing in your GWT application. This is actually really easy, and for the most part, you can just follow along with the included JSON example. However, it can be a bit tricky to get the JSON decoder working in *your* application. After copying the JSON decoder into your own project structure, it is probably easiest to leave the decoder in its own package and import it using the XML files. Firstly, you want to remove the entry-point from the JSON xml file, just rip the whole line out of there. Secondly, you want to inherit the json classes. As an example, here is what the core xml file for the lunchtime voter looks like:
<module>
<!-- Inherit the core Web Toolkit stuff. -->
<inherits name='com.google.gwt.user.User'/>
<inherits name='com.google.gwt.sample.json.JSON'/>
<!-- Specify the app entry point class. -->
<entry-point class='com.lunchtimevoter.client.Display'/>
</module>
<!-- Inherit the core Web Toolkit stuff. -->
<inherits name='com.google.gwt.user.User'/>
<inherits name='com.google.gwt.sample.json.JSON'/>
<!-- Specify the app entry point class. -->
<entry-point class='com.lunchtimevoter.client.Display'/>
</module>
And that's about it. After that, it's all standard django and or gwt stuff, so experiment and have fun :)
Friday, May 19, 2006
What do they want this time???
This post/link is for everyone who has ever tried to figure out what their website users really wanted. I present to you the FeatureVoter. As you can see, it gives your own users the option to either submit new feature requests or vote up someone else's feature request. This way, you can tell what your users really want the most, so you can give it to them before your competitors do :)
See the Live Version
To download the app (it's in python), click here.
To install the app, simply unzip it in the directory that you want it and alter the settings file appropriately. It's all fairly intuitive, but I'll go over it quickly...
web_root = (the URL of the the installation location)
admin_user = (the admin password for the feature voter itself, default feature_admin
admin_pass = (the admin password for the feature voter itself)
db_host = (wherever your mysql db is, usually localhost)
db_name = (name of your mysql database)
db_user = (username for your mysql database)
db_password = (password for your mysql database)
the feature_header and the feature_footer are whatever you want to go before and after the feature voter table in HTML... and example is provided.
After setting that up, just go to your base url and then admin.py in order to set up the database tables that the app will need, then point your user at your base url + features.py/
See the Live Version
To download the app (it's in python), click here.
To install the app, simply unzip it in the directory that you want it and alter the settings file appropriately. It's all fairly intuitive, but I'll go over it quickly...
web_root = (the URL of the the installation location)
admin_user = (the admin password for the feature voter itself, default feature_admin
admin_pass = (the admin password for the feature voter itself)
db_host = (wherever your mysql db is, usually localhost)
db_name = (name of your mysql database)
db_user = (username for your mysql database)
db_password = (password for your mysql database)
the feature_header and the feature_footer are whatever you want to go before and after the feature voter table in HTML... and example is provided.
After setting that up, just go to your base url and then admin.py in order to set up the database tables that the app will need, then point your user at your base url + features.py/
Thursday, May 18, 2006
The Python Conspiracy
Python produces some really beautiful code sometimes. It's powerful, and even better in nerd currency, Google uses it. It is, however, driving me completely nuts. Pull my hair out nuts. Stryker's son in X-men 2 nuts, and it's all because of one thing: mod_python. In what universe is it ok, that you have to restart the web server to make one little fix? I suppose this wouldn't be so bad if I had my own server to host stuff off of, but since none of my readers seem to click on my ads...ever, I am currently stuck with a more cost-effective option: shared hosting.
Then again, perhaps mod_python was written by admins as a way of extorting nerd-treats (Mountain dew, Snickers bars, etc). This could explain the last e-mail I got from my admin:
"Dear Palantar,
We thank you for your patronage, but regret to inform you that we cannot at this time restart the Apache server AGAIN as we are feeling very tired due to lack of nerd-treats (Mountain dew, Snickers bars, etc). Please feel free to send us a lot of them. "
Or I could just be paranoid...
Then again, perhaps mod_python was written by admins as a way of extorting nerd-treats (Mountain dew, Snickers bars, etc). This could explain the last e-mail I got from my admin:
"Dear Palantar,
We thank you for your patronage, but regret to inform you that we cannot at this time restart the Apache server AGAIN as we are feeling very tired due to lack of nerd-treats (Mountain dew, Snickers bars, etc). Please feel free to send us a lot of them. "
Or I could just be paranoid...
Sunday, April 30, 2006
Wiiiiii!
So as we all know, Nintendo is producing a "different" sort of console, supposedly emphasizing small size, ease of use, that sort of thing. AND Nintendo just renamed the thing "Wii". So, does this mean that when it comes out we can go to Best Buy and gawk and go, "Aww, look at Nintendo's little wii. It's so cute." ? Just wondering...
Wednesday, March 22, 2006
Kiko The Unselfish
So, I actually received an e-mail from Kiko (AJAX web calendar people) today. This shocked me because I'm fairly certain it has been the first useful reply I have gotten from an online company since the dawn of automated reply systems. Further more, it encouraged me greatly because it turns out that they're planning to add export and synchronization tools for iCal users. Good new for people with Mac laptops everywhere.
Tuesday, March 14, 2006
More on SplitNight Sleeping
So, I was rather surprised to see some actual scientific evidence that says that my new method of sleeping may actually be somewhat natural and generally beneficial. See this article.
Friday, January 13, 2006
Sleep Hack
Ok, for now, I'm just going to take a departure from my normal programming-intensive topics to go with a sleep hack. What is a sleep hack you say? Well, this one got dugg recently: Polyphasic Sleep. The short of it is that Steve Pavlina managed to condition his body to sleep for only three total hours a day by only taking short naps of around 25 minutes apiece many times throughout the day. He reports vast productivity gains with no particular downside. Evidently this comes by forcing the body to fall asleep faster and head straight down into REM sleeping almost as soon as your eyes close. Fascinating.
I've always slept for about 9 hours a day bcause I can't seem to get by on less, and I've always thought that I really had to be especially productive during the remaining 15 hours because I had so much less time to work with than my 6-7 hour a night sleeping friends. I've tried working out more and going with less sleep since it has been reported that physically active people sleep less, but I haven't found that to be true for me. However, if we really can train our bodies to drop sleep stages faster by forcing it into smaller naps, that would be great news. Unfortunately, I am a college student and I have classes to attend to and my schedule just won't let me drop off any ol' time to take a nap, so I'm going to try a different experiment, let's call it SplitNight napping.
SplitNight napping appropriately follows my own personal naming convention for classes in Java or C++, but that's completely irrelavent. More relevant is that I'm going to try splitting my night apart into a sleep phase ending at midnight and another ending at 7:00am. In between, I intend to stay up for at least 3 hours every night. If, mathematically speaking, napping once a night leads to 9 hours of sleep per day and napping 6 times a night leads to 3 hours of sleep per day, I figure that this should bring me down to somewhere around 6 hours of sleep total.
Hopefully, this less drastic plan will also allow me to actually be a functional human being during the "transition" phase which is important as my volleyball team plays Sunday and school starts the following day. :)
Day 1:
Tried to force myself to sleep before midnight. I'm not consciously aware of it happening, but I did spend a little over an hour in bed before rising from sheer boredom. In addition to helping me rest a little bit, it gave me time to think of such things as: how much extra time will I be spending changing into and out of pajamas on this new schedule?
Sleep total: 5 hours, Mental AcuityEstimate: 75%
Day 2:
I don't know if I really exactly stuck to the true intent of the experiment today. I appropriately woke up at midnight and then 7:00 as I was supposed to, but then I let myself go back to sleep again for another 3 hours session. My theory being at the time that as long as I was forcing myself into 3 hour sleep intervals, I was helping my body adjust to the new schedule. In any case, I slep another 3 hours until 10:00 and it felt great :)
Sleep Total: 7 hours, Mental Acuity Estimate: 90%
Day 3:
Today, I really tried to hold strictly to the regimen, but my biorhythms sure weren't cooperating. I only managed to get an hour in before midnight and another hour or two in before 7:00 am. A little afternoon, I fell asleep again for a couple of hourse nearly missing class. So, the good was that today I've only had my goal 6 hours of sleep, but my body is so not used to it. I could barely function earlier this morning, having to reread my homework assignments over and over to even remember what any given paragraph had just said. Can only hope this gets better tomorrow.
Sleep Total: 6 hours, Mental Acuity Estimate: 40%
Day 4:
I woke up exhausted again after a total of four hours of sleep. I have found it almost impossible to actually get to sleep for the second half of my nightly sleep. I slept for two hours, woke up at midnight like I was supposed to, and then slept for... not at all. Again. I believe the issue here is that the little 2 hour sleep invigorates me too darned much. It gives me enough energy for many hours of being awake, but my schedule isn't supporting that. So, I do believe I am going to change the experiment. I am going to try to push my two naps as far away from each other as possible so that each one only needs to energize me for the following 9 hours or so.
Sleep Total: 2 hours, Mental Acuity Estimate 75%
Day 5:
Well, I thought this after the Day 1 and Day 2 of the original experiment, but I really think that splitting my sleep schedule into two equidistant naps is going to work. Today, I woke up a bit groggy but quite competent. I could program, I went to class and payed pretty good attention, and then about 12 hours later, I was just ready for my nap. I didn't have to force myself down and I didn't feel too bad when I got up later. Part of this is probably psychological. When you sleep the whole night at once, you know its going to be another 18 hours or so before you have a chance to sleep again. Now that I'm splitting my night, I only have to wait 9 hours until I get another chance at sleep.
Sleep Total: 6 hours, Mental Acuity Estimate 85%
Day 7:
Day 8:
Well, between the snowboarding and the lack of sleep I slept for four hours+ during each of my nap sessions. I wasn't completely functional during my first daytime, but did pretty well during my second daytime. I really do try to simulate daytime during these uptimes and I feel that may be why it has been easier and easier to wake up as my body's natural biorhythms adjust to the new schedule. As soon as I wake up, the first thing I do is turn on a light and leave it on, and then make sure I keep whatever room I'm in well lit as i go.
In any case, I am starting to think that this experiment is just plain working and as such is growing less interesting with each passing day, so I'm gonna tone it down and try posting weekly updates for a couple of weeks and then (hopefully) just call it a success. :)
Sleep Total: 8 hours, Mental Acuity Estimate 80%
Week 2:
This week was a mitigated failure worthy of a thumbs down icon if I were inspired enough to go find one. The biggest issue was that life kept getting in the way of my 3-6 PM nap. The second biggest issue was the snooze button. Between the two of them I got worse and worse throughout the week until I ended up with a single 9 hour long sleep on Friday. As of Saturday, though, I am back on schedule and hoping for better progress next week.
Sleep Average: 8 hours, Mental Acuity Estimate: 95%
Week 3:
Went pretty well. Two interesting notes: You are way more likely to get woken up by random environmental noise at 3-6PM than 3-6AM, and second, possibly because of the first, I always have a LOT more trouble waking up at 6AM. I will often just roll out of bed before the alarm hits at 6PM wide awake and ready to get back to work. 6AM is still very hard for me though.
Sleep Average: 8 hours, Mental Acuity Estimate: 100%
Week 4 (The Last week):
Success! The only real drawback at present is that it can be difficult to schedule in that daytime nap. Besides that, I sleep 7 to 8 hours a day on average, which though not quite the benefits I was hoping for, is still a worthwhile gain. On occasion, I have thought about giving up this experiment, and mostly this was because of the difficulty of finding a quiet place from around 3-6PM. In the end though, I do not currently think it is worth the loss in productivity to go back to a long night's sleep.
I hope these results have helped you, and as you can see it is possible to gain a significant number of hours by splitting your night up without going to the drastic extremes of polyphasic sleep. As always, though, I'm sure mileage may vary, and if you've had some interesting experience with these please leave me a comment and let me know. :)
I've always slept for about 9 hours a day bcause I can't seem to get by on less, and I've always thought that I really had to be especially productive during the remaining 15 hours because I had so much less time to work with than my 6-7 hour a night sleeping friends. I've tried working out more and going with less sleep since it has been reported that physically active people sleep less, but I haven't found that to be true for me. However, if we really can train our bodies to drop sleep stages faster by forcing it into smaller naps, that would be great news. Unfortunately, I am a college student and I have classes to attend to and my schedule just won't let me drop off any ol' time to take a nap, so I'm going to try a different experiment, let's call it SplitNight napping.
SplitNight napping appropriately follows my own personal naming convention for classes in Java or C++, but that's completely irrelavent. More relevant is that I'm going to try splitting my night apart into a sleep phase ending at midnight and another ending at 7:00am. In between, I intend to stay up for at least 3 hours every night. If, mathematically speaking, napping once a night leads to 9 hours of sleep per day and napping 6 times a night leads to 3 hours of sleep per day, I figure that this should bring me down to somewhere around 6 hours of sleep total.
Hopefully, this less drastic plan will also allow me to actually be a functional human being during the "transition" phase which is important as my volleyball team plays Sunday and school starts the following day. :)
Day 1:
Tried to force myself to sleep before midnight. I'm not consciously aware of it happening, but I did spend a little over an hour in bed before rising from sheer boredom. In addition to helping me rest a little bit, it gave me time to think of such things as: how much extra time will I be spending changing into and out of pajamas on this new schedule?
Sleep total: 5 hours, Mental AcuityEstimate: 75%
Day 2:
I don't know if I really exactly stuck to the true intent of the experiment today. I appropriately woke up at midnight and then 7:00 as I was supposed to, but then I let myself go back to sleep again for another 3 hours session. My theory being at the time that as long as I was forcing myself into 3 hour sleep intervals, I was helping my body adjust to the new schedule. In any case, I slep another 3 hours until 10:00 and it felt great :)
Sleep Total: 7 hours, Mental Acuity Estimate: 90%
Day 3:
Today, I really tried to hold strictly to the regimen, but my biorhythms sure weren't cooperating. I only managed to get an hour in before midnight and another hour or two in before 7:00 am. A little afternoon, I fell asleep again for a couple of hourse nearly missing class. So, the good was that today I've only had my goal 6 hours of sleep, but my body is so not used to it. I could barely function earlier this morning, having to reread my homework assignments over and over to even remember what any given paragraph had just said. Can only hope this gets better tomorrow.
Sleep Total: 6 hours, Mental Acuity Estimate: 40%
Day 4:
I woke up exhausted again after a total of four hours of sleep. I have found it almost impossible to actually get to sleep for the second half of my nightly sleep. I slept for two hours, woke up at midnight like I was supposed to, and then slept for... not at all. Again. I believe the issue here is that the little 2 hour sleep invigorates me too darned much. It gives me enough energy for many hours of being awake, but my schedule isn't supporting that. So, I do believe I am going to change the experiment. I am going to try to push my two naps as far away from each other as possible so that each one only needs to energize me for the following 9 hours or so.
Sleep Total: 2 hours, Mental Acuity Estimate 75%
Day 5:
Well, I thought this after the Day 1 and Day 2 of the original experiment, but I really think that splitting my sleep schedule into two equidistant naps is going to work. Today, I woke up a bit groggy but quite competent. I could program, I went to class and payed pretty good attention, and then about 12 hours later, I was just ready for my nap. I didn't have to force myself down and I didn't feel too bad when I got up later. Part of this is probably psychological. When you sleep the whole night at once, you know its going to be another 18 hours or so before you have a chance to sleep again. Now that I'm splitting my night, I only have to wait 9 hours until I get another chance at sleep.
Sleep Total: 6 hours, Mental Acuity Estimate 85%
Day 7:
So it has been a week, and this is my sixth report, which evidently means that for one or more of the days I was absurdly sleepy and lost all track of time. Especially at the beginning of this experiment, I did notice that my sense of what day it was had drastically diminished. I think I'm getting used to it now though. In any case, yesterday I completely missed my afternoon nap and then when night snowboarding on Cypress Mountain - and had an absolute blast without really feeling tired. So, evidently, its a bit easier to lose sleep if you know you have another nap time coming soon. Sleep Total: 4 hours, Mental Acuity Estimate 100% |
Day 8:
Well, between the snowboarding and the lack of sleep I slept for four hours+ during each of my nap sessions. I wasn't completely functional during my first daytime, but did pretty well during my second daytime. I really do try to simulate daytime during these uptimes and I feel that may be why it has been easier and easier to wake up as my body's natural biorhythms adjust to the new schedule. As soon as I wake up, the first thing I do is turn on a light and leave it on, and then make sure I keep whatever room I'm in well lit as i go.
In any case, I am starting to think that this experiment is just plain working and as such is growing less interesting with each passing day, so I'm gonna tone it down and try posting weekly updates for a couple of weeks and then (hopefully) just call it a success. :)
Sleep Total: 8 hours, Mental Acuity Estimate 80%
Week 2:
This week was a mitigated failure worthy of a thumbs down icon if I were inspired enough to go find one. The biggest issue was that life kept getting in the way of my 3-6 PM nap. The second biggest issue was the snooze button. Between the two of them I got worse and worse throughout the week until I ended up with a single 9 hour long sleep on Friday. As of Saturday, though, I am back on schedule and hoping for better progress next week.
Sleep Average: 8 hours, Mental Acuity Estimate: 95%
Week 3:
Went pretty well. Two interesting notes: You are way more likely to get woken up by random environmental noise at 3-6PM than 3-6AM, and second, possibly because of the first, I always have a LOT more trouble waking up at 6AM. I will often just roll out of bed before the alarm hits at 6PM wide awake and ready to get back to work. 6AM is still very hard for me though.
Sleep Average: 8 hours, Mental Acuity Estimate: 100%
Week 4 (The Last week):
Success! The only real drawback at present is that it can be difficult to schedule in that daytime nap. Besides that, I sleep 7 to 8 hours a day on average, which though not quite the benefits I was hoping for, is still a worthwhile gain. On occasion, I have thought about giving up this experiment, and mostly this was because of the difficulty of finding a quiet place from around 3-6PM. In the end though, I do not currently think it is worth the loss in productivity to go back to a long night's sleep.
I hope these results have helped you, and as you can see it is possible to gain a significant number of hours by splitting your night up without going to the drastic extremes of polyphasic sleep. As always, though, I'm sure mileage may vary, and if you've had some interesting experience with these please leave me a comment and let me know. :)
Thursday, January 05, 2006
iTunes Style Music DB with AJAX
I recently decided that since I have some free time on my hands, something that would look really cool is a web page with a music database that works kinda like the iTunes window. The pretty tables are all good, but what I really love about the iTunes window is the Search box. You type things in and then the results appear instantly below. With AJAX, we can do the same thing...
First off, since we all like playing with the end results first, you can find one version of the results here. Do note that that Firefox has some minor issues with this page and Opera some more major ones. More on that later.
Next, we need just a little bit of background. Before starting this project, the site had a mySQL database table called "choir". The choir table holds such information as the song title, what time it was recorded, and the actual location of the audio file.
I decided to do this in PHP, so, the first thing I needed was a function to print the table where the search results would be displayed. This function needs to take two parameters: one parameter indicates which text the user is searching for (starts blank) and the second parameter indicates which entry the results should start at (starts at zero). This means that we only ever need one PHP function to print the data.
One last thing before we look at the main function. I used a very small helper function for outputting cells of a table which looks like so:
function cellPrint($cellText)
{
echo "<td>$cellText</td>";
}
All of this information is contained in a file called choirwindow.php.
Step one then is to connect to the database and query for the information.
function printChoirs($searchText,$startNumber)
{
//This variable represents the maximum number of songs to be shown
//on one page.
$maxAudioTableSize = 20;
//Keep the username/password/db stuff in easy to grep for places so they
//are easy to change if you need to later.
$username = "SuperUser";
$password = "AdminPassword";
$database = "SuperSpecialDatabase";
//Connect to the mySQL database
$link = mysql_connect(localhost,$username,$password);
@mysql_select_db($database) or die (mysql_error());
//This first query is to find out how many choir songs total match up to the
//search text. For optimization, this could be buffered and passed along in
//a post variable if the user is going to spend a lot of time selecting the NEXT
//or PREVIOUS links.
$countQuery = "SELECT count(*) from choir";
$countQuery = $countQuery . " WHERE song_name LIKE '%$searchText%'";
$countResult = mysql_query($countQuery);
$count = mysql_result($countResult,0,"count(*)");
//This second query is to retrieve the actual song information that we are
//going to display and save it in the $result variable.
$infoquery = "SELECT * FROM choir";
$infoquery = $infoquery . " WHERE song_name LIKE '%$searchText%'";
$infoquery = $infoquery . " order by date DESC";
$infoquery = $infoquery . " limit $startNumber,$maxAudioTableSize";
$result = mysql_query($infoquery);
//Let the user know how many search results he/she has.
echo "There are: " . $count . " choir song(s)";
One interesting thing to note from this is that mySQL is doing all of the work for the search mechanism through the use of the LIKE construct. This is not only lazy but good design as well as the search algorithm for mySQL is quite fast and running natively as compared to any possible solution in PHP.
Next up, we need to output the table itself. This is done quite simply by echoing out each row of the mySQL $result to a row of an HTML table. In the code, you will note that the table is completely unadorned. The visual look of the table is cleaned up later using css.
//$num is the number of results we have for THIS page.
$num = mysql_numrows($result);
//Start the HTML table.
echo "<table>";
//Output Table Header.
echo <thead><tr>";
cellPrint("Song Name");
cellPrint("Date");
cellPrint("");
echo "</tr></thead>";
//Now the table body.
echo "<tbody>";
$i = 0;
while ($i < $num)
{
echo "<tr>";
$choir_name=mysql_result($result,$i,"song_name");
$date=mysql_result($result,$i,"date");
$filename=mysql_result($result,$i,"filename");
$id=mysql_result($result,$i,"id");
$dateString = strftime("%m/%d/%y",$date);
cellPrint($choir_name);
cellPrint($dateString);
cellPrint("<a href=\"$filename\">Download</a>");
echo "</tr>";
$i++;
}
echo "</tbody>";
echo "</table>";
//Don't forget to close your db connection!
mysql_close($link);
Now that the table itself is on the page, the only other HTML we have to put out is the code for the "hyperlinks" at the bottom of the table that allow navigation through various pages of results. You may have noticed from the web page that these are not actually hyperlinks, they just look like them. They are actually connected to a javascript function called "reloadMusic" which we will get to shortly. They appear to be real links because they are set to the class "fakelink" which corresponds to the following css rule.
.fakelink{text-decoration:underline;cursor:pointer;cursor:hand}
Now, the code:
//We only need to do this if there are more total entries than current...
//Remember, $count is total entries matching the search string whereas
//$num is the number of entries returned by our mySQL query.
if ($count > $num)
{
// Print out PREV if necessary
if ($startNumber > 0)
{
$previousNumber = $startNumber-$maxAudioTableSize;
echo "<a class=\"fakelink\" onclick=\"reloadMusic('$searchText',$i);\">$page</a> ";
}
// Print out a bunch of numbers for each of the pages.
$i = 0; //This will be the base index number for each page set.
$page = 1; //Which "Page" is this located... the 1,2,3,4 selection.
while ($i < $count)
{
if ($i <> $startNumber)
{
echo " <a class=\"fakelink\" onclick=\"reloadMusic('$searchText',$i);\">$page</a> ";
}
else
{
echo " $page ";
}
$page++;
$i = $i+$maxAudioTableSize;
}
// Print out NEXT if necessary.
if ( ($startNumber+$maxAudioTableSize) < $count)
{
$nextNumber = $startNumber+$maxAudioTableSize;
echo "<a class=\"fakelink\" onclick=\"reloadMusic('$searchText',$nextNumber);\">Next</a>";
}
}
}
This brings us naturally to the question of the javascript. Obviously, if I'm calling this AJAX (Asynchronous Javascript and XML) it's gotta have some javascript in it. Although, I confess, I am not making use of XML in this example which technically makes it AJAH (Asynchronous Javascript and HTML) but that is a much more obtuse term. In any case, it is time to examine the Javascript. The javascript is printed out in a seperate PHP function which is called by parent file shortly after (or in) its header. The javascript could easily have stood on its own in this case, but I wanted to make the file as modular as possible. Hence, the PHP function that prints out javascript:
function printChoirHeader()
{
print"
<script language=\"javascript\" type=\"text/javascript\" src=\"./ajax.js\"></script>
<script language=\"javascript\" type=\"text/javascript\">
function reloadMusic(searchText,startNumber){
var url =\"choirwindow.php?searchText=\" + escape(searchText);
url = url + \"&startNumber=\" + escape(startNumber);
http.open(\"GET\", url,true);
http.onreadystatechange = handleHTTPResponse;
http.send(null);
}
function search(){
var searchText = document.getElementById(\"searchtext\").value;
var url = \"choirwindow.php?searchText=\" + escape(searchText);
url = url + \"&startNumber=0\";
http.open(\"GET\", url,true);
http.onreadystatechange = handleHTTPResponse;
http.send(null);
}
function handleHTTPResponse() {
if (http.readyState == 4) {
document.getElementById('audiotable').innerHTML = http.responseText;
}
}
var http = getHTTPObject(); // We create the HTTP Object
</script>";
}
This uses the same pattern as my appearing map example, so it might be helpful to get some review there. Basically, the reloadMusic() and search() functions take whatever parameters were previously there and ferry them on to a call to choirwindow.php and pass GET parameters for the search text and the startNumber which is the mySQL index of what will be the first entry of the new table. The reload music function is passed these parameters by the PHP code that we have previously seen and does no work for them on its own. When writing the search function, it is already known that since we are now performing a new search, we will start at a new index (0). All the javascript function has to do then is to get the search text which is does by finding the box with the id of "searchtext" in the calling page. This naturally means that any file that makes use of the choirwindow.php functionality and desires to use its search functionality must give its searchbox an id of "searchtext". (See Requirements section below). Similarly, the calling file must have a division whose id is "audiotable" in which to print this table by calling printChoirs("",0);
Now, this code calls back to choirwindow.php with its GET parameters. These are handled outside of any function by the following code:
$searchText = $_GET['searchText'];
$startNumber = $_GET['startNumber'];
if (isset($searchText))
{
printChoirs($searchText,$startNumber);
}
This means that when the javascript calls choirwindow.php with parameters, choirwindow.php will respond by printing its music table. However, when the file is included, since these parameters will not be set during the include call, no output will muddy up the top of the page.
Finally, for the sake of thoroughness, let's see the code with the search box and audiotable div:
Search: <input maxlength="60" size="60" name="searchtext" id="searchtext" onkeyup="search();">
<div id="audiotable" class="audiotable">
printChoirs("",0);
?>
Requirements:
In order to use this PHP functionality then, the calling PHP file must do the following:
Firefox and Opera issues:
Both Firefox and Opera have, on occasion, issues with this functionality. While both have different visual manifestations, they seem to have the same root cause. After the asynchronous call to reload the page, Firefox sometimes throws a javascript exception and refuses to redraw the page. The result of this is that the table dissapears. Interestingly, if you view the source that Firefox sees, you will find that the source is rendered perfectly. Additionally, you can save the source as an html file, reload with Firefox, and everything will appear correctly. Not being a Firefox developer, I cannot tell you why exactly that is, but, I can point you to the bug report where you can vote for it to get fixed faster :)
Similarly, Opera sometimes "forgets" to erase what was in the audiotable div before, resulting in two different musical tables in the results box. Like with the Firefox bug, though, this seems to be a rendering issue as you can examine the source that Opera is trying to render and the source is correct.
Internet Explorer seems to render the table perfectly every time.
First off, since we all like playing with the end results first, you can find one version of the results here. Do note that that Firefox has some minor issues with this page and Opera some more major ones. More on that later.
Next, we need just a little bit of background. Before starting this project, the site had a mySQL database table called "choir". The choir table holds such information as the song title, what time it was recorded, and the actual location of the audio file.
I decided to do this in PHP, so, the first thing I needed was a function to print the table where the search results would be displayed. This function needs to take two parameters: one parameter indicates which text the user is searching for (starts blank) and the second parameter indicates which entry the results should start at (starts at zero). This means that we only ever need one PHP function to print the data.
One last thing before we look at the main function. I used a very small helper function for outputting cells of a table which looks like so:
function cellPrint($cellText)
{
echo "<td>$cellText</td>";
}
All of this information is contained in a file called choirwindow.php.
Step one then is to connect to the database and query for the information.
function printChoirs($searchText,$startNumber)
{
//This variable represents the maximum number of songs to be shown
//on one page.
$maxAudioTableSize = 20;
//Keep the username/password/db stuff in easy to grep for places so they
//are easy to change if you need to later.
$username = "SuperUser";
$password = "AdminPassword";
$database = "SuperSpecialDatabase";
//Connect to the mySQL database
$link = mysql_connect(localhost,$username,$password);
@mysql_select_db($database) or die (mysql_error());
//This first query is to find out how many choir songs total match up to the
//search text. For optimization, this could be buffered and passed along in
//a post variable if the user is going to spend a lot of time selecting the NEXT
//or PREVIOUS links.
$countQuery = "SELECT count(*) from choir";
$countQuery = $countQuery . " WHERE song_name LIKE '%$searchText%'";
$countResult = mysql_query($countQuery);
$count = mysql_result($countResult,0,"count(*)");
//This second query is to retrieve the actual song information that we are
//going to display and save it in the $result variable.
$infoquery = "SELECT * FROM choir";
$infoquery = $infoquery . " WHERE song_name LIKE '%$searchText%'";
$infoquery = $infoquery . " order by date DESC";
$infoquery = $infoquery . " limit $startNumber,$maxAudioTableSize";
$result = mysql_query($infoquery);
//Let the user know how many search results he/she has.
echo "There are: " . $count . " choir song(s)";
One interesting thing to note from this is that mySQL is doing all of the work for the search mechanism through the use of the LIKE construct. This is not only lazy but good design as well as the search algorithm for mySQL is quite fast and running natively as compared to any possible solution in PHP.
Next up, we need to output the table itself. This is done quite simply by echoing out each row of the mySQL $result to a row of an HTML table. In the code, you will note that the table is completely unadorned. The visual look of the table is cleaned up later using css.
//$num is the number of results we have for THIS page.
$num = mysql_numrows($result);
//Start the HTML table.
echo "<table>";
//Output Table Header.
echo <thead><tr>";
cellPrint("Song Name");
cellPrint("Date");
cellPrint("");
echo "</tr></thead>";
//Now the table body.
echo "<tbody>";
$i = 0;
while ($i < $num)
{
echo "<tr>";
$choir_name=mysql_result($result,$i,"song_name");
$date=mysql_result($result,$i,"date");
$filename=mysql_result($result,$i,"filename");
$id=mysql_result($result,$i,"id");
$dateString = strftime("%m/%d/%y",$date);
cellPrint($choir_name);
cellPrint($dateString);
cellPrint("<a href=\"$filename\">Download</a>");
echo "</tr>";
$i++;
}
echo "</tbody>";
echo "</table>";
//Don't forget to close your db connection!
mysql_close($link);
Now that the table itself is on the page, the only other HTML we have to put out is the code for the "hyperlinks" at the bottom of the table that allow navigation through various pages of results. You may have noticed from the web page that these are not actually hyperlinks, they just look like them. They are actually connected to a javascript function called "reloadMusic" which we will get to shortly. They appear to be real links because they are set to the class "fakelink" which corresponds to the following css rule.
.fakelink{text-decoration:underline;cursor:pointer;cursor:hand}
Now, the code:
//We only need to do this if there are more total entries than current...
//Remember, $count is total entries matching the search string whereas
//$num is the number of entries returned by our mySQL query.
if ($count > $num)
{
// Print out PREV if necessary
if ($startNumber > 0)
{
$previousNumber = $startNumber-$maxAudioTableSize;
echo "<a class=\"fakelink\" onclick=\"reloadMusic('$searchText',$i);\">$page</a> ";
}
// Print out a bunch of numbers for each of the pages.
$i = 0; //This will be the base index number for each page set.
$page = 1; //Which "Page" is this located... the 1,2,3,4 selection.
while ($i < $count)
{
if ($i <> $startNumber)
{
echo " <a class=\"fakelink\" onclick=\"reloadMusic('$searchText',$i);\">$page</a> ";
}
else
{
echo " $page ";
}
$page++;
$i = $i+$maxAudioTableSize;
}
// Print out NEXT if necessary.
if ( ($startNumber+$maxAudioTableSize) < $count)
{
$nextNumber = $startNumber+$maxAudioTableSize;
echo "<a class=\"fakelink\" onclick=\"reloadMusic('$searchText',$nextNumber);\">Next</a>";
}
}
}
This brings us naturally to the question of the javascript. Obviously, if I'm calling this AJAX (Asynchronous Javascript and XML) it's gotta have some javascript in it. Although, I confess, I am not making use of XML in this example which technically makes it AJAH (Asynchronous Javascript and HTML) but that is a much more obtuse term. In any case, it is time to examine the Javascript. The javascript is printed out in a seperate PHP function which is called by parent file shortly after (or in) its header. The javascript could easily have stood on its own in this case, but I wanted to make the file as modular as possible. Hence, the PHP function that prints out javascript:
function printChoirHeader()
{
print"
<script language=\"javascript\" type=\"text/javascript\" src=\"./ajax.js\"></script>
<script language=\"javascript\" type=\"text/javascript\">
function reloadMusic(searchText,startNumber){
var url =\"choirwindow.php?searchText=\" + escape(searchText);
url = url + \"&startNumber=\" + escape(startNumber);
http.open(\"GET\", url,true);
http.onreadystatechange = handleHTTPResponse;
http.send(null);
}
function search(){
var searchText = document.getElementById(\"searchtext\").value;
var url = \"choirwindow.php?searchText=\" + escape(searchText);
url = url + \"&startNumber=0\";
http.open(\"GET\", url,true);
http.onreadystatechange = handleHTTPResponse;
http.send(null);
}
function handleHTTPResponse() {
if (http.readyState == 4) {
document.getElementById('audiotable').innerHTML = http.responseText;
}
}
var http = getHTTPObject(); // We create the HTTP Object
</script>";
}
This uses the same pattern as my appearing map example, so it might be helpful to get some review there. Basically, the reloadMusic() and search() functions take whatever parameters were previously there and ferry them on to a call to choirwindow.php and pass GET parameters for the search text and the startNumber which is the mySQL index of what will be the first entry of the new table. The reload music function is passed these parameters by the PHP code that we have previously seen and does no work for them on its own. When writing the search function, it is already known that since we are now performing a new search, we will start at a new index (0). All the javascript function has to do then is to get the search text which is does by finding the box with the id of "searchtext" in the calling page. This naturally means that any file that makes use of the choirwindow.php functionality and desires to use its search functionality must give its searchbox an id of "searchtext". (See Requirements section below). Similarly, the calling file must have a division whose id is "audiotable" in which to print this table by calling printChoirs("",0);
Now, this code calls back to choirwindow.php with its GET parameters. These are handled outside of any function by the following code:
$searchText = $_GET['searchText'];
$startNumber = $_GET['startNumber'];
if (isset($searchText))
{
printChoirs($searchText,$startNumber);
}
This means that when the javascript calls choirwindow.php with parameters, choirwindow.php will respond by printing its music table. However, when the file is included, since these parameters will not be set during the include call, no output will muddy up the top of the page.
Finally, for the sake of thoroughness, let's see the code with the search box and audiotable div:
Search: <input maxlength="60" size="60" name="searchtext" id="searchtext" onkeyup="search();">
<div id="audiotable" class="audiotable">
printChoirs("",0);
?>
Requirements:
In order to use this PHP functionality then, the calling PHP file must do the following:
- Must contain a search field named "searchtext" whose onClick function calls "search();"
- Must call printChoirHeader() in its header
- Must call printChoirs() from within a div whose id is "audiotable"
Firefox and Opera issues:
Both Firefox and Opera have, on occasion, issues with this functionality. While both have different visual manifestations, they seem to have the same root cause. After the asynchronous call to reload the page, Firefox sometimes throws a javascript exception and refuses to redraw the page. The result of this is that the table dissapears. Interestingly, if you view the source that Firefox sees, you will find that the source is rendered perfectly. Additionally, you can save the source as an html file, reload with Firefox, and everything will appear correctly. Not being a Firefox developer, I cannot tell you why exactly that is, but, I can point you to the bug report where you can vote for it to get fixed faster :)
Similarly, Opera sometimes "forgets" to erase what was in the audiotable div before, resulting in two different musical tables in the results box. Like with the Firefox bug, though, this seems to be a rendering issue as you can examine the source that Opera is trying to render and the source is correct.
Internet Explorer seems to render the table perfectly every time.
Subscribe to:
Posts (Atom)