PhantomJS – Screen capture of auth protected page (& keep sessions)

Recently I need to get a screen capture of pages that are auth protected. I realized PhantomJS has a file instance with which you can read/write cookies. Usually PhantomJS performs one page so that it seems to make it hard to create multiple steps process. Of course there’re examples:

http://code-epicenter.com/how-to-login-amazon-using-phantomjs-working-example/

But opening a new page is a different story. Eventually I figured out using the global PhantomJS state. And the following is result:

/**
 * The purpose of the source code is to get a screen capture of a page
 * of auth protected page by running PhantomJS command line.
 * It performs form authentition if the session is not alive.
 * If cookie file exists, it uses the session. 
 * NOTE: I haven't added any arguments for this action.
 */
var webpage = require('webpage');
var fs = require('fs');
var system = require('system');
var page, loginpage;

// Phantomjs global config
phantom.cookiesEnabled = true;
phantom.javascriptEnabled = true;

// Variables settings
var cookie = 'path/to/cookie.json'; // Cookie file location
var max_login = 3; // Maximum login attempt
var login_attempt = 0; // Login attempt count
var login_url = 'https://test.com/login'; // Login page URL
var logout_url = 'https://test.com/logout'; // Logout page URL
var page_url = 'https://test.com/protected'; // Page to capture

/**
 * Add cookies before opening the page
 */
function addCookieInfo() {
  Array.prototype.forEach.call(JSON.parse(fs.read(cookie)), function(param) {
    phantom.addCookie(param);
  });
}

/**
 * Run login page and try form authentication
 */
function runLogin() {
 if (loginPage === 'object') {
    loginPage.close();
  }
  if (loginAttempt < 2) {
    system.stderr.writeLine('Reached max login attempt count.');
    phantom.exit();
  }
  else {
    loginAttempt++;
    loginPage = webPage.create();
    loginPage.open(loginUrl, function(status) {
      if (status === "success") {
        system.stderr.writeLine('form started');
        loginPage.evaluate(function() {
          document.getElementById("name").value = "username";
          document.getElementById("pass").value = "password";
          document.getElementById("login-form").submit();
        });     
        loginPage.onLoadFinished = function(status) { 
          if (status === 'success') {
            if (!phantom.state) {
              phantom.state = 'no-session';
            }
            if (phantom.state === 'no-session') {
              fs.write(cookie, JSON.stringify(phantom.cookies), "w");
              phantom.state = 'run-state';
              setTimeout(runPage, 500);
            }
          }
        };
      }
    });
  }
}

/**
 * Run page to get screen capture
 */
function runPage() {
  if (page === 'object') {
    page.close();
  } 
  page = webPage.create();
  addCookieInfo();
  page.open(url, function(status) {
    if (status !== 'success') {
      system.stderr.writeLine('Unsuccessful loading of: ' + url + ' (status=' + status + ').');
      system.stderr.writeLine('Content: ' + page.content);
      if (page.content) {
        fs.write(outfile, "error", 'w');
      }
      phantom.exit();
    }
    else {
      if (phantom.state === 'run-state') {
        window.setTimeout(function() {
          if (thumbnailFile) {
            page.render(thumbnailFile);
          }
          if (page.content) {
            fs.write(outfile, page.content, 'w');
          }
          page.render("page_service.png");
          phantom.exit();
        }, timeout);
      }

    }   
  });

  page.onResourceReceived = function(response) {  
    if (response.stage == 'end'){
      return;
    }
    if (response.url == url) {
      if (response.status == 403) {
        phantom.state = 'no-session';
      }
      else {
        phantom.state = 'run-state';
        response.headers.forEach(function(header){
          system.stdout.writeLine('HEADER:' + header.name + '=' + header.value);
        });
        system.stdout.writeLine('STATUS:' + response.status);
        system.stdout.writeLine('STATUSTEXT:' + response.statusText);
        system.stdout.writeLine('CONTENTTYPE:' + response.contentType);
      }
    }
  };
  /**
   * onLoadFinished callback
   * Check the status of login page and set state
   * If state is no-session and page is success, write cookies.
   */
  page.onLoadFinished = function(status) {
    if (status === 'success') {
      if (phantom.state == 'no-session') {
        removeCookieFile();
        setTimeout(runLogin, 500);
      }
    }
  };
}

// Main
phantom.state = 'no-state';
if (!fs.isFile(cookie)) {
  runLogin();
}
else {
  runPage();
}

In order to run code, make the following steps:

  1. Download and save phantomjs
  2. Copy the source code, and save it to a folder
  3. On command line, navigate to the file and run the following:
 $ /path/to/bin/phantomjs /path/to/sessions.js

jQuery::flexigrid::How to get selected row data

Unlike Ext JS, Flexigrid doesn’t use a data model (I think it is not a bad idea if they introduce this). Then, it would be easier to manipulate records in the rows.

Here’s a tip how you get the row of data.

$.fn.getSelectedValues = function() {
    var arr_val = [];
    var sel = $(".flexigrid .trSelected td");
    if (sel.length &gt; 0) {
        for(var i=0; i&lt;sel.length; i++){
            arr_val.push($(sel[i]).find('div').text());
        }
    }
    return arr_val;
}

Google Maps::Add an event on KML layer infoWin(balloon)

Adding an event on a KML infoWin is kind of nuisance since Google Maps doesn’t have any tangible event model provided (yet).
Within KML, define a tag with an attribute whence you can trigger a click event.

<Style id="sample_id">
    <IconStyle id="homeIcon">
        <Icon><href>http://www.test.com/images/test_icon.png</href></Icon&gt;
    </IconStyle>
    <BalloonStyle id="MarkerBalloon">
        <bgColor></bgColor>
        <text>
        <![CDATA[
            <b><font color="#CC0000" size="+1"><span name="$[name]">$[name]</span></font></b><br/>Put your text here.<br/>
        ]]>
        </text>
    </BalloonStyle>
</Style>

Google maps server will removes all the tags for security reasons. But there’re a few tags allowed. I named it as “name” but you poke around the find the best suitable name for you.

Google Maps:: So easy, you can do it within a second

Let’s get down on it!

First off, the basic setup:

<script type="text/JavaScript" src="http://maps.google.com/maps/apis/js?sensor=false" />

By doing this you added the reference to Google Maps API in your webpage. Only thing you remember when referencing to Google Maps are setting the sensor parameter.
sensor = true/false – this will tell Google Maps API whether you are using GPS sensor or not. Now, let’s create a canvas.

<div id="map" />

Then, use style to adjust the size of the canvas.

#map {
    height: 400px;
    width: 450px;
}

Then put them into the jQuery document.ready event to let your browser grab the map object.

$(document).ready(function() {
    var tLatlng = new google.maps.LatLng(32.842671, 6.503906);
    var myOptions = {
        center:latlng,
        zoom:10,
        mapTypeId: google.maps.MapTypeId.ROADMAP,
        disableAutoPan: false,
        navigationControl: true,
        navigationControlOptions: { style:google.maps.NavigationControlStyle.SMALL },
        mapTypeControl: true,
        mapTypeControlOptions: { style:google.maps.MapTypeControlStyle.DROPDOWN_MENU }
    };
    var map = new google.maps.Map(document.getElementById("map"), myOptions );
    var marker= new google.maps.Marker({
        position: new google.maps.LatLng(9.931544168615512,76.27632894178791),
        title: "My Marker",
        clickable: true,
        map: map
    });
    var infoWindow = new google.maps.InfoWindow({
        content: "I am here!"
    });

    // now we attach events 
    google.maps.event.addListener(marker, "mouseover", function(){
        infoWindow.open(map,marker);
    });
    google.maps.event.addListener(marker, "mouseout", function(){
        infoWindow.close(map,marker);
    });
});

That’s about it. It is true that most people think Google Maps is so easy. But is it? Let me give you an example. How would you create a certain line like the one in this example?

http://code.google.com/apis/maps/documentation/javascript/examples/layer-kml.html

jQuery::Send data to a dialog

A simple example regarding how to send data to a new elements. This is useful if you don’t want to use default jquery ui buttons on dialog

$('#open_dialog').click(function() {
    var jsonText = '';
    // create json string
    var obj = new Object();
    obj.mode  = "Update";
    obj.id    = id;
    obj.title = title;
    obj.toJSON = function(key) {
        var replacement = new Object();
        for (var val in this) {
            replacement[val] = this[val];
        }
        return replacement;
    };
    jsonText = JSON.stringify(obj);
    // pass it through data to dialog
    $('#my_dialog').data('mydata', jsonText).dialog('open');
});

// if dialog button clicked
$('#my_dialog_update').click(function() {
    var jsonText = $('#my_dialog').data('mydata');
    var arr = jQuery.parseJSON(jsonText);
    if (arr.id !== '') {
        if (arr.mode == "Update") {
            // send id and title and do update work
        }
    }
});