Fusioncube

The online journey of a technophile, by Steve Brownlee

The new CSS3 specs (part of what used to be the HTML5 spec) allows you to do some amazing things to regular, ol’ HTML elements in your applications. In this short article, I’ll give a quick rundown of some of the more popular enhancements step-by-step.

Starting Resources

All you need to play around with CSS3 goodies is a browser that supports them. Since I don’t want to be constantly updating this article, I’ll provide a link to a site that has a nice graph showing browser support. My recommendation as of 04/25/2011 is Google Chrome. It has the broadest support so far.

Ok, so you’ve got that downloaded and installed, right? Cool. Now, create two files accessible to your web server named CSS3.html and CSS3.css. Open CSS3.html and insert the following markup.

<html>
   <head>
      <title>CSS3 Basics</title>
      <link href="css3.css" rel="stylesheet" type="text/css">
   </head>

   <body>
      <div id="toolbar"></div>
      <div id="notification">Show Notification</div>
   </body>
</html>

Ok, let’s start having some fun with it. Open up CSS3.css and add in the following styles to give our elements some basic definitions that we’ll enhance later.

#toolbar {
    width: 85%;
    margin: 0 0 50px 50px;
    border: 2px solid #ccc;
    background: #efefef;
    height: 50px;
}

#notification {
    position: relative;
    top: -90px;
    left: 75px;
    border: 1px solid #000;
    width: 150px;
    background-color: #cecece;
}

So I’ve give each element a border and moved the notification div inside the toolbar div with relative positioning. Basic stuff.

Show Notification

Rounded Borders

Now let’s use the new border-radius property which can give your divs a rounded border to give them a button-like appearance. For now, at least, you have to provide the -moz, -webkit, and -o prefixes to ensure that it works correctly in Mozilla, Webkit and Opera browsers.

#toolbar {
    width: 85%;
    margin: 0 0 50px 50px;
    border: 2px solid #ccc;
    background: #efefef;
    height: 50px;
}

#notification {
    position: relative;
    top: -90px;
    left: 75px;
    border: 1px solid #000;
    width: 150px;
    background: #cecece;
    border-radius: 5px;
    -moz-border-radius: 5px;
    -webkit-border-radius: 5px;
    -o-border-radius: 5px;
}
Show Notification

Transitions

Now let’s see some transitions at work. Transitions allow you specify how a transformation will take place. For example, you can change the background color on an element when the user hovers her mouse over it. Until CSS3, this transition was instantaneous. Now you have options. Let’s see an example.

I’m going to change the width of the my notification element and its background color on hover. In addition, I’ll specify, using the transition property, how long it will take to perform the transition.

#toolbar {
    width: 85%;
    margin: 0 0 50px 50px;
    border: 2px solid #ccc;
    background: #efefef;
    height: 50px;
}

#notification {
    position: relative;
    top: -90px;
    left: 75px;
    border: 1px solid #000;
    width: 150px;
    background: #cecece;
    border-radius: 5px;

    -moz-border-radius: 5px;
    -webkit-border-radius: 5px;
    -o-border-radius: 5px;

    -webkit-transition:
       background-color 0.5s ease-in-out,
       width 0.5s ease-in;
    -moz-transition:
       background-color 0.5s ease-in-out,
       width 0.5s ease-in;
    -o-transition:
       background-color 0.5s ease-in-out,
       width 0.5s ease-in;
}

#notification:hover {
    width: 175px;
    background-color: #b8860b;
}
Show Notification

Drop Shadows

You can add drop shadows to any element using the box-shadow property. I’ll add one to both the toolbar and the notification button.

#toolbar {
   -webkit-box-shadow: 5px 5px 7px #333;
   -moz-box-shadow: 5px 5px 7px #333;
   -o-box-shadow: 5px 5px 7px #333;
}

#notification {
   -webkit-box-shadow: 2px 2px 3px #333;
   -moz-box-shadow: 2px 2px 3px #333;
   -o-box-shadow: 2px 2px 3px #333;
}
Show Notification

Transformations

There are now many kinds of ways to transform the appearance of an element. These properties are skew, rotate, scale, translate, and matrix. I’m going to skew the toolbar by 40 degrees horizontally, and skew the button by 20 degrees horizontally. Then I’ll specify that the skew on the button should happen on hover and take 1 second to complete.

#toolbar {
   -webkit-transform: skew(-40deg,0deg);
   -moz-transform: skew(-40deg,0deg);
   -o-transform: skew(-40deg,0deg);
}

#notification {
    -webkit-transition:
       background-color 0.5s ease-in-out,
       width 0.5s ease-in,
       -webkit-transform 0.5s ease-in;

    -moz-transition:
       background-color 0.5s ease-in-out,
       width 0.5s ease-in,
       -webkit-transform 0.5s ease-in;

    -o-transition:
       background-color 0.5s ease-in-out,
       width 0.5s ease-in,
       -webkit-transform 0.5s ease-in;
}

#notification:hover {
   -webkit-transform: skew(-20deg,0deg);
   -moz-transform: skew(-20deg,0deg);
   -o-transform: skew(-20deg,0deg);
}
Show Notification

Animations

This section will only work right now if you are viewing this via a webkit browser (e.g. Chrome). You can specify animation “classes” that will tell the browser to convert the state of an element from X to Y. For example, you could spin the element in a circle by setting the rotate property. Let’s see it in action.

I’m going to create a keyframes class named spin that will tell the browser to rotate the element from 0 degrees to 360 degrees when the user hovers over the button. I also specify that it will take 5 seconds to complete the spin.

@-webkit-keyframes spin {
from {
    -webkit-transform: rotate(0deg);
}
to {
    -webkit-transform: rotate(360deg);
    }
}

#notification:hover {
    -webkit-animation-name: spin;
    -webkit-animation-timing-function: linear;
    -webkit-animation-duration: 5s;
}
Show Notification
Published on Monday, Apr 25,2011 | 1 Comment |

The Past

My wife and I decided to move to Tennessee just over four years ago when I was stuck in a soul-sucking job and looking to make a change, and were in the midst of adopting Sabrina. When opportunities in Pittsburgh weren’t abundant, we talked long and hard about moving to another city where technology was more prevalent, and better for my career.

After several long-distance drives to cities such as Raleigh, Charlotte and Nashville, I was presented an opportunity to work for DaVita and we made the move. Albeit in untraditional fashion. My wife moved to Guatemala one month before the move to be Sabrina’s foster mother, then I moved to an apartment in Nashville with the bare essentials, then a little later, we packed up the whole house and everyone was reunited.

Over the next four years, we moved two more times, established some good friendships and had our second daughter, Tessa. We were homesick, and I continued to look for opportunities in Pittsburgh that could bring us back home. Then, six months ago, we walked into a house that was perfect for us.

My wife and I discussed for a week about what the next step would be. Continue to rent and look for something back home, or decide that Tennessee would be our new home and raise the kids here? Well, we decided to stay. I had a good job, we had friends, it was a great community – so I took a stab at buying the dream house and we got it.

The Present

Then life had to throw us another screwball. A company in Pittsburgh contacted me about working in a new facility they had just opened up, and after the initial phone conversation, I felt I had to present the information to my wife. She had given me until the kids started first grade to have us where we were going to be, so I was just getting in under the wire.

After several days of agonizing discussions, we decided that moving back home to be close to family, in a culture that we felt comfortable raising our children, and for a job with much more potential than my current one were enough pros to give up everything and move back home.

The Future

The future looks bright. Weekends with the family, cookouts, and holidays that don’t require 20 hours of driving. Our kids actually get to know their aunts, uncles, cousins, and grandparents instead of only seeing them 3-4 times a year. We get solace in the fact that the kids will be raised in an environment that instills the values we want them to have; hard work, humility, selflessness, community.

I also get to start working for a great company working with some of the best cutting edge technologies available today for the mobile realm. I get to start on the ground floor and be a part of the team that defines the direction of the technology and processes for the company. It will be like working for startup, without the constant fear in the back of the mind of every startup employee that you’ll be out of a job in a week because there’s no money.

I get to watch every game for the Steelers and the Penguins. Trust me, going without for four years made me appreciate it, and I will watch, or go to, every single Penguins game.

Pittsburgh, here we come.

Published on Tuesday, Apr 19,2011 | 2 Comments |

Related Article(s): AjaxCFC ported to ExtJS

My integration of ExtJS, ajaxCFC and ColdFusion continues.

Note: This is for version 3.3.1. I’ve also been happily playing around with version 4, but until it’s officially released, I must continue building with 3.3.1.

Anyway, here’s what I was trying to solve. When I want to allow users to serach for items from a ComboBox, I configure a store, and inside the store, I configure an Ext.data.HttpProxy to hit a separate ColdFusion page that performs a query and returns the results.

Standard HttpProxy Method

var WidgetStore = new Ext.data.Store({
    proxy: new Ext.data.HttpProxy({
        url: 'liveQueries/widgets.cfm'
    }),
    reader: new Ext.data.JsonReader({
        root: 'data',
        totalProperty:'totalRecords'
    }, Widget)
});

ColdFusion ‘Live’ Query

<cfsetting enablecfoutputonly="true">

<cfparam name="query" default="">

<cfquery name="chargeCodes" datasource="#getDatasource().getName()#">
SELECT UNIQUE widget_seq_no, identifier, descr
FROM 	widgets
WHERE 	(REGEXP_LIKE(identifier,<cfqueryparam cfsqltype="cf_sql_varchar" value="^#query#">,'i')
   OR REGEXP_LIKE(descr,<cfqueryparam cfsqltype="cf_sql_varchar" value="^#query#">,'i'))
   AND widget_type_cd='SNAPPYDS'
ORDER BY identifier asc
</cfquery>

<cfscript>
jsonBean = createobject("component","ajaxCFC.JSON");
jsonEncodedCriteria = jsonBean.encode(data=chargeCodes, queryFormat="array");
writeOutput(jsonEncodedCriteria);
</coldfusion>

<cfsetting enablecfoutputonly="false">

I hate this, because I already am using my Ext.AjaxCFC class to query my ColdSpring beans asynchronously, so why do I have to set up these standalone ColdFusion pages to performs queries that should be executed in the component?

I decided to take matters into my own hands, and I have to give the ExtJS team props again, because it was easy to accomplish by extending the Ext.data.MemoryProxy class. As you can see, all I need to do is override the doRequest() method to execute Ext.AjaxCFC.request() with the data passed in during initialization.

Ext.ux.data.AjaxCFCProxy.js

Ext.ns('Ext.ux.data');

Ext.ux.data.AjaxCFCProxy = Ext.extend(Ext.data.MemoryProxy, {
   constructor : function(data){
      Ext.ux.data.AjaxCFCProxy.superclass.constructor.call(this);
      this.data = data;
   },
   doRequest : function(action, rs, params, reader, callback, scope, arg){
      this.data.data.query = params.query;
      Ext.AjaxCFC.request({
         bean : this.data.bean,
         method : this.data.method,
         data : this.data.data,
         success: function(rs) {
            var result = reader.readRecords(rs);
            callback.call(scope, result, arg, true);
         },
         error: function(results) {
            Ext.MessageBox.alert('Load Failed', 'Unable to load requested data');
         }
      });
   }
});

New and Improved Combobox for Searching

WidgetSearch = new Ext.form.ComboBox({
    minChars:           2,
    loadingText:        '',
    itemSelector:       'div.search-item',
    triggerClass:       'x-form-search-trigger',
    emptyText:          '',
    width:              300,
    listWidth:          0,
    store: new Ext.data.Store({
        // I've extended the base Ext.data.MemoryProxy()
        // class to use the AjaxCFC object for searching
        // instead of having to use a liveQuery. Much cleaner.
        proxy: new Ext.ux.data.AjaxCFCProxy({
            bean    : 'Widget',
            method  : 'search',
            data    : { source_table : 'WIDGET' }
        }),
Published on Friday, Apr 15,2011 | 0 Comments |

I recently read an article by a fellow named Brandon Savage entitled, “Why Recruiters Are Bad For Your Career“. While much of the experiences and opinions he shared in his article are common in the IT industry, there is also a flip side to the experience. His article got my creative juices flowing, so here is my counterpoint article.

I’ve been in the industry for over 17 years, have been on countless interviews, and have met hundreds of recruiters – both good and bad – and my first wife’s mother was, nationally, a top-notch executive coach, and I got some good advice from her.

Own Your Career

First, and foremost, I want you to know that you need to own your career path. Sitting passively by while waiting for people to call you, email you, find you, and hire you is a surefire way to end up in a job you don’t love. As Brandon pointed out, recruiters are in it for the money – even the really good ones. They may love what they do, or hate what they do, be really bad at it, or be really good at it; however, in the end, they are in it to make as much money as possible and be successful, just like the rest of us.

Given that, you need to take control. You are your #1 salesman, not another person trying to represent you. They can’t. They can do their best, but the only person who can adequately express what your true talents are, and what your passions are, is you.

You need to find companies for which you want to work. Browse Indeed.com on a regular basis, get involved in local user groups – even if you don’t work in the technology – because knowing, and talking to, the IT staff of the companies in your area is the only real way to learn who the best companies are.

Next, you need to find the right recruiter, and this is the trickiest part. Because, as a team, you really can find the job that’s going to make you happy. Just remember that it is a team effort, and you are the captain of the team. To make a team work at its best, each has to be working towards the end goal, and follow the instructions of the team captain.

How do you find the right guy or gal, though, to be on your team?

Learn To Say No

When a recruiter that you do not know sends you an email about a job, my advice is to immediately send her a polite email saying that if she is truly interested in helping you find the right job, then “please call me during {your free hours} at {your phone number}”. In essence, you’re telling them that you are in charge of the process and that you expect them to provide great service.

Right off the bat, this will eliminate a vast majority of the carrion vultures in the industry. The volume recruiters simply don’t want to make the time to have initial phone calls with candidates. It’s not in their best interest. Of the remaining minority of recruiters who take their job seriously, they will make the time to call you.

From this point, common sense kicks in. If, during a phone call in which you’ve stated what you’re looking for, the recruiter comes back with a laundry list of unrelated or uninspiring opportunities, you will know that she is not a good person for your team. You can then easily say that you are not interested and end the relationship right there (after the obligatory statement from the recruiter, “Well, if you can do my job for me.”, I mean, “If you know of anyone interested in this, please let me know.”)

Again, don’t be passive in the process and don’t accept anything less than exceptional service.

You Are Not A Mail Order Bride

I see this mentality a lot in the IT field, and it ends up getting people working at jobs they are under qualified for, not a good match for, or simply unhappy with.

This goes back to being the captain of your team, even if you are the only person on the team. When you go on interviews, do not be in a defensive or passive state of mind. You have to go into the process with yourself in charge. You are interviewing the company, even more than they are interviewing you.

They are simply trying to find another piece of their giant puzzle, and seeing if you with fit in the empty space. However, you are deciding where you are going to be investing the vast majority of your talent, skills and time for several years. This is a much bigger deal for you than it is for the company you are interviewing.

Believe me, corporations are not doing you a favor by hiring you. They are paying you money in compensation for your efforts in making their business better. Period. If, for any reason, you no longer provide the value for which you are being compensated, your employment will abruptly end.

The recruiter you work with can help streamline this process. Great recruiters know the good companies; and they know the bad ones. However, it is also their job to present you with every opportunity that they currently have in house that is a potential fit.

If she is truly interested in putting you in a company where you will be happy, then you need to communicate all of your feelings, experiences and interactions with the candidate company. Remember, even the good recruiters are looking to close the deal all the time, so it’s up to you to make sure that you are in charge.

Walking the Line Between Friend and Commodity

Lastly, it helps immensely if you like the recruiters that you work with. Just like how you pick friends, you need to enjoy your time together and have mutual respect for each other.

If you feel ignored when speaking with a recruiter, walk out the door and keep looking. If the person makes you uncomfortable, then it’s a sign that something it wrong with the relationship, even if you can’t verbalize it. Time to move on.

For anyone who has used a recruiter to find a new job, you know it’s not an easy process. If you found it to be easy, then you are one of the lucky ones, or simply didn’t put in the effort needed to find a good teammate.

It takes time, dedication, and several attempts before finding someone who truly cares about matching you up with the right job. It pays off.

Published on Wednesday, Mar 30,2011 | 4 Comments |

First off, thanks to everyone that I used as a resource to get up and running. Most notably, Devon Govett’s article, Learning Server-Side JavaScript with Node.js. Most of the code I have for this article is based on his tutorial code. In fact, I strongly suggest you read his article.

If you are new to Node.js and are running on Windows, as I am, you will need to read the Building node.js on Cygwin (Windows) wiki page first. Also, you will want to bookmark the Node.js Manual & Documentation page. This was crucial to me getting an understand of the syntax and capabilities of the library.

I’ve included a link to the entire source code at the end of the article, and it has been verified by JSHint for the node.js environment option.

First off, what is node.js? I’ll let our friend, stackoverflow, answer that. If you want the short answer, it is server-size JavaScript. It allows you to define a lightweight HTTP server (among many other things, but that’s what I focus on in this article) and processing logic using the JavaScript language. The obvious benefit being a single development platform.

With the glut of high-quality, powerful, professional JavaScript frameworks and libraries available today, application developers can now build top-notch software using JavaScript in the browser. Node.js allows you to take the language and use it on the server side.

Creating an HTTP Server

As others have done, I’ll start with the code for starting up your HTTP server which can respond to requests from a client. Create a file called TwitterSearch.js in your node directory, paste in the following code, and start it up by typing node TwitterSearch.js

/*
 * Include all Node modules needed for this example. Not all
 * of these are needed now, but will be later.
 */
var http = require("http"),
	url=require("url"),
	path=require("path"),
	fs=require("fs"),
	events=require("events"),
	sys = require("sys");

/**
 * Create an HTTP server listening on port 8081
 *
 * @param {Object} request
 * @param {Object} response
 */
http.createServer(function (request, response) {
   response.writeHead(200, { "Content-Type" : "text/plain" });
   response.write("Open the pod bay doors, Hal.");
   response.end();
}).listen(8081);

Is it really that simple? It sure is. Open your browser and hit 127.0.0.1:8081 and you’ll see your message. It accepts a request, and passes it to the closure function which itself accepts the request object, and a corresponding response object, as arguments. You simply write a 200 header and some content into the response object, and then end the response.

Serving Static Content

Of course, one of the main purposes of an HTTP server is to serve static content contained in HTML pages on your system. Let’s see how that’s done. Node.js provides the url.parse() method to allow you to extract all the different parts from the full URL. For this, you just want the path to the requested document, which is the pathname property.

Then, to get the path of the file system, you use the process.cwd() method, and join the two together to get the final location where to look for the document.

Next, you determine if the requested file exists on the file system. The path.exists(path, [callback]) returns a boolean and passes that value to the callback function – which you can, of course, define anonymously.

The rest of the code simply handles errors and returning the document, if found.

http.createServer(function (request, response) {
   // Parse the entire URI to get just the pathname
   var uri = url.parse(request.url).pathname,query;

   // Use process.cwd() to get the file system's current
   // working directory, and join it with the path to the
   // requested document
   var filename = path.join(process.cwd(), uri);

   // If path.exists function takes a string parameter - which is a path to
   // the document being requested - and a function which gets passed a boolean
   // argument which is true if a file at the path exists, and false if it doesn't
   path.exists(filename, function(exists) {

      // File not found. Return a 404 error.
      if (!exists) {
         response.writeHead(404, {"Content-Type": "text/plain"});
         response.write("Four Oh Four! Wherefour art thou?");
         response.end();
         return;
      }

      // File does exist. Execute the FileSystem.readFile() method
      // with a closure that returns a 500 error if the file could not
      // be read properly.
      fs.readFile(filename, "binary", function(err, file) {

         // File could not be read, return a 500 error.
         if (err) {
            response.writeHead(500, {"Content-Type": "text/plain"});
            response.write(err+"\n");
            response.end();
            return;
         }

         // File was found, and successfully read from the file system.
         // Return a 200 header and the file as binary data.
         response.writeHead(200);
         response.write(file, "binary");

         // End the response.
         response.end();
      });
   });
});

Requesting a Twitter Search Feed

Now that we’re able to serve static content, let’s create a simple HTML file that requests a Twitter feed based on a specific search term. Create a file in your working directory named index.htm. If you now request http://127.0.0.1:8081/index.htm a blank page will now appear in your browser. Let’s put the required HTML in there now.

We will use the jQuery.getJSON() method (which is just shorthand for the .ajax() method) to request the /twitter directory from our server, and pass along one argument which is our search term. In this example, I made it “javascript”, but you can place anything there that you like. I set an interval to request tweets every five seconds, but again, you can set that value to anything you like.

<html>
	<head>
		<title>Node.js Twitter Search</title>

		<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script>

		<style>
			#tweets li {
			    list-style: none;
			    font-family: 'Arial, Helvetica, sans-serif';
			    border : 1px solid black;
			    background: yellow;
			    padding: 5px;
			    margin: 2px;
			}
		</style>
	</head>

	<body>
	Here is your Twitter Search Feed...

        <ul id="tweets"></ul>
        <script type="text/javascript">
        var tweet_list = $("#tweets");  

        function load_tweets() {
            $.getJSON("/twitter?javascript", function(tweets) {
                $.each(tweets.results, function() {
                    $("<li>").html(this.text).prependTo(tweet_list);
                });
            });
        }  

        // Request tweets every five seconds
        setInterval(load_tweets, 5000);
        </script>
	</body>
</html>

Searching Twitter with node.js

Let’s start off with creating an object that will hold all pertinent parameters and objects we’ll be using to search Twitter.

var Twitter = (function(){
   var eventEmitter = new events.EventEmitter();

   return {
      EventEmitter : eventEmitter,  // The event broadcaster
      latestTweet : 0               // The ID of the latest searched tweet
   };
})();

Now let’s create a function for hitting the Twitter Search API and parsing the results for return to the client. I’m going to break this code up into chunks to follow the logic tree.

First thing is to create the get_tweets function with a single parameter – the search term. The first action in this function is to send an HTTP GET request to search.twitter.com. The q parameter is the search term, the rpp parameter is results per request, and the since_id parameter will only search Twitter for tweets with an ID higher than the one provided. I do this because Twitter Search is streaming.

/**
 * Pings the Twitter Search API with the specified query term
 *
 * @param {Object} query
 */
function get_tweets(query) {

	// Send a search request to Twitter
	var request = http.request({
		host: "search.twitter.com",
		port: 80,
		method: "GET",
		path: "/search.json?since_id=" + Twitter.latestTweet + "result_type=recent&rpp=5&q=" + query
	})

Next, add a listener to the response() event on the Twitter Search request.

	/*
	 * When an http request responds, it broadcasts the response() event,
	 * so let's listen to it here. Now, this is just a simple 'Hey, I got
	 * a response' event, it doesn't contain the data of the response.
	 */
	.on("response", function(response){
		var body = "";

The response object itself broadcasts the data() event as chunks of data are received from Twitter. Listen to that event and build the JSON structure as it comes in. Try to parse it after each chunk is received. Once there is a valid structure, we broadcast the tweets() event on our custom EventEmitter.

		/*
		 * Now as the the response starts to get chunks of data streaming in
		 * it will broadcast the data() event, which we will listen to. When
		 * we receive data, append it to a body variable.
		 */
		response.on("data", function(data){
			body += data;

			try {
				/*
				 * Since the Twitter Search API is streaming, we can't listen to
				 * the end() method, so I've got some logic where we try to parse
				 * the data we have so far. If it can't be parsed, then the
				 * response isn't complete yet.
				 */
				var tweets = JSON.parse(body);

				/*
				 * The data was successfully parsed, so we can safely assume we
				 * have a valid structure.
				 */
				if (tweets.results.length > 0) {
					/*
					 * We actually got some tweets, so set the Twitter.latestTweet
					 * value to the ID of the latest one in the collection.
					 */
					Twitter.latestTweet = tweets.max_id_str;

					/*
					 * Remember, node.js is an event based framework, so in order
					 * to get the tweets back to the client, we need to broadcast
					 * a custom event named 'tweets'. There's a function listening
					 * for this event in the createServer() function (see below).
					 */
					Twitter.EventEmitter.emit("tweets", tweets);
				}

After the tweets() event is broadcast and all listeners have captured the JSON structure, clear all listeners in case there is a reference hanging around from a previous search.

				/*
				 * I'm clearing all objects listening for the 'tweets' event here to
				 * clean up any listeners created on previous requests that did not
				 * find any tweets.
				 */
				Twitter.EventEmitter.removeAllListeners("tweets");
			}

If a the request data cannot be parsed, then we are still waiting for more chunks to come in.

			catch (ex) {
				/*
				 * If we get here, it's because we received data from the request,
				 * but it's not a valid JSON struct yet that can be parsed into an
				 * Object.
				 */
				console.log("waiting for more data chunks...");
			}
		});
	});

	// End the request
	request.end();
}

Sending Tweets Back to the Client

Ok, now that we’ve got a function to search Twitter, and then broadcast an event when some are found – and properly parsed – we need something that will listen for that event and send the data back to the client. We will do this in the createServer() method we already have.

First things first. We have to put in logic that will check for the “/twitter?search_term” URI and then pass that search term off to our get_tweets() method. Also, to clean up our createServer() method, I’ve also created a function named load_static_web_file() that contains the code we created for retrieving static content. Here’s that function.

/**
 * Handle the serving of files with static content
 *
 * @param {Object} uri
 * @param {Object} response
 */
function load_static_web_file(uri, response) {
	var filename = path.join(process.cwd(), uri);

	// If path.exists function takes a string parameter - which is a path to
	// the document being requested - and a function which gets passed a boolean
	// argument which is true if a file at the path exists, and false if it doesn't
	path.exists(filename, function(exists) {

		// File not found. Return a 404 error.
        if (!exists) {
            response.writeHead(404, {"Content-Type": "text/plain"});
            response.write("Four Oh Four! Wherefour art thou?");
            response.end();
            return;
        }

		// File does exist. Execute the FileSystem.readFile() method
		// with a closure that returns a 500 error if the file could not
		// be read properly.
        fs.readFile(filename, "binary", function(err, file) {

			// File could not be read, return a 500 error.
            if (err) {
                response.writeHead(500, {"Content-Type": "text/plain"});
                response.write(err+"\n");
                response.end();
                return;
            }

			// File was found, and successfully read from the file system.
			// Return a 200 header and the file as binary data.
            response.writeHead(200);
            response.write(file, "binary");

			// End the response.
            response.end();
        });
    });
}

Now we can look for the /twitter pattern in the URI and call get_tweets(), otherwise, we call load_static_web_file().

/**
 * Create an HTTP server listening on port 8081
 *
 * @param {Object} request
 * @param {Object} response
 */
http.createServer(function (request, response) {
   // Parse the entire URI to get just the pathname
   var uri = url.parse(request.url).pathname, query;

   // If the user is requesting the Twitter search feature
   if(uri === "/twitter") {

      /*
       * On each request, if it takes longer than 20 seconds, end the response
       * and send back an empty structure.
       */
      var timeout = setTimeout(function() {
         response.writeHead(200, { "Content-Type" : "text/plain" });
         response.write(JSON.stringify([]));
         response.end();
      }, 20000);

      /*
       * Register a listener for the 'tweets' event on the Twitter.EventEmitter.
       * This event is fired when new tweets are found and parsed.
       *      (see get_tweets() method above)
       */
      Twitter.EventEmitter.once("tweets", function(tweets){
         // Send a 200 header and the tweets structure back to the client
         response.writeHead(200, {
            "Content-Type": "text/plain"
         });
         response.write(JSON.stringify(tweets));
         response.end();

         // Stop the timeout function from completing (see below)
         clearTimeout(timeout);
      });

      // Parse out the search term
      query = request.url.split("?")[1];

      // Search for tweets with the search term
      get_tweets(query);

   /*
    * For all other requests, try to return a static page by calling the
    * load_static_web_file() function.
    */
    } else {
        load_static_web_file(uri, response);
    }
}).listen(8081);

The entire source code for TwitterSearch.js

Published on Monday, Mar 7,2011 | 3 Comments |

About Steve

I am a technologist, and have been ever since 1980 when I got my very first TRS-80 and programmed it to do my math homework. I love to share the gift of technology with others and show them the wonderful things it can do for them, and how they should not fear it, but embrace it.

Latest Tweets

  • Ok... stayed up way too late trying out website designs for my wife's new nonprofit. The kids will be getting me u... — http://t.co/QrKh5iBI
  • Am I the only one who likes Google’s new privacy policy? http://t.co/qwcym5wH
  • All that time wasted learning the .NET framework - Fusioncube - http://t.co/krANoWmg
  • @marcesher libraries like Less only do what you tell them. You can make a mixin to do that, but it doesn't assume anything (which is good)
  • Circus about to start. Girls are so excited they can't stand it!!!! (with Sabrina and Tessa at @BrdgstoneArena) [pic] — http://t.co/PXwi5emj

Subscribe

Entries (RSS)
Comments (RSS)