Showing posts with label jQuery. Show all posts
Showing posts with label jQuery. Show all posts

Wednesday, June 6, 2012

Dissapointed Dad


Another pass at the paralax plugin I developed a few months ago. This time messing around with inserting negative rel values for different layers. I think the effect is a lot more compelling.

The collage named "Dissapointed Dad" was create by my friend Tim Scahill.

Tuesday, May 10, 2011

jQuery OOP - Inheritance

In my last post I outlined how to achieve some encapsulation behaviors within a jQuery plugin framework. This time I'll be taking a look at inheritance. Specifically I want to able to inherit or override members of one class for use in an inheriting class.

The full objects and examples can be found here.

Super Class

To facilitate inheritance one change had to be made to the super class. The plugin needs a a function (hasFunction) that determines if the plugin responds to a given function.

(function($) {
        //public functions
        var methods = {
                init : function(options) {
                        var defaults = {};
                        $.extend(this, defaults, options);
                        ...
                        return this
                },
                //function to check if plugin responds to function
                hasFunction : function(functionName){
                        return (methods[functionName] !== undefined);
                }
        };                
        $.fn.objectA = function(method) {
                if (methods[method]) {
                        return methods[method].apply(this, 
                               Array.prototype.slice.call(arguments, 1));
                } else if (typeof method === 'object' || !method) {
                        return methods.init.apply(this, arguments);
                } else {
                        $.error('Method ' + method + ' does not exist on jQuery.');
                }
        };
})(jQuery);

Sub Class

Some more work has to be done on the sub class. There is the hasFunction method again, but now that function has to check its parent as well.

Also, In the initialization block two extra changes have to be made. After checking if this plugin responds to a method name, the plugin must check to see if the parent responds, and if so, call the method on the parent plugin. If the plugin is initializing, the parent must be initialized with the same parameters,

(function($) {
        //public functions
        var methods = {
                init : function(options) {
                        var defaults = {};
                        $.extend(this, defaults, options);
                        this.objectBInstance = "Intance B";
                        
                        return this;
                },
                //function to check if plugin responds to function
                hasFunction : function(functionName){
                    //does this or the super class respond to this?
                    return (methods[functionName] !== undefined || this.$super(functionName));
                }
        };        
        /*
                Initialization
        */        
        $.fn.objectB = function(method) {
                if (methods[method]) { //check this class for the method
                        return methods[method].apply(this, 
                                Array.prototype.slice.call(arguments, 1));
                } else if(this.objectA("hasFunction", method)){ //next check the parent
                        //use apply without changing to maintain args
                        return this.objectA.apply(this, arguments);
                }else if (typeof method === 'object' || !method) {//try to init
                        //inherit first inheritance method
                        this.objectA.apply(this, arguments); //inherit from objectA
                        this.$super = this.objectA; //objectA is $super                        
                        return methods.init.apply(this, arguments); //initialize this 
                } else {//if missing from parent/self and not init err out
                        $.error('Method ' + method + ' does not exist on jQuery.');
                }
        };
})(jQuery);

Summary
The inheritance method I outlined above enables public methods and instance variables can be inherited from the super class. In the future I would like to extend this to facilitate multiple inheritance.

Monday, April 4, 2011

jQuery OOP - Encapsulation

Since I already include jQuery in every project I work on. I figured why not try to implement some features of OOP within the framework of a jQuery plugin. In this post I'll be outlining how to implement some useful features of encapsulation.

The full object and examples can be found here.

Frame and Public/Private Functions

I would like to thank my friend Zach for the plugin method utilized in this code. The plugin reads in the method name and calls it from an object's methods. These methods can then be indirectly called outside of the plugin (ie. $(obj).plugin("function") ):
(function($) {
     var methods = {
          init : function(options) {              
               var settings = {
                    ...
               };
               $.extend(this, settings, options);
               
               $t = $(this);
               $t.each(function(){
                    ...
               });
               return this;
          },
          func1 : function(){}
     },

     $.fn.object = function(method) {
          if (methods[method]) {
               return methods[method].apply(this,
                             Array.prototype.slice.call(arguments, 1));
          } else if (typeof method === 'object' || !method) {
               return methods.init.apply(this, arguments);
          } else {
               $.error('Method ' + method + ' does not exist on jQuery.');
          }
     };
})(jQuery);     
The functions contained in the methods object also return values as expected. Since these functions are accessible outside of the plugin, and other functions defined inside the plugin are not; it is then possible to create public and private functions.
(function($) {
     //public functions
     var methods = {
          ....
          foo : function(){
               ...
          },
          bar : function (){
               //call private function
               privy.apply(this);
          }
     },
  //private functions
     function privy(){
          ...
     }
    ...
})(jQuery);     
The apply function is used to maintain consistent scoping for the this pointer. apply() should be used whenever the called function needs to reference the this object. To call the public version of a method inside of the plugin you have to call it from the methods object, (ie. methods.bar.apply(this)). Once instance variables are implemented, private methods can be used as getters and setters.

Class and Instance Variable
To attatch instance variables to individual objects, I used the $.extend function to attach the passed options object to this:
(function($){
     //public functions
     var methods = {
          init : function(options) {              
               $.extend(this, settings, options);
               //apply functionality to every member of the set sent in    
               $(this).each(function(){});
               return this;
          }
     ...
})(jQuery);     
Now these instance variables are accessible throughout the plugin.

The apply method should be used to call methods from inside the plugin. Otherwise this will be defined as the methods object (public methods) or the wrapping function itself (private methods).
(function($){
     //public functions
     var methods = {
          init : function(options) {              
               //default instance vars
               var settings = {
                    name : ""
               };
               $.extend(this, settings, options);
               //apply functionality to every member of the set sent    
               $(this).each(function(){});
               return this;
          },
          foo : function(){
               console.log(this.name);
          },
          bar : function (){
               bar.apply(this);
          }
     },
     ...
     function bar(){
          console.log(this.name);
     }
     ...
})(jQuery);     
Class variables are even simpler to implement, for example the methods object is a class variable. Any variable instantiated in the same scope will be a class variable.
(function($){
     var methods = {
     ...
     },
     //class variables
     class1 = 0;
     //private functions
     function bar(){
          class1++;
          console.log(class1);
     }
     ....
})(jQuery);     
Summary
In this post I've show how to implement parts of encapsulation within a jQuery plugin. Instance and class variables, as well as public and private methods were implemented. I figure thats a pretty good start.

Sunday, March 20, 2011

Richard III

When I was first learning jQuery, what immediately excited me was the ease of creating cool animations. Anyone who has tried to animate in JavaScript using setInterval can appreciate the elegance of $.animate(). One plugin that really caught my eye was a paralax plugin. It handled image layers: stacking them on top of each other and shifted them horizontally and vertically to simulate depth in a scene. With the right scene the effect is pretty cool.

Art
I'd like to thank my friend  Nolan Tredway for letting me use some of his work for this demo. The graphics in this scene come from a piece that depicts the nursery rhyme 'Humpty Dumpty' and its roots in the story of King Richard III of England and the Battle of Bosworth Field.


HTML/CSS
The html and css is pretty simple. Each layer of the scene is a png image with transparency. A relatively positioned div wraps the list of layer images from back to front in the scene. Each <img />  tag has a rel value corresponding to the z value read in by the plugin.

JavaScript
Once the images a lined up on top of each other, a JavaScript plugin tracks the mouse cursor and adjusts the position of the layers accordingly.
To do the plugin must:
  • keep track of mouse position
  • keep track of window size
  • update layer position on interval

Keep track of mouse position and window size
With jQuery tracking the mouse position is trivial.
var methods = {
     init : function(options){
          ...
          //listen for mouse move
          $(document).mousemove(methods.onMouseMove);
          ...
     },
     onMouseMove : function(event){
               mouseX = event.clientX;
               mouseY = event.clientY;
     }
}
...
var mouseX, mouseY;
As is tracking the mouse position.
var methods = {
     init : function(options){
          ...
          //get and keep window size
          $window.resize(methods.onWindowResize).resize();
          ...
     },
     onWindowResize : function(event){onWindowResize : function(event){
               wHeight = $window.height();
               wWidth = $window.width();
      }
}
...
var wHeight, wWidth;

}

Updating layer position
The meat of the plugin is responsible for shifting each layer based on its z index and mouse position.
var methods = {

var settings = {
        magnitudeX: 100, //control baseline of x movement
        magnitudeY: 50,  //control baseline of y movement
        paralaxFactor : 10 //sets baseline zindex for paralax movement
    }, $frames, originalPosition; //list of frames and original position

var methods = {
      init : function(options) {          
         if(options){
              $.extend(settings, options);
         }
            
         settings = $.extend(settings, options);
          ...
          $t = $(this);
          $frames = $t.children(".frame");
          $frames.each(function(){
               //store paralax index as float
               $.data(this, "paralaxLayer", parseFloat($(this).attr('rel')));
          });
          originalPosition = $frames.position();
          ...
          //reposition on interval rather than mouse move
          setInterval(methods.calculatePosition, 100);
          ...
     },
     calculatePosition : function(){
          //calculate baseline offset for this mouse position
          var offsetX = (settings.magnitudeX)*(mouseX/wWidth);
                      - (settings.magnitudeX/2),
          offsetY = (settings.magnitudeY)*(mouseY/wHeight);
                      - (settings.magnitudeY/2),
          frame, paralaxLayer, paralaxFactorX, paralaxFactorY;
          
          //go through and reposition each frame
          for(i=0; i<$frames.length; i++){
              $frame = $($frames[i]);
              paralaxLayer = $.data($frames[i], "paralaxLayer")/
              settings.paralaxFactor;
              paralaxFactorX = -paralaxLayer*offsetX,
              paralaxFactorY = -paralaxLayer*offsetY;
                   
              //use animate for smooth transition
              $frame.clearQueue().animate({
                  top: originalPosition.top+paralaxFactorY,
                  left: originalPosition.left+paralaxFactorX,
              }, 300);
          }
     }
}

There were a couple of steps I took to increase the performance of the position calculation. In the init function the $.each() function is used to loop through frames for ease of implementation, but the calculatePosition function needs to run a bit faster so I used a for loop. Secondly, I decided to calculate the new position of the frames on an interval, rather than on mouse move. Mouse move occurs too often to perform any significant calculation and translation, so a interval and the jQuery animate plugin was utilized.


Possible Improvements
  • load layer data into object or array instead of using $.data plugin
  • integrate text shadow into plugin

Thursday, March 10, 2011

Applescript Adventures: How to visit 3,000 webpages in 10 minutes

My first experience with AppleScript has left me with some mixed feelings about the language. I was able to complete a menial task that I would not have wanted to do manually. However, I found the natural language structure to be an obstacle to performing progressively more advanced tasks. That said, would recommend learning AppleScript, it's pretty awesome.

Task
The impetus for learning ApplesSript was a simple but mind numbing assignment. I had pull down around images and data for almost 1000 items from a system while maintaining a relationship between the data and the image files. The system was behind password protection so wget alone wouldn't suffice. Also, to download the images the click event had to be triggered on the page (thank you ASP.NET). And the company who built and maintains the system was not being helpful.

Hello AppleScript!
I was left with no alternative but to visit every page and click all the links, LAME! Thankfully AppleScript is pretty powerful. Every Apple user has to try this out at least once. AppleScript can open applications and perform different operations, then pass the results to another application. Why do something when I can tell my computer to do it for me?

The script itself ended up being pretty simple:
  • grab jQuery
  • Open browser
  • grab list of links
  • visit each link
    • inject jQuery
    • get data from this link
    • download image
  • go to next link

Opening up applications is trivial with AppleScript. The tricky parts are pulling down the list of links and then visiting them in order.

Gimme jQuery
Both Chrome and Safari allow JavaScript to be executed through AppleScript. To make everything easier jQuery was injected on page load. First jQuery has to be loaded by the AppleScript.

set jqueryFile to ("/Path/to/jQuery/file/jquery.js")
open for access jqueryFile
set jqueryContents to (read jqueryFile)
close access jqueryFile

Grabbing links
The next step is to open up Safari, tell it to open into a new window, go to an address and inject jquery. Unfortunately, Safari doesn't provide an easy way to determine a page is loading. Setting a delay is a pretty quick if unreliable solution.
tell application "Safari"
 activate
 --make new document and wait for new page to load
 delay 1 
 tell front document to set URL to "http://sickawesome.com"
 delay 15
 
 set doc to front document
 tell doc  
  do JavaScript jqueryContents
  -- the do JavaScript command returns javascript arrays as a list
  set image_hrefs to (do JavaScript "var hrefs = []; $('#list of links').each(function(){hrefs.push($(this).attr('href'))}); hrefs;")
 end tell
end tell

The downside of scripting Safari is already apparent. The loading status of a current document isn't directly available to AppleScript. Some other method has to be found to delay until the document is ready to run JavaScript. However, Safari was used for this step, because of how well it handles JavaScript. Safari, unlike Chrome, returns the value of a JavaScript statement, so that AppleScript can use it later. When Safari returns a JavaScript array AppleScript handles it as a list, no conversion to do. Awesome.

Quick Visits Only
The next step is the longest of the whole process. Visiting each page in succession, pulling down info and then going to the next. Chrome was chosen for two reasons: Chome is fastest browser out there, and Chrome tabs provide access to the loading status of the page. This is important, because after a couple dozen pages the connection speed fell dramatically, rendering delays ineffective.

However, as I mentioned before, Chrome does not return values from executed JavaScript. So a little more creativity is required. Fortunately both JavaScript and AppleScript have access to the title of a tab. So as long as the value can be cast as a string (unsure about arrays), Chrome can still pull out the data.

tell application "Google Chrome"
 activate
 tell (make new window) to tell tab 1  
  -- repeat loop essentially like python for in loop
  repeat with href in image_hrefs
   execute JavaScript "window.location ='https://baseURL" & href & "'"
 
   my waitForReady()
   execute JavaScript jqueryContents   
   --get something
   execute JavaScript "document.title = $('#block').html()"
   delay 0.2
   set value to get title
               end repeat
      end tell

A delay was stuck in just to make sure the JavaScript has time to execute before the AppleScript assumes it is done. The wait for ready subroutine is the key to Chromes suitability for this task.

on waitForReady()
 delay 1
 tell application "Google Chrome"
  tell window 1 to tell tab 1
   repeat
    execute JavaScript "document.title = document.readyState"
    set status to get title
    if status is "complete" and loading is not true then
     return true
    else
     delay 0.1
    end if
    
   end repeat
  end tell
 end tell
end waitForReady

The above function executes ten times a second until the the browser and document are ready loaded. JavaScript can be run when the document.readyState is "interactive", but sometimes the content of the page isn't ready to pull.

Conclusion
Writing this script was fun. Once I discovered the dictionaries, it was much easier to start experimenting. The uses for this language are innumerable. However, it has to be used in the right situation, writing and debugging these scripts can be a little frustrating. It could easily take more time to write than the script ends up saving.

As a programmer, I didn't appreciate the natural language syntax for AppleScript. I found it a little verbose and somewhat confusing. That is, I found it difficult to look at AppleScript samples and figure out how I could manipulate the code for another situation.

Google Book Stuffs
Go here for free info:
AppleScript: Definitive Guide
AppleScript: The Comprehensive Guide to Scripting and Automation on Mac OS X

Sunday, February 13, 2011

What 9000!

If this isn't the wackiest piece of code I've ever written, then it is pretty close. There are three components to the page: a randomly generated grid, the changing letter sizes, and the "awesome song" in the background.

Random Grid

The toughest part was generating the random grid. The grid is made up of two types of components. The grid itself, and the blocks. Each the grids and blocks are have a visual and a data component. The visual components is handled by css,

Grid size

The first step is to determine the number of rows and columns that the grid will have. This is done by taking the ceiling of the quotient of the width or height of the container divided by the height or width of the cell. In this case the cells are 50x50 (40px with 10px of padding). I used the ceiling function rather than the floor because I wanted to fill the screen. Also the number of cells is recalculated on window resize.
$(window).resize(function(){
     hcells = Math.ceil($grid.width()/50); //collumns
     vcells = Math.ceil($grid.height()/50); //rows
}).resize();

Representing grid and shapes

Both the grid and the shapes are represented by 2-dimensional boolean matrices. The grid is initialized so that every cell contains a false value (meaning that grid position is not occupied).
for(i = 0; i < hcells; i++){
     grid.push([]);
     for(j = 0; j < vcells; j++){
          grid[i].push(false);                    
     }
}
Likewise, each of the block shapes has a shape array where the positions occupied by the shape are represented by a true value.
//default shape, single square block 
     oneSquare = {
        shape: [ [true] ],
        className: "oneSquare"
    };
    //three wide by one tall 
    threeXOne = {
        className: "threeXone",            
        shape: [ [true], [true], [true] ]
    };

Placing Blocks

After the grid is initialized, each position is looped through and a block shape is chosen at random. Then the shape array of the block is compared the the grid starting at the current position. The block is placed into the grid if all the positions that will be take up by this shape are unoccupied.
function placeBlock(i, j, shape){              
        //loop over space checking for occupation
        for(x = 0; x < shape.length; x++){
            for(y = 0; y < shape[x].length; y++){
                 if (grid[x + i][y + j] || x+i >= grid.length || y+j >= grid[x+i].length) {                             
                      return false;                              
                  }                                          
            }                                      
        }
        //go back and mark spots as taken up
        for (x = 0; x < shape.length; x++) {
            for (y = 0; y < shape[x].length; y++) {
                grid[x + i][y + j] = true;
            }
        }              
        return true;       
    }
If the function returns true then the block is placed absolutely in the grid using the cell size and i,j coordinates. The process of emptying the grid and filling it with random block is set to repeat 10 times a second. This makes the grid a pretty interesting browser benchmark.

Title

To spice up the title a little bit. I use the lettering.js a jQuery plugin to wrap each of the letters in there own div. Then on an interval the letters are looped through and a random size (within bounds) is chosen for each letter, and it is assigned one of 4 colors;
var colors = ["#0099FF", "#800080", "#0FF000", "#FF0000"];
     function logoDance(){
        $logo.children("span").each(function(){
            randy = Math.random();                 
            $(this).css({
                fontSize: Math.round(randy*150)+75,
                color: colors[Math.floor(4 * randy)]
            });
        });
    }

Audio

The music is played through an HTML audio tag.To get the OGG version of the file I used Firefogg.

Summary

That pretty much covers it. Some arrays operations to create and fill the grid, lettering.js to jazz up the title page, and some quick html audio for the background music.