<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-4276667104978676221</id><updated>2012-01-30T15:44:52.437-08:00</updated><category term='ruby'/><category term='facebook'/><category term='Heroku'/><category term='hackintosh'/><category term='jQuery'/><category term='sortable'/><category term='UI'/><category term='DelayedJob'/><category term='api'/><category term='scheduled'/><category term='django'/><category term='Apple'/><category term='draggable'/><category term='proprietary platform'/><category term='OS X'/><category term='tasks'/><category term='ruby on rails'/><category term='iPhone'/><category term='register'/><category term='jobs'/><category term='clone()'/><category term='mac finder'/><category term='rails'/><category term='turbogears'/><category term='iPhone OS 4'/><category term='oauth'/><category term='registration'/><category term='iPad'/><category term='tree'/><category term='opengraph'/><category term='closed platform'/><title type='text'>WiseJive</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://www.wisejive.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>20</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-4276667104978676221.post-4350434938192969429</id><published>2011-03-31T12:02:00.000-07:00</published><updated>2011-04-11T20:01:30.561-07:00</updated><title type='text'>Proxied - Web Service for the static page web app</title><content type='html'>I &lt;a href="http://www.wisejive.com/2010/11/static-page-apps-where-is-database.html"&gt;mentioned&lt;/a&gt; in a previous post how I had been building a couple of static page apps that used only Javascript with &lt;a href="http://en.wikipedia.org/wiki/JSONP"&gt;JSONP&lt;/a&gt; to get and push data.  These apps I have been working on mostly involved using &lt;a href="http://www.facebook.com"&gt;Facebook&lt;/a&gt; and &lt;a href="http://www.twitter.com"&gt;Twitter&lt;/a&gt;, and therefore I didn't run into too many problems with the fact that I had no way to persist data (leaving out HTML5 local storage, which doesn't allow aggregation across users anyway).  However I have gotten to the point with the projects where I'd really like to be able to do some things that involve aggegation of data across users, and frankly I have not found a webservice to do this (I went through all this in my &lt;a href="http://www.wisejive.com/2010/11/static-page-apps-where-is-database.html"&gt;last post&lt;/a&gt; on this subject).&lt;br /&gt;&lt;br /&gt;So having thought this over more I think such a service, which I have named Proxied, would have the following:&lt;br /&gt;&lt;br /&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Login/Registration/Session as a service capability - Proxied would have the ability to establish a user session per Proxied application, so that other Proxied API calls could be limited to logged in users&lt;/li&gt;&lt;li&gt;True to its name Proxied would proxy calls to any api, ensuring they return JSONP&lt;/li&gt;&lt;li&gt;For many API's some sort of authentication is needed, in those cases Proxied would store your API credentials and do whatever signing is needed for that API.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;I think a specific example is probably most helpful.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Say you want to build a new &lt;a href="http://news.ycombinator.com/"&gt;HackerNews&lt;/a&gt; like web app, you'll need&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;Login system&lt;/li&gt;&lt;li&gt;Database to store submitted links&lt;/li&gt;&lt;li&gt;Database to store up-votes, down-votes, karma&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;To do this with a static page web application and Proxied we'd use the Proxied login system to create user sessions and then a cloud database server (say &lt;a href="http://aws.amazon.com/simpledb/"&gt;SimpleDB&lt;/a&gt;, or &lt;a href="https://mongohq.com/home"&gt;MongoHQ&lt;/a&gt;) to store the data.  We'd create whatever tables (or no tables for &lt;a href="http://en.wikipedia.org/wiki/NoSQL"&gt;NOSQL&lt;/a&gt;) we need to in the cloud DB, and then figure out what the cloud DB API endpoint url(s) would be for updating the tables, we'd set those endpoint URLs in Proxied along with our cloud DB credentials, and signing methodology (&lt;a href="http://oauth.net/"&gt;OAUTH&lt;/a&gt; or whatever).  Proxied would then produce a JSONP accessible endpoint (or better yet, with clever CNAME's, JSONP wouldn't be necessary) that would proxy the call to the cloud DB API to update our application's data.  In Proxied we could set the cloud DB proxy endpoint(s) to be accessible only by logged in users, therefore restraining willy nilly use of our cloud DB by nefarious folks.&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Basically one would use Proxied to setup API endpoints for what, in a traditional webapp, would be the controller (in Rails parlance) endpoints on your own server.  For a RESTFUL architecture you'd have Proxied endpoints to GET/PUT/DELETE/POST your data to a cloud service.  As you add more data tables (or non-tables for NOSQL), you'd add more endpoints in Proxied to service that data.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;At this point you can just write the app as a static web page that uses Javascript to update the application data as necessary. Need to crunch data?  Well that should be done offline anyway, using Map-Reduce or the like, just update the cloud DB with your crunching results and make that available through Proxied.  &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Nothing restrains you from using more than one web page, but all the pages would be static.  I'd then host the web pages on &lt;a href="http://www.google.com/url?sa=t&amp;amp;source=web&amp;amp;cd=1&amp;amp;ved=0CDMQFjAA&amp;amp;url=https%3A%2F%2Fs3.amazonaws.com%2F&amp;amp;ei=DfaUTb6QD9SdgQfzrLDLCA&amp;amp;usg=AFQjCNHQ4UMp4GxlQT0pG49496kbsWCSUw&amp;amp;sig2=G6DgBRgW3QkjpvZU8_2oLQ"&gt;Amazon &lt;/a&gt;&lt;a href="http://aws.amazon.com/s3/"&gt;S3&lt;/a&gt; thereby obviating the need for ANY server at all on our part, and ensuring the application is scalable (to the degree the underlying services are scaleable). &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;What I want to know is whether this is a service that developers would use? If so, how much would you be willing to pay for it?  &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4276667104978676221-4350434938192969429?l=www.wisejive.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.wisejive.com/feeds/4350434938192969429/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.wisejive.com/2011/03/proxied-web-service-for-static-page-web.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/4350434938192969429'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/4350434938192969429'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/2011/03/proxied-web-service-for-static-page-web.html' title='Proxied - Web Service for the static page web app'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4276667104978676221.post-2139884794868681838</id><published>2010-11-30T22:09:00.000-08:00</published><updated>2011-04-08T08:53:26.690-07:00</updated><title type='text'>Phonegap, JQuery Mobile, Twitter and Facebook</title><content type='html'>Recently I have been working on a mobile website that I wanted to turn into a iOS app.  Not knowing (or wanting to know) Objective C, I decided to use &lt;a href="http://www.phonegap.com/"&gt;PhoneGap&lt;/a&gt; to build a HTML/Javascript/CSS based application.  My mobile website uses &lt;a href="http://jquerymobile.com/"&gt;JQuery Mobile&lt;/a&gt;, so I wanted to continue to use that framework if at all possible (it offers a lot of easy mobile styling and touch based events).  My application also makes substantial use of the &lt;a href="http://www.facebook.com/"&gt;Facebook&lt;/a&gt; and &lt;a href="http://twitter.com/"&gt;Twitter&lt;/a&gt; APIs, so I also needed to find a way to authenticate with those services (both use a form of &lt;a href="http://oauth.net/"&gt;OAuth&lt;/a&gt;).&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The first thing I tackled was the OAuth integration portions.  I naively thought that the two services respective Javascript login procedures would work in the WebKit window that PhoneGap creates, however I found neither operated.  That meant that I had to fall back on doing actual OAuth.  I found a great &lt;a href="http://www.pushittolive.com/post/1239874936/facebook-login-on-iphone-phonegap"&gt;blog post&lt;/a&gt; describing how to do this for Facebook, however it didn't do everything I needed it to do, so I thought I describe what I did in more detail.  &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The first thing you need to do is download the &lt;a href="http://github.com/purplecabbage/PhoneGap-Plugins"&gt;ChildBrowser Plugin&lt;/a&gt; and drag and drop the files under the ChildBrowser/iPhone to the plugins folder of your XCode project.  Then put the ChildBrowser.js file in your www folder and make sure to include the file as a script tag in your html files &lt;b&gt;AFTER the phonegap.js file&lt;/b&gt;.&lt;/div&gt;&lt;pre class="brush: csharp"&gt;&amp;lt;script src="phonegap.js" type="application/x-javascript" charset="utf-8"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&amp;lt;script src="ChildBrowser.js" type="application/x-javascript" charset="utf-8"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&lt;/pre&gt;The next step is to fire off the following when you want to login to Facebook (like a click event or the like).&lt;br /&gt;&lt;pre class="brush: csharp"&gt;function(){&lt;br /&gt; var my_client_id = "YOUR_CLIENT_ID", my_redirect_uri = "http://www.facebook.com/connect/login_success.html", my_type =            "user_agent", my_display = "touch";&lt;br /&gt;&lt;br /&gt; var authorize_url = "https://graph.facebook.com/oauth/authorize?";&lt;br /&gt; authorize_url += "client_id=" + my_client_id;&lt;br /&gt; authorize_url += "&amp;amp;redirect_uri=" + my_redirect_uri;&lt;br /&gt; authorize_url += "&amp;amp;display=" + my_display;&lt;br /&gt; authorize_url += "&amp;amp;scope=read_stream,publish_stream,offline_access,publish_checkins"&lt;br /&gt;&lt;br /&gt; client_browser = ChildBrowser.install();&lt;br /&gt; client_browser.onLocationChange = function(loc){&lt;br /&gt;     facebookLocChanged(loc);&lt;br /&gt; };&lt;br /&gt; if (client_browser != null) {&lt;br /&gt;    window.plugins.childBrowser.showWebPage(authorize_url);&lt;br /&gt; }&lt;br /&gt;});&lt;/pre&gt;This basically does the first step of the OAuth flow and shows the Facebook Authorization page int a ChildBrowser window.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The next thing we do is wait for a change in the location of the ChildBrowser window to the redirect_uri.&lt;/div&gt;&lt;pre class="brush: csharp"&gt;function facebookLocChanged(loc){&lt;br /&gt;/* Here we check if the url is the login success */&lt;br /&gt;&lt;br /&gt;if (loc.indexOf("http://www.facebook.com/connect/login_success.html") &amp;gt; -1) {&lt;br /&gt; client_browser.close(); &lt;br /&gt; var fbCode = loc.match(/code=(.*)$/)[1]&lt;br /&gt; $.ajax(&lt;br /&gt;  {&lt;br /&gt;     url:'https://graph.facebook.com/oauth/access_token?client_id=YOUR_CLIENT_ID&amp;amp;client_secret=YOUR_CLIENT_SECRET&amp;amp;code='+fbCode+'&amp;amp;redirect_uri=http://www.facebook.com/connect/login_success.html',&lt;br /&gt;     data: {},&lt;br /&gt;     success: function(data, status){&lt;br /&gt;    localStorage.facebook_token = data.split("=")[1];&lt;br /&gt;    client_browser.close();&lt;br /&gt;    initialize_facebook();&lt;br /&gt;     },&lt;br /&gt;     error: function(error) {&lt;br /&gt;    client_browser.close();&lt;br /&gt;     },&lt;br /&gt;     dataType: 'text',&lt;br /&gt;     type: 'POST'&lt;br /&gt;  }&lt;br /&gt; )&lt;br /&gt;}&lt;br /&gt;}&lt;/pre&gt;This completes the OAuth process closes the ChildBrowser (I close the ChildBrowser first because I like the user experience) and stores the facebook_token.  Note here we have taken advantage of the fact that Cross-Domain AJAX calls are allowed when using PhoneGap.  We can now make Facebook calls.  I wrote a little helper method for appending on our facebook_token to urls when making calls.&lt;div&gt;&lt;pre class="brush: csharp"&gt;function create_facebook_url(base) {&lt;br /&gt;if(device) {&lt;br /&gt; if(base.indexOf("?") == -1) {&lt;br /&gt;  return base + "?access_token=" + localStorage.facebook_token&lt;br /&gt; } else {&lt;br /&gt;  return base + "&amp;amp;access_token=" + localStorage.facebook_token  &lt;br /&gt; }&lt;br /&gt;} else {&lt;br /&gt; return base;&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// Example of using create_facebook_url&lt;br /&gt;FB.api(create_facebook_url("/me"), function(result){&lt;br /&gt;   current_facebook_user = result;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;For Twitter we follow a similar procedure, though the Twitter OAuth is markedly more complicated.  To handle some of the more complex OAuth operations I opted to use an &lt;a href="http://oauth.googlecode.com/svn/code/javascript/"&gt;oauth javascript library&lt;/a&gt;, so this also needs to be included in your index.html files.&lt;/div&gt;&lt;pre class="brush: csharp"&gt;&amp;lt;script src="/javascripts/sha1.js" type="text/javascript"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&amp;lt;script src="/javascripts/oauth.js" type="text/javascript"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&lt;/pre&gt;Note the sha1.js file is included with the oauth library.&lt;br /&gt;&lt;br /&gt;Once we have the libraries included we can now start the OAuth process.&lt;pre class="brush: csharp"&gt;accessor =&lt;br /&gt;{ consumerKey   : "YOUR_CONSUMER_KEY"&lt;br /&gt;, consumerSecret: "YOUR_CONSUMER_SECRET"&lt;br /&gt;, serviceProvider:&lt;br /&gt;{ signatureMethod     : "HMAC-SHA1"&lt;br /&gt; , requestTokenURL     : "http://api.twitter.com/oauth/request_token"&lt;br /&gt; , userAuthorizationURL: "https://api.twitter.com/oauth/authorize"&lt;br /&gt; , accessTokenURL      : "https://api.twitter.com/oauth/access_token"&lt;br /&gt; , echoURL             : "http://localhost/oauth-provider/echo"&lt;br /&gt;}&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;var message = {&lt;br /&gt;method: "post", action: accessor.serviceProvider.requestTokenURL&lt;br /&gt;, parameters: [["scope", "http://www.google.com/m8/feeds/"]]&lt;br /&gt;};&lt;br /&gt;var requestBody = OAuth.formEncode(message.parameters);&lt;br /&gt;OAuth.completeRequest(message, accessor);&lt;br /&gt;var authorizationHeader = OAuth.getAuthorizationHeader("", message.parameters);&lt;br /&gt;var requestToken = new XMLHttpRequest();&lt;br /&gt;requestToken.onreadystatechange = function receiveRequestToken() {&lt;br /&gt;if (requestToken.readyState == 4) {&lt;br /&gt;var results = OAuth.decodeForm(requestToken.responseText);&lt;br /&gt;var oauth_token = OAuth.getParameter(results, "oauth_token");&lt;br /&gt;var authorize_url = "http://api.twitter.com/oauth/authorize?oauth_token="+oauth_token;&lt;br /&gt;client_browser = ChildBrowser.install();&lt;br /&gt;client_browser.onLocationChange = function(loc){&lt;br /&gt; twitterLocChanged(loc, requestToken, accessor);&lt;br /&gt;};&lt;br /&gt;if (client_browser != null) {&lt;br /&gt; window.plugins.childBrowser.showWebPage(authorize_url);&lt;br /&gt;}&lt;br /&gt;}};&lt;br /&gt;requestToken.open(message.method, message.action, true);&lt;br /&gt;requestToken.setRequestHeader("Authorization", authorizationHeader);&lt;br /&gt;requestToken.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");&lt;br /&gt;requestToken.send(requestBody);&lt;br /&gt;&lt;/pre&gt;Basically we are forming the proper OAuth request to get a request_token from Twitter and once we receive back our token we use it to open a ChildBrowser pointing at the proper Authorization page.  Next we are (same as Facebook) look for a location change on our ChildBrowser.  This time we look for the location to change to the callback that we set in our Twitter Application definition.&lt;br /&gt;&lt;pre class="brush: csharp"&gt;function twitterLocChanged(loc, requestToken, accessor){&lt;br /&gt;/* Here we check if the url is the login success */&lt;br /&gt;if (loc.indexOf("http://www.headingtoo.com/") &amp;gt; -1) {&lt;br /&gt; client_browser.close();&lt;br /&gt; var results = OAuth.decodeForm(requestToken.responseText);&lt;br /&gt; message = {method: "post", action: accessor.serviceProvider.accessTokenURL};&lt;br /&gt; OAuth.completeRequest(message,&lt;br /&gt;  { consumerKey : accessor.consumerKey&lt;br /&gt;  , consumerSecret: accessor.consumerSecret&lt;br /&gt;  , token : OAuth.getParameter(results, "oauth_token")&lt;br /&gt;  , tokenSecret : OAuth.getParameter(results, "oauth_token_secret")&lt;br /&gt; });&lt;br /&gt; var requestAccess = new XMLHttpRequest();&lt;br /&gt; requestAccess.onreadystatechange = function receiveAccessToken() {&lt;br /&gt;  if (requestAccess.readyState == 4) {&lt;br /&gt;   var params = get_url_vars_from_string(requestAccess.responseText);&lt;br /&gt;   localStorage.twitter_token = params["oauth_token"];&lt;br /&gt;   localStorage.twitter_secret_token = params["oauth_token_secret"];&lt;br /&gt;   localStorage.twitter_user_name = params["screen_name"];&lt;br /&gt;   localStorage.twitter_user_id = params["user_id"];&lt;br /&gt;   intialize_twitter();&lt;br /&gt;  }&lt;br /&gt; };&lt;br /&gt; requestAccess.open(message.method, message.action, true);&lt;br /&gt; requestAccess.setRequestHeader("Authorization", OAuth.getAuthorizationHeader("", message.parameters));&lt;br /&gt; requestAccess.send(); &lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;This code is fired when we notice our ChildBrowser has gone to the defined callback url, we then complete the OAuth process by asking for an access_token, which we put in local storage.   We can then use that access token to make request on behalf of our user.&lt;br /&gt;&lt;pre class="brush: csharp"&gt;// helper&lt;br /&gt;function get_url_vars_from_string(url) {&lt;br /&gt;    var vars = [], hash;&lt;br /&gt;    var hashes = url.slice(url.indexOf('?') + 1).split('&amp;');&lt;br /&gt;    for(var i = 0; i &lt; hashes.length; i++)&lt;br /&gt;    {&lt;br /&gt;        hash = hashes[i].split('=');&lt;br /&gt;        vars.push(hash[0]);&lt;br /&gt;        vars[hash[0]] = hash[1];&lt;br /&gt;    }&lt;br /&gt;    return vars;&lt;br /&gt; &lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// helper&lt;br /&gt;function create_twitter_oauth_ajax_data(url, method, params, success, error) {&lt;br /&gt;var ajax_data = {&lt;br /&gt;               url: url,&lt;br /&gt;               data: params,&lt;br /&gt;               dataType: 'json',&lt;br /&gt;               type: method,&lt;br /&gt;   timeout: 60*1000,&lt;br /&gt;               beforeSend: function(req){&lt;br /&gt;                   var message = {&lt;br /&gt;                       method: method,&lt;br /&gt;                       action: url&lt;br /&gt;                   };&lt;br /&gt;                   var message_params = params;&lt;br /&gt;    message.parameters = message_params;&lt;br /&gt;    debug_log(message.parameters);&lt;br /&gt;                   OAuth.completeRequest(message, {&lt;br /&gt;                       consumerKey: accessor.consumerKey,&lt;br /&gt;                       consumerSecret: accessor.consumerSecret,&lt;br /&gt;                       token: localStorage.twitter_token,&lt;br /&gt;                       tokenSecret: localStorage.twitter_secret_token&lt;br /&gt;                   });&lt;br /&gt;    debug_log(message);&lt;br /&gt;    req.setRequestHeader("oauth_consumer_key", accessor.consumerKey);&lt;br /&gt;    req.setRequestHeader("oauth_nonce", message.parameters['oauth_nonce']);&lt;br /&gt;    req.setRequestHeader("oauth_signature_method", 'HMAC-SHA1');&lt;br /&gt;    req.setRequestHeader("oauth_token", localStorage.twitter_token);&lt;br /&gt;    req.setRequestHeader("oauth_timestamp", message.parameters['oauth_timestamp']);&lt;br /&gt;    req.setRequestHeader("oauth_version", '1.0');&lt;br /&gt;    req.setRequestHeader("Authorization", OAuth.getAuthorizationHeader("", message.parameters));&lt;br /&gt;                req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");&lt;br /&gt;   },&lt;br /&gt;               success: success,&lt;br /&gt;               error: error&lt;br /&gt;           }&lt;br /&gt;return ajax_data;&lt;br /&gt;}&lt;br /&gt;var success = function(data, responseText) {&lt;br /&gt;current_twitter_user = data;&lt;br /&gt;};&lt;br /&gt;var error = function(error){&lt;br /&gt;debug_log(error['responseText']);&lt;br /&gt;current_twitter_user = null;&lt;br /&gt;debug_log("problem getting twitter user");&lt;br /&gt;};&lt;br /&gt;var ajax_data = create_twitter_oauth_ajax_data('http://api.twitter.com/1/users/show.json', 'get', {screen_name:localStorage.twitter_screen_name, user_id:localStorage.twitter_user_id}, success, error);&lt;br /&gt;$.ajax(&lt;br /&gt;ajax_data&lt;br /&gt;);&lt;br /&gt;&lt;/pre&gt;Above I show some helper methods I created and a call to get a user's data (which doesn't necessarily need authentication, but demonstrates how it is done anyway).&lt;br /&gt;&lt;br /&gt;The final thing that I learned has to do with the current version of JQuery Mobile.  I was having trouble getting the app to switch pages when the pages were all in different files.  JQuery Mobile is supposed to do an AJAX request for the separate files and then include them in the "one-page-app" that is creates.  This didn't seem to be working, so what I did was include all my pages in the index.html page, and stopped trying to use separate files.  To do this you just have to build separate divs with each of the pages.&lt;br /&gt;&lt;pre class="brush: csharp"&gt;&amp;lt;div data-role="page"&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;div id="home_page" data-role="header"&amp;gt;&lt;br /&gt;&amp;lt;/div&amp;gt;&amp;lt;!-- /header --&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;div class="home_page" data-role="content"&amp;gt;&lt;br /&gt; &amp;lt;div id="main"&amp;gt;&lt;br /&gt;  &amp;lt;div id="home"&amp;gt;&lt;br /&gt;    &amp;lt;div id="logins"&amp;gt;&lt;br /&gt;              &amp;lt;span id="twitter_login" class="button twitter"&amp;gt;&amp;lt;img id="twitter_login_button" alt="twitter login" src="/images/twitter_16.png"/&amp;gt;&amp;amp;nbsp;Login&amp;lt;/span&amp;gt;&amp;lt;span class="logout no_show" id="twitter_logout_container" style="display:none"&amp;gt;&amp;lt;img id="twitter_avatar" alt="" height="16" src="" width="16"&amp;gt;&amp;lt;img src="/images/twitter_16.png" alt='Twitter' title="Twitter" /&amp;gt;&amp;lt;a id="twitter_logged_in_link" href="#" target="blank"&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;a href="#" id="twitter_logout" class="logout"&amp;gt;logout&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;     &amp;lt;span id="facebook_login" class="button facebook"&amp;gt;&amp;lt;img src="/images/facebook_16.png" /&amp;gt;&amp;amp;nbsp;Login&amp;lt;/span&amp;gt;&amp;lt;span class="logout no_show" id="facebook_logout_container" style="display:none;"&amp;gt;&amp;lt;img id="facebook_avatar" alt="" height="16" src="" width="16"&amp;gt;&amp;lt;img src="/images/facebook_16.png" alt='Facebook' title="Facebook" /&amp;gt;&amp;lt;a id="facebook_logged_in_link" href="" target="blank"&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;a id="facebook_logout" href="#" class="logout"&amp;gt;logout&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;          &amp;lt;/div&amp;gt;&lt;br /&gt;  &lt;br /&gt;    &amp;lt;div class="logo"&amp;gt;&lt;br /&gt;     &amp;lt;img src="/images/logo.png"/&amp;gt;&lt;br /&gt;    &amp;lt;/div&amp;gt;&lt;br /&gt;    &amp;lt;input type="text" id="where" value="Where are you headed?"/&amp;gt;&lt;br /&gt;    &amp;lt;div id="where_message_container"&amp;gt;&lt;br /&gt;     &amp;lt;textarea id="where_message"&amp;gt;&amp;lt;/textarea&amp;gt;&lt;br /&gt;     &amp;lt;a href="#" id="where_message_submit"&amp;gt;Submit&amp;lt;/a&amp;gt;&lt;br /&gt;    &amp;lt;/div&amp;gt;&lt;br /&gt;  &amp;lt;/div&amp;gt;&lt;br /&gt; &amp;lt;/div&amp;gt;&lt;br /&gt; &amp;lt;div id="fb-root"&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt; &amp;lt;script src="http://connect.facebook.net/en_US/all.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt; &amp;lt;script&amp;gt;&lt;br /&gt;    FB.init({appId: '161124300591022', status: true, cookie: true,&lt;br /&gt;              xfbml: true, scope:'publish_checkins'});&lt;br /&gt; &amp;lt;/script&amp;gt;    &lt;br /&gt; &amp;lt;/div&amp;gt;&lt;br /&gt;  &amp;lt;!-- /content --&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;div data-role="footer"&amp;gt;&lt;br /&gt; &amp;lt;div class="ui-grid-b"&amp;gt;&lt;br /&gt;  &amp;lt;div class="ui-block-a"&amp;gt;&lt;br /&gt;   &amp;lt;a href="#ui-page-start" data-role="button"&amp;gt;&lt;br /&gt;    Heading Too&lt;br /&gt;   &amp;lt;/a&amp;gt;&lt;br /&gt;  &amp;lt;/div&amp;gt;&lt;br /&gt;  &amp;lt;div class="ui-block-b"&amp;gt;&lt;br /&gt;      &amp;lt;a href="#/mobile/friends_headingtoos.html" data-role="button"&amp;gt;&lt;br /&gt;       Friends&lt;br /&gt;      &amp;lt;/a&amp;gt;&lt;br /&gt;  &amp;lt;/div&amp;gt;&lt;br /&gt;  &amp;lt;div class="ui-block-c"&amp;gt;&lt;br /&gt;      &amp;lt;a href="#/mobile/nearby_headingtoos.html" data-role="button"&amp;gt;&lt;br /&gt;       Everyone&lt;br /&gt;      &amp;lt;/a&amp;gt;&lt;br /&gt;  &amp;lt;/div&amp;gt;&lt;br /&gt; &amp;lt;/div&amp;gt;&amp;lt;!-- /grid-a --&amp;gt;&lt;br /&gt;&amp;lt;/div&amp;gt;&amp;lt;!-- /page --&amp;gt;&lt;br /&gt;&amp;lt;/div&amp;gt;&lt;br /&gt;&amp;lt;div id="/mobile/friends_headingtoos.html" data-role="page"&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;div data-role="header"&amp;gt;&lt;br /&gt; &amp;lt;h1&amp;gt;Friends&amp;lt;/h1&amp;gt;&lt;br /&gt;&amp;lt;/div&amp;gt;&amp;lt;!-- /header --&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;div data-role="content"&amp;gt;&lt;br /&gt;&amp;lt;div id="friends_headingtoos"&amp;gt;&lt;br /&gt;   &amp;lt;ul&amp;gt;&amp;lt;/ul&amp;gt;&lt;br /&gt;&amp;lt;/div&amp;gt;&lt;br /&gt;&amp;lt;/div&amp;gt;&amp;lt;!-- /content --&amp;gt;&lt;br /&gt;&amp;lt;div data-role="footer"&amp;gt;&lt;br /&gt; &amp;lt;div class="ui-grid-b"&amp;gt;&lt;br /&gt;  &amp;lt;div class="ui-block-a"&amp;gt;&lt;br /&gt;   &amp;lt;a href="#ui-page-start" data-role="button"&amp;gt;&lt;br /&gt;    Heading Too&lt;br /&gt;   &amp;lt;/a&amp;gt;&lt;br /&gt;  &amp;lt;/div&amp;gt;&lt;br /&gt;  &amp;lt;div class="ui-block-b"&amp;gt;&lt;br /&gt;      &amp;lt;a href="#/mobile/friends_headingtoos.html" data-role="button"&amp;gt;&lt;br /&gt;       Friends&lt;br /&gt;      &amp;lt;/a&amp;gt;&lt;br /&gt;  &amp;lt;/div&amp;gt;&lt;br /&gt;  &amp;lt;div class="ui-block-c"&amp;gt;&lt;br /&gt;      &amp;lt;a href="#/mobile/nearby_headingtoos.html" data-role="button"&amp;gt;&lt;br /&gt;       Everyone&lt;br /&gt;      &amp;lt;/a&amp;gt;&lt;br /&gt;  &amp;lt;/div&amp;gt;&lt;br /&gt; &amp;lt;/div&amp;gt;&amp;lt;!-- /grid-a --&amp;gt;&lt;br /&gt;&amp;lt;/div&amp;gt;&amp;lt;!-- /header --&amp;gt;&lt;br /&gt;&amp;lt;/div&amp;gt;&lt;br /&gt;&amp;lt;div id="/mobile/nearby_headingtoos.html" data-role="page"&amp;gt;&lt;br /&gt;&amp;lt;div data-role="header"&amp;gt;&lt;br /&gt;&amp;lt;h1&amp;gt;Nearby&amp;lt;/h1&amp;gt;&lt;br /&gt;&amp;lt;/div&amp;gt;&amp;lt;!-- /header --&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;div data-role="content"&amp;gt;&lt;br /&gt;&amp;lt;div id="nearby_headingtoos"&amp;gt;&lt;br /&gt;  &amp;lt;div class="map"&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;   &amp;lt;ul&amp;gt;&amp;lt;/ul&amp;gt;&lt;br /&gt;&amp;lt;/div&amp;gt;&lt;br /&gt;&amp;lt;/div&amp;gt;&lt;br /&gt;&amp;lt;div data-role="footer"&amp;gt;&lt;br /&gt; &amp;lt;div class="ui-grid-b"&amp;gt;&lt;br /&gt;  &amp;lt;div class="ui-block-a"&amp;gt;&lt;br /&gt;   &amp;lt;a href="#ui-page-start" data-role="button"&amp;gt;&lt;br /&gt;    Heading Too&lt;br /&gt;   &amp;lt;/a&amp;gt;&lt;br /&gt;  &amp;lt;/div&amp;gt;&lt;br /&gt;  &amp;lt;div class="ui-block-b"&amp;gt;&lt;br /&gt;      &amp;lt;a href="#/mobile/friends_headingtoos.html" data-role="button"&amp;gt;&lt;br /&gt;       Friends&lt;br /&gt;      &amp;lt;/a&amp;gt;&lt;br /&gt;  &amp;lt;/div&amp;gt;&lt;br /&gt;  &amp;lt;div class="ui-block-c"&amp;gt;&lt;br /&gt;      &amp;lt;a href="#/mobile/nearby_headingtoos.html" data-role="button"&amp;gt;&lt;br /&gt;       Everyone&lt;br /&gt;      &amp;lt;/a&amp;gt;&lt;br /&gt;  &amp;lt;/div&amp;gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;/div&amp;gt;&amp;lt;!-- /grid-a --&amp;gt;&lt;br /&gt;&amp;lt;/div&amp;gt;&amp;lt;!-- /header --&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Basically just adding in DIV's with the proper id's guarantees they will be loaded when a link with a href="#the_id" is clicked.  This works with much more regularity than using external files.  Since JQuery Mobile is so young I'd guess that this will be a non-issue at some point.&lt;br /&gt;&lt;br /&gt;As always ask questions and leave feedback in the comments.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4276667104978676221-2139884794868681838?l=www.wisejive.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.wisejive.com/feeds/2139884794868681838/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.wisejive.com/2010/11/phonegap-jquery-mobile-twitter-and.html#comment-form' title='33 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/2139884794868681838'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/2139884794868681838'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/2010/11/phonegap-jquery-mobile-twitter-and.html' title='Phonegap, JQuery Mobile, Twitter and Facebook'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><thr:total>33</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4276667104978676221.post-3195386101241236618</id><published>2010-11-17T12:49:00.000-08:00</published><updated>2010-11-17T14:43:11.428-08:00</updated><title type='text'>Static page apps -- where is the database?</title><content type='html'>Recently I've been doing a lot of work on applications integrating with social networks and came to the conclusion that with the jsonp apis available from &lt;a href="http://www.twitter.com/"&gt;Twitter&lt;/a&gt; and &lt;a href="http://www.facebook.com/"&gt;Facebook&lt;/a&gt; there is no need to actually have a database server, a web application server for dynamic html and all the other accoutrements that most web applications need. &lt;br /&gt;&lt;br /&gt;This works rather well in a lot of cases (especially because of &lt;a href="http://jquery.com/"&gt;JQuery&lt;/a&gt; and newer frameworks for organizing single page apps, like &lt;a href="http://code.quirkey.com/sammy/"&gt;sammy.js&lt;/a&gt;), I've only found 2 main things that frustrate going this way with all apps.&lt;br /&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;Some very interesting API's do not have JSONP interfaces, this limits what we can work with and therefore what we can accomplish.&lt;/li&gt;&lt;li&gt;There is no way to store data in a centralized database (HTML5 offers local storage options, but that doesn't allow for aggregation of data).&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;The first point above can be addressed using &lt;a href="http://pipes.yahoo.com/"&gt;Yahoo!Pipes&lt;/a&gt; to essentially proxy calls and convert other kinds of output (XML, HTML, etc) to JSONP (see &lt;a href="http://ajaxian.com/archives/using-yql-as-a-proxy-for-cross-domain-ajax"&gt;this&lt;/a&gt;).  Yahoo!Pipes is a nice solution but doesn't have a strict SLA, published rate limits and an easy way to upgrade to a pay per request service.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The second point I haven't found any solution for at all.  The problem seems to stem from the fact that private keys cannot be hidden client-side, therefore if one want to use something like &lt;a href="http://aws.amazon.com/simpledb/"&gt;Amazon SimpleDB&lt;/a&gt; one has to proxy the call themselves to keep their private keys stored securely server-side.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I think there is a spot for some cloud service businesses to fill the voids mentioned above (thinking Amazon style pay-per-use pricing model).  I quickly mocked up a &lt;a href="http://nodejs.org/"&gt;node.js&lt;/a&gt; app that takes a request with a API URL that is supposed to be proxied, gets the API URL, parses it and returns it as a JSONP response.  Node.js is perfect for this kind of work because it is so IO intensive.  The database issue is a slightly more difficult problem but is solvable as well. &lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Soooo.... Anyone interested in working on this?  Anyone have some solutions I missed?  Lemme know in the comments.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4276667104978676221-3195386101241236618?l=www.wisejive.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.wisejive.com/feeds/3195386101241236618/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.wisejive.com/2010/11/static-page-apps-where-is-database.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/3195386101241236618'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/3195386101241236618'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/2010/11/static-page-apps-where-is-database.html' title='Static page apps -- where is the database?'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4276667104978676221.post-6967149125307103858</id><published>2010-06-21T21:07:00.000-07:00</published><updated>2010-06-21T21:57:36.040-07:00</updated><title type='text'>Code tests - What do they test?</title><content type='html'>So recently I had a recruiter set me up with an interview in a completely different industry (the financial industry) than my native industry (Internet).  My understanding was I was coming as a senior, experienced PhD, with a background in making real systems work and scale.  I wasn't particularly interested in a new job in the financial industry, but it sounded intriguing and they sounded like they really wanted me to check it out.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Things went swimmingly till I was asked to do a 2 hour timed code test.  I balked, saying that I wasn't sure what they were going to learn from it, catching the HR person quite off guard.  Long story short, they decided I wasn't "gung-ho" enough for the job, and we both moved on.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Well this whole thing got me thinking, what are code tests good for?  I basically see 3 categories of job candidates, senior, intermediate and junior and the usefulness of code tests vary wildly from senior to junior.  I think that HR folks need to be trained to handle each type differently.  First off identifying the types:&lt;/div&gt;&lt;ol&gt;&lt;li&gt;Senior - Years of experience (3 or more, but not hard and fast), example sites (a portfolio), production code to look at.  Blog or tweets related to the field.  &lt;/li&gt;&lt;li&gt;Intermediate - Years of experience (less than 3, but not hard and fast), some example sites they worked on as a team but not many (a thin portfolio).&lt;/li&gt;&lt;li&gt;Junior - Years of experience (less than 1, right out of school), very few or no examples.&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;If the HR person can't identify the person then don't give a code test right away, do a screening interview (&amp;lt; 30 minutes)&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Once you have the person in/around a category (nothing is black and white) I think you handle all 3 differently.&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;Senior - no code tests, respect their experience.  Get another senior developer to interview (on the phone) to determine if the candidate is experienced, and most importantly has the &lt;b&gt;right experience&lt;/b&gt; for the job.  Make sure to ask about things that are key to your product/business, example: If you run a high traffic website, make sure to ask about cache-semantics, and/or eventual consistency.&lt;/li&gt;&lt;li&gt;Intermediate - maybe give a code test (I wouldn't).  Understand that a code test will be no more useful than a good interview at figuring out the candidate's skill set.&lt;/li&gt;&lt;li&gt;Junior - by all means give a code test.  The candidate has no reason to expect not to get a test, they have no proof of their ability.&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;So I think code tests are useful in some instances, but no matter what I think a &lt;b&gt;good interview&lt;/b&gt; (not even a long one) will be able to determine a lot more than a code test.  To be honest an interview is easier on the candidate, and the engineer who would have to grade the code test (boring, tedious work).  The plus of an interview is that you get to feel out the candidate on culture as well, find out if they are funny, moody, easily frustrated...&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;One other thing is to consider the ordering of the process.  I think a code test (as you can probably guess from above), should not be an initial step.  With this finance company I had a 15 minute interview with an HR person and then was supposed to take a test.  I had yet to know what the job would be (what tech is used, what Artificial Intelligence is used, if any, what the big problems are, in short, how fun the job would be for me), and I was being asked to devote 2 hours out of my (busy) life to do a timed test (which stressed me out).  &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So I think the best way to handle the process is to do some sort of screening interview with an engineer first.  This is for the hiring company &lt;b&gt;and&lt;/b&gt; the candidate!  Both will have a much better idea of each other's interest level after that.  Remember the candidate is &lt;b&gt;interviewing you &lt;/b&gt;as much as you are interviewing the candidate.  This especially goes for the highly experienced, highly skilled candidates.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So that is it, I guess that could be classified as a rant, but it is not a very aggressive one...&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4276667104978676221-6967149125307103858?l=www.wisejive.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.wisejive.com/feeds/6967149125307103858/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.wisejive.com/2010/06/code-tests-what-do-they-test.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/6967149125307103858'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/6967149125307103858'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/2010/06/code-tests-what-do-they-test.html' title='Code tests - What do they test?'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4276667104978676221.post-7012428643648006715</id><published>2010-06-10T09:45:00.000-07:00</published><updated>2010-06-28T13:26:56.480-07:00</updated><title type='text'>Twitter @Anywhere Javascript API exposed</title><content type='html'>&lt;div&gt;&lt;br /&gt;&lt;/div&gt;So I have been working on some serious &lt;a href="http://www.twitter.com/"&gt;Twitter&lt;/a&gt; and &lt;a href="http://www.facebook.com/"&gt;Facebook&lt;/a&gt; integration on the client-side for a recent project.  To do some of the things I wanted to do I needed to use the &lt;a href="http://platform.twitter.com/js-api.html"&gt;Twitter @Anywhere Javascript API&lt;/a&gt;.  The only problem with using the Twitter @Anywhere API is that it is very thinly documented and &lt;a href="http://groups.google.com/group/twitter-dev-anywhere/browse_thread/thread/a7cc30dd9ccf9ec0/68e5b8caf8eaa20d#68e"&gt;not likely to be updated soon&lt;/a&gt;.  &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Being the enterprising fellow I am, I pulled down there main API Javascript code from &lt;a href="http://getfirebug.com/"&gt;Firebug&lt;/a&gt; and put it into &lt;a href="http://www.eclipse.org/"&gt;Eclipse&lt;/a&gt; (with &lt;a href="http://www.aptana.com/"&gt;Aptana&lt;/a&gt;) highlighted the one line of code (there were no line breaks), did an Apple-Shift-F on the code (fix the syntax), and boom!  I now had a nicely formatted Javascript file with the Twitter @Anywhere API.  I won't publish it here as that is Twitter's property and I don't want to get into any trouble.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;What I will talk about is some of the things I learned about how to use the @Anywhere API; some things that your really need to make it feasible to use at all, even just to test.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;First major find was how to pass parameters through the @Anywhere API to the regular &lt;a href="http://apiwiki.twitter.com/Twitter-REST-API-Method:-statuses-home_timeline"&gt;Twitter API&lt;/a&gt;.  The current @Anywhere cheatsheet describes doing calls like:&lt;/div&gt;&lt;br /&gt;&lt;pre class="brush: csharp"&gt;&lt;br /&gt;User.current.homeTimeline()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;That by default will only give you back the first 20 items in the current user's feed, it doesn't allow using a "since_id" or a "max_id" parameter to restrict what items are returned, and it doesn't let you do pagination of the returned results.  &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;From looking over the code I noticed you could input options to the call by doing the following:&lt;/div&gt;&lt;br /&gt;&lt;pre class="brush: csharp"&gt;&lt;br /&gt;User.current.homeTimeline({count:200, rpp:3, since_id:171981, max_id:181981});&lt;br /&gt;&lt;/pre&gt;This would fire off a request to the regular Twitter API that looks like:&lt;br /&gt;&lt;pre class="brush: csharp"&gt;http://api.twitter.com/1/statuses/home_timeline.xml?count=200&amp;amp;rpp=3&amp;amp;since_id=171981&amp;amp;max_id=181981&lt;/pre&gt;Which is exactly what we want, now we can control what we get back with the same precision as the regular Twitter API.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The next thing I figured out was how to avoid using the type of statements that the Twitter @Anywhere API cheatsheet examples use.  For example the cheatsheet uses stuff like this:&lt;/div&gt;&lt;br /&gt;&lt;pre class="brush: csharp"&gt;&lt;br /&gt;User.current.homeTimeLine().first(20).each(&lt;br /&gt;    function(status) {&lt;br /&gt;         alert(status.text);&lt;br /&gt;    }&lt;br /&gt;);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This is great when you want to do something atomically with each status you get back, but absolutely sucks when you want to know when you have completed going through all the statuses.  The above example immediately returns, it is not synchronous, which makes it nearly impossible to know when it is done (maybe some closure could do it, but I couldn't figure it out).&lt;br /&gt;&lt;br /&gt;To combat this I dug into the Twitter @Anywhere API further and found that I can also pass in options to put my own callback for success and error:&lt;br /&gt;&lt;pre class="brush: csharp"&gt;&lt;br /&gt;User.current.homeTimeline({count:200, rpp:3, since_id:171981, max_id:181981, success:my_success_method, error:my_error_method});&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now your callbacks will be called and you will see the "data" element passed in to your callback has an "array" element, which contains all the returned results.  Now you can go through your results at your leisure, and know when you are done to fire off other methods.&lt;br /&gt;&lt;br /&gt;&lt;div&gt;That is pretty much all I have found out thus far, but it is enough to do quite a bit of work with the Twitter @Anywhere Javascript API.  As always cheers and jeers in the comments...&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4276667104978676221-7012428643648006715?l=www.wisejive.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.wisejive.com/feeds/7012428643648006715/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.wisejive.com/2010/06/twitter-anywhere-javascript-api-exposed.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/7012428643648006715'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/7012428643648006715'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/2010/06/twitter-anywhere-javascript-api-exposed.html' title='Twitter @Anywhere Javascript API exposed'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4276667104978676221.post-549692965104313432</id><published>2010-05-07T12:27:00.001-07:00</published><updated>2010-08-20T08:28:21.087-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='api'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby on rails'/><category scheme='http://www.blogger.com/atom/ns#' term='opengraph'/><category scheme='http://www.blogger.com/atom/ns#' term='facebook'/><category scheme='http://www.blogger.com/atom/ns#' term='oauth'/><title type='text'>Facebook OAuth on Rails..</title><content type='html'>So last night I took on trying to integrate &lt;a href="http://developers.facebook.com/docs/authentication/"&gt;Facebook's new OAuth&lt;/a&gt; authorization in &lt;a href="http://www.rubyonrails.com/"&gt;Ruby On Rails&lt;/a&gt;, and in this post I am going to try and give everyone some idea of how to do this.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In the past I have use &lt;a href="http://github.com/mmangino/facebooker"&gt;facebooker&lt;/a&gt; to integrate with facebook, so I already had a facebook.yml setup with all the configuration information for my facebook app:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;pre class="brush: csharp"&gt;&lt;br /&gt;development:&lt;br /&gt;application_id: XXXXXXXXXXXX&lt;br /&gt;api_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&lt;br /&gt;secret_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&lt;br /&gt;canvas_page_name: blah&lt;br /&gt;callback_url: http://localhost:3000/&lt;br /&gt;pretty_errors: true&lt;br /&gt;set_asset_host_to_callback_url: false&lt;br /&gt;tunnel:&lt;br /&gt;public_host_username:&lt;br /&gt;public_host:&lt;br /&gt;public_port: 4007&lt;br /&gt;local_port: 3000&lt;br /&gt;server_alive_interval: 0&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;The only real difference between this and the normal facebooker file is the addition of the application_id, which is now used in the OAuth authentication.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The first step is to add an endpoint to start the first leg of the OAuth.&lt;/div&gt;&lt;div&gt;&lt;pre class="brush: csharp"&gt;&lt;br /&gt;def new_facebook&lt;br /&gt;facebook_settings = YAML::load(File.open("#{RAILS_ROOT}/config/facebooker.yml"))&lt;br /&gt;redirect_to "https://graph.facebook.com/oauth/authorize?client_id=#{facebook_settings[RAILS_ENV]['application_id']}&amp;amp;redirect_uri=#{APP_URL}/facebook_credentials/facebook_oauth_callback&amp;amp;scope=offline_access,publish_stream"&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;This will redirect you to Facebook, it sets the "client_id" to the "application_id" mentioned above.  This particular redirect also asks Facebook to ask your user to grant some special permissions, namely "offline_access", and "publish_stream" (allow me to access my users accounts anytime, and let me publish to their streams), there are many other permission detailed &lt;a href="http://developers.facebook.com/docs/authentication/permissions"&gt;here&lt;/a&gt;.  Also note that we provided a "redirect_url" to Facebook which Facebook will redirect to once the user grants us access to their Facebook account.  So now we need to create an endpoint for the "redirect_url"&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;pre class="brush: csharp"&gt;&lt;br /&gt;def facebook_oauth_callback&lt;br /&gt; if not params[:code].nil?&lt;br /&gt;     facebook_settings = YAML::load(File.open("#{RAILS_ROOT}/config/facebooker.yml"))&lt;br /&gt;     callback = "#{APP_URL}/facebook_credentials/facebook_oauth_callback"&lt;br /&gt;     url = URI.parse("https://graph.facebook.com/oauth/access_token?client_id=#{facebook_settings[RAILS_ENV]['application_id']}&amp;amp;redirect_uri=#{callback}&amp;amp;client_secret=#{facebook_settings[RAILS_ENV]['secret_key']}&amp;amp;code=#{CGI::escape(params[:code])}")&lt;br /&gt;     http = Net::HTTP.new(url.host, url.port)&lt;br /&gt;     http.use_ssl = (url.scheme == 'https')&lt;br /&gt;     tmp_url = url.path+"?"+url.query&lt;br /&gt;     request = Net::HTTP::Get.new(tmp_url)&lt;br /&gt;     response = http.request(request)     &lt;br /&gt;     data = response.body&lt;br /&gt;     access_token = data.split("=")[1]&lt;br /&gt;     url = URI.parse("https://graph.facebook.com/me?access_token=#{CGI::escape(access_token)}")&lt;br /&gt;     http = Net::HTTP.new(url.host, url.port)&lt;br /&gt;     http.use_ssl = (url.scheme == 'https')&lt;br /&gt;     tmp_url = url.path+"?"+url.query&lt;br /&gt;     request = Net::HTTP::Get.new(tmp_url)&lt;br /&gt;     response = http.request(request)        &lt;br /&gt;     user_data = response.body&lt;br /&gt;     user_data_obj = JSON.parse(user_data)&lt;br /&gt;     flash[:notice] = 'Facebook successfully connected.'&lt;br /&gt;     @social_credential = SocialCredential.create_or_find_new_facebook_cred(access_token, session['rsecret'])&lt;br /&gt; end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;Here we are doing a lot of things.  First we make sure that Facebook returns us the "code" parameter, we need this to complete the authentication.  Once we have the code, we then need to request an "access_token" from Facebook.  This was tricky for me, because the url is an SSL/HTTPS url and normal httparty or net/http tricks didn't work.  Eventually I got it worked out as can be seen in the code above.  Once we get the "access_token" we are pretty much done, in this case I go ahead and get the current user's Facebook data by hitting "https://graph.facebook.com/me?access_token=XXXXXXXXXXXXX" and then populate an ActiveRecord object with the data I receive (including the "access_token") for later use.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Keeping the "access_token" is important, because in my case it lets me update the users stream whenever I need to (hence the "offline_access" in the initial request).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I know from my Twitter stream that there is work on wrapping the Facebook OpenGraph API in ruby but I don't know where it stands.  It is a pretty simple api, and won't require much, but it would be nice to have it abstracted some.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Okay, cheers and jeers in the comments please.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4276667104978676221-549692965104313432?l=www.wisejive.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.wisejive.com/feeds/549692965104313432/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.wisejive.com/2010/05/facebook-oauth-on-rails.html#comment-form' title='12 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/549692965104313432'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/549692965104313432'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/2010/05/facebook-oauth-on-rails.html' title='Facebook OAuth on Rails..'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><thr:total>12</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4276667104978676221.post-8290788739347489452</id><published>2010-04-11T19:41:00.000-07:00</published><updated>2010-04-11T20:24:39.674-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='hackintosh'/><category scheme='http://www.blogger.com/atom/ns#' term='OS X'/><category scheme='http://www.blogger.com/atom/ns#' term='iPhone OS 4'/><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><category scheme='http://www.blogger.com/atom/ns#' term='iPad'/><category scheme='http://www.blogger.com/atom/ns#' term='proprietary platform'/><category scheme='http://www.blogger.com/atom/ns#' term='closed platform'/><category scheme='http://www.blogger.com/atom/ns#' term='Apple'/><title type='text'>My (new) Apple Manifesto</title><content type='html'>So the iPad came out and it is a pretty cool looking, interesting device.  Following right on the heels of that was the iphone 4.0 OS announcement with all kinds of goodies and one glaring baddie.  The baddie was Apple becoming even more closed by cutting anything that is not developed in Objective C out of the App Store.  This change coupled with the random App Store rejections prompted me to rethink my philosophy on buying and using Apple products.&lt;br /&gt;&lt;br /&gt;So I have an iPhone, a Mac Book Pro (MBP) and I also have a hackintoshed desktop running Snow Leopard.  I like all of them (although for computers, I really just like OS X).  I plan on keeping them all.  I am also a developer and my philosophy on development is to keep things as open as possible.  Therefore the Apple policies really bug me.&lt;br /&gt;&lt;br /&gt;My decision (I know you have been holding your proverbial breathe) is &lt;span style="font-weight:bold;"&gt;NOT&lt;/span&gt; to boycott Apple products but to devote no time to building software that runs on their proprietary closed platform.  If I want an iPad, or the new iPhone I'll get one, no need for self-denial.  I just won't build anything for their systems, everything I build will be a web application of some sort using HTML 5 or whatever is available.&lt;br /&gt;&lt;br /&gt;Maybe it is a cop-out, as I might still be feeding the beast, but I am not just going to ignore the best products on the market and by building more rich web applications instead of apps, I'll be opening up the Apple products despite what Apple is trying to do.  I certainly will look at all the other options much more thoroughly, as I'd rather be on a more open platform.  Lastly, I will definitely hackintosh more machines (I don't expect to buy ANY more Apple laptops or desktops, as I can hackintosh much cheaper and more powerful machines with no loss of OS X goodness).&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So that is it, my new Apple manifesto, leave the cheers and jeers in the comments.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4276667104978676221-8290788739347489452?l=www.wisejive.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.wisejive.com/feeds/8290788739347489452/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.wisejive.com/2010/04/my-new-apple-manifesto.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/8290788739347489452'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/8290788739347489452'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/2010/04/my-new-apple-manifesto.html' title='My (new) Apple Manifesto'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4276667104978676221.post-4529767651177365207</id><published>2010-04-10T17:14:00.000-07:00</published><updated>2010-06-28T13:28:34.456-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mac finder'/><category scheme='http://www.blogger.com/atom/ns#' term='clone()'/><category scheme='http://www.blogger.com/atom/ns#' term='sortable'/><category scheme='http://www.blogger.com/atom/ns#' term='UI'/><category scheme='http://www.blogger.com/atom/ns#' term='tree'/><category scheme='http://www.blogger.com/atom/ns#' term='jQuery'/><category scheme='http://www.blogger.com/atom/ns#' term='draggable'/><title type='text'>JQuery, OS X like finder and  Sortables fun...</title><content type='html'>So I have been working on a new project where I need to do some advanced web/javascript stuff with jQuery.  Mainly what I am trying to do is emulate the Mac OS X finder behavior for an arbitrary tree structure (given that directory and file structures really are just trees).  I found something that would help me do it, someone had already build a &lt;a href="http://www.nicolas.rudas.info/jQuery/Finder/"&gt;jQuery tree plugin&lt;/a&gt;, but it had its problems.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;First off the plugin makes use of jQuery's clone() function to move elements out of an html tree (html tree consisting of ol/ul's and li's) and into separate div's for the various finder windows.  I don't mind the using of clone(), but it does screw up any processing that wants to manipulate the elements that were cloned by id.  Basically it clones the nodes, so you'll have 2 nodes in the DOM with the same id, and you won't be able to find both with jQuery (it will return the first it encounters).  To get around this you can use classes instead of id's but really in the end there should only be one copy of the elements in the tree, and cloning to me seemed like a hack.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To fix it I first started storing as part of finder viewer div the original parent id (old_parent_id) of where the given tree fragment started:&lt;/div&gt;&lt;pre class="brush: csharp"&gt;&lt;span class="Apple-style-span"   style="font-family:Georgia, serif;font-size:130%;"&gt;&lt;span class="Apple-style-span"  style=" white-space: normal;font-size:16px;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;Finder.prototype.selectItem = function(url,noCache,targets){&lt;br /&gt; var self = this,&lt;br /&gt;  settings = self.settings,&lt;br /&gt;  target = (targets) ? targets[0] : null,&lt;br /&gt;  listItem = (targets) ? targets[1] : null,&lt;br /&gt;  type = (listItem) ? listItem[0].className.match(/(file|folder)/)[0] : 'folder',&lt;br /&gt;  // data will be whatever is found under the current element (listItem)&lt;br /&gt;  data = (url == 'root')&lt;br /&gt;     ? (settings.url) ? null : this.element&lt;br /&gt;     : $('&gt; ul, &gt; ol, &gt; div',listItem).eq(0),//.clone(true),&lt;br /&gt;  url = (url == 'root' &amp;amp;&amp;amp; typeof settings.url === 'string') ? settings.url : url;&lt;br /&gt;&lt;br /&gt;   if(listItem != null) {&lt;br /&gt;  var oldId = listItem.attr("id");&lt;br /&gt;   } else {&lt;br /&gt;    if(oldId == null || oldId == 'undefined') {&lt;br /&gt;     oldId = "";&lt;br /&gt;    }&lt;br /&gt;   }&lt;br /&gt;   ......&lt;br /&gt;     // Append the new data&lt;br /&gt; self.appendNewColumn(url,data,[target,listItem],type,oldId);&lt;br /&gt;   ......&lt;br /&gt;   Finder.prototype.appendNewColumn = function(url,data,targets,type,oldId){&lt;br /&gt;   .......&lt;br /&gt;   // Specify new column, and add necessary attributes&lt;br /&gt; newColumn = $('&lt;div class="ui-finder-column ui-widget-content ui-finder-new-col"&gt;')&lt;br /&gt; // Avoid showing the column when it's not yet ready&lt;br /&gt; // Also, setting display to none makes DOM manipulation a bit faster&lt;br /&gt;  .css('display','none')&lt;br /&gt;  .attr('data-finder-list-id',columnId)&lt;br /&gt;  .attr('data-finder-list-source',url)&lt;br /&gt;  .attr('data-finder-list-level',columnLevel)&lt;br /&gt;  .attr('id', 'data_finder_level_'+columnLevel)&lt;br /&gt;  .attr('old_parent_id', oldId)&lt;br /&gt;  .css('z-index',0); // Keep beneath other columns&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Then I made sure that in contrast to before these changes when we were just removing the cloned items, we now are careful about putting the cloned items back where they started.&lt;br /&gt;&lt;pre class="brush: csharp"&gt;&lt;br /&gt;Finder.prototype.select = function(target,noCache,actionType) {&lt;br /&gt;......&lt;br /&gt;// Remove visible lists which should not be visible anymore&lt;br /&gt; wrapperLists.each(function(){&lt;br /&gt;  var finderListWrapper = $(this),&lt;br /&gt;   finderListLevel = finderListWrapper.attr('data-finder-list-level');&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  if( finderListLevel &gt;= targetLevel ) {   &lt;br /&gt;   $('.ui-finder-list-item.ui-finder-list-item-active',finderListWrapper)&lt;br /&gt;    .removeClass('ui-finder-list-item-active ' + classesActive );  }&lt;br /&gt; &lt;br /&gt; &lt;br /&gt;  if( finderListLevel &gt; targetLevel ) {&lt;br /&gt;   debug(finderListWrapper.attr("old_parent_id"));&lt;br /&gt;   if(finderListWrapper.attr("old_parent_id") != null &amp;amp;&amp;amp; finderListWrapper.attr("old_parent_id") != 'undefined' &amp;amp;&amp;amp; finderListWrapper.attr("old_parent_id").length &gt; 0) {&lt;br /&gt;    debug(finderListWrapper.children().find("ul"));&lt;br /&gt;    var old_parent = $("#"+finderListWrapper.attr("old_parent_id"));&lt;br /&gt;    debug("old_parent: "+old_parent);&lt;br /&gt;    finderListWrapper.find("div.ui-finder-content").children().each(function() {&lt;br /&gt;     try {&lt;br /&gt;          var child = $(this);&lt;br /&gt;          // prepend is important b/c the finder code looks for the first ul/li/div when rendering&lt;br /&gt;          // and therefore this stuff needs to go back to the top of the old_parent's children&lt;br /&gt;          old_parent.prepend(child);&lt;br /&gt;     } catch(e) {&lt;br /&gt;      debug(e);&lt;br /&gt;     }&lt;br /&gt;    });&lt;br /&gt;   }&lt;br /&gt;   finderListWrapper.remove();&lt;br /&gt;  }&lt;br /&gt; });&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;and presto we are no longer cloning the nodes and we have the same behaviour as when we did clone().  Note that this code is not thoroughly tested, but once it is I'll submit it back to the jQuery tree guy to see if he wants to incorporate the changes.  Also note, that I tried to indicate where everything goes by using function names and "...." to represent, skips, I couldn't really give line numbers as they have all changed in my version.&lt;br /&gt;&lt;br /&gt;So now that I had the cloning stuff straightened out I needed to implement drag and drop on the finder so I could drag things from one part of the finder tree representation to another.  To do that I setup my ul's to be sortables in the finder code after a new column was added (in order to ensure that all columns are always sortable):&lt;br /&gt;&lt;pre class="brush: csharp"&gt;&lt;br /&gt;Finder.prototype.appendNewColumn = function(url,data,targets,type,oldId){&lt;br /&gt;.....&lt;br /&gt;//Append new column&lt;br /&gt;// Plain DOM scripting used as opposed to jQuery as it's faster&lt;br /&gt;self.wrapper[0].appendChild(newColumn[0]);&lt;br /&gt;&lt;br /&gt;newColumn[0].appendChild($(data)[0]);&lt;br /&gt;$(newColumn[0]).find(".child_container").sortable({&lt;br /&gt; connectWith: '.child_container',&lt;br /&gt; appendTo: $(".ui-finder-container"),&lt;br /&gt; opacity: .5,&lt;br /&gt;    zIndex: 100,&lt;br /&gt;    helper: 'clone',&lt;br /&gt;    revert: true,&lt;br /&gt;    scrollElements: '.ui-finder-content',&lt;br /&gt; receive: function(event, ui) {&lt;br /&gt;  debug(ui.sender);&lt;br /&gt;  debug($(this));&lt;br /&gt;  debug(ui.item);&lt;br /&gt;  var sender_id = ($(this)).attr("id").split("_")[2];&lt;br /&gt;  var other_id = $(ui.item).attr("id").split("_")[1];&lt;br /&gt;  debug(sender_id+" "+other_id);&lt;br /&gt;  if(sender_id != other_id) {&lt;br /&gt;   if(ui.sender.attr("id").indexOf("child_container_") &gt; -1) {&lt;br /&gt;    $(ui.sender).find(ui.item.attr("id")).remove();&lt;br /&gt;   } else if(ui.sender.attr("id").indexOf("li") &gt; -1){&lt;br /&gt;    ui.item.remove();&lt;br /&gt;   }&lt;br /&gt;  } else {&lt;br /&gt;   debug("attempting to cancel");&lt;br /&gt;   $(ui.sender).sortable('cancel');&lt;br /&gt;   display_message("You dragged a parent onto a child, which would remove the parent and child from the interaction entirely");&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;});&lt;br /&gt;$(newColumn[0]).disableSelection()&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This snippet sets up the child container to be sortable, and sets up some of the options for &lt;a href="http://jqueryui.com/demos/sortable/"&gt;sortables&lt;/a&gt;.  I need to work on making this an option in the finder code, as this is quite specific to what I am doing...  Key options are the helper:'clone', which must be this way to drag across the finder viewer div's (if helper is not clone the element can only be dragged within its parent div).  Also appendTo: $(".ui-finder-container"), is important, because it tells jQuery to append the cloned helper to the upper container that contains all the finder viewer div's, again this is needed so we can drag across the finder viewer areas.  Finally connectWith: '.child_container' tells jQuery that we can drop the element on any other sortable list with a class of 'child_container', which all my ul's are classed as in my finder viewer areas.&lt;br /&gt;&lt;br /&gt;The last piece to the puzzle is the receive function which calls back anytime a sortable element is dropped on another sortable element.  In here I do some work to figure out if the place where the element that is dropped is a child of the original element, and I then revert the change if so.  I do this because I don't want to allow an element to be dropped onto itself basically.&lt;br /&gt;&lt;br /&gt;The next thing I had to do to get this going was hack up the jQuery sortables code to use more than one element as a drop zone and scroll all the dropzones.   I changed the _mouseDrag method to use the connectWith selector to find all the dropzones and loop through them to scroll any that are appropriate:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: csharp"&gt;&lt;br /&gt;_determineScroll:function(event, scrollParent, o) {&lt;br /&gt; if(scrollParent[0] != document &amp;&amp; scrollParent[0].tagName != 'HTML') {&lt;br /&gt;  &lt;br /&gt;  if((this.overflowOffset.top + scrollParent[0].offsetHeight) - event.pageY &lt; o.scrollSensitivity)&lt;br /&gt;   scrollParent[0].scrollTop = scrolled = scrollParent[0].scrollTop + o.scrollSpeed;&lt;br /&gt;  else if(event.pageY - this.overflowOffset.top &lt; o.scrollSensitivity)&lt;br /&gt;   scrollParent[0].scrollTop = scrolled = scrollParent[0].scrollTop - o.scrollSpeed;&lt;br /&gt;&lt;br /&gt;  if((this.overflowOffset.left + scrollParent[0].offsetWidth) - event.pageX &lt; o.scrollSensitivity)&lt;br /&gt;   scrollParent[0].scrollLeft = scrolled = scrollParent[0].scrollLeft + o.scrollSpeed;&lt;br /&gt;  else if(event.pageX - this.overflowOffset.left &lt; o.scrollSensitivity)&lt;br /&gt;   scrollParent[0].scrollLeft = scrolled = scrollParent[0].scrollLeft - o.scrollSpeed;&lt;br /&gt;&lt;br /&gt; } else {&lt;br /&gt;&lt;br /&gt;  if(event.pageY - $(document).scrollTop() &lt; o.scrollSensitivity)&lt;br /&gt;   scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);&lt;br /&gt;  else if($(window).height() - (event.pageY - $(document).scrollTop()) &lt; o.scrollSensitivity)&lt;br /&gt;   scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);&lt;br /&gt;&lt;br /&gt;  if(event.pageX - $(document).scrollLeft() &lt; o.scrollSensitivity)&lt;br /&gt;   scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);&lt;br /&gt;  else if($(window).width() - (event.pageX - $(document).scrollLeft()) &lt; o.scrollSensitivity)&lt;br /&gt;   scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);&lt;br /&gt;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt;},&lt;br /&gt;&lt;br /&gt;_mouseDrag: function(event) {&lt;br /&gt;&lt;br /&gt; //Compute the helpers position&lt;br /&gt; this.position = this._generatePosition(event);&lt;br /&gt; this.positionAbs = this._convertPositionTo("absolute");&lt;br /&gt;&lt;br /&gt; if (!this.lastPositionAbs) {&lt;br /&gt;  this.lastPositionAbs = this.positionAbs;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; //Do scrolling&lt;br /&gt; if(this.options.scroll) {&lt;br /&gt;  var o = this.options, scrolled = false;&lt;br /&gt;  var currentElements = [];&lt;br /&gt;  if(this.options.scrollElements != null) {&lt;br /&gt;   currentElements = $(this.options.connectWith);&lt;br /&gt;  }&lt;br /&gt;  for(var j = 0; j &lt; currentElements.length; j++) {&lt;br /&gt;   var scrollParent = $(currentElements[j]).scrollParent();&lt;br /&gt;   this._determineScroll(event, scrollParent, o);&lt;br /&gt;  }&lt;br /&gt;  // and do the the helper's scroll parent (default)&lt;br /&gt;  this._determineScroll(event, this.scrollParent, o);&lt;br /&gt;  if(scrolled !== false &amp;&amp; $.ui.ddmanager &amp;&amp; !o.dropBehaviour)&lt;br /&gt;   $.ui.ddmanager.prepareOffsets(this, event);&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; //Regenerate the absolute position used for position checks&lt;br /&gt; this.positionAbs = this._convertPositionTo("absolute");&lt;br /&gt;&lt;br /&gt; //Set the helper position&lt;br /&gt; if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';&lt;br /&gt; if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';&lt;br /&gt;&lt;br /&gt; //Rearrange&lt;br /&gt; for (var i = this.items.length - 1; i &gt;= 0; i--) {&lt;br /&gt;&lt;br /&gt;  //Cache variables and intersection, continue if no intersection&lt;br /&gt;  var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);&lt;br /&gt;  if (!intersection) continue;&lt;br /&gt;&lt;br /&gt;  if(itemElement != this.currentItem[0] //cannot intersect with itself&lt;br /&gt;   &amp;&amp; this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before&lt;br /&gt;   &amp;&amp; !$.ui.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked&lt;br /&gt;   &amp;&amp; (this.options.type == 'semi-dynamic' ? !$.ui.contains(this.element[0], itemElement) : true)&lt;br /&gt;  ) {&lt;br /&gt;&lt;br /&gt;   this.direction = intersection == 1 ? "down" : "up";&lt;br /&gt;&lt;br /&gt;   if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {&lt;br /&gt;    this._rearrange(event, item);&lt;br /&gt;   } else {&lt;br /&gt;    break;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   this._trigger("change", event, this._uiHash());&lt;br /&gt;   break;&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; //Post events to containers&lt;br /&gt; this._contactContainers(event);&lt;br /&gt;&lt;br /&gt; //Interconnect with droppables&lt;br /&gt; if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);&lt;br /&gt;&lt;br /&gt; //Call callbacks&lt;br /&gt; this._trigger('sort', event, this._uiHash());&lt;br /&gt;&lt;br /&gt; this.lastPositionAbs = this.positionAbs;&lt;br /&gt; return false;&lt;br /&gt;&lt;br /&gt;},&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I am going to try and submit this to jQuery as a enhancement to the current scrollables functionality, as I can see this being useful for a lot of folks (in terms of scrolling multiple drop zones).&lt;br /&gt;&lt;br /&gt;So that is about it, I now have a working, non-cloning finder tree viewer along with drag and drop working between the finder viewer areas, and even some rudimentary validation (don't drop a element on its own sub-tree).  I should probably spend the time to package this up and give it back to jQuery but I just don't have that much time.  If anyone is interested in doing this, I'll be glad to help.&lt;br /&gt;&lt;br /&gt;Ping me with questions/complaints in the comments...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4276667104978676221-4529767651177365207?l=www.wisejive.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.wisejive.com/feeds/4529767651177365207/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.wisejive.com/2010/04/jquery-sortables-fun.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/4529767651177365207'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/4529767651177365207'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/2010/04/jquery-sortables-fun.html' title='JQuery, OS X like finder and  Sortables fun...'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4276667104978676221.post-2091208459134691111</id><published>2009-10-10T13:46:00.000-07:00</published><updated>2009-10-10T23:02:20.098-07:00</updated><title type='text'>Snow Leopard Hackintosh How to</title><content type='html'>So those of you who follow me on Twitter know I have been working on a hackintosh for a few days now, with limited success... until now!&lt;br /&gt;&lt;br /&gt;I have a fully working hackintosh now, and really it wasn't that hard, it is just assimilating information from too many places.  I am going to remedy this now.&lt;br /&gt;&lt;br /&gt;First things first my hardware:&lt;br /&gt;&lt;br /&gt;Motherboard: Gigabyte, EP-UD4P&lt;br /&gt;VideoCard: Galaxy GTS-250 (512MB)&lt;br /&gt;&lt;br /&gt;To start follow the directions at &lt;a href="http://lifehacker.com/5360150/install-snow-leopard-on-your-hackintosh-pc-no-hacking-required"&gt;lifehacker&lt;/a&gt;.  I mean follow them to the "T".  First time I tried, I used a USB harddrive instead of USB Memory stick, I recommend the stick (much faster to install).  If your BIOS looks different, that is okay, key on the following:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Change settings away from IDE to AHCI&lt;/li&gt;&lt;li&gt;Change the boot order to boot your thumb drive first&lt;/li&gt;&lt;li&gt;Change your power settings as in the lifehacker article&lt;/li&gt;&lt;/ul&gt;If you follow the instructions above, then you should be able to boot into (a somewhat crippled) hackintosh.&lt;br /&gt;&lt;br /&gt;Now, your network won't work (onboard at least, a PCI NIC may work), your display will not be very good (no dual monitors and no way to adjust resolutions).  You may even get some kernel panics (black code shows up on the side of your screen and the system is completely crashed and needs a reboot).&lt;br /&gt;&lt;br /&gt;First things first, lets get the video card working.&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;The thread I found the good kext (mac equivalent of a driver) for the GTS-250 is &lt;a href="http://www.insanelymac.com/forum/lofiversion/.../t113489.html"&gt;here&lt;/a&gt; and the actual driver dmg is &lt;a href="http://rapidshare.com/files/217112530/GeForce_GTS_250.dmg.html"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Also get kext_utility &lt;a href="http://www.insanelymac.com/forum/index.php?showtopic=140647"&gt;here&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Copy the driver and kext_utility  over to your new hackintosh (I used a USB stick to do this).&lt;/li&gt;&lt;li&gt;On your hackintosh, install the kext_utility (open the dmg, drag to Applications)&lt;/li&gt;&lt;li&gt;Open the driver dmg, get the appropriate driver (512MB for me).&lt;/li&gt;&lt;li&gt;Drag the driver onto kext_utility.&lt;/li&gt;&lt;li&gt;Enter your password&lt;/li&gt;&lt;li&gt;Let it install&lt;/li&gt;&lt;/ol&gt;Now we are going to make sure that when we boot up we will refresh the drivers (kexts).&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Under either your USB Hackintosh drive or your Local drive (depending on whether you still need the USB stick to boot or not, see the lifehacker article for details) under /Extra.&lt;/li&gt;&lt;li&gt;Edit the apple.com.boot.plist file.&lt;/li&gt;&lt;li&gt;Under kernel flags (it will be empty), add "-v -f -x32" (not including quotes).  For more on boot flags see this &lt;a href="http://apple2pc.blogspot.com/2008/02/darwin-boot-options.html"&gt;blog post&lt;/a&gt;.  Briefly the flags say , boot verbose (you'll see lots of text), force refresh of driver cache, and run in 32 bit mode.&lt;/li&gt;&lt;li&gt;Reboot your system&lt;/li&gt;&lt;/ol&gt;Now you should get better video settings (dual monitors, more resolution options).  Lets see about getting the onboard network card working now.&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Get the kext &lt;a href="http://www.psystar.com/opensource/r1000"&gt;here&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Copy over to your hackintosh (again using a USB stick or the like).&lt;/li&gt;&lt;li&gt;Drag onto kext_utility, enter password and install.&lt;/li&gt;&lt;li&gt;Reboot your system&lt;/li&gt;&lt;/ol&gt;After reboot check your network preferences, should be working.&lt;br /&gt;&lt;br /&gt;Now sometime during doing this stuff you may get kernel panics (see above).  I got them and they were all related to mdwarm or mdworker, which Googling quickly told me was a problem with spotlight (which indexes the disks and obviously was having a problem warming its indices).  I don't care about spotlight, and I found out how to turn it off &lt;a href="http://buildahackintosh.com/2009/09/13/guest-guide-snow-leopard-core-i7-hackintosh/"&gt;here&lt;/a&gt; (note this link is a good resoure, though he installed differently than the lifehacker article).  So specifically to turn off spotlight if you are getting kernel panics:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Boot to single user mode (this took me a stupidly long time to figure out, but in the chameleon boot loader, click any key on the boot screen, then highlight your hackintosh, and press the down-arrow key, it'll give you options, one of which is single user, select that one and hit enter).&lt;/li&gt;&lt;li&gt;You'll get a unix #root prompt, mount the hard disk (right above the prompt OSX kindly gives that command verbatim)&lt;/li&gt;&lt;li&gt;Now we can turn off spotlight indexing, type: &lt;strong&gt;mdutil –a –I off&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;Reboot, indexing is now off and I stopped getting panics&lt;/li&gt;&lt;/ol&gt;So now you should be setup (I was) so start installing all your favorite mac software.&lt;br /&gt;&lt;br /&gt;Once you are setup, you probaby want to remove some of your boot flags (see above) from the com.apple.boot.plist file.  I left -v in, b/c I like to see what is going on during boot, but you can remove all, &lt;span style="font-weight: bold;"&gt;except -x32, I suggest you keep that&lt;/span&gt; (the kernel is smart it runs any 64bit program in 64 bit mode even with -x32 set).&lt;br /&gt;&lt;br /&gt;One last note: the one other good boot flag to know is -x (not -x32 that is different), which boots into "safe mode", if you get kernel panics and what I said above doesn't work, try getting into safe-mode and then fixing permissions (disk utility&gt;verify permissions).&lt;br /&gt;&lt;br /&gt;There you have it, I am now watching ESPN360 football with 64 bit cocoa eclipse running while itunes churns away making me a genius list!  I'll report back with anymore instability here.&lt;br /&gt;&lt;br /&gt;Update:&lt;br /&gt;&lt;br /&gt;So tonight I fought with Samba, and I won!  Problem actually wasn't Samba at all, but the fact that one more hackintosh patch was needed.  When I would try to get Samba to run (or some other programs too), I would get the following (in the logs or command line):&lt;br /&gt;&lt;br /&gt;_CFGetHostUUIDString: unable to determine UUID for host. Error: 35&lt;br /&gt;&lt;br /&gt;I found the fix &lt;a href="http://sneosx86.freeflux.net/blog/archive/2007/11/11/_cfgethostuuidstring-final-extension-fix.html"&gt;here&lt;/a&gt; basically grab the file at the top of the page, then drop it in the directory he says.  Then, and this is important, open disk utility, and fix permissions (if you don't when you come back up your network will not work).  Reboot and boom samba ran just fine.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4276667104978676221-2091208459134691111?l=www.wisejive.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.wisejive.com/feeds/2091208459134691111/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.wisejive.com/2009/10/snow-leopard-hackintosh-how-to.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/2091208459134691111'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/2091208459134691111'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/2009/10/snow-leopard-hackintosh-how-to.html' title='Snow Leopard Hackintosh How to'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4276667104978676221.post-2947568678126492390</id><published>2009-08-01T10:10:00.001-07:00</published><updated>2010-06-28T13:29:28.869-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='scheduled'/><category scheme='http://www.blogger.com/atom/ns#' term='Heroku'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby on rails'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='jobs'/><category scheme='http://www.blogger.com/atom/ns#' term='DelayedJob'/><category scheme='http://www.blogger.com/atom/ns#' term='tasks'/><title type='text'>Heroku and  jobs on a timer...</title><content type='html'>So in the intervening time since my last post, I started a new project in &lt;a href="http://rubyonrails.org/"&gt;Ruby on Rails&lt;/a&gt; and I have to say I am enjoying the framework .  Getting used to Ruby wasn't as hard as I thought although I am sure I am writing Ruby too much like Python :)&lt;br /&gt;&lt;br /&gt;One of the great things about using Ruby is the plethora of hosting options as compared to Python Turbogears (where my own server was pretty much the only option).  For the current project I am working I chose to take a look at hosting with &lt;a href="http://heroku.com/"&gt;Heroku&lt;/a&gt; which is what I guess I'd call a hybrid PAAS (Platform As A Service).  Hybrid b/c it lets you use a database unlike Google App Engine, but it does not give you root access to a machine.  Basically Heroku expects you to be using rails, and lets you do all the "railsy" things you would expect like running rake tasks.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Heroku has been great, though there are two things I'll point out for those intersted&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Heroku only supports Postgres, so start your project with it!  I didn't, but I also wasn't too far along so was able to port my data over from Mysql fairly easily.&lt;/li&gt;&lt;li&gt;If you are running Windows, be prepared for a bit of fight.  &lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;(2) may be more of a Rails thing than Heroku, but a &lt;span style="font-weight: bold;"&gt;LOT &lt;/span&gt;of the Rails plugins expect you to be running *nix  Point in fact a lot of them expect a full *nix build environment.   So, my advice, make sure you are running &lt;a href="http://www.cygwin.com/"&gt;Cygwin&lt;/a&gt;, and specifically make sure you pull down basically all the DB libraries (to build both the &lt;a href="http://www.postgresql.org/"&gt;Postgres &lt;/a&gt;and the &lt;a href="http://www.mysql.com/"&gt;Mysql&lt;/a&gt; adapters I needed them, and to build &lt;a href="http://adam.blog.heroku.com/past/2009/2/11/taps_for_easy_database_transfers/"&gt;Taps&lt;/a&gt;, a DB migration library Heroku uses, you'll need &lt;a href="http://www.sqlite.org/"&gt;Sqllite3&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;Nugget: Once you have all these libs, you may get the following error from Cygwin:  Unable to remap [libary name], do the following&lt;br /&gt;&lt;br /&gt;1.) Shutdown all bash shells and X windows&lt;br /&gt;2.) In command prompt run "ash rebaseall"&lt;br /&gt;3.) Wait to finish&lt;br /&gt;4.) Profit (I mean you should be good to go)&lt;/blockquote&gt;So once I finally got all that Windows specific stuff put to bed I got my application up and running on Heroku, but I needed something that would schedule and run a task every minute.  Heroku offers a &lt;a href="http://docs.heroku.com/cron"&gt;Cron Job API&lt;/a&gt;, but the most often it will run is every hour, which is not sufficient for my needs.  However Heroku does offer the &lt;a href="http://docs.heroku.com/background-jobs"&gt;DelayedJob&lt;/a&gt; plugin for Rails, could I somehow use that to accomplish the same task?&lt;br /&gt;&lt;br /&gt;Short answer is yes.  DelayedJob offers an input parameter called "run_at" which allows you to not only delay the job but schedule when it should run.  By some clever utilization of this we are able to run a job every minute.  How?  Here's the code.&lt;br /&gt;&lt;br /&gt;First off we need to add the first delayed job when the application starts up, so in environment.rb add the following right before the end of the "Rails::Initializer.run do |config|" block.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: csharp"&gt;&lt;br /&gt;config.after_initialize do&lt;br /&gt;Delayed::Job.all.each do |old_job|&lt;br /&gt; old_job.destroy&lt;br /&gt;end&lt;br /&gt;Delayed::Job.enqueue SocialChecker.new(nil), 3&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This will delete any old jobs that are hanging around in the database (this is important, you'll see why later) and queues up a new job to be run immediately (I didn't set the run_at time for this job).&lt;br /&gt;&lt;br /&gt;Now in the "perform" method of my SocialChecker class (that is how DelayedJob works, you must have a perform method in the class you want to enqueue, and that method is called when the job is run) I have the following.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: csharp"&gt;&lt;br /&gt;def perform&lt;br /&gt;log_info("Checking: %s" % Time.now)&lt;br /&gt;check()&lt;br /&gt;new_time = Time.now.advance(:minutes =&gt; 1)&lt;br /&gt;Delayed::Job.enqueue SocialChecker.new(nil), 3, new_time&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This little block performs the "check()" method, which is the meat of the things I actually want to do on a one minute timer.  The important part is after the "check()" method, here I create a Time object and make it one minute later than the current time.  Then I queue up a new job setting the run_at to the one-minute-later Time object.  So what this does is it always adds a new job to the DelayedJob queue and that new job is always one minute in the future, so when the "new" job gets executed, it'll add yet another new job to the queue one minute in the future and on and on....  The overall effect is of a scheduled task running every minute (in my case, but just change "Time.now.advance(:minutes =&gt; 1)" to whatever you need.&lt;br /&gt;&lt;br /&gt;So lets talk potential problems.  First off I think one can see why I added the code to delete leftover jobs on initialize, because if there are leftover jobs, when they run each of them will add a new Job for a minute later so we could get into a situation where we would get a Job explosion.  The deletion code just keeps us clean on startup.  To be safe one could add that code block into the perform method as well, to also make sure we are clean before we add another job.&lt;br /&gt;&lt;br /&gt;One other problem is the potential failure of the perform task (assuming the error is not caught, which of course it should be), one might think this would mean that our future jobs wouldn't get pushed into the queue and we'd lose our scheduled task.  That is partially right, if the perform task keeps failing then we would not push a new Job into the queue, &lt;span style="font-weight: bold;"&gt;but&lt;/span&gt; one of the great things about DelayedJob is that it will retry your Jobs with an exponential backdown.  So as long as your job doesn't continuously fail (which of course is a much bigger issue), the schedule task should recover, eventually.&lt;br /&gt;&lt;br /&gt;Note that to use background tasks in Heroku one must pay $15/month and Heroku will give you "1 thread" to consume your background jobs.  So if you have a lot of scheduled jobs in mind then they might conflict this might not work that well.  My hope would be that eventually Heroku will allow us to purchase more threads to consume the jobs, allowing this process to scale.&lt;br /&gt;&lt;br /&gt;So one thing I am bit worried about is, because of their interesting mechanisms for sharing load among servers, whether Heroku wants people doing these short time-scale Jobs.  I am heartened by their documentations saying that one gets a "thread" to perform your Jobs, so my assumption is once they give you a thread it'll hang around and you may as well use it. Hopefully someone from Heroku will get back to me if there is a huge problem with this, it is not my intention to bypass their restrictions, at least not if they are there for a good reason ;)&lt;br /&gt;&lt;br /&gt;Hope this helps people looking for some Heroku info, please leave some comments or twitter me @jostheim to help improve my code above.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4276667104978676221-2947568678126492390?l=www.wisejive.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.wisejive.com/feeds/2947568678126492390/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.wisejive.com/2009/08/heroku-and-jobs-on-timer.html#comment-form' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/2947568678126492390'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/2947568678126492390'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/2009/08/heroku-and-jobs-on-timer.html' title='Heroku and  jobs on a timer...'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4276667104978676221.post-2315261596957474180</id><published>2009-02-26T12:50:00.000-08:00</published><updated>2009-02-26T13:30:04.167-08:00</updated><title type='text'>Twitter please deal with shortened urls...</title><content type='html'>So this is a pseudo-rant that really isn't a rant, but this is really annoying me.  Twitter doesn't resolve urls when they are presented in the twitter interface.  This is really annoying.  Why you ask?&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Well for one I am never sure if one of my friends is posting something from their own blog, or some other place I am interested in, or they are posting about something completely unrelated/unintresting.  I know, I know the message is supposed say something about the url, but it doesn't always, and it certainly doesn't let me eliminate sites I don't like (say I don't like &lt;a href="http://www.mashable.com"&gt;Mashable&lt;/a&gt;, not true of course, I can't know that one of my friends just posted a Mashable link).&lt;/li&gt;&lt;li&gt;It is huge security problem.  The links are anonymous, so we have no idea where they go.  As a web professional I can identify some weird links just from there look, but perhaps more importantly I don't think many of the automatic url scanners in the browsers (for phishing, etc.) actually resolve the url to its source, and therefore don't identify malware sites which have been shortened.&lt;/li&gt;&lt;/ol&gt;So what should be done... Very simple, for every url that gets posted to twitter they need to do a HTTP Head, and resolve the url to it's source (&lt;a href="http://www.re-searchr.com"&gt;re-searchr&lt;/a&gt; does this for most of the shortened urls it sees).  Yes this is another step.  Yes it is more load on there servers.  Yes it still should be done.  Note this will &lt;span style="font-weight: bold;"&gt;NOT&lt;/span&gt; effect the 140 character limit, use as short a url as you want in the message, only when the message is displayed should it be expanded.&lt;br /&gt;&lt;br /&gt;The other half of this issue is for the &lt;a href="http://search.twitter.com/"&gt;Twitter search&lt;/a&gt;, try doing a search for http://www.techcrunch.com on Twitter's search engine.  You'll find many fewer results than there really are... that is unless you start getting the shortened versions of the Techcrunch url.  Before you say, well okay I'll get the tinyurl and bit.ly url and a few others and be done with it (and with the explosion of shortened url services, good luck with finding the best shortened urls to use)... Twitter search has a 140 character limit, so good luck doing more than 2 shortened urls.  Additionally good luck doing a wildcard search for http://www.techcrunch.com/* to get all techcrunch posts urls as well, this is impossible with these anonymized urls that hide the actual url structure. &lt;br /&gt;&lt;br /&gt;So there is my rant, and  my evidence, let me know what you think either in the comments or &lt;a href="http://www.twitter.com/jostheim"&gt;@jostheim&lt;/a&gt; on twitter, but for goodness sake don't include a shortened url!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4276667104978676221-2315261596957474180?l=www.wisejive.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.wisejive.com/feeds/2315261596957474180/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.wisejive.com/2009/02/twitter-please-deal-with-shortened-urls.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/2315261596957474180'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/2315261596957474180'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/2009/02/twitter-please-deal-with-shortened-urls.html' title='Twitter please deal with shortened urls...'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4276667104978676221.post-3182610311997044127</id><published>2009-02-09T09:15:00.000-08:00</published><updated>2009-02-09T10:04:29.948-08:00</updated><title type='text'>Searching Socially (not Social Search)</title><content type='html'>As an addendum to my previous post about social search I wanted to talk about one of the other things my project re-searchr.com does which is search socially.  First off I had better come clean and explain what the heck the difference is between social search and searching socially. Here is what they mean to me (and re-searchr):&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;social search: using your social networks &lt;span style="font-weight: bold;"&gt;explicitly&lt;/span&gt; to find the answer to a query.  Example, asking questions to your friends.&lt;/li&gt;&lt;li&gt;searching socially: using your social networks &lt;span style="font-weight: bold;"&gt;to influence&lt;/span&gt; standard web searching.  Example, re-weighting search results based on how many times your friends socially bookmarked sites in the search results&lt;/li&gt;&lt;/ul&gt;Social search is very explicit, while searching socially can be much more subtle.&lt;br /&gt;&lt;br /&gt;I think that searching socially is going to be a big deal in the future.  The best filter for content that one has is through trusted sources, why do I listen to &lt;a href="http://twit.tv/"&gt;This Week in Technology&lt;/a&gt;, because they have proved that I can trust their judgements.  Why do people listen to Warren Buffet? Because he has proven that he is highly trustworthy in the field of investments.  Trusted sources for the bulk of us (who aren't Robert Scoble) can be found in our social networks.  They are people we have made the explicit decision to listen to, probably not because we think they are stupid and untrustworthy, more likely because we value their opinions.&lt;br /&gt;&lt;br /&gt;Once we have established who are trustworthy friends are we can start to mine our relationships and apply it to search.  The very simplest example is to take a default set of search results form google and find out how many times a given search result url has been mentioned by our social networks.  That immediately establishes more value for results that are mentioned more than for results that are mentioned less.  Why?  Well, not simply because the results were mentioned more, but because they were mentioned more by &lt;span style="font-weight: bold;"&gt;people we trust&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;This simple example is just the beginning we can start to mine &lt;span style="font-weight: bold;"&gt;sentiment&lt;/span&gt; out of our friends comments and then define not just mentions but good and bad mentions for re-ranking.  We can also look at how many times a domain was mentioned for more aggregate rankings.  We can expand out a shell around our friends, going layer by layer through the social graph for more information.&lt;br /&gt;&lt;br /&gt;The important thing to note here is that searching socially will require nothing more from the searcher, than having affiliated social networks and giving access to them.  This kind of interaction is ideal as most users (self-included) are lazy and do not want to have to do much work to start getting advantages.&lt;br /&gt;&lt;br /&gt;To summarize, social search and searching socially are different, and re-searchr is trying to do both.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4276667104978676221-3182610311997044127?l=www.wisejive.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.wisejive.com/feeds/3182610311997044127/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.wisejive.com/2009/02/searching-socially-not-social-search.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/3182610311997044127'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/3182610311997044127'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/2009/02/searching-socially-not-social-search.html' title='Searching Socially (not Social Search)'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4276667104978676221.post-9125873989407733192</id><published>2009-02-07T21:26:00.001-08:00</published><updated>2009-02-07T21:26:52.231-08:00</updated><title type='text'>Minnedemo</title><content type='html'>Some video of me demoing my project re-searchr at Minnedemo.&lt;br /&gt;&lt;br /&gt;&lt;object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="437" height="333" id="viddler"&gt;&lt;param name="movie" value="http://www.viddler.com/player/e1555da9/" /&gt;&lt;param name="allowScriptAccess" value="always" /&gt;&lt;param name="allowFullScreen" value="true" /&gt;&lt;param name="wmode" value="transparent"/&gt;&lt;embed src="http://www.viddler.com/player/e1555da9/" width="437" height="333" type="application/x-shockwave-flash" allowScriptAccess="always" allowFullScreen="true" wmode="transparent" name="viddler" &gt;&lt;/embed&gt;&lt;/object&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4276667104978676221-9125873989407733192?l=www.wisejive.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.wisejive.com/feeds/9125873989407733192/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.wisejive.com/2009/02/minnedemo.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/9125873989407733192'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/9125873989407733192'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/2009/02/minnedemo.html' title='Minnedemo'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4276667104978676221.post-1417976088681596398</id><published>2009-01-17T14:47:00.000-08:00</published><updated>2009-01-17T14:50:19.775-08:00</updated><title type='text'>Social Search and re-searchr</title><content type='html'>I am writing this post to clarify my thoughts on the social search space where &lt;a href="http://www.re-searchr.com/"&gt;my project&lt;/a&gt; stands among all the others out there, what social search means, and my philosophy on where it should go.&lt;br /&gt;&lt;br /&gt;So to start where does social search stand?  Well we have some engines, &lt;a href="http://www.mahalo.com/"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_0"&gt;Mahalo&lt;/span&gt;&lt;/a&gt;, &lt;a href="http://www.wikia.com/"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_1"&gt;Wikia&lt;/span&gt;&lt;/a&gt;, etc (help me out add links the comments).  We have some platforms, &lt;a href="http://developer.yahoo.com/search/boss/"&gt;Yahoo &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_2"&gt;SearchBoss&lt;/span&gt;&lt;/a&gt;, &lt;a href="http://www.google.com/coop/cse/"&gt;Google Custom Search Engines&lt;/a&gt; that well not social search engines are capable of being extended socially. Then we have plays by the major search engines, namely &lt;a href="http://googleblog.blogspot.com/2008/11/searchwiki-make-search-your-own.html"&gt;Google Search Wiki&lt;/a&gt;.  Lets try and review these in turn.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:180%;" &gt;The Sites&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="font-weight: bold;"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_3"&gt;Mahalo&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_4"&gt;Mahalo&lt;/span&gt; is a social search engine in that it lets users of the site build "pages" around search results. Users of &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_5"&gt;Mahalo&lt;/span&gt; can add links, "fast facts", categories and more. &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_6"&gt;Mahalo&lt;/span&gt; recently introduced &lt;a href="http://www.mahalo.com/answers/"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_7"&gt;Mahalo&lt;/span&gt; Answers&lt;/a&gt;, a system by which users can ask questions and &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_8"&gt;Mahalo&lt;/span&gt; users can respond (very similar to &lt;a href="http://answers.yahoo.com/"&gt;Yahoo Answers&lt;/a&gt;), there is a payment system in &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_9"&gt;Mahalo&lt;/span&gt; dollars that can encourage the answering process.&lt;br /&gt;&lt;br /&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_10"&gt;Mahalo's&lt;/span&gt; focus seems to be around the "common query" space. If they can define good pages around common queries then they can handle the bulk of the search traffic and show human parsed, edited and validated links, that in theory should be better than any algorithm for finding good content related to a query. The &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_11"&gt;Mahalo&lt;/span&gt; Answers system ties into this by letting users find out about specific things not covered by the common query space or not covered on a &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_12"&gt;Mahalo&lt;/span&gt; results page.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_13"&gt;Wikia&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_14"&gt;Wikia&lt;/span&gt; has much the same focus as &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_15"&gt;Mahalo&lt;/span&gt;, building &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_16"&gt;wiki's&lt;/span&gt; around common queries. &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_17"&gt;Wikia&lt;/span&gt; does not &lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_18"&gt;currently&lt;/span&gt; have an answer system built into it. Everything else said above for &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_19"&gt;Mahalo&lt;/span&gt; essentially applies.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:180%;" &gt;The Platforms&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;Yahoo Search Boss&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Yahoo search boss is not a social search site, it is an &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_20"&gt;API&lt;/span&gt; into Yahoo's search system that allows social features to be built on top of it. &lt;a href="http://www.oneriot.com/"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_21"&gt;OneRiot&lt;/span&gt;&lt;/a&gt; is a good example of this. &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_22"&gt;OneRiot&lt;/span&gt; is a site where you can see the "popularity" of a given search result &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_23"&gt;overlayed&lt;/span&gt; onto the result. The whole site is driven by the Yahoo Search Boss &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_24"&gt;API&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;To get to the most basic crux, the Yahoo Search Boss &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_25"&gt;API&lt;/span&gt; allows 3rd parties to start appending meta-data to the Yahoo results, including social aspects.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;G&lt;span style="font-weight: bold;"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_26"&gt;oogle&lt;/span&gt; Custom Search Engine&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Google Custom Search Engine's are a different beast than the Yahoo Boss system. Google &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_27"&gt;CSE's&lt;/span&gt; allow builders of the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_28"&gt;CSE&lt;/span&gt; to limit the web that is being searched over. While not social to start, this simple concept allows 3rd parties to build &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_29"&gt;CSE's&lt;/span&gt; for specific users, essentially socially limiting their search. I had a project about 1 year ago that did this, called &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_30"&gt;iPrecis&lt;/span&gt;.com, it turns out to be relatively hard to figure out how to limit a person's search, without a lot of setup by the user (in my opinion).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Big Player's Endeavors&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;Google Search Wiki&lt;/span&gt;&lt;span style="font-size:180%;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;Google Search Wiki is a recently introduced "overlay" on the Google search results page. Basically it allows you to "move" results around for your own &lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_31"&gt;searches&lt;/span&gt;. If I did a search for "James &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_32"&gt;Ostheimer&lt;/span&gt;" and my blog came in at the bottom of the page, I could push it to the top. I could also write a comment about the search result right on the search results page.&lt;br /&gt;&lt;br /&gt;For now Google is not using this user defined info for much (like changing search results ordering). In the future they may integrate this information into their algorithms.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;My Thoughts&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;I think that all of these products have their place. I like the crowd sourcing idea behind &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_33"&gt;Mahalo&lt;/span&gt; and &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_34"&gt;Wikia&lt;/span&gt;. Yahoo Search Boss is creating a lot of innovation with it's full &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_35"&gt;API&lt;/span&gt; into Yahoo search. Google Custom Search Engine, well having a lot of potential, I think is turning out to be somewhat of a dud. It simply hasn't grabbed hold of being a great platform to build on, mostly, &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_36"&gt;imho&lt;/span&gt; b/c it does not allow the reuse of search results in a full fledged manner as Yahoo Search Boss does. Google Search Wiki seems innocuous to me until Google decides to actually use the user data in a meaningful way.&lt;br /&gt;&lt;br /&gt;This all being said, I think there is a huge hole here. All of these services put the macro "crowd" into use here, while &lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_37"&gt;ignoring&lt;/span&gt; the micro "crowd". There has been a rapid emergence of social networking sites that are part of the "live web". &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_38"&gt;Facebook&lt;/span&gt;, Twitter, &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_39"&gt;FriendFeed&lt;/span&gt;, &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_40"&gt;Jaiku&lt;/span&gt;, &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_41"&gt;Identica&lt;/span&gt;... all of these sites are about live or near time interaction with a users social network. The micro "crowd" to an individual user is his/her social networks as defined by whatever services he/she is on. These micro "crowds" can be ten to hundreds to thousands of people, and the key to this "crowd" is that they are trusted more than the macro "crowd" of anyone on the &lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_42"&gt;Internet&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;An example will help illustrate this: Lets say you want to get a new mortgage for your house, you have questions about what type of loan to get (&lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_43"&gt;apropos&lt;/span&gt; I know). You ask a question on &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_44"&gt;Mahalo&lt;/span&gt; about this and get a number of answers, some answers say get an ARM, some not. You ask your &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_45"&gt;Facebook&lt;/span&gt; network the same question, you brother says "if you get an ARM I won't speak to you anymore". Which do you go with?&lt;br /&gt;&lt;br /&gt;So yeah, the above is contrived, but another example, you want to know the best way to fix a memory leak in some Javascript code. Would you go with a stranger on &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_46"&gt;Mahalo&lt;/span&gt; with no knowledge of you or your skill level? Or do you go with the guy you work with who you know is a genius?&lt;br /&gt;&lt;br /&gt;This is the power of micro "crowds", TRUST. You trust your friends, co-workers, family, and even &lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_47"&gt;acquaintances&lt;/span&gt; more than you trust some random person on the &lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_48"&gt;Internet&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;This is a hole re-&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_49"&gt;searchr&lt;/span&gt; is trying to fill. It will try to tie into as many social networks as it can, and become your glue for getting answers to questions. Notice I said glue, re-&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_50"&gt;searchr&lt;/span&gt; is not trying to supplant your social networks. To use a computer analogy, your social networks are the operating system, and re-search just wants to run on top of them.&lt;br /&gt;&lt;br /&gt;Right now re-&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_51"&gt;searchr&lt;/span&gt; is actually doing more than this, because it was originally conceived as a platform for re-analyzing search results. I am right now trying to figure out whether that blends into the micro "crowd" concept or whether I need to split out these products (comments please!).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4276667104978676221-1417976088681596398?l=www.wisejive.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.wisejive.com/feeds/1417976088681596398/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.wisejive.com/2009/01/social-search-and-re-searchr.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/1417976088681596398'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/1417976088681596398'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/2009/01/social-search-and-re-searchr.html' title='Social Search and re-searchr'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4276667104978676221.post-611266385641886955</id><published>2009-01-13T13:02:00.000-08:00</published><updated>2009-01-13T13:56:33.756-08:00</updated><title type='text'>DRY Information Architecture</title><content type='html'>So today at my job I heard from one of the front-end guys that the new Information Architecture involved repeating a subnavigation several places on a single page.  This got me thinking, I, as a developer know that I should do my best to stay DRY (don't repeat yourself), does this same axiom apply to Information Architecture/Design?&lt;br /&gt;&lt;br /&gt;I think it does.  As a developer when I see a pattern form, I know that it is forming because of some end goal that I am needing to produce many times, most likely this is a useful end goal.  I then refactor/rearchitect the code to extract the pattern, make it reusable and extensible (well not always, but if I have my druthers I do). &lt;br /&gt;&lt;br /&gt;The same should go for design.  If we see that we are doing a sub-navigation 3 times on a single page, then don't we think that maybe our whole page might need some work?  Why is the user experience such that the nav has to be 3 places?  Is the page too long?  Is there too much on the page? Would one sub-nav suffice but it is not big enough or doesn't stand out enough?&lt;br /&gt;&lt;br /&gt;My theory (the Ostheimer DRY Design Theory) is that IA/Design are confined to the same DRY theory as software engineering:  if you are repeating yourself a lot and that seems to be the only solution, then something bigger is wrong.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4276667104978676221-611266385641886955?l=www.wisejive.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.wisejive.com/feeds/611266385641886955/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.wisejive.com/2009/01/dry-information-architecture.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/611266385641886955'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/611266385641886955'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/2009/01/dry-information-architecture.html' title='DRY Information Architecture'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4276667104978676221.post-8796579263966286985</id><published>2009-01-13T12:06:00.000-08:00</published><updated>2009-01-13T12:37:45.743-08:00</updated><title type='text'>Somebody make IE extensions easier!!</title><content type='html'>I have written a number of things in the Firefox XUL extension environment, some of which I keep to myself, some of which I publish.  When it comes to actually trying to build a product around an extension (which is a hard sell anyway), having just a firefox extension is a non-starter.  For my latest project &lt;a href="http://www.re-searchr.com"&gt;re-searchr.com&lt;/a&gt; I am exactly in this position, I have a Firefox extension that I have spent a lot of time on, which works fairly well.  However since Firefox is only 22% (and rising) of the browser market share, I can't really get all that far.  I need an IE extension.&lt;br /&gt;&lt;br /&gt;IE extensions are difficult, you have to have &lt;a href="http://msdn.microsoft.com/en-us/vstudio/default.aspx"&gt;Visual Studio&lt;/a&gt; .net and you have to know C# or VisualBasic, Visual C++ or [insert microsoft specific language here].  I don't know any of these languages and do not use Visual Studio, so I am at an extreme disadvantage.  It would take a long time for me to climb the learning curve to make a very good IE extension.  Ideally Microsoft would see this as a bad thing and come out with a more web developer friendly api to IE, but that is not likely to happen.&lt;br /&gt;&lt;br /&gt;So I need work arounds.  My first step and one that would have been a good idea anyway is to port my Firefox extension functionality to a bookmarklet.  My &lt;a href="http://www.re-searchr.com/get_toolbar"&gt;re-searchr toolbar&lt;/a&gt; wants to capture page load events, which is impossible with a bookmarklet, but if I treat the bookmarklet click as a load event I can follow a similar process as the Firefox extension to produce inline analysis to a users search results page (this is what re-searchr does).&lt;br /&gt;&lt;br /&gt;This actually went fine, the only hiccup being cross-site scripting (which in the extension sandbox in Firefox is NOT a security problem), which I solved using JSON and adding "SCRIPT" tags to the page instead of using XMLHTTPRequests.  So now I have partial functionality in a bookmarklet, this is a good thing.  Why?  Well a lot of people don't want to download toolbars, so if you can build a bookmarklet that shows off what your toolbar will do (at least partially) then folks can "try before buy" your toolbar, see if they like the results and then download the toolbar.&lt;br /&gt;&lt;br /&gt;I digress, I still want a IE based extension for re-searchr, and now I have a set of code in a bookmarklet that will properly run in IE (all Firefox specific extension stuff is removed).  This should not be all that hard, what I need is a IE BHO (&lt;a href="http://msdn.microsoft.com/en-us/library/bb250436.aspx"&gt;browser helper object&lt;/a&gt;) that will, at the very least, add my bookmarklet javascript onto a page and run it's "start" function on page load.  This seems like it should be trivial, and if someone could write this BHO then web developers could develop things as JS/HTML/Flash based web apps and plug them into this framework (which just loads javascript onto a tab's page) and vwhala we have an extension environment for IE.  For bonus points:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;If the BHO could also set the security constraints to allow cross domain XMLhttprequests in the context of the javascript that BHO loads (not sure if this is possible) then that would be bonus points, and would really allow for a lot more functionality&lt;/li&gt;&lt;li&gt;The BHO allow attachement to page load events, so that the developer could detect if the page loaded is something he wants to play with (to remove ads, or change content), and then either exit, or load the rest of the javascript (so as decrease the load on the browser, load a bit of js to determine whether to load the rest).&lt;/li&gt;&lt;li&gt;The BHO, could become a full fledged javascript API into the Microsoft IE API, this would ideal and awesome.&lt;/li&gt;&lt;/ol&gt;This seems like an absolute no brainer, download some prebuilt BHO code, with a properties file specifing where the javascript files live (urls, or maybe files in the install package), and the initial callback, fill in the properties, package, and use.  I'd pay a $100 one time fee to get the code that I can then distribute as my extension.&lt;br /&gt;&lt;br /&gt;So why hasn't this happened?  Is this really hard?  Maybe someone will read this and give me some feedback.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4276667104978676221-8796579263966286985?l=www.wisejive.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.wisejive.com/feeds/8796579263966286985/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.wisejive.com/2009/01/somebody-make-ie-extensions-easier.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/8796579263966286985'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/8796579263966286985'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/2009/01/somebody-make-ie-extensions-easier.html' title='Somebody make IE extensions easier!!'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4276667104978676221.post-7968687990951933954</id><published>2009-01-08T17:47:00.000-08:00</published><updated>2010-06-28T13:29:54.880-07:00</updated><title type='text'>Django quickstart</title><content type='html'>So I have been expirementing with Django as a replacement for Turbogears, and as in my previous &lt;a href="http://jostheim.blogspot.com/2008/12/django-and-turbogears-registration.html"&gt;post &lt;/a&gt;Django has been giving me trouble with quickstarting apps.  So I did finally get some stuff rolling and I thought I would share it with the world.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;First off, to setup the registration plugin, follow this prescription to the letter.  Don't easy_install django-registration, that installs the code in your site-packages, we want to put it in your project so you can customize the views.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Definitely setup a base.html (master template). Create a directory /project_root/templates.  Put your master template in /project_root/templates/base.html.  &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;For my base.html I stole the basic turbogears one with some of my own edits:&lt;/div&gt;&lt;pre class="brush: csharp"&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"&lt;br /&gt;"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&amp;gt;&lt;br /&gt;&amp;lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"&amp;gt;&lt;br /&gt;&amp;lt;head&amp;gt;&lt;br /&gt;&amp;lt;title&amp;gt;{% block title %}My amazing site{% endblock %}&amp;lt;/title&amp;gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;style type="text/css"&amp;gt;&lt;br /&gt;    #pageLogin&lt;br /&gt;    {&lt;br /&gt;        font-size: 10px;&lt;br /&gt;        font-family: verdana;&lt;br /&gt;        text-align: right;&lt;br /&gt;   float:right;&lt;br /&gt;   margin-top:5px;&lt;br /&gt;   margin-right:5px;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    a.logo_image img{&lt;br /&gt;     text-decoration:none;&lt;br /&gt;     border-right:none;&lt;br /&gt;     border-left:none;&lt;br /&gt;     border-top:none;&lt;br /&gt;     border-bottom:none;&lt;br /&gt;   float:left;&lt;br /&gt;   padding:5px;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;  ul.menu_list {&lt;br /&gt;   margin: 0px auto;&lt;br /&gt;   list-style-type:none;&lt;br /&gt;   margin-bottom:50px;&lt;br /&gt;  }&lt;br /&gt;  ul.menu_list li {&lt;br /&gt;   display:inline;&lt;br /&gt;   padding-right:115px;&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  ul.menu_list li a {&lt;br /&gt;   text-decoration:none;&lt;br /&gt;   font-weight:bold;&lt;br /&gt;   font-size:110%;&lt;br /&gt;  }&lt;br /&gt;  ul.footer_list {&lt;br /&gt;   margin: 0px auto;&lt;br /&gt;   list-style-type:none;&lt;br /&gt;   padding-top:5px;&lt;br /&gt;   margin-bottom:20px;&lt;br /&gt;   font-size:140%;&lt;br /&gt;  }&lt;br /&gt;  ul.footer_list li {&lt;br /&gt;   display:inline;&lt;br /&gt;   padding-right:15px;&lt;br /&gt;   padding-top:5px;&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  ul.footer_list li a {&lt;br /&gt;   text-decoration:none;&lt;br /&gt;   font-weight:bold;&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  #login_submit {&lt;br /&gt;   text-align:center;&lt;br /&gt;   padding-left:70px;&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  div#menu {&lt;br /&gt;   margin-left:20px;&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  li#first {&lt;br /&gt;   padding-left:20px; &lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  li#last{&lt;br /&gt;   padding-right:20px;&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  div#loginBox a {&lt;br /&gt;   padding-bottom:10px;&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  a.login{&lt;br /&gt;   padding-right:5px;&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;&amp;lt;/style&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;link rel="stylesheet" href="/static/css/style.css" /&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/head&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;body&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;div id="header"&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;a class="logo_image clear" href="/"&amp;gt;&lt;br /&gt;  &amp;lt;img src="" alt="" /&amp;gt;&lt;br /&gt;  &amp;lt;/a&amp;gt;&lt;br /&gt;  &amp;lt;div id="pageLogin"&amp;gt;&lt;br /&gt;      &amp;lt;span&amp;gt;&lt;br /&gt;   &amp;lt;a class="login" href="" &amp;gt;Login&amp;lt;/a&amp;gt;&lt;br /&gt;    &amp;lt;/span&amp;gt;&lt;br /&gt;  &amp;lt;/div&amp;gt;&lt;br /&gt; &amp;lt;/div&amp;gt; &lt;br /&gt;&amp;lt;div id="main_content"&amp;gt;&lt;br /&gt; &amp;lt;div id="menu"&amp;gt;&lt;br /&gt;  &amp;lt;ul class="menu_list"&amp;gt;&lt;br /&gt;   &amp;lt;li id="first"&amp;gt;&amp;lt;a href=""&amp;gt;home&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;   &amp;lt;li&amp;gt;&lt;br /&gt;    &amp;lt;a href=""&amp;gt;menu1&amp;lt;/a&amp;gt;&lt;br /&gt;   &amp;lt;/li&amp;gt;&lt;br /&gt;   &amp;lt;li&amp;gt;&amp;lt;a href=""&amp;gt;menu2&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;   &amp;lt;li&amp;gt;&amp;lt;a href=""&amp;gt;menu3&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;   &amp;lt;li id="last"&amp;gt;&amp;lt;a href=""&amp;gt; menu4&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt; &lt;br /&gt;  &amp;lt;/ul&amp;gt;&lt;br /&gt;  &amp;lt;/div&amp;gt;&lt;br /&gt;&lt;br /&gt;     &amp;lt;div id="content"&amp;gt;&lt;br /&gt;         {% block content %}{% endblock %}&lt;br /&gt;     &amp;lt;/div&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;!-- End of main_content --&amp;gt;&lt;br /&gt;&amp;lt;/div&amp;gt;&lt;br /&gt;&amp;lt;div id="footer" &amp;gt;&lt;br /&gt; &amp;lt;ul class="footer_list"&amp;gt;&lt;br /&gt;  &amp;lt;li&amp;gt;Copyright &amp;amp;copy; 2008&amp;lt;/li&amp;gt;&lt;br /&gt;  &amp;lt;li&amp;gt;&amp;lt;a href=""&amp;gt;Terms of Service&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;  &amp;lt;li&amp;gt;&amp;lt;a href=""&amp;gt;Terms of Service&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt; &lt;br /&gt;  &amp;lt;li&amp;gt;&amp;lt;a href="" &amp;gt;Company&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;  &lt;br /&gt;  &amp;lt;li&amp;gt;&amp;lt;a href=""&amp;gt;Contact&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;  &lt;br /&gt; &amp;lt;/ul&amp;gt;&lt;br /&gt;&amp;lt;/div&amp;gt;&lt;br /&gt;&amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Okay so if we did the registration and setup this base.html, we have the beginnings of a quickstart.  Ideally we would then port all the registration templates that turbogears has over to django, but I haven't had time to do all that.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;As I port the registration templates over I'll post more here.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4276667104978676221-7968687990951933954?l=www.wisejive.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.wisejive.com/feeds/7968687990951933954/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.wisejive.com/2009/01/django-quickstart.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/7968687990951933954'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/7968687990951933954'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/2009/01/django-quickstart.html' title='Django quickstart'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4276667104978676221.post-997421208375786246</id><published>2008-12-18T22:06:00.000-08:00</published><updated>2008-12-18T22:15:53.811-08:00</updated><title type='text'>Mock Testing = Evil?</title><content type='html'>&lt;span class="Apple-style-span"  style=" ;font-family:'Times New Roman';"&gt;&lt;div style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 3px; padding-right: 3px; padding-bottom: 3px; padding-left: 3px; width: auto; font: normal normal normal 100%/normal Georgia, serif; text-align: left; "&gt;I guess today is get off my chest day.  &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I don't like mock testing.  We do it at my job and I pretty much think it is worthless.  Here is my problem with mock testing in a nutshell... what are you actually testing?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;As far as I can tell you are testing that you can read request parameters, and then test whether the flow of the controller is correct for given parameter sets.  That has some uses, but usually is not where bugs are found (b/c we usually can test these things by viewing the page).   &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;More often than not the problems turn out to be in the middleware somewhere, down in the depths where the data is pushed through some business logic and something goes wrong.  Not only are these the bulk of the problems, but they are also &lt;span class="Apple-style-span" style="font-weight: bold;"&gt;the hard problems&lt;/span&gt; to find, diagnose and fix.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Lets face it, if the controller can't to the right thing based on the parameters that is going to be pretty obvious.  But if you are sorting by the wrong date down in the depths after a bunch of data massaging, well that is going to be hard.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I should mention by the way that determining that the parameters match the right flow is a good thing, but why not push the test out to a higher level, instead of mocking the objects, use real objects and check the produced html for expectations.   This test buys you a lot more, it checks the flows working, but also checks your rendering.  While a mock test, would only check one of these things and always takes time and maintenance to &lt;span class="Apple-style-span" style="font-style: italic;"&gt;setup&lt;/span&gt; because YOU maintain your mock objects, while in a html unit test your controller (you know the thing you actually are trying to build!!) does the mock stuff for you.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I'll end this rant with this.  If the goal of testing is to expose bugs before they get to your QA team, then mock testing is not the best use of resources.&lt;/div&gt;&lt;/div&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4276667104978676221-997421208375786246?l=www.wisejive.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.wisejive.com/feeds/997421208375786246/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.wisejive.com/2008/12/mock-testing-evil.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/997421208375786246'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/997421208375786246'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/2008/12/mock-testing-evil.html' title='Mock Testing = Evil?'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4276667104978676221.post-8789506635495957253</id><published>2008-12-17T10:05:00.000-08:00</published><updated>2008-12-17T10:21:46.085-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='registration'/><category scheme='http://www.blogger.com/atom/ns#' term='register'/><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='turbogears'/><title type='text'>Django and Turbogears: The registration problem</title><content type='html'>So I downloaded django a couple weeks ago to get some experience with before I go and get my non-profit on at the &lt;a href="http://overnightwebsitechallenge.com"&gt;http://overnightwebsitechallenge.com&lt;/a&gt; (where I'll be working in django).   I normally work in turbogears and have the&lt;a href="http://patrickhlewis.googlepages.com/registration.html"&gt; turbogears registration egg&lt;/a&gt; installed already for use on my other &lt;a href="http://www.re-searchr.com"&gt;projects&lt;/a&gt;.  &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So here is problem, I installed the &lt;a href="http://www.bitbucket.org/ubernostrum/django-registration/wiki/Home"&gt;django registration eggs&lt;/a&gt;, and wanted to get started with users and registration on django, but I kept getting errors with the import of the registration in settings.py.  &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Well long story short, the problem turns out to be that both django and turbogears want to use 'registration' as the name of the module to import.  With that being the case, whichever egg was installed first will be importing first and therefore be used.  In my case that was the turbogears egg, b/c I installed that first (a long time ago).  So onto the fix... in manage.py:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;import sys&lt;/div&gt;&lt;div&gt;sys.path.insert(0, "yourpathhere/django_registration-0.7-py2.5.egg")&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;That will ensure that the django registration package is pulled in at the top of your python path and will be used instead of the turbogears one.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Problem solved. After 5 hours.&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4276667104978676221-8789506635495957253?l=www.wisejive.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.wisejive.com/feeds/8789506635495957253/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.wisejive.com/2008/12/django-and-turbogears-registration.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/8789506635495957253'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/8789506635495957253'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/2008/12/django-and-turbogears-registration.html' title='Django and Turbogears: The registration problem'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4276667104978676221.post-4973460294385326616</id><published>2008-12-16T08:38:00.000-08:00</published><updated>2008-12-16T08:40:17.523-08:00</updated><title type='text'>Starter Post....</title><content type='html'>Consider this a warning, this blog is going to be mostly about technical things I run into during my working day (which is pretty  much 24 hours a day).  There may be an occasional political post, but for the most part expect python, java and other goodness  (well if we are talking Java then badnesss) to pervade.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4276667104978676221-4973460294385326616?l=www.wisejive.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.wisejive.com/feeds/4973460294385326616/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.wisejive.com/2008/12/starter-post.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/4973460294385326616'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4276667104978676221/posts/default/4973460294385326616'/><link rel='alternate' type='text/html' href='http://www.wisejive.com/2008/12/starter-post.html' title='Starter Post....'/><author><name>Jamie</name><uri>http://www.blogger.com/profile/07322322127591539602</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/__zmTGYsardY/StPZbymvCzI/AAAAAAAAABo/hVTNqGlL6tI/S220/Camera+Pics+290.jpg'/></author><thr:total>0</thr:total></entry></feed>
