Saturday, April 16, 2011

IE, Javascript and jQuery: some common pitfalls (and workarounds)

At www.kingranks.com we have two developers: me and a colleague. As much as we would love to have a front-end developer, we do not have the resources (or the necessity) to hire one and so we pretty much do all the HTML/CSS and Javascript work even though we are back-end engineers at our core. I don't hate doing it but there is one particular thing that ticks me off: cross-browser compatibility. I know (some) folks are trying really hard to move towards standardization, but is not a reality just yet, specially when you are trying to make everything work in Microsoft's Internet Explorer.

This is not meant to be a post detailing why some things don't work in IE, I don't even know for the most part. This is meant to be a quick post showing some of the problems we had making some particular things work in IE and how we got around them. This is not, by any means, a complete or comprehensive guide, it's just several things I always try to keep in mind when writing front-end code and markup.

Every item has been tested in IE8. Almost 90% of all our IE users use IE8 so that's our main focus. I do have reasons to believe it should also work in IE7 and IE9 but I haven't tested it myself.

1) Use the shortcut $. instead of jQuery()

    This is pretty mysterious. For some reason IE complained about the jQuery() but it accepted the $. shortcut. The jQuery Cookbook has a small recipe on how to avoid the shortcut $. from creating global conflicts but I never had any problems with it.

2) Preventing event default behavior with event.preventDefault() doesn't work. Use
event.preventDefault ? event.preventDefault() : event.returnValue = false;
instead. Notice that we test for the preventDefault function, and fallback to setting it's return value to false if it is not defined.
    When creating a web 2.0 experience, the "default" behavior is sometimes (or quite often) not what you want. You might expect a GET request and a new page load when you click a link, but nowadays you might as well expect an AJAX request that renders some new content without reloading a page. So preventing default behavior is actually quite essential.

3) If you use onClick="someFunction(event)" html attribute, event is not passed as a parameter

    In IE the event will not be passed as a parameter. Instead, it will be in window.event, example:

jQuery:
  
        function someFunction(event) {
            if(!event) event = window.event;
            // do something with event
        }

HTML:

        <a href="somelink" onClick="someFunction(event)">link</a>

    Some people might argue that using an onClick="someFunction(event)" on an HTML tag, is a bad practice since markup is not supposed to tell the behavior or functionality of it's elements (see [1]).
    I did have a performance problem using jQuery's register method $(element).click() with a table that had lots (hundreds or couple thousands) elements that had to be registered for a click and using the onClick() helped. I think the same goes for onChange, onBlur, etc...

4) If you want to get the target of the event (as a jQuery object), use:

        var targetElement = $(event.target || event.srcElement);

In IE, the element is not the target, but the srcElement. Again, if event.target is not defined, we fallback to the srcElement.

5) Always declare your variables with the var keyword.

    IE gave me a hard time when I didn't explicitly declared my vars.

6) Remove all console.log(). IE does not understand console unless you have debugging tools activated, which I don't believe normal users do. Use this nice little code instead (from[2]):

        function trace(s) {
            try { console.log(s) } catch (e) { return; }
        };

    Then you can use trace("message"); in your javascript code. Notice that it is not going to log anything on IE unless you have developer tool bar on but it is not going to generate errors. If console is not defined, the function doesn't do anything.

7) Use jQuery functions whenever you can. It's safer since jQuery people do an outstanding job at trying to make everything cross-browser and portable. For example, IE8 simply doesn't implement the string.trim() function (go figure...), as I imagine there might be a number of other functions it doesn't implement. So try looking for the equivalent jQuery function, for instance, instead of using string.trim() (and breaking your JS on IE8), use string = $.trim(string). Instead of using string.slice(), use string = $.slice(string) and so on and so forth.

8) This is more some piece of advice than a workaround for some problem: use IE8 developer tool bar. It actually works (surprise!) and you can set breakpoints and pinpoint exactly where the error is.

9) Use object detection. This is specially useful when you can't test on every browser (I don't have an IE6 to test on, for instance). Test to see if the function or attribute you are going to use actually exists before using it. This prevents your code from breaking on browsers that don't implement it. For instance:

        if (windows.focus) windows.focus();

There is a nice article about this on [3].

Let me know if you have any more pitfalls, and happy coding!

[1] http://en.wikipedia.org/wiki/Unobtrusive_JavaScript
[2] http://stackoverflow.com/questions/690251/what-happened-to-console-log-in-ie8
[3] http://www.quirksmode.org/js/support.html

Monday, February 28, 2011

Eclipse Egit + github - A quick guide to creating, configuring, pushing and pulling from a github repository.

It does sound simpler than it actually is, but configuring eclipse and Egit to work with github has quite a few bumps. This is meant to be a quick guide and focus on the problems along the way instead of the process, since there are already good guides out there that focus on the entire process (see the references in the bottom, specially [3]). For the record, I'm using:
  • Eclipse 3.6 (codename Helios)
  •  Ubuntu 10.10
This post will cover the following:
  1. Create a repository on github;
  2. Install git;
  3. Configure ssh keys.
  4. Install Egit in Eclipse;
  5. Configure Eclipse;
  6. Push your current project to github repository;
  7. Check out (pull) project from github.
Create a repository

This is fairly straight forward. Go to your account on http://github.com/, click on "Dashboard" (top right) and then "Create a repository". Just follow the simple instructions and you'll get an address for the repo that you'll use later on.

Installing git

That's pretty easy. Just:
$ sudo apt-get install git
You can also install git-doc and git-gui if you like. You should also set your username and email to properly sign your commits:
$ git config --global user.name "your name"
$ git config --global user.email "youremail@yourdomain.com"
Configuring ssh keys

Github provides a pretty good tutorial on generating ssh keys [1], but I'll reproduce the main steps to make it work. Refer to [1] for a more detailed explanation.

Create .ssh folder:
$ mkdir ~/.ssh
 If the directory already exists, it will simply throw an error and skip. Generate rsa key pair:
$ ssh-keygen -t rsa -C "youremail@yourdomain.com"
The -C is just an optional comment. By default, it will be saved in ~/.ssh/ directory. For now leave the passphrase empty! This is important, since Eclipse has a bug handling passphrase protected keys. We will give it a passphrase later. Now, go to your account on github, go to "Account Settings" (top right), than click on "SSH Public Keys" on the left menu and "Add another public key". The tittle should be some comment that identifies which computer this key is from (such as "Home-desktop"), and the "key" should be the exact contents of the file ~/.ssh/id_rsa.pub. There is a neat trick using xclip to copy the contents of the file to the clipboard without messing anything (such as adding whitespace and newlines):
$ sudo apt-get install xclip
$ cat ~/.ssh/id_rsa.pub | xclip -sel clip
Those commands copy the contents of the file to your clipboard. Now just ctrl+v in the "Key" box in github and you should be done. To test everything is working, try running:
$ ssh git@github.com
 And you should receive a message that looks like:
Hi user! You've successfully authenticated,
    but GitHub does not provide shell access.
                                               Connection to github.com closed.
One possible problem here is getting a message that looks like:
Permission denied( publickey).
If you do, try manually adding your generated key to ssh path and checking permissions are correct:
chmod 700 ~/.ssh/id_rsa
ssh-add ~/.ssh/id_rsa
I had this problem because I regenerated the pairs a few times and ssh somehow couldn't find my key anymore. If that doesn't work, please refer to git troubleshooting guide [2] for other possible problems.

Installing Egit

If you are running Eclipse 3.6+, you can install Egit through the marketplace. In Eclipse, go to Help -> Eclipse Marketplace...  , search for Egit and install it. Restart Eclipse afterwards. If you are not using Eclipse 3.6+, refer to Egit documentation for your eclipse version [4].

Configuring Eclipse

Here, we need to configure SSH.
  1. Go to Windows -> Preferences and filter SSH. Select SSH2 on the left tab (should be under General -> Network connections). 
  2. Check that SSH2 home is set to /home/<yourusername>/.ssh and id_rsa is in Private Keys
  3. On Key Management tab, click on Load Existing Key... and select your id_rsa (private) key under ~/.ssh/id_rsa. If your key has a non-empty passphrase, eclipse will not be able to load it, even if you provide the correct password. 
  4. Now, just save it in the same location you loaded it from, confirm empty passphrase, overwrite it, apply and click ok.
Pushing your project to your github repo

Final step is to push your project to your remote repository on github. 
  1. Finally, select the project you want to push to your created repository, right click it, go to Team -> Share Project... and select git.
  2. Select your project, click on Create Repository and Finish. Now you should be able to commit and update changes to your local git repository
  3. To push it to your remote github repo, right click your project, select Team -> Remote -> Push... 
  4. Fill the URI with your project SSH address, which is shown when you enter your repository page on github.
  5. Select ssh protocol, user is git and empty password.
  6. Cross your fingers, hit next. If it doesn't work for some reason at this point, try restarting Eclipse (it worked for me after restart).
  7. Select Source Ref and Destination Ref (basically the branch), click on Add Spec and Finish on the next screen.
  8. Hopefully everything goes well.  
Importing a project from a github repo

Easier than pushing is checking out a project.
  1. From Eclipse, select File -> Import and select Projects from Git under Git.
  2. Select Clone and fill this form just like step 4 of the previous section.
  3. Select the branch you would like to check out.
  4. Select the location you would like to checkout to.
  5. You should be back to Import Projects from Git window with your newly cloned repo showing.
  6. Select your repository and follow the instructions (defaults should be fine).
Adding a passphrase to your ssh key

After configuring eclipse, you should probably set a non-empty passphrase to your ssh key. It is not the scope of this post to discuss why you shouldn't leave your passphrase empty, but here is how:
$ cd ~/.ssh
$ ssh-keygen -f id_rsa -p
This command will prompt for a new passphrase and you are done!

Hope this was helpful. Feel free to comment or add any info.

[1] http://help.github.com/linux-key-setup/
[2] http://help.github.com/troubleshooting-ssh/
[3] http://blog.doityourselfandroid.com/2010/08/14/git-github-and-egit-eclipse-integration/
[4] http://www.eclipse.org/egit/

Cheers,
Lucas Piva

Saturday, February 19, 2011

Fixing AJAX POST requests after Django update

I'm developing a personal project using Python Django and today I stumbled into a problem with Django's CSRF-protection mechanism and AJAX POST requests which took me a few (annoying) hours to get around.

As you probably know, to protect against Cross-site request forgery (CSRF) [1] Django uses a token mechanism. Every time you make a POST request to an internal URL, you have to provide the CSRF token as a POST parameter ('csrfmiddlewaretoken' field) which is checked by the CSRF middleware (django.middleware.csrf.CsrfViewMiddleware) and if the provided token doesn't match with the server-side token, you get a 403 error. The steps required to correctly enable CSRF protection are documented in Django's site [2]. In the same link, there is also a workaround for AJAX requests which weren't checked for CSRF and worked fine... until a couple of weeks ago.

This past week, I updated my Ubuntu system and suddenly all my AJAX POST requests stopped working. I checked [2] to see if everything was configured correctly and didn't find any errors. After some research, I discovered through Django's weblog that some engineers at Google discovered that a combination of browser plugins and redirects could allow AJAX requests to provide custom HTTP headers on a request to any website, making AJAX requests also a possible source of CSRF attacks [3]. On the same post, they also announced an update for Django's development trunk, 1.1 and 1.2 that pretty much breaks all your internal AJAX POST requests. They also provided a solution by including the CSRF token in the HTTP header on every AJAX request. For sake of simplicity, here is their proposed solution in jQuery:

$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!(/^http:.*/.test(settings.url)
|| /^https:.*/.test(settings.url))) {
// Only send the token to relative URLs i.e. locally.
xhr.setRequestHeader("X-CSRFToken",
$("#csrfmiddlewaretoken").val());
}
}
});

Now, there is just one problem with that: it doesn't work. Luckily, there is only a minor mistake there:

xhr.setRequestHeader("X-CSRFToken",
$("#csrfmiddlewaretoken").val());

Should be:

xhr.setRequestHeader("X-CSRFToken",
$('input[name="csrfmiddlewaretoken"]').val());


Also, don't forget to include the {% csrf_token %} tag in any html template that has an AJAX POST and use RequestContext in the associated views (steps 2 and 3 of [2]) like so:
from django.template import RequestContext

return render_to_response('my_page.html', some_dict,
context_instance=RequestContext(request))
[1] http://en.wikipedia.org/wiki/Cross-site_request_forgery
[2] http://docs.djangoproject.com/en/1.2/ref/contrib/csrf/#
[3] http://www.djangoproject.com/weblog/2011/feb/08/security/