Thursday, June 12, 2008

Stylize the last element in jQuery

posted by Alex Grande
Here is how to stylize the border of the last element using jQuery.
$(document).ready(function() {
    $("table.innercart tr:last").css("border", "none");
});
You can compare to prototype by going here

Labels: ,

Saturday, June 7, 2008

AJAX Google API to Minify Javascript using Ruby on Rails

posted by Jonah Dempcy

How to optimizing JavaScript performance in Ruby on Rails. This will only focus on one aspect of JS performance optimization, namely, writing a build script to concatenate/minify the JS, and setting up Rails to easily toggle between the compressed and normal files. Also, if your site uses a JavaScript library, we'll explore including it from Google's AJAX Libraries API.

The reason to do these optimizations are for client-side performance. Concatenating many files together into one is especially effective: 10 1-kilobyte files are much slower to download than a single 10k file. You might not think it makes much of a difference, but I estimate that removing 10 additional JS files, for instance, will shave 500-1000ms off latency. Plus, all of the time spent loading the JS will leave the page blank, if you put it in the head.

We'll need to be able to easily toggle between fully commented code and minified code, both for code hosted by us and code from the Google AJAX Libraries API. Since Google offers both minified and normal versions of the code, this should be no problem.

Including JavaScript in Ruby on Rails

First of all, let's look at how JavaScript is included in Ruby on Rails, the javascript_include_tag method. The method quite simply takes any number of source URLs and returns an HTML script tag for each of the sources provided. For instance:

  javascript_include_tag("script1.js", "script2.js", "script3.js")

Result:

<script type="text/javascript" src="/javascripts/script1.js"></script> <script type="text/javascript" src="/javascripts/script2.js"></script> <script type="text/javascript" src="/javascripts/script3.js"></script>

There's a little bit of magic you can do which is to include all JavaScript files in the /public/javascripts folder automatically, as well as having them concatenated into a single file. It looks like this:

  javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is true

The resulting code is:

<script type="text/javascript" src="/javascripts/all.js"></script>

If you don't want them concatenated into a single file, e.g. for development purposes, you could do something like this:

# config/environments.rb:
DEBUG_JS = false;

# environments/development.rb:
DEBUG_JS = true;

# app/views/layouts/site.html.erb or wherever you include the JavaScript in your site:
<%=   javascript_include_tag(:all, :cache => true) if DEBUG_JS == false || ENV["RAILS_ENV"] != "development"
 javascript_include_tag(:all) if DEBUG_JS == true && ENV["RAILS_ENV"] == "development"
%>

I'm not sure how to integrate JSMin, YUICompressor or any other minification into the cached file, though, so I will explore the options for manual building and inclusion next.

Something else to remember is that caching needs ActionController::Base.perform_caching to be true in order to work. This is the default in production, but not development, though of course you can override it in your environments/development.rb file. For more information, view the docs for the javascript_include_tag.

JavaScript Concatenation and Minification

Next, we'll write a custom build script to generate a concatenated, minified version of our files. Ideally, Rails would automatically run it through a minifier, but I'm not sure how to set that up. In the meantime, this is a working solution that is pretty straightforward and requires minimal effort. I wish I were more skilled at shell scripting (and I highly urge readers to contribute their own code), but I'll show my implementation as an example regardless.

I'm on a Windows machine and wrote it as a batch file (.bat), but you can easily do this on linux, just using pipe (the | character) instead of the two right-angle brackets (>>). Here goes:

java -jar yuicompressor-2.3.5.jar "script1.js" -o main.js
java -jar yuicompressor-2.3.5.jar "script2.js" >> main.js
java -jar yuicompressor-2.3.5.jar "script3.js" >> main.js
java -jar yuicompressor-2.3.5.jar "script4.js" >> main.js

Nothing too sophisticated, just a straightforward script that generates main.js using the YUICompressor. To use it, simply run the batch file in the same folder as the JavaScripts and yuicompressor-2.3.5.jar. It will output a main.js file. I chose to run it separately for each individual file so if there are any errors, you can see where they occurred. If you concatenate all of the files into one and then compress it, debugging is difficult as the line number which threw the error is unknown.

I chose to use YUICompressor over JSMin since it seems to have better error messaging, warnings, and be a bit more strict. One of my former colleagues did some tests that determined that JSMin was faster in terms of using less CPU than YUICompressor (and certainly for minifiers which use eval(), such as Dean Edwards' packer). Unfortunately, I can't remember the specifics of the test or which platforms it was on, so that data is pretty much worthless. In any case, I decided on YUICompressor but you can use JSMin or whichever minifier you prefer.

If you are going to use YUICompressor, you must have Java installed and included in your path. If it isn't in your path, you can use the full path to Java, for instance, "C:\Program Files\Java\bin\java.exe" -jar [filename] [options].

Using the Google AJAX Libraries

Depending on your opinion about Google's recent offer to host the world's JavaScript frameworks with its AJAX Libraries API, you may not find this suggestion too useful, but I think it's helpful, especially to those without access to their hosting provider's response header settings, to present here. Without going into too much detail, suffice it to say that besides the caching benefit of using Google's API, their servers are configured "correctly" for best performance (far future expires headers, Gzip, etc).

One thing, though, is that you will want to include a minified version of the JavaScript in production and a full version in development, to ease debugging. It's impossible to debug minified JavaScript (don't even try), so we have to set a toggle like in the above example. Say your site is including jQuery. You may want to include a minified version on the live server but read the full code locally.

Toggling Minified Google JS

Here's how you can include minified jQuery in the production Ruby on Rails code while including the full jQuery library, comments and all, in development:

<%=   
javascript_include_tag("http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js",
                                            :all, :cache => true) if DEBUG_JS == false || ENV["RAILS_ENV"] != "development"

 javascript_include_tag("http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.js",
                                          :all) if DEBUG_JS == true && ENV["RAILS_ENV"] == "development"
%>
<

Or, if you didn't want to include everything in the JS folder, and wanted to granularly include only specific files, you might rewrite it like so:

<%= if DEBUG_JS == false || ENV["RAILS_ENV"] != "development"
   javascript_include_tag ("http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js",
                                            "main.js)
    elsif DEBUG_JS == true && ENV["RAILS_ENV"] == "development"
    javascript_include_tag("http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.js",
                                            "script1.js",
                                            "script2.js",
                                            "script3.js",
                                            "script4.js")
 end %>

That's all there is to it! Just switch the DEBUG_JS flag when you want to test in development mode with/without compression, and don't worry about when it's in production since it will always serve up the minified, concatenated JavaScript. The logic ensures that you won't accidentally start serving up the separate files in production, while giving you the flexibility to store your JavaScript in as many files as you like without negative consequences on the front end, as well as reading fully-commented code./p>

Labels: , , ,

Saturday, May 10, 2008

jQuery drop down menu delay (setTimeout)

posted by Alex Grande
To create a navbar with a subnav (drop down menu for instance) with a delay before closing using jQuery (jQ) follow this code. An example can be found here. Javascript (js):
function navbar() {
// create variables
var subNavTimer;
var open;
// to make sure that when user mouses over sub menu ul it stays open
$("ul.subNav").mouseover(function() {
 $(this).show();
 // lets remember what's open
 open = $(this).parent();
});
 
// when user mouses over main item in navbar
$("li.navMainLink").mouseover(function() {
 // close other nav item submenus
 if (open != null) open.children("ul").hide();
 // stop the timer
 clearTimeout(subNavTimer);
 // show this nav item's sub menu
 if ($(this) != open) $(this).children("ul:hidden").show();
 
});

// when user's mouse leaves the navbar item
$("li.navMainLink").mouseout(function() {
 // lets keep tabs of what is open
  open = $(this);
 // start the timer for 2 seconds until it closes.
  subNavTimer = setTimeout('open.children("ul:visible").hide();', 2000);
});
}

$(document).ready(function() {
 navbar();
});
HTML:
<ul id="navMain">
<li class="navMainLink">
  <a class="navLink" href="#">Performances</a>
  <ul class="subNav">
   <li>
    <a href="#">Season Subscriptions</a>
   </li>
   <li>
    <a href="#">About the Shows</a>
   </li>
  </ul>
 </li>
</ul>
CSS: Your decision! Make it a vertical drop down or make it a horizontal fly out. Just make sure to have the ul.subNav be display: none; by default in your CSS.

Labels: , ,

Monday, May 5, 2008

jQuery Plugin Validation with AJAX

posted by Alex Grande
The JQuery plugin Validation by bassistance.de is extension, powerful, and quick-to-deploy. I recommend this library for people already using the jQ framework to provide high-quality, usable client-side form validation. We are going to discuss basic properties of this framework as well as AJAX and scalability. Here is the code we are going to step through. This code was specifically designed to create an ajax experience for the medical technology company eMedTV.com. The live example can be found at this address. Here's my validation function:
$(document).ready(function() {
    if (pageName=="researchCenter") {
// Just use a different variable for each form you use. It is too difficult to use just one for all forms because each form has it's own plethora of needs (usually).
    var validator = $("#researchCenterForm").validate({
// #messageBox holds the all encompassing message at the top to let all the users know  there is a problem.
         errorContainer:"#messageBox",
// This is very important for manipulating the placement and styles of the error message for individual cases, such as first name or zip. I'll explain this later.
         errorClass: "formError",
// wrapper is only needed if you want to wrap the error message in something. If you  want a list the errors it can be a 
  • . I didn't really find any need for this unless you were listing all the errors at the top. wrapper: null, // errorPlacement is very important. It is where in the DOM you want the error of each idividual case to appear. I wanted each error to be placed at the beginning of each form element list item. See HTML example below. errorPlacement: function(error, element) { error.insertBefore(element); }, // On to the rules and without this the form wouldn't be what it is! rules: { // All the methods are found as having the name's inside the form. Thus in order for email to work I have to have a form element with name="email". zip: { // required:true is a must if you want the validator to check this element. required:true, // digits is one of the mime types of only digits. You put in alpha characters and it lets you know it doesn't like that! digits: true, minlength: 5, maxlength: 5 }, betterEmail: { required: true, email: true }, // If you have a confirm or email again area then we have some special rules to make sure this works. confirmEmail: { required: true, // #email is the id of the first email. For example, equalTo: "#email", email: true } required:true } }, //Here are all the messages for the different errors on the page. This is OOP so the method (firstname:) must match the ID of the required field. If it is not required it will not receive an error message. messages: { firstname: "Please enter your first name.", lastname: "Please enter your last name.", username: { required: "Please enter a username.", minLength: "Your username must consist of at least 2 characters." }, password: { required: "Please provide a password.", minLength: "Your password must be at least 5 characters long." }, confirmPassword: { required: "Please provide a password.", minLength: "Your password must be at least 5 characters long.", equalTo: "Please enter the same password as above." }, email: "Please enter a valid email address.", confirmEmail: "Please enter the same email.", zip: "Please enter a valid zip code.", agreeToService: "Please check the box." }, // Here is the fun part and the tricky part: AJAX // SubmitHandler is called when the form is called. When the users clicks submit on your form this is what happens. Since we want to do ajax you use the $.ajax jQ object. It has to start with function() {} for it to work. submitHandler: function() { $.ajax({ See serialize here http://docs.jquery.com/Ajax/serialize data:$("#researchCenterForm").serialize(), // The url you are going to submit to url: submit.php, timeout: 2000, // Sends a message to the console if it failed for testing purposes. error: function() { console.log("Failed to submit"); }, // If successful you can do something here. You can change elements, update text, or simply alert the response to first test to see if it worked. success: function(response) { alert(response); } }); } }) } });
  • First thing you'll probably notice is this plugin is formatted in object oriented programming. So it is very straight forward to know which methods and which properties to use for your form. Once you locate the ultimate guide to how to use the validator, it can become relatively straight forward to use. How to stylize where error label fields go on the page CSS:
    .formError {
            position: absolute;
            margin: 21px -13pt 0pt;
    }
    li {
            position: relative;
    }
    
    The margin is to position the error label below the form field, such as an email input box. Positioning it absolutely makes sure the forms look and feel is not altered by static elements. HTML:
    You'll notice the class name "betterEmail" and "required". This is to enter in jQ validation methods into the html markup itself. Cool huh? The HTML after the error has been applied in real-time:
    Once you have your form all setup you can start writing your own mime types if you include the mime type plugin as well. Currently the validator checks if your email is alex@alexgrande and that's it. If you want to make sure there is a .com or .org, etc you need this:
    jQuery.validator.addMethod("betterEmail", function(value, element) {
     return this.optional(element) || /^[^\x00-\x20()<>@,;:\\".[\]\x7f-\xff]+(?:\.[^\x00-\x20()<>@,;:\\".[\]\x7f-\xff]+)*\@[^\x00-\x20()<>@,;:\\".[\]\x7f-\xff]+(?:\.[^\x00-\x20()<>@,;:\\".[\]\x7f-\xff]+)+$/i.test(value);
    }, "Valid Email Please"); 
    
    See I how I use betterEmail above. Another is if you have a drop down menu like a select box with options. There isn't a default way to do this. My solution is the following. Notice the option with the value="none".
    jQuery.validator.addMethod("selectCountry", function(value, element) {
     return this.optional(element) || ( value.indexOf("none") == -1);
    }, "Please choose a Country."); 
    
    
    
    Another problem I have found is if you have three different select boxes, such as month, day, and year and you want only one error message for all three since they are all related. My solution is to make all three error message overlap making it appear they are all the same to the user. HTML:
  • What is your (or the participant) date of birth?

  • CSS:
    li#date .formError {
     left: 2px;
     bottom: -15px;
    }
    
    Thus every formError will overlap each other with the same position on the page. Last problem is fixing error form position issues in IE. That's easy. If you read my article about handling cross-browser issues then you'll know most of what to do. Since we positioned the error message absolutely we can position it using IE conditional comments only for IE. CSS:
    .ie li#date .formError {
     top: 1000px;
    }
    
    This is all for this discussion. If I think of more I'll add to it later. In either case, please send questions or comments.

    Labels: , ,

    Sunday, April 6, 2008

    How do I make tabs in Jquery? (Without jQ Tabs plugin)

    posted by Alex Grande
    Strategies and Technologies: This is the beginning of a series where I build a tab presented component from scratch. Each series on the tab will increase in functionality and ux. For starters lets just build it. To see the finishing product of this lesson please check out this link to see a tab example. Because we are using CSS Sprites there is only one image for the tabs. Check out this link to see the image.

    HTML

    <!-- This is to deal with Internet Explorer-->
    <!--[if lte IE 5]><br /><div class="ie"><br /><![endif]-->
    <!--[if IE 6]><br /><div class="ie ie6"><br /><![endif]-->
    <!--[if IE 7]><br /><div class="ie ie7"><br /><![endif]-->
    
    <!-- This is the list of tabs. Notice the id begins with the content in the tab. This can be easily replaced with 1, 2, 3 using a simple js loop. Or your backend developer can supply you with a variable to make it scalable. -->
    <div id="tabSelection">
       <div class="tab-selects" id="salsa-tab">
           <h3>salsa</h3>
       </div>
       <div class="tab-selects" id="fern-tab">
           <h3>fern</h3>
       </div>
       <div class="tab-selects" id="jazz-tab">
           <h3>jazz</h3> 
       </div>
       <div class="tab-selects" id="sodo-tab">
           <h3>sodo</h3>
       </div>
    </div>
    
    <!-- This is the content of the tabs. See how the id matches the name in the tab?-->
    <div id="content">
       <div id="salsa-tab-content" class="view-content">
     <img src="http://farm3.static.flickr.com/2205/2346526197_81a11816fa.jpg?v=0" />
    </div>
       <div id="fern-tab-content" class="view-nonselected view-content">
     <img src="http://farm1.static.flickr.com/188/404190495_7c193a1873.jpg?v=0" />
    </div>
       <div id="jazz-tab-content" class="view-nonselected view-content">
     <img src="http://farm4.static.flickr.com/3207/2338432523_c9029c38b9.jpg?v=0" />
    </div>
       <div id="sodo-tab-content" class="view-nonselected view-content">
     <img src="http://farm4.static.flickr.com/3113/2339197062_01d0950920.jpg?v=0" />
    </div>
    
    </div>
    <!--[if IE]><br /></div><br /><![endif]-->
    

    CSS

    body {
    margin-top:50px;
    }
    
    /* Here are the sprites. Only the left part of the image is shown for the selected tab. I love sprites! */
    div.tab-selects {
    width:111px;
    background:url(tab-select-nonselect.gif) no-repeat;
    background-position:left center;
    float:left;
    margin:-40px 10px 0pt;
    position:relative;
    cursor:pointer;
    height:51px;
    }
    
    /* Here is a non selected tab. The image on the right! */
    div.tab-nonselected {
    background-position:right center;
    }
    
    div.view-wrapper {
    position:relative;
    margin-top:74px;
    }
    
    /* IE sucks */
    .ie div#content {
    clear:both;
    margin-top:-10px;
    }
    
    div#content {
    width:610px;
    position:relative;
    cursor:default;
    background-color:#5C81AD;
    margin-top:-1px;
    border-bottom:1px solid #9D8979;
    border-right:1px solid #9D8979;
    border-left:1px solid #9D8979;
    height:410px;
    }
    
    div.view-content {
    top:0px;
    position:absolute;
    width:100%;
    overflow-y:auto;
    height:410px;
    }
    
    div.tab-selects {
    text-align:center;
    }
    
    div.view-content img {
    padding:23px;
    }
    
    div.view-nonselected {
    visibility:hidden;
    }
    
    

    jQuery Javascript Tabs

    
    //The first tab is going to be opened. We need to know that.
    $TabIsOpen = $("#tabSelection div:first");  
        
    // The click event
     $('.tab-selects').click(
      function() {   
    // So if what you click is not the tab that is already opened then lets change its class
       if ($(this) != $TabIsOpen) {
    // Look at the CSS to see the properties of this clas
       $TabIsOpen.addClass('tab-nonselected');
        $(this).removeClass('tab-nonselected');
    // Now that this tab is selected we gotta remember it.
       $TabIsOpen = $(this);
      
    // jQ can be annoying in creating selectors. But basically we take the id of the tab and it is associated with the content item. We just have to add -content to the end. For instance, #salsa-content.
       contentIdChildDiv = "#" + $TabIsOpen.attr("id") + "-content";
    // Same deal with the tabs; Hiding and showing.  
     $("div.view-content").addClass("view-nonselected");
       $(contentIdChildDiv).removeClass("view-nonselected");
      }
     });
    
    // We want to make every instance but the first one hidden for both tabs and content. That's what that crazy div:not(div:first) is all about.
    $("#content div:not(div:first)").addClass("view-nonselected");
    $("#tabSelection div:not(div:first)").addClass("tab-nonselected");
    
    

    Labels: , ,

    Sunday, March 23, 2008

    Managing Cross Browser issues with JS browser detects & Conditional Comments

    posted by Alex Grande
    Until all browsers completely comply with w3c specifications we have to find workarounds in order for our websites to be pixel perfect in various browsers. The browsers we need to be most concerned with include Internet Explorer, Safari, and Firefox.

    Firefox is a web developers wet dream. It features many plugins that allow a web developer to write CSS, JS, and HTML in real time. Thus you do not need a hack or workaround for Firefox since you are developing your websites in Firefox.

    With the release of Safari 3 for both Mac and Windows, Safari is becoming a strong player. When Safari-centric adjustments need to be made, you can use a browser detect, or when you write javascript that detects the user's browser and can set styles only to that browser. You may have heard that browser detects bad and should be avoided, but when you do not have any other options it may be your only hope. Using Core DOM API, or just plain old javascript, browser detect code can be confusing and long. A library, like dojo, prototype, or mootools, can make life get much easier and tasks can be checked off much faster. In jquery, a popular library, it is easy to detect the browser.

    In this example, not only do I detect Safari but set a CSS class called "safari" to the tag seen only by Safari's eyes.
    // detects the browser in an if statement
    if ($.browser.safari) {
       // Writes a class safar on the <body> tag. Or in other words, <body class="safari">
       $("body").addClass("safari");
    }
    
    And in this example we write it in Mootools,

    if (window.safari) {
       $$("body").addClass("safari");
    }
    


    On to Internet Explorer. For styling (CSS) we can use conditional comments placed in your html. Right after the tag insert the following code:
    <!--[if lte IE 5]>
    <div class="ie">
    <![endif]-->
    <!--[if IE 6]>
    <div class="ie ie6">
    <![endif]-->
    <!--[if IE 7]>
    <div class="ie ie7">
    <![endif]-->
    
    Results:

    IE5+ gets <div class="ie">

    IE6 gets <div class="ie6">

    IE7 gets <div class="ie7">

    At the bottom of your document, right before the closing tag add these lines of code to close the IE <div>s.
    <!--[if IE]>
    </div>
    <![endif]-->
    
    On to the stylizing in CSS:

    To write styles for Safari just start any style rule with .safari. For instance,
    .safari div#title {
       margin: 0 0 3px 0;
    }
    
    And similar for IE:
    .ie div#title {
       position: relative;
    }
    
    .ie7 div#title {
       margin: 0 0 3px 0;
    }
    
    .ie6 div#title {
       left: 305px;
    }
    
    If you have to make browser specific Javascript adjustments only browser detects will suffice. We already covered Safari. For Internet explorer you can write:

    Jquery:
    if ($.browser.msie) {
       // js goes here for internet explorer
    }
    
    Mootools:
    if (window.ie) {
       // js goes here for internet explorer
    }
    
    Luckily, Safari and internet explorer styling adjustments are often similar. Having already adjusted IE to match Firefox, I usually need to apply similar Safari rules to the same trouble elements.

    For example,
    .ie div#rightNav,
    .safari div#rightNav {
       top: -3px;
    }
    

    Labels: , , ,