<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title>Guillermo Esteves</title>
  <link href="http://blog.gesteves.com/atom.xml" rel="self"/>
  <link href="http://blog.gesteves.com/"/>
  <updated>2012-05-17T07:43:31-04:00</updated>
  <id>http://blog.gesteves.com/</id>
  <author>
    <name>Guillermo Esteves</name>
    
  </author>

  
  <entry>
    <title>I love Chrome’s automatic updates</title>
    <link href="http://blog.gesteves.com/2011/09/25/i-love-chromes-automatic-updates/"/>
    <updated>2011-09-25T17:00:00-04:00</updated>
    <id>http://blog.gesteves.com/2011/09/25/i-love-chromes-automatic-updates</id>
    <content type="html">&lt;p&gt;Last night I signed up for &lt;a href=&quot;http://getclicky.com/239148&quot;&gt;Clicky Web Analytics&lt;/a&gt;, and looking around their site I saw that they offer &lt;a href=&quot;http://getclicky.com/marketshare/&quot;&gt;market share&lt;/a&gt; stats for the major browsers (&lt;abbr&gt;IE&lt;/abbr&gt;, Chrome, Safari, Firefox, and Opera,) both in general, and split by browser version. Looking at Chrome&amp;#8217;s stats, I noticed something interesting in the graph:&lt;/p&gt;

&lt;p class=&#8217;image&#8217;&gt;&lt;img class=&#8217;small &#8217; src=&#8217;http://blog-assets.gesteves.com/images/chrome-market-share.png&#8217; alt=&#8217;Chrome market share&#8217; title=&#8217;Chrome market share&#8217; /&gt;&lt;/p&gt;


&lt;p&gt;Thanks to Chrome&amp;#8217;s silent automatic updates, as soon as a new version is released, the previous one virtually disappears in a matter of days! I&amp;#8217;m sure there are valid arguments against updating software automatically and silently, for example at organizations that need to control what software their employees use, or that need to test existing applications in new browser versions before deploying them; but from a developer&amp;#8217;s point of view I think it&amp;#8217;s awesome, because for all intents and purposes there&amp;#8217;s only one version of Chrome &amp;#8211; the current one. Since older versions aren&amp;#8217;t a big concern, testing in Chrome becomes simpler and easier: there&amp;#8217;s no need to hunt down and keep multiple versions for testing.&lt;/p&gt;

&lt;p&gt;Compare to Internet Explorer, where the four most recent versions coexist, so if it represents a major portion of your visits (and it probably does,) then you&amp;#8217;ll have to support at least two of them: Internet Explorer 8, for the large number of people still running Windows XP; and 9, for those running Windows Vista and 7. Unfortunately, unless dropping XP and Vista is an option, you&amp;#8217;ll probably have to keep supporting them even after Internet Explorer 10 comes out, since &lt;a href=&quot;http://www.pcmag.com/article2/0,2817,2383640,00.asp#fbid=9CphUBgOJbN&quot;&gt;it won&amp;#8217;t support Windows Vista&lt;/a&gt;.&lt;/p&gt;

&lt;p class=&#8217;image&#8217;&gt;&lt;img class=&#8217;small &#8217; src=&#8217;http://blog-assets.gesteves.com/images/internet-explorer-market-share.png&#8217; alt=&#8217;Internet Explorer market share&#8217; title=&#8217;Internet Explorer market share&#8217; /&gt;&lt;/p&gt;


&lt;p&gt;Safari&amp;#8217;s market share behaves a bit like &lt;abbr&gt;IE&lt;/abbr&gt;&amp;#8217;s, inasmuch as it doesn&amp;#8217;t automatically update and the newest version coexists with the older one, but remarkably Safari 5.1 has already overtaken the previous version, just a month after its release with the launch of Lion. Still, until 5.0 is gone, testing in it might be problematic unless you have an older Mac nearby, or a Snow Leopard Server disc you can install in VMware Fusion or Parallels.&lt;/p&gt;

&lt;p class=&#8217;image&#8217;&gt;&lt;img class=&#8217;small &#8217; src=&#8217;http://blog-assets.gesteves.com/images/safari-market-share.png&#8217; alt=&#8217;Safari market share&#8217; title=&#8217;Safari market share&#8217; /&gt;&lt;/p&gt;


&lt;p&gt;&lt;aside&gt;&lt;p&gt;If Firefox 3.6 is a concern for you, and you need or want to test in it, you can &lt;a href=&quot;http://www.mozilla.org/en-US/firefox/all-older.html&quot;&gt;download it here&lt;/a&gt;.&lt;/p&gt;&lt;/aside&gt;&lt;/p&gt;

&lt;p&gt;Firefox, meanwhile, behaves in a combination of both ways. After Firefox 4, which introduced automatic updates, it behaves like Chrome, with the previous version dropping off after a new release; but with a good number of users still on version 3.6, which didn&amp;#8217;t have automatic updates.&lt;/p&gt;

&lt;p class=&#8217;image&#8217;&gt;&lt;img class=&#8217;small &#8217; src=&#8217;http://blog-assets.gesteves.com/images/firefox-market-share.png&#8217; alt=&#8217;Firefox market share&#8217; title=&#8217;Firefox market share&#8217; /&gt;&lt;/p&gt;


&lt;p&gt;And Opera… oh, who cares, I&amp;#8217;m pretty sure Opera&amp;#8217;s market share is composed entirely of developers testing their sites in Opera.&lt;/p&gt;

&lt;p&gt;Anyway, knowing that I can stop worrying about testing in older versions of Chrome (and to a much lesser degree, Firefox and Safari) personally makes my job much easier, but as usual, your mileage may vary. Let your own browser stats be your guide.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Better infinite scrolling with the HTML5 History API</title>
    <link href="http://blog.gesteves.com/2011/09/22/better-infinite-scrolling-with-the-html5-history-api/"/>
    <updated>2011-09-22T16:21:00-04:00</updated>
    <id>http://blog.gesteves.com/2011/09/22/better-infinite-scrolling-with-the-html5-history-api</id>
    <content type="html">&lt;p&gt;Now that &lt;a href=&quot;http://piictu.com&quot;&gt;Piictu&lt;/a&gt; &lt;a href=&quot;http://techcrunch.com/2011/09/22/piictu-launches-grabs-seed-funding-to-grow-its-game-ified-photo-sharing-app/&quot;&gt;finally launched&lt;/a&gt; and is out of beta, I want to write a bit about one of my favorite things I worked on as the front-end web developer there, which is our implementation of an infinite scrolling page improved by the use of the &lt;abbr&gt;HTML5&lt;/abbr&gt; History &lt;abbr&gt;API&lt;/abbr&gt;, the problem it tried to solve, and the solution we arrived at.&lt;/p&gt;

&lt;h2&gt;What&amp;#8217;s Piictu?&lt;/h2&gt;

&lt;p&gt;A bit of background first. In case you haven&amp;#8217;t tried it (and you totally should,) &lt;a href=&quot;http://piictu.com&quot;&gt;Piictu&lt;/a&gt; is an iPhone social photo app that revolves around the concept of &amp;#8220;photo streams&amp;#8221;, or threads of photos by different users on the same subject. For example, you can take a photo of a sandwich, start a stream titled &amp;#8220;eating a sandwich&amp;#8221;, and watch as your friends and followers reply with their own photos of their own sandwiches, or whatever they&amp;#8217;re having for lunch. &lt;a href=&quot;http://itunes.apple.com/us/app/piictu/id439888569?mt=8&amp;amp;ls=1&quot;&gt;Check it out&lt;/a&gt;, there are some incredibly creative games and memes going on over there. It&amp;#8217;s a lot of fun.&lt;/p&gt;

&lt;p&gt;Since these streams could conceivably have hundreds of photos, and we wanted an uninterrupted photo-viewing experience, we immediately decided to implement each photo stream as an infinitely-scrolling page, instead of using regular pagination. However, this concept of streams of thematically-related photos defined one of the main requirements for the design: we never wanted to take a photo out of its context, which meant that when people shared them, we couldn&amp;#8217;t have traditional permalinks with just the one photo. The challenge was to figure out the best way to allow a user to share any photo without taking it out of the context of its stream.&lt;/p&gt;

&lt;h2&gt;The problem with infinite scroll&lt;/h2&gt;

&lt;p&gt;I&amp;#8217;m not a big fan of many sites&amp;#8217; implementations of infinite/endless scroll, and given a choice, I turn it off. Most times it just drives me nuts. For example, in most sites that use it, if my Internet connection goes out or there&amp;#8217;s a server error or my browser crashes, I&amp;#8217;m forced to start back at the top, which I find infuriating if I&amp;#8217;m really deep down the page. Another problem is that I usually can&amp;#8217;t bookmark my position, so if I leave and come back later, I&amp;#8217;ll have to start over. So, in addition to the photo-sharing-on-an-infinite-page problem, I also wanted to tackle these issues, for a better user experience.&lt;/p&gt;

&lt;h2&gt;The old Ajax way&lt;/h2&gt;

&lt;p&gt;My first idea when tackling this problem was a traditional solution using Ajax and fragment identifiers, so we could start the stream of photos at an arbitrary point defined by storing the ID of the desired photo in a &lt;abbr&gt;URL&lt;/abbr&gt; hash (e.g. &lt;code&gt;/stream/123/#/photo/456&lt;/code&gt;.) Since anything after the hash (#) character, or &lt;a href=&quot;http://en.wikipedia.org/wiki/Fragment_identifier&quot;&gt;fragment identifier&lt;/a&gt;, in a &lt;abbr&gt;URL&lt;/abbr&gt; isn&amp;#8217;t sent to the server, this would require passing the photo ID to the server using Ajax, and loading the correct photos in the sequence with JavaScript. To make sharing easier, I wanted the hash fragment to be updated with the ID of the photo currently in the viewport as the user scrolls up and down, so they could share it by simply copying and pasting the &lt;abbr&gt;URL&lt;/abbr&gt;.&lt;/p&gt;

&lt;p&gt;&lt;aside&gt;&lt;p&gt;For further reading about why this approach is not a very good idea, I recommend Tim Bray’s &lt;a href=&quot;http://www.tbray.org/ongoing/When/201x/2011/02/09/Hash-Blecch&quot;&gt;Broken Links&lt;/a&gt;, Jeremy Keith’s &lt;a href=&quot;http://adactio.com/journal/4346/&quot;&gt;Going Postel&lt;/a&gt;, and Mike Davies’s &lt;a href=&quot;http://isolani.co.uk/blog/javascript/BreakingTheWebWithHashBangs&quot;&gt;Breaking the Web with Hashbangs&lt;/a&gt;.&lt;/p&gt;&lt;/aside&gt;&lt;/p&gt;

&lt;p&gt;However, I had a few issues with this approach. The first obvious one was that it doesn&amp;#8217;t degrade gracefully. If the visitor doesn&amp;#8217;t have JavaScript enabled or an error prevents the JavaScript from loading, then the user will get a nice empty page &amp;#8211; probably not the best experience. It also prevents the page from being crawled, not just by Google, but also by Facebook. When sharing or liking a page, Facebook determines what title, description, and thumbnail to display in the News Feed by crawling the page and looking for &lt;a href=&quot;https://developers.facebook.com/docs/opengraph/&quot;&gt;Open Graph&lt;/a&gt; tags, falling back to things like the &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; tag, description meta tags, and other images on the page. On a traditional permalink page like &lt;a href=&quot;http://instagr.am/p/C5f6F/&quot;&gt;Instagram&lt;/a&gt;&amp;#8217;s, it&amp;#8217;s easy, just set the Open Graph tags with the metadata of the one photo in the page. But on a page with a multitude of Ajax-loaded photos, without the server knowing which photo is being requested (remember, the server never gets the hash fragment,) how do you set these tags? If Facebook can&amp;#8217;t see that information for the photo being shared, it wouldn&amp;#8217;t know what to display in the News Feed, undermining what we set out to accomplish in the first place, which was to make it easier for users to share the photos. As an up-and-coming startup looking to get traffic and exposure, this was a real deal-breaker, so I quickly scrapped this solution.&lt;/p&gt;

&lt;h2&gt;A better solution using the &lt;abbr&gt;HTML5&lt;/abbr&gt; History &lt;abbr&gt;API&lt;/abbr&gt;&lt;/h2&gt;

&lt;p&gt;&lt;aside&gt;&lt;p&gt;For more information about the &lt;abbr&gt;HTML5&lt;/abbr&gt; History &lt;abbr&gt;API&lt;/abbr&gt;, read &lt;a href=&quot;http://www.whatwg.org/specs/web-apps/current-work/multipage/history.html&quot;&gt;Session History and Navigation&lt;/a&gt; in the &lt;abbr&gt;HTML5&lt;/abbr&gt; spec, &lt;a href=&quot;https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history&quot;&gt;Manipulating the Browser History&lt;/a&gt; at Mozilla Developer Center, and &lt;a href=&quot;http://diveintohtml5.org/history.html&quot;&gt;Manipulating History for Fun &amp;amp; Profit&lt;/a&gt; in Mark Pilgrim’s &lt;cite&gt;Dive into &lt;abbr&gt;HTML5&lt;/abbr&gt;&lt;/cite&gt;.&lt;/p&gt;&lt;/aside&gt;&lt;/p&gt;

&lt;p&gt;Instead, I decided to use the &lt;a href=&quot;http://www.whatwg.org/specs/web-apps/current-work/multipage/history.html&quot;&gt;&lt;abbr&gt;HTML5&lt;/abbr&gt; History &lt;abbr&gt;API&lt;/abbr&gt;&lt;/a&gt;. Instead of getting the ID of the photo currently in the viewport and using it to change the fragment identifier, I update the &lt;abbr&gt;URL&lt;/abbr&gt; in the address bar by calling the &lt;code&gt;replaceState()&lt;/code&gt; method. The basic idea is this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wait for the &lt;code&gt;scroll&lt;/code&gt; event to fire. (Note that since the &lt;code&gt;scroll&lt;/code&gt; event can fire &lt;em&gt;a lot&lt;/em&gt;, for performance reasons it&amp;#8217;s best to run any code attached to this event after a small delay, using a &lt;code&gt;setInterval&lt;/code&gt;, as per &lt;a href=&quot;http://ejohn.org/blog/learning-from-twitter/&quot;&gt;John Resig&amp;#8217;s recommendation&lt;/a&gt;.)&lt;/li&gt;
&lt;li&gt;When the page has scrolled, get the ID of the top-most photo in the viewport. For this I used the &lt;a href=&quot;http://www.appelsiini.net/projects/viewport&quot;&gt;Viewport Selectors&lt;/a&gt; jQuery plugin, which adds a handy &lt;code&gt;:in-viewport&lt;/code&gt; selector. I also embedded the ID of each photo as a &lt;code&gt;data-photo-id&lt;/code&gt; attribute in their markup, to make it easy to get with JavaScript.&lt;/li&gt;
&lt;li&gt;If the browser supports the History &lt;abbr&gt;API&lt;/abbr&gt;, use &lt;code&gt;replaceState()&lt;/code&gt; to add the photo ID to the base &lt;abbr&gt;URL&lt;/abbr&gt; of the stream page, or remove it, if it&amp;#8217;s the first photo in the stream (i.e. if we scroll back to the top.) The reason I chose to use &lt;code&gt;replaceState()&lt;/code&gt; (which updates the current browser history entry) instead of &lt;code&gt;pushState()&lt;/code&gt; (which adds a new history entry) was because I didn&amp;#8217;t want to have to click &amp;#8220;back&amp;#8221; a bunch of times and go back through every photo just to get to the previous page.&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;An abridged version of the JavaScript code used in Piictu looks somewhat like this (I removed some functionality that wasn&amp;#8217;t really necessary for the History &lt;abbr&gt;API&lt;/abbr&gt; explanation from the code, such as the actual infinite scroll implementation. I hope it&amp;#8217;s clear enough):&lt;/p&gt;

&lt;figure role=code&gt; &lt;div class=&quot;highlight&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre class=&quot;line-numbers&quot;&gt;&lt;span class=&#8217;line-number&#8217;&gt;1&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;2&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;3&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;4&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;5&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;6&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;7&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;8&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;9&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;10&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;11&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;12&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;13&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;14&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;15&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;16&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;17&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;18&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;19&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;20&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;21&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;22&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;23&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;24&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;25&lt;/span&gt;
&lt;span class=&#8217;line-number&#8217;&gt;26&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#8217;code&#8217;&gt;&lt;pre&gt;&lt;code class=&#8217;js&#8217;&gt;&lt;span class=&#8217;line&#8217;&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;scroll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;  &lt;span class=&quot;nx&quot;&gt;did_scroll&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;&lt;span class=&quot;c1&quot;&gt;// Every 250ms, check if the page scrolled&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;&lt;span class=&quot;nx&quot;&gt;setInterval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;did_scroll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;      &lt;span class=&quot;nx&quot;&gt;updatePhotoPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;      &lt;span class=&quot;nx&quot;&gt;loadMorePhotos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Infinite scroll, etc.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;      &lt;span class=&quot;nx&quot;&gt;did_scroll&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;250&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;&lt;span class=&quot;c1&quot;&gt;// Update the URL&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;updatePhotoPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;new_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;in_viewport&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;div.piic:in-viewport&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;replaceState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;in_viewport&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hasClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;original&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;          &lt;span class=&quot;nx&quot;&gt;new_url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;base_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// The original URL of the stream page&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;          &lt;span class=&quot;nx&quot;&gt;new_url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;base_url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;/photo/&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;in_viewport&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;photoId&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;  &lt;span class=&quot;nx&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;replaceState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;new_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;span class=&#8217;line&#8217;&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/figure&gt;


&lt;p&gt;You can see this in action by going to any stream on Piictu, such as this &lt;a href=&quot;http://piictu.com/streams/4df4fcc02d26880001000353&quot;&gt;Hipstamatic&lt;/a&gt; stream I started a few months ago. As you scroll up and down, you&amp;#8217;ll notice that the ID of the photo in the viewport is appended to the &lt;abbr&gt;URL&lt;/abbr&gt; of the stream page, and when you return to the top, it&amp;#8217;s restored to the original &lt;abbr&gt;URL&lt;/abbr&gt; (the &lt;code&gt;base_url&lt;/code&gt; variable in the source code, which is also saved in a &lt;code&gt;data-*&lt;/code&gt; attribute in the markup for easy retrieval.)&lt;/p&gt;

&lt;p class=&#8217;image&#8217;&gt;&lt;img class=&#8217;large screenshot &#8217; src=&#8217;http://blog-assets.gesteves.com/images/piictu-stream.jpg&#8217; alt=&#8217;Screenshot of a Piictu stream page&#8217; title=&#8217;Screenshot of a Piictu stream page&#8217; /&gt;&lt;/p&gt;


&lt;p&gt;So what happens on the server when we request a stream? If we request a plain stream &lt;abbr&gt;URL&lt;/abbr&gt;, such as &lt;code&gt;/streams/123&lt;/code&gt;, the server returns the first few photos normally, starting with the first photo in the stream. If we request a &lt;abbr&gt;URL&lt;/abbr&gt; that contains a photo ID, like &lt;code&gt;/streams/123/photo/345&lt;/code&gt;, the server again returns a few photos, but this time starting at the photo with the specified ID, with an option to load the photos above it, or scroll down to load more photos below. No need to use JavaScript to figure out which photos to show, it&amp;#8217;s all returned directly from the server. Also, the metadata of the photo being requested is also returned as Open Graph tags in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of the page, so when you post &lt;code&gt;/streams/123/photo/345&lt;/code&gt; on Facebook or Google+, they&amp;#8217;ll show the correct thumbnail and caption for that photo. It solves our goal for the photos, which was to help users to easily share them: regardless of whether they use the sharing buttons next to each photo, or simply grab the &lt;abbr&gt;URL&lt;/abbr&gt; from the address bar and paste it in an instant message or their favorite social network, &lt;em&gt;it&amp;#8217;ll just work&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It also alleviates some of my pet peeves with infinite scrolling. Since the &lt;abbr&gt;URL&lt;/abbr&gt; updates automatically as you move up and down the page, you can easily bookmark your position, which is particularly handy on very long streams; and if for whatever reason you&amp;#8217;re forced to reload the page or your browser crashes, you&amp;#8217;ll start where you left off, avoiding the frustration of having to start over (assuming your browser reopens your tabs after a crash.)&lt;/p&gt;

&lt;p&gt;Finally, it degrades somewhat gracefully, as it&amp;#8217;ll show the appropriate photos even if JavaScript is disabled, since JavaScript isn&amp;#8217;t necessary to figure out which photos to load. (I say &amp;#8220;somewhat&amp;#8221; because it doesn&amp;#8217;t yet offer regular pagination as a fallback, but it&amp;#8217;s on the to-do list.)&lt;/p&gt;

&lt;h2&gt;What about Internet Explorer?&lt;/h2&gt;

&lt;p&gt;As always, the biggest issue with using any modern technology is Internet Explorer, since in this case it doesn&amp;#8217;t support the History &lt;abbr&gt;API&lt;/abbr&gt; in versions 9 and below. I briefly worked on a workaround for &lt;abbr&gt;IE&lt;/abbr&gt;, using the ol&amp;#8217; hash fragments as a fallback. In the end we simply decided not to support &lt;abbr&gt;IE&lt;/abbr&gt;, mainly because between January and May, Internet Explorer accounted for only 2.42% of the visits to our signup and teaser page, so the added effort and maintenance it would require seemed counterproductive. In addition, our implementation degrade gracefully in &lt;abbr&gt;IE&lt;/abbr&gt;. The &lt;abbr&gt;URL&lt;/abbr&gt; may not change as the user scrolls, but everything else works properly and sharing photos is still possible, using the Twitter and Facebook buttons. In other words, it simply behaves as a traditional implementation of infinite scroll. Finally, it&amp;#8217;s a temporary situation, as &lt;a href=&quot;http://msdn.microsoft.com/en-us/ie/hh272905#_HTML5History&quot;&gt;Internet Explorer 10 &lt;em&gt;will&lt;/em&gt; support the History &lt;abbr&gt;API&lt;/abbr&gt;&lt;/a&gt;, and it shouldn&amp;#8217;t require any further work. I tested it in the &lt;a href=&quot;http://msdn.microsoft.com/en-us/windows/apps/br229516&quot;&gt;Windows 8 Developer Preview&lt;/a&gt;, which includes a preview version of Internet Explorer 10, and it worked perfectly.&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;I really believe that using the &lt;abbr&gt;HTML5&lt;/abbr&gt; History &lt;abbr&gt;API&lt;/abbr&gt; to augment infinite scrolling offers a superior user experience by alleviating some of the annoyances caused by traditional approaches, such as the lack of bookmarking and sharing. I expect this technique will be used more once Internet Explorer supports the History &lt;abbr&gt;API&lt;/abbr&gt;, but if you&amp;#8217;re willing to live without &lt;abbr&gt;IE&lt;/abbr&gt; support for a bit (or use one of the many &lt;a href=&quot;https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills&quot;&gt;polyfills&lt;/a&gt; available,) it&amp;#8217;s definitely worth giving it a try now.&lt;/p&gt;

&lt;p&gt;Let me know what you think about this; I look forward to your comments and questions, even though I still haven&amp;#8217;t gotten around to adding comments to this blog. In the meantime, feel free to &lt;a href=&quot;https://twitter.com/intent/tweet?text=%40gesteves%20&quot;&gt;tweet @gesteves&lt;/a&gt; or &lt;a href=&quot;http://www.google.com/recaptcha/mailhide/d?k=01CK-3HSgYO0ll-0vD3cKyWw==&amp;amp;c=Oz_FAOl8l-PeBA0djkKF_A==&quot; onclick=&quot;window.open(&#8216;http://www.google.com/recaptcha/mailhide/d?k\07501CK-3HSgYO0ll-0vD3cKyWw\75\75\46c\75Oz_FAOl8l-PeBA0djkKF_A\75\075&#8217;, &#8221;, &#8216;toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300&#8217;); return false;&quot; title=&quot;Reveal this e-mail address&quot;&gt;send me an email&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Hello, World</title>
    <link href="http://blog.gesteves.com/2011/09/19/hello-world/"/>
    <updated>2011-09-19T17:14:00-04:00</updated>
    <id>http://blog.gesteves.com/2011/09/19/hello-world</id>
    <content type="html">&lt;p&gt;After almost four years on &lt;a href=&quot;http://www.tumblr.com&quot;&gt;Tumblr&lt;/a&gt;, I&amp;#8217;ve decided it&amp;#8217;s time to switch blog platforms. My blog now runs on &lt;a href=&quot;http://octopress.org&quot;&gt;Octopress&lt;/a&gt; and &lt;a href=&quot;http://www.heroku.com&quot;&gt;Heroku&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The reason I&amp;#8217;m switching, and the reason I was using Tumblr in the first place, are a bit of a long story. I used to have a real blog, one I built myself in 2005 or so, when I was teaching myself Rails, and which I loved and updated frequently. However, a couple of years later, thanks to Venezuela&amp;#8217;s foreign currency restrictions which forbid us from spending more than US$400 a year in electronic payments, having a self-hosted blog running on paid hosting &amp;#8211; even cheap, shared hosting &amp;#8211; became untenable. Back then I was spending $9 a month at &lt;a href=&quot;http://railsplayground.com/&quot;&gt;Rails Playground&lt;/a&gt; to host my blog, which may not sound like a lot, but it added up to $108 a year &amp;#8211; over a quarter of what the venezuelan government allows me to spend on the Internet in a year. So, in 2008, when Tumblr began to take off in popularity, I decided to cancel my hosting plan, scrap my blog, write a small script to import all my content, and switch to Tumblr.&lt;/p&gt;

&lt;p&gt;It had the advantage of not having to worry about servers or hosting costs while being flexible enough to allow me to tinker with the code and design to my heart&amp;#8217;s content, plus an amazing community that led me to meet some of the best friends I&amp;#8217;ve ever had. However, in the past few months I&amp;#8217;ve become quite dissatisfied with the service and the constant outages and downtime, like &lt;a href=&quot;http://staff.tumblr.com/post/10264121525/outage&quot;&gt;this recent&lt;/a&gt;, ongoing issue. I&amp;#8217;m also a bit uneasy with the content I post over there, because it&amp;#8217;s a strange mix of work/professional stuff and personal posts, reblogs, memes, and inside jokes that probably aren&amp;#8217;t interesting to anyone except my close circle of friends. So I thought it would be better to have a place that&amp;#8217;s just for &lt;a href=&quot;http://gestev.es/AGnN&quot;&gt;serious business&lt;/a&gt;, and leave Tumblr for personal posts, socializing with my friends, and sharing photos of cats. &lt;a href=&quot;http://tumblr.gesteves.com&quot;&gt;All-Encompassing Trip&lt;/a&gt; will keep going at its new address, but this will be my primary blog for now.&lt;/p&gt;

&lt;p&gt;As for my choice of platform, I chose &lt;a href=&quot;http://octopress.org&quot;&gt;Octopress&lt;/a&gt; after reading &lt;a href=&quot;http://mattgemmell.com&quot;&gt;Matt Gemmell&lt;/a&gt; &lt;a href=&quot;http://mattgemmell.com/2011/09/12/blogging-with-octopress/&quot;&gt;rave about it&lt;/a&gt;. I&amp;#8217;ve always liked the idea of having a “&lt;a href=&quot;http://inessential.com/2011/03/16/a_plea_for_baked_weblogs&quot;&gt;baked&lt;/a&gt;” blog (i.e. one that&amp;#8217;s entirely static &lt;abbr&gt;HTML&lt;/abbr&gt;), and I&amp;#8217;ve experimented in the past with things like &lt;a href=&quot;http://nanoc.stoneship.org/&quot;&gt;nanoc&lt;/a&gt;, but Octopress makes it dead simple to set up, generate, and deploy a static &lt;abbr&gt;HTML&lt;/abbr&gt; blog. If you&amp;#8217;re considering starting a blog, and feel comfortable working in the Terminal, I can&amp;#8217;t recommend Octopress enough.&lt;/p&gt;

&lt;p&gt;There are plenty of advantages to the &amp;#8220;baked&amp;#8221; approach. Since there are no slow and expensive database calls, it&amp;#8217;s blazing fast, lighter and more responsive, and it won&amp;#8217;t fall down the first traffic spike it gets &amp;#8211; not that I&amp;#8217;m expecting to get &lt;a href=&quot;http://daringfireball.net&quot;&gt;fireballed&lt;/a&gt; or anything, but for me it also means that it&amp;#8217;s lightweight enough that I can probably get away with running it with a single Heroku dyno for the foreseeable future &amp;#8211; which makes it free. There&amp;#8217;s also a security argument to be made, since there&amp;#8217;s no admin interface to hack, or any chance of &lt;abbr&gt;SQL&lt;/abbr&gt; injection. I also like that it makes backups really simple: the posts live on my computer, so they get backed up to Time Machine and SuperDuper as part of my regular backup process; my Sites folder is symlinked in my Dropbox folder, so there&amp;#8217;s a backup there too; and since the posts are also under source control, everything gets committed to my Github repo as well. Finally, migration is trivial, because it&amp;#8217;s just a bunch of static &lt;abbr&gt;HTML&lt;/abbr&gt; files. Just put them on a new server and it&amp;#8217;ll work. Octopress can even automate the process of copying the files to the server with rsync after writing a post.&lt;/p&gt;

&lt;p&gt;A few other things of note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The theme is mostly built from scratch, based on the design of &lt;a href=&quot;http://www.gesteves.com&quot;&gt;my website&lt;/a&gt;. Unfortunately, I mostly destroyed all the sensible patterns and defaults &lt;a href=&quot;https://twitter.com/#!/imathis&quot;&gt;Brandon&lt;/a&gt; (the creator of Octopress) created for theming it, so I think I&amp;#8217;ll have to do some work rebuilding them to keep the code more organized. I did keep his awesome port of &lt;a href=&quot;http://ethanschoonover.com/solarized&quot;&gt;Solarized&lt;/a&gt; syntax highlighting, though.&lt;/li&gt;
&lt;li&gt;I modified it for deployment to Heroku. This included, on Brandon&amp;#8217;s instructions, removing the &lt;code&gt;public&lt;/code&gt; folder from &lt;code&gt;.gitignore&lt;/code&gt;, but I also added everything &lt;em&gt;but&lt;/em&gt; the &lt;code&gt;public&lt;/code&gt; folder and the config files to &lt;code&gt;.slugignore&lt;/code&gt;, to keep the slug size as small as possible (it clocks in at 460 KB, vs. 4.4 MB for &lt;a href=&quot;http://www.gesteves.com&quot;&gt;my website&lt;/a&gt;&amp;#8217;s slug); and I added a rake task for Heroku deployment, which is mostly a copy of the default Github one, but pushing to Heroku &lt;code&gt;master&lt;/code&gt; instead.&lt;/li&gt;
&lt;li&gt;I wanted to use &lt;a href=&quot;http://www.iawriter.com/&quot;&gt;iA Writer&lt;/a&gt; as my blogging software, so I modified the &lt;code&gt;new_post&lt;/code&gt; rake task so that it calls &lt;code&gt;open #{filename}&lt;/code&gt; at the end, which opens the newly created post in the default editor for Markdown files, which I had previously set to Writer.&lt;/li&gt;
&lt;li&gt;I also symlinked the &lt;code&gt;_posts&lt;/code&gt; folder to the &lt;a href=&quot;http://www.iawriter.com/&quot;&gt;Writer&lt;/a&gt; and &lt;a href=&quot;http://www.secondgearsoftware.com/elements/&quot;&gt;Elements&lt;/a&gt; folders in my Dropbox, so I can theoretically write posts from my iPad and iPhone, although I&amp;#8217;d have to &lt;abbr&gt;SSH&lt;/abbr&gt; into my computer to actually publish them.&lt;/li&gt;
&lt;li&gt;I&amp;#8217;m trying to simplify the process of creating new posts and deploying to Heroku, and integrate it better to the OS. I&amp;#8217;m currently trying to figure out how to add the rake tasks to OS X&amp;#8217;s Services menu, to make it easier to publish posts after writing them. I also replaced some of the &lt;code&gt;puts&lt;/code&gt; calls in the Rakefile with calls to &lt;code&gt;growlnotify&lt;/code&gt;, to get nice Growl notifications on successful deploys and whatnot.&lt;/li&gt;
&lt;li&gt;I still haven&amp;#8217;t decided whether or not I want comments here, or if I want to use &lt;a href=&quot;http://disqus.com/&quot;&gt;Disqus&lt;/a&gt; or &lt;a href=&quot;https://developers.facebook.com/docs/reference/plugins/comments/&quot;&gt;Facebook Comments&lt;/a&gt;. In the meantime, you can &lt;a href=&quot;https://twitter.com/intent/tweet?text=%40gesteves%20&quot;&gt;tweet @gesteves&lt;/a&gt; with any comments.&lt;/li&gt;
&lt;li&gt;I didn&amp;#8217;t import any of the old content from Tumblr; I couldn&amp;#8217;t figure out a good way to do it without breaking a ton of links, since Tumblr&amp;#8217;s permalink format doesn&amp;#8217;t match Octopress&amp;#8217;s. I thought about modifing Octopress&amp;#8217;s permalinks and work something out using my local backup of Tumblr, but instead I opted for a clean break and a fresh start, using a bit of &lt;a href=&quot;http://www.sinatrarb.com/&quot;&gt;Sinatra&lt;/a&gt; code to 301-redirect any traffic looking for a Tumblr permalink to my Tumblr&amp;#8217;s &lt;a href=&quot;http://tumblr.gesteves.com&quot;&gt;new domain&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Anyway, I&amp;#8217;m not entirely sure how much or how often I&amp;#8217;ll write here &amp;#8211; Twitter &amp;amp; Tumblr seem to have atrophied my ability to write more than 140 characters at a time, and writing this post took longer than I care to admit &amp;#8211; but I do hope to at least comment on interesting web design &amp;amp; development resources I find, in the style of &lt;a href=&quot;http://labnotes.org/&quot;&gt;Assaf Arkin&lt;/a&gt;&amp;#8217;s &amp;#8220;Rounded Corners&amp;#8221; series &amp;#8211; which I love &amp;#8211; and maybe get back in the habit of writing well enough to, you know, express, like, opinions and stuff. Wish me luck, and thanks for reading.&lt;/p&gt;
</content>
  </entry>
  
</feed>

