FLEX – live scroll datagrid

March 2nd, 2009 by alinca in General

A nice feature on search engines would be a live scroll, so right before you get to the end of the current page the next page is automatically loaded.

I’ve played a bit with Adobe Flex SDK and implemented a short proof of concept.

First you need a mx.rpc.http.HTTPService to map the search service. For the service I created a simple php page that returns some random list of data and mapped my HTTPService to it.

I created a list of type ArrayCollection to hold the search results, and a DataGrid component with the list binded as dataProvider.

When the application starts, the HTTPService sends a first request for data and after the result is received, it will parse it and add the data to the ArrayCollection which is binded to the DataGrid, so the results will show up in the DataGrid

For the moment, the application only loads the first page of results so we need to load the next page each time the user gets to the end of the results. This can be done by adding an event listener for the DataGrid scroll event. On this listener we check if the user scrolled down using the ScrollEvent direction and delta properties. If so, we check if the user is close to the end (there are 3 more results to show) and then we send a new request to the search service for next page o data.

Here you can checkout the example. (right-click on the flash to view the source code).

One problem that I was not able to solve was that when loading the first page with data the maxVerticalScrollPosition of the DataGrid does not get updated, I was using this in the status label from the bottom to show the index of data show in the DataGrid; maxVerticalScrollPosition was only updated when making the first scroll. For the status label, I used a hack that you can check in the code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
	creationComplete="creationCompleteImpl()"
	width="400"
	height="500"
 viewSourceURL="srcview/index.html">
	<mx:Script>
		<![CDATA[
			import mx.events.ScrollEventDirection;
			import mx.events.ScrollEvent;
			import mx.controls.Alert;
			import mx.rpc.http.HTTPService;
			import mx.rpc.events.FaultEvent;
			import mx.rpc.events.ResultEvent;
			import mx.collections.ArrayCollection;
 
			[Embed(source="assets/loading.swf")]
			private var loadingAnimation:Class;
 
			//keep a request count not to make new request until last request data is received
			private var loadRequests:int = 0;
 
			[Bindable]
			private var list:ArrayCollection = new ArrayCollection();
			//http service from where you get random data
			private var httpService:HTTPService = new HTTPService();
			/**
			 * after load complete add event listeners for httpService and make a first call 
			 */
			private function creationCompleteImpl():void
			{
				//add an event listener for the httpService events
				httpService.url = parameters.serviceUrl;
				httpService.addEventListener(ResultEvent.RESULT, httpServiceEvent);
				httpService.addEventListener(FaultEvent.FAULT, httpServiceEvent);
				getNewData();
			}
			/**
			 * httpService event handler 
			 */
			private function httpServiceEvent(event:Event):void
			{
				if(event is ResultEvent)//if we get a result from the httpService
				{
					//parse the result and add the values to the datagrid
					//on a request the test php returns 50 random values
					var result:String = (event as ResultEvent).result as String;
					var values:Array = result.split("\n");
					for each(var value:String in values)
					{
						if(value.length > 0)
						{
							var props:Array = value.split(/(\d+)/);
							list.addItem({text:props[0], number:props[1]});
						}
					}
				}
				else
				{
					//show an alert if error
					Alert.show((event as FaultEvent).message.toString());
				}
				loadRequests--;
				if(loadRequests == 0)
				{
					loadingImage.visible = false;
				}
			}
			/**
			 * call the php service 
			 */
			private function getNewData():void
			{
				if(loadRequests == 0)
				{
					httpService.send();
					loadingImage.visible = true;
					loadRequests++;
				}
			}
			/**
			 * event handler for datagrid scrolling 
			 */
			private function listScrolled(event:ScrollEvent):void
			{
				if(event.direction == ScrollEventDirection.VERTICAL && event.delta > 0)
				{
					//if close to last item make a new call to get new results
					if(liveDataGrid.maxVerticalScrollPosition - event.position < 3)
					{
						getNewData();
					}
				}
			}
		]]>
	</mx:Script>
	<mx:VBox paddingTop="10" paddingBottom="10" paddingLeft="10" paddingRight="10"
		horizontalAlign="center" 
		width="100%" height="100%">
		<mx:Canvas width="100%">
			<mx:Label text="Live scroll" left="0" verticalCenter="0"/>
			<mx:Image id="loadingImage" source="{loadingAnimation}" right="0" verticalCenter="0" visible="false"/>
		</mx:Canvas>
		<mx:DataGrid id="liveDataGrid" height="100%" width="100%"
			scroll="listScrolled(event)"
			dataProvider="{list}"
			>
			<mx:columns>
		        <mx:DataGridColumn dataField="text" headerText="Text"/>
		        <mx:DataGridColumn dataField="number" headerText="Random value"/>
		    </mx:columns>
		</mx:DataGrid>
		<mx:Label
			visible="{list.length > 0}" 
			text="{'Showing ' + (liveDataGrid.verticalScrollPosition + 1) + ' to ' + (liveDataGrid.verticalScrollPosition + Math.round((liveDataGrid.height - liveDataGrid.headerHeight) / liveDataGrid.rowHeight) - 1)}" width="100%"/>
	</mx:VBox>
</mx:Application>