Pooper Lube Day March 6, 2012
jQuery Events Expanded March 5, 2012
Typically, when writing Javascript, you'll want to handle the click event for a certain element, or set of elements, so you can do this:
jQuery(".my-class").click(function(ev){
alert(jQuery(this).attr(id) + " was clicked");
});
What is less realized and utilized, is that you can use jQuery to add custom events to any Javascript object! This is pretty awesome. This is how it is done, in the most simple sense.
var MyObject = {
makeCall: function(){ $.ajax("http://example.com/x", { method: "GET", success: this.completed }); },
completed: function(data, status, xhr){ $(this).trigger("complete", data); }
};
In the most simple case, this can be used as follows:
$(MyObject).on("complete", function(e, data){
alert(JSON.stringify(data));
});
However, I would typically add convenience methods to the object, as follows:
var MyObject = {
... // other stuff.
onComplete: function(fn){ $(this).on("complete", fn); }
}
So now your code looks like this: MyObject.onComplete(function(e, data){ alert(JSON.stringify(data)); });
Of course, that is simply a global object. We would want to be able to allocate many instances of this, so then you change your code to this:
function MyObject(){
}
MyObject.prototype.onComplete = function(fn) { $(this).on("complete", fn); };
MyObject.prototype.makeCall = function(){ $.ajax("http://example.com/x", { method: "GET", success: this.completed } ); };
MyObject.prototype.completed = function(data, status, xhr){ $(this).trigger("complete", data); };
Or any number of ways to skin that cat.
Where I used this was in a Javascript geolocator object. So, browsers now offer an ability to geolocate the user. This is more helpful on mobile browsers, but it's also available, albeit less accurately, on desktop browsers (although, to be fair, sometimes it's dead on!). The browser may not have this ability, however, so we should offer a fallback, like enter in a zip code or address, and allow Google's Maps API to geocode it. Like so:
;(function(window){
var GeoLocation = {
locationRegex: /^[0-9\.\-,]+$/,
parse: function(str){
var coords = { latitude: 0, longitude: 0 };
// parse "y,x", return { longitude: y, latitude: x }
},
init: function(cookieName){
var self = this;
this.cookieName = cookieName;
this.on("located", function(e, coords){ self.updateLocation(coords); });
var stored = this.getStoredLocation();
if (this.checkLocation(stored))
$(self).trigger("located", stored);
},
hasStoredLocation: function(){
// check cookie
},
getStoredLocation: function(){
// get cookie as y,x call parse, return { longitude: y, latitude: x }
},
clearStoredLocation: function(){
// erase cookie
},
canGeocode: function(){
return navigator.geolocation != null && typeof (navigator.geolocation.getCurrentPosition) == "function";
},
getLocation: function(input){
var self = this;
var defaultLocation = { longitude: 0, latitude: 0 };
if (typeof(input) == "string" && input != null && input.length > 0){
var geocoder = new google.maps.Geocoder();
geocoder.geocode({address: input}, function(result, status){
if (status == google.maps.GeocoderStatus.OK){
var res = result[0];
var loc = {latitude: res.geometry.location.lat(), longitude: res.geometry.location.lng()};
$(self).trigger("located", loc);
}
else $(self).trigger("failure", defaultLocation);
});
}
else if (self.canGeocode()){
navigator.geolocation.getCurrentPosition(function(position){
$(self).trigger("located", position.coords);
}, function(error){
$(self).trigger("failure", defaultLocation);
});
}
else {
$(self).trigger("failure", defaultLocation);
}
},
checkLocation: function(coords){
return !(coords.longitude == 0 && coords.latitude == 0);
},
updateLocation: function(coords){
// store cookie
},
// use: GeoLocation.on('located', function(coords){ alert(JSON.stringify(coords)); });
on: function(ev, cb){
$(this).on(ev, cb);
}
};
window.GeoLocation = GeoLocation;
})(window);
So you can add a listener for any time that the user's location is determined, pretty much anywhere, thanks to jQuery, even without interacting with the DOM or DOM elements.
$(document).ready(function(){
GeoLocation.on("located", function(coords){
$("body").append($("<div>Located you at " + JSON.stringify(coords) + "</div>"));
});
GeoLocation.on("failure", function(coords){
$("body").append($("<div>Couldn't locate you</div>"));
});
GeoLocation.init("mysite-location-cookie");
GeoLocation.getLocation("Philadelphia, PA"); // a string parameter will cause it to invoke Google's Geocoding service.
});
Javascript is a neat, capable little language. I hope this helps display that a little bit.
Web Fonts February 27, 2012
Habits February 24, 2012
The Power of Runtime File Combining February 23, 2012
Posting to Twitter with Node.js February 3, 2012
I wanted to take some pain out of doing social sharing, so posting items automatically to twitter seemed like a good step to take. Of course, with my paradigm shift into Node.js and MongoDB, and it being fairly new across the board, I would have to write it myself, with the added benefit of being able to share how it was done!
Twitter's API documentation is pretty good, as far as API documentation goes. Their OAuth docs are on par with Google's and Facebook's (I use both of those on the site as well, as they are OAuth 2.0 and 2.0 is generally much easier than 1.0). All of their documentation can be found on dev.twitter.com, and that's also where you would create and manage your app, app keys and secrets, etc.
Data You'll Need
Now, completely ignoring the rate limiting part of the Twitter API, we can write a pretty straightforward method to send status updates as the user you created the application under. You can get the access tokens right in the Details section of your application. You'll need the access token and access token secret, as well as the consumer key and the consumer secret key. Don't give these to anyone!
The hardest part about OAuth 1.0 is generating the signature correctly. It's not that hard, either, since twitter provides excellent documentation on creating the signature. The base string used for the signature is created like this
The Code
These are the only requires you'll require (heh)
var https = require("https"), querystring = require("querystring"), crypto=require("crypto");
var oauthData = { oauth_consumer_key: consumerKey, oauth_nonce: nonce, oauth_signature_method: "HMAC-SHA1", oauth_timestamp: timestamp, oauth_token: accessToken, oauth_version: "1.0" };
var sigData = {};
for (var k in oauthData){
sigData[k] = oauthData[k];
}
for (var k in data){
sigData[k] = data[k];
}
Here we're gathering up all of the data passed to create the tweet (status, lat, long, other parameters), and also the oauthData minus the signature. Then we do this:
var sig = generateOAuthSignature(url.method, "https://" + url.host + url.path, sigData);
oauthData.oauth_signature = sig;
Which calls this function
function generateOAuthSignature(method, url, data){
var signingToken = urlEncode(consumerSecret) + "&" + urlEncode(accessSecret);
var keys = [];
for (var d in data){
keys.push(d);
}
keys.sort();
var output = "POST&" + urlEncode(url) + "&";
var params = "";
keys.forEach(function(k){
params += "&" + urlEncode(k) + "=" + urlEncode(data[k]);
});
params = urlEncode(params.substring(1));
return hashString(signingToken, output+params, "base64");
}
function hashString(key, str, encoding){
var hmac = crypto.createHmac("sha1", key);
hmac.update(str);
return hmac.digest(encoding);
}
With this function, you can successfully generate the signature. The next part is passing the OAuth headers correctly. I simply do this:
var oauthHeader = "";
for (var k in oauthData){
oauthHeader += ", " + urlEncode(k) + "=\"" + urlEncode(oauthData[k]) + "\"";
}
oauthHeader = oauthHeader.substring(1);
And then create the request and pass it along on the request like this:
var req = https.request(url, function(resp){
resp.setEncoding("utf8");
var respData = "";
resp.on("data", function(data){
respData += data;
});
resp.on("end", function(){
if (resp.statusCode != 200){
callback({error: resp.statusCode, message: respData });
}
else callback(JSON.parse(respData));
});
});
req.setHeader("Authorization", "OAuth" + oauthHeader);
req.write(querystring.stringify(data));
req.end();
Twitter Limits
There are other checks, like when you include a URL in the tweet text, you'll need to see that your text plus the length of the generated t.co URL doesn't exceed 140 characters. That URL length will change fairly infrequently, and slower and slower as time goes on, since more URLs can be generated with just 1 more character added. This data is available though. I have another function that gets the configuration from twitter, and passes that along to my function that actually generates tweets from the database.
function getHttpsNonAuthJSON(host, path, query, callback){
var url = { host: host , path: path };
if (query != null) url.path = url.path + "?" + querystring.stringify(query);
https.get(url, function(resp){
resp.setEncoding("utf8");
var respData = "";
resp.on("data", function(data){
respData += data;
});
resp.on("end", function(){
callback(JSON.parse(respData));
});
});
}
This is a general function to get any non-authenticated https request and perform a callback with a JS object. I might call it like this, for example
getHttpsNonAuthJSON("api.twitter.com", "/1/help/configuration.json", null, function(config){
console.log(config.short_url_length_http);
});
For some completeness, this is the complete function that makes POST requests with OAuth
function postHttpsAuthJSON(host, path, data, nonce, callback){
var url = { host: host, path: path, method: "POST" };
var timestamp = Math.floor(new Date().getTime() / 1000);
var oauthData = { oauth_consumer_key: consumerKey, oauth_nonce: nonce, oauth_signature_method: "HMAC-SHA1", oauth_timestamp: timestamp, oauth_token: accessToken, oauth_version: "1.0" };
var sigData = {};
for (var k in oauthData){
sigData[k] = oauthData[k];
}
for (var k in data){
sigData[k] = data[k];
}
var sig = generateOAuthSignature(url.method, "https://" + url.host + url.path, sigData);
oauthData.oauth_signature = sig;
var oauthHeader = "";
for (var k in oauthData){
oauthHeader += ", " + urlEncode(k) + "=\"" + urlEncode(oauthData[k]) + "\"";
}
oauthHeader = oauthHeader.substring(1);
var req = https.request(url, function(resp){
resp.setEncoding("utf8");
var respData = "";
resp.on("data", function(data){
respData += data;
});
resp.on("end", function(){
if (resp.statusCode != 200){
callback({error: resp.statusCode, message: respData });
}
else callback(JSON.parse(respData));
});
});
req.setHeader("Authorization", "OAuth" + oauthHeader);
req.write(querystring.stringify(data));
req.end();
}
Thanks for reading! I hope this helps you out. A reminder, this is just for posting as a single user, it doesn't go get the auth token or do any other of the handshaking that is needed for OAuth. Twitter generates an access token and a access secret token for use with the user that created the application, and you can do calls to the API with that, as that user.
Photography Lighting January 9, 2012
In recent weeks, I've become interested in going to the next level of photography. Not to say that I've mastered any previous part, but going to the next level will help me better understand the previous levels, and therefore I'll be half decent at any part. Lighting is the next level, I've determined.
In researching "The Holy Grail of Photography", henceforth, "lighting", I came across some blogs of old pros, namely the "Strobist". There, I read and read and read until I fell asleep. I bought a few Kindle prints of Joe McNally's books. I read and read and read, then did some practicing.
This is all in anticipation of the baby coming in April. I will blind that kid :P
Aside from books, here's what I've purchased:
- Nikon SB-700 flash
- CowboyStudios wireless flash trigger
- CowboyStudios light stand with mounting bracket and umbrella
- LumiQuest mini-softbox
I haven't been uploading sample shots with every piece of equipment yet, but the pictures up right now (as of 1/9/2012) include sample shots taken with the flash and the mini-softbox. I have shots with the wireless flash trigger that aren't up on flickr yet, and I just received the stand and umbrella today, and I haven't gone home yet.
I've been getting up to date on all of the terminology used. I know what effects the aperture has vs. shutter speed when flash comes into play. With flash and without flash, actually. And ISO plays a big part in that as well.
I shoot in manual mode on my camera without a flash, and so I've brought that over to flash photography as well. My method now as always is shoot and review to see if I'm getting enough light. If not, bump the flash power or open/close the aperture, etc. Or refocus, since I also shoot in manual focus mode. With practice, I'm getting quicker at it..
Most photogs suggest shooting in aperture priority mode, since aperture is what effects flash the most. If you want to darken the flash, close the aperture, and vice versa. In non-flash photography, it also makes sense, because you can limit the depth of field (make the background blurry) or go infinite on that mother, and the camera will choose the proper shutter speed. I have opted to really learn everything before I go that way. The side effect is I take about 1 good photo for every 5 times my shutter opens :)
A good side effect of all of this photography, is I'm becoming a photo snob! If I look at a bad photo, either blurry or not enough light, or too much light, I get an ill feeling down in my bowels and have to just close my eyes or look away. It's good to have.
Lots of photos to come, especially when there's a baby AND a pug in the house :D
50mm f/1.8 Lens December 27, 2011
For Christmas, I got a new lens! As well as some other awesome stuff, but the lens is the focal point of this post.
I'm generally a beginner photographer, but over the past few weeks I've been taking hundreds of pictures, I got a new flash (the SB-700) and have been playing with that. I want to try getting the flash off the camera, and there's tons of little accessories I don't own yet that will help.
Here is a sample picture of what I've been able to do with the f/1.8 lens. It's pretty awesome so far! That's my nephews, with Ethan in the foreground and Caden in the background, both playing their new Nintendo 3DS's that they got for Christmas :)
My other favorite photo with the new lens, Amanda and Beaker, enjoying the Snoogle that I got for Amanda for Christmas!
Photographer's Blessing December 21, 2011
Another Baby Appointment December 20, 2011
Baby December 13, 2011
The baby is growing!
The baby is 10 oz and about the length of a banana! We aren't finding out the sex so I don't want to say "He/She" so I'll just refer to the baby as "the baby".
Friday is just around the corner November 17, 2011
Humility November 15, 2011
Another Example of my SyncArray November 2, 2011
I refer you to my original post with my SyncArray code
function getSubdirs = function(path, callback){
fs.readdir(path, function(err, files){
var sync = new SyncArray(files);
var subdirs = [];
sync.forEach(function(file, index, array, finishedOne){
fs.stat(file, function(err, stats){
if (stats.isDirectory()){
subdirs.push(file);
}
finishedOne();
});
}, function(){
callback(subdirs);
});
});
}