Organizing JavaScript

Reason

It’s an undeniable fact that JavaScript is becoming as important to the functionality of modern websites as the .NET code that Sitecore developers write on a daily basis, but more often than not JavaScript seems to be written hastily and with very little structure, if any.

The following article describes two simple patterns found in Learning JavaScript Design Patterns which I’ve found helpful in improving the maintainability and reuse of JavaScript. Some examples use jQuery 1.7.2.

Code

JavaScript namespaces

Due to the dynamic nature of JavaScript, namespacing simply entails creating a “top level object” and nesting other objects and functions within it, as shown below.
The level of namespace nesting is a matter of personal preference; the following example simply creates a top level namespace called “Customer” in which an object called “Viewport” is nested, which in turn contains some helper functions.

Example

// When using the same namespace across multiple files, make sure only to create it once.
if(typeof Customer == "undefined")
  Customer = {};

Customer.Viewport = {
  currentWidth: window.innerWidth,
  smallDeviceWidth: 640,
  tabletDeviceWidth: 1536,

  isSmallDevice: function(){
    // Consider possible scope issues when using the "this" keyword instead of the "fully qualified name".
    return Customer.Viewport.currentWidth <= Customer.Viewport.smallDeviceWidth;
  },

  isTabletDevice: function(){
    return !Customer.Viewport.isSmallDevice() && Customer.Viewport.currentWidth <= Customer.Viewport.tabletDeviceWidth;
  }
}

Code

JavaScript modules

This technique has certain similarities to the factory and decorator pattern popularized by GoF. In the example shown below, the namespacing technique described earlier is used to define two functions (“Gallery” and “GalleryElement”) within the namespace “Customer”. Each function has a single parameter, intended to be a HTML element.
Within each of these “factory methods”, various variables and functions are created which exist within the scope of the enclosing function (e.g. “Customer.Gallery”), allowing private or public access depending on whether or not they’re “decorating” the HTML element (i.e. “element.fu = function(){…}” vs. “function fu(){…}” and “element.overlaySize = 20” vs. “var overlaySize = 20”).

When accustomed to working with C# and similar languages, the use of scope to create “private” fields and functions (and the ever changing answer to “what does this point to?”) can be confusing – a bit of googling can help to clear things up.

Example

<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="utf-8">
    <title>Organizing JavaScript</title>
    <style type="text/css">
      .gallery { width: 640px; margin-left: auto; margin-right: auto; }
      .gallery-elements { list-style-type: none; height: 130px; }
      .gallery-elements li { float: left; margin-right: 15px; cursor: pointer; padding: 2px; }
      .gallery-elements .selected { border: 3px dashed Gray; }
      .gallery-elements .full-size-image { max-height: 125px; }
      .gallery-elements .thumbnail-image { max-height: 75px; }
      .gallery-content { clear: both; text-align: center; }
      .gallery-content .full-size-image { max-height: 300px; }
    </style>
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
    <script type="text/javascript">
      if(typeof Customer == "undefined")
        Customer = {};

      Customer.Gallery = function(container){
        // Private fields
        var $ = jQuery;
        var gallery = container;
        var galleryContent = $(".gallery-content", gallery);
        var galleryElements = $(".gallery-elements li", gallery).each(createGalleryElement);

        // Private functions
        function createGalleryElement(){
          return Customer.GalleryElement(this);
        };

        function setSelected(galleryElement){
          galleryElements.removeClass("selected");
          $(galleryElement).addClass("selected");
        };

        // Public functions
        gallery.showFirstImage = function(){
          if(galleryElements.length == 0)
            return;

          var firstGalleryElement = galleryElements[0];
          firstGalleryElement.showImageIn(galleryContent);
          setSelected(firstGalleryElement);
        };

        // Set events
        galleryElements.click(function(){
          this.showImageIn(galleryContent);
          setSelected(this);
        });

        return gallery;
     }

     Customer.GalleryElement = function(container){
       // Private fields
       var $ = jQuery;
       var galleryElement = container;
       var thumbnailImage = $(".thumbnail-image", galleryElement);
       var fullSizeImage = $(".full-size-image", galleryElement);

       // Initialize
       fullSizeImage.hide();

       // Private functions
       function showThumbnailImage(){
         fullSizeImage.hide();
         thumbnailImage.show();
       };

       function showFullSizeImage(){
         thumbnailImage.hide();
         fullSizeImage.show();
       };

       // Public functions
       galleryElement.showImageIn = function(element){
         $(element).empty();
         var image = fullSizeImage.clone();
         $(element).append(image);
         image.show();
       };

       // Set events
       $(galleryElement).hover(showFullSizeImage, showThumbnailImage);

       return galleryElement;
    }
  </script>
</head>
<body>
  <section class="gallery">
    <ul class="gallery-elements">
      <li>
        <img class="full-size-image" alt="Image" src="http://1.bp.blogspot.com/-2dAGkAm7bnA/TelKXKl62DI/AAAAAAAABaE/6PION-vwGD8/s400/cute-puppy-pictures1.jpg"/>
        <img class="thumbnail-image" alt="Thumbnail" src="http://1.bp.blogspot.com/-2dAGkAm7bnA/TelKXKl62DI/AAAAAAAABaE/6PION-vwGD8/s400/cute-puppy-pictures1.jpg"/>
      </li>
      <li>
        <img class="full-size-image" alt="Image" src="http://4.bp.blogspot.com/-_zMPmxGw2os/TelKZWp4AtI/AAAAAAAABaI/pMWFvpkAogQ/s1600/cute-puppy-pictures2.png"/>
        <img class="thumbnail-image" alt="Thumbnail" src="http://4.bp.blogspot.com/-_zMPmxGw2os/TelKZWp4AtI/AAAAAAAABaI/pMWFvpkAogQ/s1600/cute-puppy-pictures2.png"/>
      </li>
      <li>
        <img class="full-size-image" alt="Image" src="http://2.bp.blogspot.com/-D_q1hUzwEiU/TelKex4hq6I/AAAAAAAABaM/1jyECRVfqHI/s1600/cute-puppy-pictures3.jpg"/>
        <img class="thumbnail-image" alt="Thumbnail" src="http://2.bp.blogspot.com/-D_q1hUzwEiU/TelKex4hq6I/AAAAAAAABaM/1jyECRVfqHI/s1600/cute-puppy-pictures3.jpg"/>
      </li>
      <li>
        <img class="full-size-image" alt="Image" src="http://3.bp.blogspot.com/-BAmdzP3o288/TelKjerfBkI/AAAAAAAABaY/HB_Cy-KH2Po/s1600/cute-puppy-pictures4.jpg"/>
        <img class="thumbnail-image" alt="Thumbnail" src="http://3.bp.blogspot.com/-BAmdzP3o288/TelKjerfBkI/AAAAAAAABaY/HB_Cy-KH2Po/s1600/cute-puppy-pictures4.jpg"/>
      </li>
    </ul>
    <div class="gallery-content">
    </div>
  </section>
  <hr />
  <section class="gallery">
    <ul class="gallery-elements">
      <li>
        <img class="full-size-image" alt="Image" src="http://alltheragefaces.com/img/faces/png/troll-troll-face.png"/>
        <img class="thumbnail-image" alt="Thumbnail" src="http://alltheragefaces.com/img/faces/png/troll-troll-face.png"/>
      </li>
      <li>
        <img class="full-size-image" alt="Image" src="http://alltheragefaces.com/img/faces/png/determined-challenge-accepted.png"/>
        <img class="thumbnail-image" alt="Thumbnail" src="http://alltheragefaces.com/img/faces/png/determined-challenge-accepted.png"/>
      </li>
      <li>
        <img class="full-size-image" alt="Image" src="http://alltheragefaces.com/img/faces/png/me-gusta-me-gusta.png"/>
        <img class="thumbnail-image" alt="Thumbnail" src="http://alltheragefaces.com/img/faces/png/me-gusta-me-gusta.png"/>
      </li>
    </ul>
  <div class="gallery-content">
  </div>
</section>
<script type="text/javascript">
  $(document).ready(function(){
    $(".gallery").each(function(){
      var gallery = Customer.Gallery(this);
      gallery.showFirstImage();
    });
  });
</script>
</body>
</html>

One thought on “Organizing JavaScript

  1. this code doesn’t take advantages of prototype

    function init() {
    var data, customer;
    data = {
    name:’jim’,
    ele: document.querySelectorAll(‘.gallery-elements li’)
    };
    customer = new Customer(data);
    customer.observer();
    }

    function Customer (data) {
    this.name = data.name;
    this.clickables = data.ele;
    }

    Customer.prototype.observe() {
    var i;
    // your event listeners here
    for (i = 0; i < this.clickables.length; i += 1) {
    this.clickables[i].onclick = function (e) {
    // go ninjas ; )
    }
    }
    }

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s