Tuesday, January 28, 2014

Ruby Wrapper for Google Closure Compiler

I've been using Google's Closure Compiler to compress all the custom JavaScript I've been writing in my projects at work for some time now. Recently, I created a small wrapper script in ruby that will collect all the scripts in a folder and write them into one compressed application.min.js file.

With the script below, just set the #config params and point it at your JavaScript source folder. If you create a file called application.js in your source folder, it will be the first file that is written in the final file.

puts 'Google Closure Compiler';
puts 'Concatenate and minify all JavaScript in src directory';

# config 
closure  = 'C:/closure-compiler/compiler.jar'
src_dir  = '../assets/js/src'
src_dest = '../assets/js/application.min.js'


# load all files from src_dir
files_arr = Dir.glob(File.join(src_dir, "*.js"));

# apply sort order so application is first
files_arr.sort! do |file_a, file_b|
  
  if File.basename(file_a) == "application.js" then
    -1
  else
    0
  end
end

# compile all application src code
cmd = "java -jar #{closure} --js " + files_arr.join(" ") + " --js_output_file #{src_dest}"

puts "running: #{cmd}"

IO.popen(cmd);

Along with this script, here is a version for all the vendor scripts in your project. Point this at your 'vendor' or 'libs' folder and it will compress and create a *.min.js file for each script in that folder.

puts 'Google Closure Compiler';
puts 'Minifying all vendor src code';

# config 
closure  = 'C:/closure-compiler/compiler.jar'
src_dir  = '../assets/js/vendor'
src_dest = '../assets/js/vendor'

# compile all vendor src code
Dir.glob(File.join(src_dir, "*.js")).each do |f|
  
  unless f.include? '.min.js'
    
    name = File.basename f
    new_name = name.gsub(/.js/, '.min.js');
  
    cmd = "java -jar #{closure} --js #{src_dir}/#{name} --js_output_file #{src_dest}/#{new_name}"
 puts "running: #{cmd}"
    IO.popen(cmd);
    
  end
  
end;

Thursday, January 2, 2014

Using Flash SharedObject to Store User Info Similar to Cookies

Browser cookies are great, but can be limiting and easily lost (or cleared). Some browsers will auto clear cookies when they are closed based on user settings. Recently, while building a Facebook page tab application I ran into an issue where Safari wouldn't keep any cookie data because of iframe security. This ended up being painful and while banging my head on my desk I thought of something. Why not use at tiny flash swf to save cookie data? I know flash is not very popular but it's still installed on most people's browser even if they don't know it.

Here is the ActionScript 3 code to get your very own 10px by 10px flash swf to save cookie data with.
//create a file named FlashCookies.as as add the following code to it:
package
{
 import flash.display.Sprite;
 import flash.events.Event;
 import flash.events.TimerEvent;
 import flash.external.ExternalInterface;
 import flash.net.SharedObject;
 import flash.utils.Timer;
 
 [SWF(width = "10", height = 10, fps = 10)]
 
 public class FlashCookies extends Sprite 
 {
  
  private var cookieName:String;
  private var readyTimer:Timer;
  
  public function Main():void 
  {
   if (stage) init();
   else addEventListener(Event.ADDED_TO_STAGE, init);
  }
  
  private function init(e:Event = null):void 
  {
   removeEventListener(Event.ADDED_TO_STAGE, init);
   
   cookieName = loaderInfo.parameters['c'];
   
   
   if (ExternalInterface.available 
     && cookieName != null 
     && cookieName.length > 0)
   {
    try 
    { 
     // This calls the isContainerReady() method, which in turn calls 
     // the container to see if Flash Player has loaded and the container 
     // is ready to receive calls from the SWF. 
     var containerReady:Boolean = isContainerReady(); 
     if (containerReady) 
     { 
      // If the container is ready, register the SWF's functions. 
      setupCallbacks(); 
     } 
     else 
     { 
      // If the container is not ready, set up a Timer to call the 
      // container at 100ms intervals. Once the container responds that 
      // it's ready, the timer will be stopped. 
      readyTimer = new Timer(100); 
      readyTimer.addEventListener(TimerEvent.TIMER, timerHandler); 
      readyTimer.start(); 
     } 
    } 
    catch(e:Error) { }
    
   }
   else 
   { 
    trace("External interface is not available for this container."); 
   } 
   
  }
  
  private function setupCallbacks():void
  {
   //add callbacks
   ExternalInterface.addCallback('getCookie', getSOCookieData);
   ExternalInterface.addCallback('setCookie', setSOCookieData);
   ExternalInterface.addCallback('clearCookie', clearSOcookieData);
   
   //notify javascript that swf is ready
   ExternalInterface.call('flash_setSWFIsReady');
  }
  
  private function isContainerReady():Boolean 
  { 
   var result:Boolean = ExternalInterface.call("flash_isJSReady"); 
   return result; 
  }
    
  public function getSOCookieData(key:String):String
  {
   var so:SharedObject = SharedObject.getLocal( cookieName, "/" );
   if ( so.data.hasOwnProperty(key) )
    return so.data[key];
   return null;
  }
  
  public function setSOCookieData(key:String, value:String):void
  {
   var so:SharedObject = SharedObject.getLocal( cookieName, "/" );
   so.data[key] = value;
   so.flush();
  }
  
  public function clearSOcookieData():void
  {
   var so:SharedObject = SharedObject.getLocal( cookieName, "/" );
   so.clear();
  }
  
  //Event handlers
  private function timerHandler(event:TimerEvent):void 
  { 
   // Check if the container is now ready. 
   var isReady:Boolean = isContainerReady(); 
   if (isReady) 
   { 
    // If the container has become ready, we don't need to check anymore, 
    // so stop the timer. 
    readyTimer.stop(); 
    readyTimer.removeEventListener(TimerEvent.TIMER, timerHandler);
    readyTimer = null;
    
    // Set up the ActionScript methods that will be available to be 
    // called by the container. 
    setupCallbacks(); 
   } 
  }
 }
 
}

Now you will need to compile this it into a swf with either Adobe Flash or Flash Develop. Along with this generated swf, you'll also need a bit of JavaScript, which isn't much if you use swfobject.
//flash communication methods
var hasFlash = false;
var jsReady = false;
var swfReady = false;

window.flash_isJSReady = function()
{
    return jsReady;
};

window.flash_setSWFIsReady = function()
{
    swfReady = true;
};

function getSwf()
{
    if (navigator.appName.indexOf("Microsoft") !== -1) 
        return window["FlashCookies"]; 
    return document["FlashCookies"]; 
} 

$(document).on('ready', function()
{ 
    //embed flash on page
    var flashvars = {
        //add a custom name for this cookie
        c : 'my_so_cookie'
    };
    var params = {
        allowScriptAccess: "always",
        wmode: "transparent"
    };
    var attributes = {
        id:"FlashCookies"
    };
    swfobject.embedSWF("FlashCookies.swf", "altContent", 10, 10, "9.0.0", null, flashvars, params, attributes);

    // Record that JavaScript is ready to go. 
    jsReady = true; 
} 

And the html would just be a div with the id "altContent" (or whatever you want, just make sure to update the swfobject.embedSWF() call with the matching value.

Once all this is setup, all you need to do is call getSwf().setCookie("email","john@home.net"); and the flash cookie is saved and much harder to clear. The next time that user comes to your site, you can check the cookie getSwf().getCookie("email");.

Nice huh? Since I tried this, I've noticed this technique on a few major website like ebay and Amazon (I think). Also a lot of banner ads do this, which makes me a bit mad since I think they are one of the many reason Flash died so fast, but that's for another post.

How to Extend a Javascript Pseudo Class

I recently built an HTML 5 Canvas project using CreateJS which I ended up really enjoying since I spent a few years of my career building Adobe Flash apps. I remember thinking "Why doesn't Adobe take Flash/ActionScript 3 and write a version that works with HTML5 Canvas/Javascript?" we'll they did (sort of). In a future post, I hope to write more about that project, but this post is more on how to extend JavaScript classes since that's what you do with the CreateJS framework.

We all use jQuery and that's awesome, so you don't really need to worry about JavaScript pseudo classes, but once in a while you may find yourself writing plain JavaScript (or at least using a different framework than jQuery). Since JavaScript's biggest downfall is that there isn't a built in class structure we are left with pseudo classes.

This is all explained very well in David Shariff's blog that can be found here:
http://davidshariff.com/blog/javascript-inheritance-patterns/
So, I'm just going to show how to used the Pseudoclassical pattern like I did with CreateJS.

In CreateJS, you have a bunch of core JavaScript pseudo classes already available to you, and all you need to do is extend the correct ones for you app's needs. This functionality can be used without CreateJS since it's all basic javascript, for example:
(function() {
    var Car = function(color)
    {
        this._wheels = 4;
        this._condition = 'good';

        //this will serve as our 'super()' method
        this.initialize(color);
    }
    Car.prototype.initialize = function(color)
    {
        this._color = color;
        ...
    }
    Car.prototype.drive = function(speed)
    {
        ...
    }
    //setup a namespace if you want
    window.Car = Car;
}());

//create a car and drive
var myCar = new Car(#bada55);
myCar.drive(90);


Now we end up with the class 'Car' that we can re-use in our JavaScript application. Now, we have a 'Truck' object that is a lot like car so we should extend the 'Car' class to reduce code. How do we do this with Javascript since you can't just say:
function Truck() extends Car()
{
    //call car constructor
    super(color);

    //set variables for truck
    this._type = 'pickup';
} 

This is where we use the prototype attribute, but we can't just say Truck.prototype = Car.prototype because now any new methods on Truck will be passed back to Car. The trick is to pass a unique reference for a Car to a temp object and then add to it. So, now the 'Truck' class ends up looking like this:
(function() {
    var Truck = function(color)
    {
        this._type = 'pickup';

        this.initialize(color);
    }
    //here we are creating a Car and passing a unique reference 
    //to the var 'tmp'
    var tmp = Truck.prototype = new Car();

    //now we need to save a reference to the parent 
    //initialize method (like calling 'super()' in other languages)
    Truck.prototype.Car_initialize = tmp.initialize;
    
    //now create a initialize() method for truck that calls 
    //Cars initialize() method
    Truck.prototype.initialize = function(color) 
    {
        this.Car_initialize(color);
        ...
    };
    
    //add a new method only to the 'Truck' class
    Truck.prototype.tow = function(weight)
    {
        ...
    }
    //setup a namespace if you want
    window.Truck = Truck;
}());

I thought this was slick, and you can even move this into a method like so:
var classExtend = function(childClass, parentClass)
{
    var tmpObj = function() { };
    tmpObj.prototype = new parentClass();
    childClass.prototype = new tmpObj();
    childClass.prototype.constructor = childClass;
};

var Car = function()
{
    this._type = 'car';
    ...
}
var Truck = function()
{
    this._type = 'truck';
    ...
}
Truck.prototype.tow = function(weight) { }

//extend the car class
classExtend(Truck, Car);

Thursday, October 24, 2013

Ruby Photo Sorter

Today, I worked on a personal project in Ruby that would sort my ever expanding library of family photos into a common folder structure. I like the way iTunes handles this by sorting them into sub-folders as /year/month the photo was taken so that's what I went with.
#!/usr/bin/env ruby

# Summary:
# This utility will take each jpg, png, gif in the specified directory, including sub-directories, and 
# copy it to the specified output directory (optional) for the year and month the photo was taken/created. 
# (example:  ./unsorted/image.jpg -> ./sorted/2013/10/image.jpg)
# 
# Code Author: Jason Savage
#------------------------------------------

require 'exifr'
require 'fileutils'

def run()
  
  return if ARGV.length < 1
  
  # get dir argument
  in_dir  = ARGV[0]
  out_dir = ARGV[1] || in_dir
  
  # fix path if on windows
  in_dir = in_dir.gsub(%r{\\}) { "/" }
  out_dir = out_dir.gsub(%r{\\}) { "/" }
  
  puts 'in: ' + in_dir, 'out: ' + out_dir
  
  if Dir.exist? in_dir
    
    # loop through each *.jpg in the folder and move to dir/#{year}/
    Dir.glob( in_dir + '/**/*.{jpg,png,gif}') do |file|
      
      move_file(out_dir, file)
      
    end
    
  end
  
end;

def move_file(dir, file)
  
  date_time = EXIFR::JPEG.new(file).date_time || File.mtime( file )
  
  if date_time != nil && date_time.year != ''
    
    # get path as #{dir}/year/month
    out_dir = File.join(dir, date_time.year.to_s, zero_pad(date_time.month.to_s))
    
    # create #{dir}/year/month if it doesn't exist?
    FileUtils.mkdir_p out_dir unless Dir.exist?(out_dir)
    
    # check if image file name is already used
    i     = 1
    ext   = File.extname(file)
    fname = File.basename(file, ext)
    
    while File.exist? File.join(out_dir, fname + ext) 
      fname = File.basename(file, ext) + '_' + i.to_s
      i += 1
    end
    
    # move file to new directory
    FileUtils.cp( file, File.join(out_dir, fname + ext) )
    
  end
  
end


def zero_pad(str)
  
  if str.to_f < 10
    return '0' + str
  end
  return str
  
end



# run script
run if __FILE__ == 'photo_date_sort.rb'

Wednesday, October 9, 2013

SQL to find distance from latitude/longitude

This is a quick post, mostly so i never forget this. A guy I work with either wrote this or found it on the internet. If you have a database of addresses with lat/long pairs for each entry, this SQL statement will find the distance (I think in miles) from a given lat/long.

SELECT 
    ( 3959 * acos( cos( radians(origin_lat) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians(origin_long) ) + sin( radians(origin_lat) ) * sin( radians( latitude ) ) ) ) AS distance 
FROM
    zip_codes
HAVING
    distance <= _miles
ORDER BY
    zip;

Monday, September 30, 2013

YouTube Browser-based Uploading with OAuth

YouTube has changed over to OAuth for using their data API (API 3.0)
https://developers.google.com/youtube/v3/

Which is fine but it seems they haven't move all of their code over to the new API or haven't finished the docs for it yet, so this is sort of a missing manual for implementing the old API 2.0 Browser-based Uploading seen here:
https://developers.google.com/youtube/2.0/developers_guide_protocol_browser_based_uploading


Step 1- register your app with Google

If you are creating this for one of your brands you need to login/create a Google account for them. After you logged in, you'll need to register your application by following the instructions found here:
https://developers.google.com/youtube/registering_an_application

- when you create a client ID, set application type to web application
- redirect url can be this: http://localhost/oauth2callback, you only need it once

You will also need to get a developer key from here:
https://code.google.com/apis/youtube/dashboard/

The developer_key will not change so make sure you add this to your config file.


Step 2 - get a refresh_token

https://developers.google.com/youtube/v3/guides/authentication

Now that you have your application registered, you should have an oAuth Client ID and Client Secret. Go ahead and save them into your project as a config variable since they don't change, but you need them to get an access_token. You now need to get an authorization code from the API. This is a one time use token that can be exchanged for an access_token and refresh_token. They call it a refresh_token in the docs, but it's really a long term token that doesn't expire, which is what we want.

The code url will be this:
$url = 'https://accounts.google.com/o/oauth2/auth?';  
$url .= 'client_id='   . {{insert_your_client_id_here}}  
$url .= '&redirect_uri=' . urlencode('http://localhost/oauth2callback'); //this is whatever url you set in the "create client id dialog"  
$url .= '&scope='    . urlencode('https://gdata.youtube.com');  
$url .= '&response_type=code';  
$url .= '&access_type=offline';  
$url .= '&approval_prompt=force';  


After you call this url you will be taken to a page and asked if your application is allowed to access this Google account. Click accept and you will be redirected to the url you specified earlier with the get variable code=... added to the end of the url. This is your one time use token so save it into your text editor while you create the next request.

To make a request to get the refresh_token, which is another one time thing, you can use PHP cURL code below or the command line.

 //using PHP cUrl  
 $curl = curl_init( 'https://accounts.google.com/o/oauth2/token' );  
 $post_fields = array(  
   'code'     => '{{insert_the_code_you_just_got_here}}',  
   'client_id'   => '{{insert_your_client_id_here}}',  
   'client_secret' => '{{insert_your_client_secret_here}}',  
   'redirect_uri' => 'http://localhost/oauth2callback', //this doesn't do anything, but it's validated so i needs to match what you've been using  
   'grant_type'  => 'authorization_code'  
 );  

 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);  
 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);  
 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);  
 curl_setopt($curl, CURLOPT_POST, 1);  
 curl_setopt($curl, CURLOPT_HEADER, 0);  
 curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($post_fields));
  
 curl_setopt($curl, CURLOPT_HTTPHEADER, array(  
   'Content-Type: application/x-www-form-urlencoded'  
 )); 
 
 //send request  
 $response = curl_exec($curl); 
 
 print_r($response);  


If everything worked, you should get something like this back:

{  
  "access_token" : "ya29.AHES6ZTtm7SuokEB-RGtbBty9IIlNiP9-eNMMQKtXdMP3sfjL1Fc",  
  "token_type" : "Bearer",  
  "expires_in" : 3600,  
  "refresh_token" : "1/HKSmLFXzqP0leUihZp2xUt3-5wkU7Gmu2Os_eBnzw74"  
 }  

Bingo!, copy and pasted the refresh_token into your config file. Like I said before, it doesn't expire like the access_token will. You will use it to get a new access_token each time your form loads. Using a token to get a token seems strange but it's Google so I guess they know what they're doing.


Step 3 - post url and upload token

We are back to this guide:
https://developers.google.com/youtube/2.0/developers_guide_protocol_browser_based_uploading

You need to create a video object on the YouTube server to upload a video to. To do this, you'll need to get an upload_token and a post_url from the YouTube API.
http://gdata.youtube.com/action/GetUploadToken

To create a video object, a video title and description along with some keywords and a category are required before can you get the post_url so, in my case, I just created temporary ones and updated them later.

The category definitions can be found here:
http://gdata.youtube.com/schemas/2007/categories.cat

public function get_video_upload_info()  
{  
   //get youtube access token  
   $access_token = $this->get_access_token();  

   //create a video obj with temp info  
   $video_title  = 'Video Temp Title ' . rand(1, 9999);  
   $video_desc   = 'Temp Desc';  
   $video_keywords = 'facebook, contest';  

   //setup request body as xml  
   $xml_str = implode('', array(  
     '<?xml version="1.0"?>',  
     '<entry xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xmlns:yt="http://gdata.youtube.com/schemas/2007">',  
       '<media:group>',  
         '<media:title type="plain">' . $video_title . '</media:title>',  
         '<media:description type="plain">' . $video_desc . '</media:description>',  
         '<media:category scheme="http://gdata.youtube.com/schemas/2007/categories.cat">Animals</media:category>',  
         '<media:keywords>' . $video_keywords . '</media:keywords>',  
         //'<yt:private/>',  
         '<yt:accessControl action="list" permission="denied"/>', //causes the video to be unlisted  
       '</media:group>',  
     '</entry>'));
  
   //use curl to call youtube api  
   $ch = curl_init( 'http://gdata.youtube.com/action/GetUploadToken' );  
   curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);  
   curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);  
   curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);  
   curl_setopt($ch, CURLOPT_HEADER, 0);
  
   curl_setopt($ch, CURLOPT_HTTPHEADER, array(  
     'Authorization: Bearer ' . $access_token,  
     'GData-Version: 2',  
     'X-GData-Key: key=' . {{your_developer_key}}],  
     'Content-Type: application/atom+xml; charset=UTF-8'  
   ));
  
   curl_setopt($ch, CURLOPT_POSTFIELDS, $xml_str);  

   //send request  
   $xml_response = curl_exec($ch);  

   //close connection  
   curl_close($ch); 
  
   $result = simplexml_load_string( $xml_response );  
   if( $result->getName() === 'errors' )  
   {  
     return array('post_url' => 'broke', 'upload_token' => 'not_a_real_token');     
   }  
   return array('post_url' => (string) $result->url, 'upload_token' => (string) $result->token);  
 }  


In the code above, the method '''get_access_token();''' uses the refreash_token to make a cURL request to the YouTube API to get a valid access_token.

private function get_access_token()  
 {  
   $ch = curl_init( 'https://accounts.google.com/o/oauth2/token' );  
   $post_fields = array(  
     'client_id'   => {{insert_your_client_id_here}},  
     'client_secret' => {{insert_your_client_secret_here}},  
     'refresh_token' => {{insert_your_refresh_token_here}},  
     'grant_type'  => 'refresh_token'  
   );  

   curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);  
   curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);  
   curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);  
   curl_setopt($ch, CURLOPT_POST, 1);  
   curl_setopt($ch, CURLOPT_HEADER, 0);  
   curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_fields)); 
 
   curl_setopt($ch, CURLOPT_HTTPHEADER, array(  
     'Content-Type: application/x-www-form-urlencoded'  
   ));  

   //send request  
   $response = json_decode( curl_exec($ch) );  

   curl_close($ch);  

   return $response->access_token;  
 }  



Step 4 - upload the video to YouTube

Using the post_url we can now display the form to complete this whole process. The post_url you will use in your form will need to have the GET variable next=... added to it. This will be the url that the form will redirect to after the video has finished uploading.

The form needs to have the 2 fields 'token' and 'file', which the API is expecting:

<form method="post" action="{{post_url}}?next={{next_url}}" enctype="multipart/form-data">  
   <input name="token" type="hidden" value="{{upload_token}}"/>   
   <input type='file' id='file' name='file' accept="video/*" />  
 </form>   

When video is finished uploading, the form will redirect to the url you set for next=... along with some extra GET variables from YouTube. The variable status=... is always returned, so you can use that to check if it was successful, which the value would be 200.

If the upload was a success, you will also get the variable id=... which will have the video's new id ( http://www.youtube.com/watch?v={{id}} ).

If the status wasn't 200, then an error occured and you will get the variable code=... which will describe the error.



That should be it. Easy huh? after 3 days I finally got this working so I figured it needed documenting and like i said, it's probably going to change soon so who knows how long this will work.

Google also has some code libraries you can use:
https://developers.google.com/youtube/v3/code_samples/php

I looked into them, but was having trouble fitting the code into my project, so maybe if I started with all this in mind, I would've been able to use them.

Monday, June 10, 2013

The Selected() Plugin

Part of the code I write requires a sort of radio button or toggle button functionality. The core ability is to be able to set an item as selected (or checked, active, clicked, etc.). I ran into this a lot and found myself writing the same simple code over and over again until I created the selected plugin.

Now i can simply write this with jQuery:
$("a").selected(true);
var isSelected = $("a").selected();
Of course this led to being able to have a group of buttons with only one selected at a time:
$("a.tab").selectedGroup();
I have the plugin up on GitHub with a little more explanation and a demo:
https://github.com/jasonsavage2/jquery.selected

Let me know what you think.