<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff></diff>
      <filename>09_TwitterSpider/air/TwitterSpider.air</filename>
    </modified>
    <modified>
      <diff>@@ -4,6 +4,8 @@
 	Then provides stats
 	
 	Todo:
+		Architecture:
+			refactor to Cairngorm				
 		Application
 			display loading at startup
 		TwitterSpider
@@ -41,7 +43,10 @@
 	xmlns:data=&quot;data.*&quot; 
 	xmlns:account=&quot;account.*&quot;
 	xmlns:tweetList=&quot;tweetList.*&quot;
-	layout=&quot;vertical&quot; xmlns:local=&quot;*&quot;&gt;
+	xmlns:local=&quot;*&quot;	
+	backgroundGradientColors=&quot;[#ffffff, #ffffff]&quot;
+	layout=&quot;vertical&quot;&gt;
+	&lt;mx:Style source=&quot;style/yflexskin.css&quot;/&gt;
 &lt;mx:Script&gt;
 	&lt;![CDATA[
 		import data.TweetDataAggregator;
@@ -57,11 +62,52 @@
 		private var dataAggregator:TweetDataAggregator;
 				
 		private function filterByDates(fromDate:Number, toDate:Number):void {
+			this.fromDate = fromDate;
+			this.toDate = toDate;
 			dataAggregator.aggregateForDateRange(fromDate, toDate);
 			tweets = new ArrayCollection(dataAggregator.getTweetsInTimeRange());
 			list.filter();
 		}
 		
+		private function showFullRange():void {
+	        var dateRange:Object = dataAggregator.getFullDateRange(); 
+			filterByDates(dateRange.fromDate, dateRange.toDate);
+			fullChart.fullDateRange()
+		}
+		
+		static private const MILLIS_TO_DAY:Number = 1000*60*60*24;
+		private function showOneMonth():void {
+	        var dateRange:Object = dataAggregator.getFullDateRange(); 
+	        var spreadDays:Number = (dateRange.toDate - dateRange.fromDate)/MILLIS_TO_DAY;
+	        dateRange.fromDate = dateRange.toDate - ((spreadDays&gt;30) ? 30*MILLIS_TO_DAY : spreadDays*MILLIS_TO_DAY);
+			filterByDates(dateRange.fromDate, dateRange.toDate);
+			fullChart.setFromDate(dateRange.fromDate); 
+		}
+		
+		private function showOneWeek():void {
+	        var dateRange:Object = dataAggregator.getDateRange(); 
+	        var spreadDays:Number = (dateRange.toDate - dateRange.fromDate)/MILLIS_TO_DAY;
+	        dateRange.fromDate = dateRange.toDate - ((spreadDays&gt;7) ? 7*MILLIS_TO_DAY : spreadDays*MILLIS_TO_DAY);
+			filterByDates(dateRange.fromDate, dateRange.toDate);
+			fullChart.setFromDate(dateRange.fromDate); 
+		}
+		private function changeDateRange(range:String):void {
+			if (dataAggregator==null) return;
+			//TODO: implement smart range fixes...i.e. 
+			//       if fromDate can be moved back then move it back for range
+			//	     if toDate needs to be moved forward, do it 
+			//		 if too small...do what you can (i.e. only 20 days there, the show 20 for month)
+			switch(range) {
+				case &quot;Full&quot;  : showFullRange(); 
+							   break;
+				case &quot;Month&quot; : showOneMonth();
+							   break;						
+				case &quot;Week&quot;  : showOneWeek();
+				               break;
+			}
+		}
+		
+		// FIXME: similar to full range...merge
 		private function accountSelected(dataAggregator:TweetDataAggregator):void {
 			if (view.selectedIndex==0) view.selectedIndex=2;
 			this.dataAggregator = dataAggregator;
@@ -84,28 +130,48 @@
 			loader.load(dataAggregator);
 		}			
 		
+		private function formatDate(date:Number):String {
+			return dateFormatter.format(new Date(date));
+		}
 	]]&gt;
 &lt;/mx:Script&gt;
+&lt;mx:DateFormatter id=&quot;dateFormatter&quot; formatString=&quot;MMM.DD.YY&quot; /&gt;
 &lt;mx:HDividedBox width=&quot;100%&quot; height=&quot;100%&quot;&gt;
-	&lt;account:AccountList width=&quot;230&quot; height=&quot;100%&quot;  
+	&lt;account:AccountList
+		id=&quot;accountList&quot;
+		width=&quot;230&quot; height=&quot;100%&quot;  
 		accountSelected=&quot;accountSelected(event.account as TweetDataAggregator);&quot; 
 		loadAccount=&quot;loadAccount(event.account as TweetDataAggregator)&quot;/&gt;
 	&lt;mx:VBox width=&quot;100%&quot; height=&quot;100%&quot;&gt;
+		&lt;mx:HBox width=&quot;100%&quot; horizontalAlign=&quot;center&quot;&gt;
+			&lt;mx:Label text=&quot;{accountList.selectedAccountName} {formatDate(fromDate)}-{formatDate(toDate)}&quot; 
+				width=&quot;100%&quot; fontWeight=&quot;bold&quot; textAlign=&quot;center&quot; fontSize=&quot;22&quot;/&gt;
+			&lt;mx:Label text=&quot;Range:&quot;  /&gt;
+			&lt;mx:ToggleButtonBar itemClick=&quot;changeDateRange(event.label)&quot; &gt;
+				&lt;mx:dataProvider&gt;
+					&lt;mx:String&gt;Full&lt;/mx:String&gt;
+					&lt;mx:String&gt;Month&lt;/mx:String&gt;
+					&lt;mx:String&gt;Week&lt;/mx:String&gt;
+				&lt;/mx:dataProvider&gt;
+			&lt;/mx:ToggleButtonBar&gt;			
+		&lt;/mx:HBox&gt;
 		&lt;mx:TabNavigator id=&quot;view&quot; width=&quot;100%&quot; height=&quot;100%&quot; creationPolicy=&quot;all&quot;&gt;
 			&lt;data:HTMLSpider id=&quot;loader&quot; width=&quot;100%&quot;  height=&quot;100%&quot; label=&quot;Progress&quot; 
 						loaded=&quot;accountSelected(loader.dataAggregator)&quot; /&gt;
 			&lt;tweetList:TweetList id=&quot;list&quot; label=&quot;Tweets ({tweets.length})&quot; 
 								 tweets=&quot;{tweets}&quot;
-								 width=&quot;100%&quot; height=&quot;100%&quot; /&gt;	    					
+								 width=&quot;100%&quot; height=&quot;100%&quot; /&gt;
 			&lt;mx:VBox width=&quot;100%&quot; height=&quot;100%&quot; label=&quot;Stats&quot; creationPolicy=&quot;all&quot;&gt;
-				&lt;mx:HBox width=&quot;100%&quot; height=&quot;33%&quot;&gt;
+				&lt;mx:HBox width=&quot;100%&quot; height=&quot;50%&quot;&gt;
 					&lt;charts:ReplyChart id=&quot;replyChart&quot; width=&quot;50%&quot; height=&quot;100%&quot; /&gt;
 					&lt;charts:HoursAndDayChart id=&quot;hoursAndDayChart&quot; width=&quot;50%&quot; height=&quot;100%&quot; /&gt;			
 				&lt;/mx:HBox&gt;
-				&lt;charts:DayWeekMonthBreakdownChart id=&quot;dwmChart&quot; width=&quot;100%&quot; height=&quot;33%&quot; /&gt;
+				&lt;charts:DayWeekMonthBreakdownChart id=&quot;dwmChart&quot; width=&quot;100%&quot; height=&quot;50%&quot; /&gt;
 			&lt;/mx:VBox&gt;		
 		&lt;/mx:TabNavigator&gt;
-		&lt;charts:ChartSlider id=&quot;fullChart&quot; width=&quot;100%&quot; height=&quot;33%&quot;  dateRangeChanged=&quot;filterByDates(event.fromRange, event.toRange)&quot; /&gt;				
+		&lt;charts:DateRange id=&quot;fullChart&quot; width=&quot;100%&quot; height=&quot;33%&quot;  
+			dateRangeChanged=&quot;filterByDates(event.fromRange, event.toRange)&quot; /&gt;				
 	&lt;/mx:VBox&gt;	
 &lt;/mx:HDividedBox&gt;
 &lt;/mx:WindowedApplication&gt;
+</diff>
      <filename>09_TwitterSpider/air/src/TwitterSpider.mxml</filename>
    </modified>
    <modified>
      <diff>@@ -30,6 +30,11 @@
 		private function formatTweetCountLabel(item:Object, column:DataGridColumn):String {
 			return item.aggregation.getTweets().length;
 		}
+		
+		[Bindable(event=&quot;accountSelected&quot;)]
+		public function get selectedAccountName():String {
+			return grid.selectedItem.aggregation.accountName;
+		}
 		private function accountSelected():void {
 		   var event:AccountEvent = new AccountEvent(&quot;accountSelected&quot;);
 		   event.account = 	grid.selectedItem.aggregation;</diff>
      <filename>09_TwitterSpider/air/src/account/AccountList.mxml</filename>
    </modified>
    <modified>
      <diff>@@ -40,7 +40,7 @@
 	        &lt;/mx:verticalAxisRenderers&gt;
 	    
 	        &lt;mx:series&gt;
-	            &lt;mx:BubbleSeries yField=&quot;w&quot; xField=&quot;h&quot; radiusField=&quot;count&quot;  showDataEffect=&quot;{interpolateIn}&quot; /&gt;
+	            &lt;mx:BubbleSeries yField=&quot;w&quot; xField=&quot;h&quot; radiusField=&quot;count&quot;  /&gt; &lt;!-- showDataEffect=&quot;{interpolateIn}&quot; /&gt; --&gt;
 	        &lt;/mx:series&gt;
 	        
 	    &lt;/mx:BubbleChart&gt;			</diff>
      <filename>09_TwitterSpider/air/src/charts/HoursAndDayChart.mxml</filename>
    </modified>
    <modified>
      <diff>@@ -22,7 +22,7 @@ package data
 		}
 		
 		public function get updates():Number {
-			return Number(stats.children()[3].span.toString().replace(',',''));
+			return Number(stats.span.(@id==&quot;update_count&quot;).toString().replace(',',''));
 		}
 		public function get pageCount():Number {
 			return Math.ceil(updates/20);
@@ -36,6 +36,8 @@ package data
 		
 		/**
 		 * Converts these
+		 * &lt;span id=&quot;update_count&quot; class=&quot;stats_count numeric&quot;&gt;1,393&lt;/span&gt;
+		 * 
 		 * &lt;ul class=&quot;stats&quot;&gt;
 		 * &lt;li&gt;&lt;a class=&quot;label&quot; href=&quot;/Scobleizer/friends&quot;&gt;Following&lt;/a&gt; &lt;span class=&quot;numeric stats_count&quot;&gt;21,164&lt;/span&gt;&lt;/li&gt;
     	 * &lt;li&gt;&lt;a class=&quot;label&quot; href=&quot;/Scobleizer/followers&quot;&gt;Followers&lt;/a&gt;&lt;span class=&quot;stats_count numeric&quot;&gt;23,874&lt;/span&gt;&lt;/li&gt;
@@ -45,19 +47,27 @@ package data
 		 */
 		public function get stats():XML {
 			if (_stats != null) return _stats;
-			_stats = partial('&lt;ul class=&quot;stats&quot;&gt;', '&lt;/ul&gt;')
+			var following:XML =  partial('&lt;span id=&quot;following_count&quot; class=&quot;stats_count numeric&quot;&gt;', '&lt;/span&gt;');
+			var followers:XML =  partial('&lt;span id=&quot;follower_count&quot; class=&quot;stats_count numeric&quot;&gt;', '&lt;/span&gt;');
+			var updates:XML =  partial('&lt;span id=&quot;update_count&quot; class=&quot;stats_count numeric&quot;&gt;', '&lt;/span&gt;'); 
+			_stats = &lt;stats/&gt;
+			_stats.appendChild(following);
+			_stats.appendChild(followers);
+			_stats.appendChild(updates);
 			return _stats;
 		}
 		
 		public function get tweets():XML {
 			if (_tweets != null) return _tweets;
-			_tweets = partial('&lt;table class=&quot;doing&quot; id=&quot;timeline&quot; cellspacing=&quot;0&quot;&gt;', '&lt;/table&gt;');
+			_tweets = partial('&lt;tbody id=&quot;timeline_body&quot;&gt;', '&lt;/tbody&gt;');
 			return _tweets;
 		}
 		
 		public function get ids():Array {
 			if (_ids != null) return _ids;
-			var list:XMLList = tweets.tr.(attribute('class')=='hentry'||attribute('class')=='hentry_over').@id;
+			//FIXME: check if ||attribute('class'=='hentry latest-status' is also required
+			var a = tweets;
+			var list:XMLList = tweets.tr.(attribute('class')=='hentry status latest-status'||attribute('class')=='hentry status').@id;
 			var result:Array = [];
 			for each (var xml:XML in list) {
 				result.push(Number(xml.toString().replace(&quot;status_&quot;, &quot;&quot;)));
@@ -75,12 +85,15 @@ package data
 		  &lt;/tr&gt;
 		*/
 		public function get tweetsContent():XMLList {
-			var list:XMLList = tweets.tr.td.(attribute('class')=='content');
+			var tbody:XML = tweets;
+//			var list:XMLList = tbody.tr.td.div.(attribute('class')=='status-body');
+			var list:XMLList = tbody.tr.td.(attribute('class')=='status-body').div;
+//			var list:XMLList = tbody.tr.(attribute('class')=='hentry status latest-status'||attribute('class')=='hentry status')
 			return list;
 		}
 		
 		/**
-		 * Convert list of td into array of stat objects
+		 * Convert list of tr into array of stat objects
 		 */		
 		public function get tweetsArray():Array {
 			var list:XMLList = tweetsContent;
@@ -98,6 +111,16 @@ package data
 		/**
 		 * Extracting message, published_time
 		 * returns {message, time, time_breakdown:{y, m, d, h}, client, in_reply_to, 
+		 * &lt;tr id=&quot;status_870589015&quot; class=&quot;hentry&quot;&gt;
+			&lt;td&gt;
+				&lt;div class=&quot;status-body&quot;&gt;
+					&lt;span class=&quot;entry-content&quot;&gt; iPhone restored. But I'm a wreck. Back to bed. &lt;/span&gt;
+					&lt;span class=&quot;meta entry-meta&quot;&gt;
+					&lt;/span&gt;
+				&lt;/div&gt;
+			&lt;/td&gt;
+			 ...
+		 * OLD:
 		 * &lt;td class=&quot;content&quot;&gt;
 			  &lt;span class=&quot;entry-content&quot;&gt;
 			    @
@@ -129,14 +152,14 @@ package data
 					from = entryMetaChildren[2].toString()
 					respondsToIndex = 3
 			} else {
-					from = entryMetaChildren[1].toString().replace(&quot;from &quot;,&quot;&quot;);
+					from = entryMetaChildren[1].toString().replace(&quot;from&quot;,&quot;&quot;);
 			}
 			if (respondsToIndex&lt;entryMetaChildren.length()) {
 				respondsTo = entryMetaChildren[respondsToIndex].toString().replace(&quot;in reply to &quot;,&quot;&quot;);
 			}
 			return {
 				message: content.span.(attribute('class')=='entry-content').toString(),
-				time:entryMeta.a.abbr.@title,
+				time:entryMeta.a.span.(attribute('class')=='published').@title,
 				time_breakdown:null,
 				client: from,
 				in_reply_to: respondsTo</diff>
      <filename>09_TwitterSpider/air/src/data/HTMLParser.as</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,7 @@
 package data
 {
 	import mx.collections.ArrayCollection;
+	import mx.utils.ObjectUtil;
 	
 	/**
 	 * Incrementally sumarize tweets
@@ -9,6 +10,7 @@ package data
 	{
 		private var tweets:Array;
 		private var tweetsInTimeRange:Array;
+		private var dateRange:Object; 
 		private var ids:Object;
 		private var account:String;
 		
@@ -95,10 +97,28 @@ package data
 			tweetsByHour = new ArrayCollection(mapToArray(agg.tweetsByHour));
 			tweetsReplies = new ArrayCollection(mapToArray(agg.tweetsReplies, true, 'value', Array.DESCENDING||Array.NUMERIC, 10));
 			tweetsHourAndDay = new ArrayCollection(mapHourAndDayToArray(agg.tweetsHourAndDay));
+			// Sort
+			//this.tweets.sortOn('time', Array.NUMERIC);
+			this.tweets.sort(tweetDateCompare);
+			dateRange = getFullDateRange();
+		}
+		private function tweetDateCompare(t0:Object, t1:Object):Number {
+			return ObjectUtil.numericCompare(t1.time_breakdown.date, t0.time_breakdown.date);
+		}
+		
+		public function getFullDateRange():Object {
+			//FIXME: that doesn't work...!
+			//FIXME: add support for zero tweets.
+			return {fromDate:tweets[0].time_breakdown.date, 
+				   toDate:tweets[tweets.length-1].time_breakdown.date}
+		}
+		public function getDateRange():Object {
+			return dateRange;
 		}
 		
 		//FIXME: refactor with above to remove duplicate code.
 		public function aggregateForDateRange(fromDate:Number, toDate:Number):void {
+			dateRange = {fromDate:fromDate, toDate:toDate};
 			tweetsInTimeRange = [];
 			var agg:Object = getAggregationStructure();
 			for each (var tweet:Object in tweets) {
@@ -174,7 +194,7 @@ package data
 			var dt:Date = new Date(y,m-1,d);
 			var dateAndTime:Date = new Date(y,m-1,d, h, parts[4], parts[5]);
 			var w:Number = dt.getDay();
-			return {dateandtime:dateAndTime, date:dt.time, y:y, m:m, d:d, h:h, w:w}
+			return {dateandtime:dateAndTime, date:dt.getTime(), y:y, m:m, d:d, h:h, w:w}
 		}
 	}
 }
\ No newline at end of file</diff>
      <filename>09_TwitterSpider/air/src/data/TweetDataAggregator.as</filename>
    </modified>
    <modified>
      <diff>@@ -21,14 +21,22 @@
 		private function dateLabel(item:Object, col:DataGridColumn):String {
 			return item.time_breakdown ? dtFormatter.format(item.time_breakdown.dateandtime) : &quot;&quot;;
 		}
+		//FIXME: this is ridiculus...make simpler
+		private function sortDate(o1:Object, o2:Object):int {
+			var d1:int = o1&amp;&amp;o1.time_breakdown ? o1.time_breakdown.dateandtime.time : 0;
+			var d2:int = o2&amp;&amp;o2.time_breakdown ? o2.time_breakdown.dateandtime.time : 0
+			return d1&lt;d2?-1:(d1&gt;d2?1:0);
+		}
 	]]&gt;
 &lt;/mx:Script&gt;	
 	&lt;mx:TextInput id=&quot;search&quot; change=&quot;filter()&quot; /&gt;
 	&lt;mx:DateFormatter id=&quot;dtFormatter&quot;  formatString=&quot;MMM.DD.YY HH:NN:SS&quot; /&gt;
 	&lt;mx:DataGrid width=&quot;100%&quot; height=&quot;100%&quot; dataProvider=&quot;{tweets}&quot; rowHeight=&quot;30&quot;&gt; 
 		&lt;mx:columns&gt;
-			&lt;mx:DataGridColumn headerText=&quot;Time&quot; labelFunction=&quot;dateLabel&quot; /&gt;
-			&lt;mx:DataGridColumn headerText=&quot;Message&quot; dataField=&quot;message&quot; width=&quot;750&quot;  &gt;
+			&lt;mx:DataGridColumn headerText=&quot;Time&quot; 
+							   labelFunction=&quot;dateLabel&quot;  
+							   /&gt;
+			&lt;mx:DataGridColumn headerText=&quot;Message&quot;  width=&quot;750&quot;  &gt;
 					&lt;mx:itemRenderer&gt;
 						&lt;mx:Component&gt;
 							&lt;mx:HTML htmlText=&quot;{data.message}&quot; horizontalScrollPolicy=&quot;off&quot; 
@@ -36,7 +44,14 @@
 						&lt;/mx:Component&gt;
 					&lt;/mx:itemRenderer&gt;						
 			&lt;/mx:DataGridColumn&gt;
-			&lt;mx:DataGridColumn headerText=&quot;Client&quot; dataField=&quot;client&quot;/&gt;
+			&lt;mx:DataGridColumn headerText=&quot;Client&quot; dataField=&quot;client&quot;&gt;
+					&lt;mx:itemRenderer&gt;
+						&lt;mx:Component&gt;
+							&lt;mx:HTML htmlText=&quot;{data.client}&quot; horizontalScrollPolicy=&quot;off&quot; 
+									paintsDefaultBackground=&quot;false&quot; backgroundAlpha=&quot;0&quot;/&gt;
+						&lt;/mx:Component&gt;
+					&lt;/mx:itemRenderer&gt;										
+			&lt;/mx:DataGridColumn&gt;
 			&lt;mx:DataGridColumn headerText=&quot;In Reply&quot; dataField=&quot;in_reply_to&quot;/&gt;
 		&lt;/mx:columns&gt;
 	&lt;/mx:DataGrid&gt;</diff>
      <filename>09_TwitterSpider/air/src/tweetList/TweetList.mxml</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>27f798f8343203a132d97526c996219b5085343c</id>
    </parent>
  </parents>
  <author>
    <name>Daniel Wanja</name>
    <email>daniel@daniel-wanjas-macbook.local</email>
  </author>
  <url>http://github.com/danielwanja/railsconf2008/commit/05f008f3414c68ee042a8942275ab8ae0f23a1e1</url>
  <id>05f008f3414c68ee042a8942275ab8ae0f23a1e1</id>
  <committed-date>2008-11-01T20:46:42-07:00</committed-date>
  <authored-date>2008-11-01T20:46:42-07:00</authored-date>
  <message>Realigned with new Twitter HTML structure.</message>
  <tree>7605886d23a39d9e279e303851fca90995a144b1</tree>
  <committer>
    <name>Daniel Wanja</name>
    <email>daniel@daniel-wanjas-macbook.local</email>
  </committer>
</commit>
