Flex Search on Google App Engine

This entry is part of 3 in the series Flex on GAE

3. FlexSearch client

In the first section of this article, I spent a lot of time introducing Flex and the technologies it depends on. I assumed that Flex is less known than Python – it is for me anyway. Here and there, I presented bits and pieces of the Flex front end.

3.1. SearchRequest.as

The Flex client side is also using JSON. For this, a special package is needed, that is part of the as3corelib, an open source project. A search request is sent to search.py and search results displayed in a grid. Some KeyboardEvent listeners are defined, so that, j scrolls down, k goes up, o opens a page, g goes back to the search box and b adds a bookmark in Google Bookmarks. The main goal was to introduce easier navigation without a mouse, and revolutionize web user interfaces ;) .

flex search flex search flex search flex search



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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package net.ivanidris.flexsearch
{
	import mx.controls.LinkButton;
 
	public class SearchRequest {
    	import com.adobe.serialization.json.JSON;
        import flash.events.Event;
        import flash.net.URLLoader;
        import flash.net.URLRequest;
        import mx.containers.Grid;
        import mx.containers.GridItem;
        import mx.containers.GridRow;
        import flash.events.MouseEvent;
        import flash.events.KeyboardEvent;
        import flash.net.navigateToURL;
        import net.ivanidris.flex.Bookmarks;
        import mx.managers.PopUpManager;
        import net.ivanidris.flex.Counter;
 
 
        private var loader:URLLoader;
        private var app;
        private var COLSPAN:Number = 2;
        private var SCROLL:Number = 20;
        private var counter:Counter;
        private var rows:Array;
        private var resultGrid:Grid;
        private var urls:Array;
        private var titles:Array;
 
        public function SearchRequest(app):void {
        	this.app = app;
 
            // remove previous results if present
        	if(this.app.searchResult.numChildren > 1) {
        		this.app.searchResult.removeChildAt(1);
        	}
 
            this.counter = new Counter(0);
            resultGrid = new Grid();
            rows = new Array();
            urls = new Array();
            titles = new Array();
            initGrid();
            search(this.app.q.text);
        }
 
        public function initGrid():void {
        	var row:GridRow = new GridRow();
            var item:GridItem = new GridItem();
            item.colSpan = COLSPAN;
            item.addChild(resultGrid);
            row.addChild(item);
            this.app.searchResult.addChild(row);
        }
 
        public function search(q:String):void {
            var url:String = '/search.py?q=' + encodeURI(q);
            var request:URLRequest = new URLRequest(url);
            loader = new URLLoader(request);
            loader.addEventListener("complete", onServerResponse);
        }
 
        public function onServerResponse(e:Event):void {
              loadResults();
              this.resultGrid.setFocus();
              this.app.searchResult.addEventListener(Event.ACTIVATE, getFocusBack);
              this.app.searchResult.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
	    }
 
	    public function getFocusBack(e:Event):void {
	    	setStyle(counter.get());
	    }
 
	    public function onKeyDown(e:KeyboardEvent) :void{
	    	if(e.target.name.indexOf('Grid') < 0) {
	    		return;
	    	}
            if(String.fromCharCode(e.charCode) == 'j' ) {
            	counter.increment();
            	setStyle(counter.get());
                this.app.verticalScrollPosition += SCROLL;
            }
 
            if(String.fromCharCode(e.charCode) == 'k' ) {
            	counter.decrement();
            	setStyle(counter.get());
            	this.app.verticalScrollPosition -= SCROLL;
            }
 
            if(String.fromCharCode(e.charCode) == 'o' ) {
            	navigateToURL(new URLRequest(urls[counter.get()]));
            }
 
            if(String.fromCharCode(e.charCode) == 'g' ) {
            	this.app.verticalScrollPosition = 0;
            	this.app.q.editable = false;
            	this.app.q.setFocus();
            	this.app.q.setSelection(this.app.q.length, this.app.q.length);
            	this.app.q.editable  = true;
            }
 
            if(String.fromCharCode(e.charCode) == 'b' ) {
            	var bookmarksWindow:Bookmarks =
                    Bookmarks(PopUpManager.createPopUp(this.app, Bookmarks, false));
            	bookmarksWindow.url.text = this.urls[counter.get()];
            	bookmarksWindow.t.text = this.titles[counter.get()];
            }
	    }
 
	   public function loadResults():void {
	       try {
                var json = JSON.decode(loader.data);
 
                for each(var i in json) {
                    var row:GridRow = new GridRow();
                    var item:GridItem = new GridItem();
                    item.colSpan = COLSPAN;
                    var linkButton:LinkButton = new LinkButton();
                    linkButton.label = i.title;
 
                    this.urls.push(i.url);
                    this.titles.push(i.title);
 
                    linkButton.addEventListener(MouseEvent.CLICK, function(e:MouseEvent):void {
                        navigateToURL(new URLRequest(i.url));
                 });
 
                    item.addChild(linkButton);
                    row.addChild(item);
                    resultGrid.addChild(row);
                }
 
            	this.rows = this.resultGrid.getChildren();
            	counter.setMax(this.rows.length);
            	setStyle(0);
 
              } catch(ignored:Error) {
                    trace('Error decoding JSON data\n' + loader.data);
              }
	   }
 
	   public function setStyle(index:Number):void {
	   	   var row = this.rows[index];
 
	   	   if(row != null) {
                row.setFocus();
                row.drawFocus(true);
           }
	   }
    }
}

LinkButtons are being used to mimic web links in a single column table. The addEventListener method lets you register event listener functions for a certain event. By clicking on a LinkButton, you can go to the page Google found. The text of the buttons comes from the original title of the search result item. The biggest issue I had was, how to deal with losing focus. Unfortunately, I haven’t figured it out yet. My current solution is very fragile and only works some of the time. I use the Actionscript Array class a lot. It is more or less comparable to ArrayList in Java. The HTTP GET request is performed with the utility classes URLLoader and URLRequest.

3.2 Tracing

To see the trace() output you need to install the Flash Debug Player. Trace logging can be enabled in the mm.cfg configuration file. Mine is in my home directory.

1
2
3
4
ivan-idris:~ ivanidris$ cat mm.cfg
ErrorReportingEnable=1
TraceOutputFileEnable=1
MaxWarnings=1

The log can be followed with the tail command.

1
2
3
ivan-idris:~ ivanidris$ tail -f /Users/ivanidris/Library/Preferences/Macromedia/Flash\ Player/Logs/flashlog.txt
Warning: Reference to undeclared variable, 'com'
Warning: Reached warning limit of 1

3.3 FlexSearch Eclipse project

Here is the .project file from FlexBuilder formerly known as Eclipse. The way it works is, that you give projects Flex nature in Eclipse. When you edit mxml or actionscript files and save your changes, the code gets compiled, just like Java code is compiled in the Java Perspective.

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
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
	<name>FlexSearch</name>
	<comment></comment>
	<projects>
	</projects>
	<buildSpec>
		<buildCommand>
			<name>com.adobe.flexbuilder.project.flexbuilder</name>
			<arguments>
			</arguments>
		</buildCommand>
	</buildSpec>
	<natures>
		<nature>com.adobe.flexbuilder.project.flexnature</nature>
		<nature>com.adobe.flexbuilder.project.actionscriptnature</nature>
	</natures>
	<linkedResources>
		<link>
			<name>bin-debug</name>
			<type>2</type>
			<location>/Users/ivanidris/Documents/eclipseProjects/FlexApps/src/static/search</location>
		</link>
		<link>
			<name>bin-release</name>
			<type>2</type>
			<location>/Users/ivanidris/Documents/eclipseProjects/FlexApps/src/static/search/bin-release</location>
		</link>
	</linkedResources>
</projectDescription>

FlexSearch Eclipse .project file

I setup the build to be exported into the corresponding Python project, which is located in /Users/ivanidris/Documents/eclipseProjects/FlexApps.

3.4 .actionScriptProperties

My FlexSearch project depends on the aforementioned as3corelib and another project with useful utilities. There is a Maven 2 plugin for Flex, but I wanted to use FlexBuilder without it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<actionScriptProperties mainApplicationPath="FlexSearch.mxml" version="3">
<compiler additionalCompilerArguments="-locale en_US" copyDependentFiles="true" enableModuleDebug="true" generateAccessible="false" htmlExpressInstall="true" htmlGenerate="true" htmlHistoryManagement="true" htmlPlayerVersion="9.0.124" htmlPlayerVersionCheck="true" outputFolderLocation="/Users/ivanidris/Documents/eclipseProjects/FlexApps/src/static/search" outputFolderPath="bin-debug" sourceFolderPath="src" strict="true" useApolloConfig="false" verifyDigests="true" warn="true">
<compilerSourcePath/>
<libraryPath defaultLinkType="1">
<libraryPathEntry kind="4" path=""/>
<libraryPathEntry kind="1" linkType="1" path="libs"/>
<libraryPathEntry kind="3" linkType="1" path="/Users/ivanidris/Frameworks/corelib/bin/corelib.swc" useDefaultLinkType="false"/>
<libraryPathEntry kind="3" linkType="1" path="/FlexUtils/bin/FlexUtils.swc" useDefaultLinkType="false"/>
</libraryPath>
<sourceAttachmentPath/>
</compiler>
<applications>
<application path="FlexSearch.mxml"/>
</applications>
<modules/>
<buildCSSFiles/>
</actionScriptProperties>

3.5 FlexSearch launch configuration

Eclipse or actually FlexBuilder allows you to debug Flex applications easily. Of course, in my setup the Google App Engine must be running, and FlexBuilder needs to know, where the swf file is. The configuration implies that you can profile your code. I did not use the profiler, so I would appreciate it, if someone would like to share his experiences with profiling in FlexBuilder.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<launchConfiguration type="com.adobe.flexbuilder.debug.launchConfigurationType.flash">
<stringAttribute key="com.adobe.flexbuilder.debug.ATTR_APPLICATION" value="src/FlexSearch.mxml"/>
<stringAttribute key="com.adobe.flexbuilder.debug.ATTR_DEBUG_URL" value="http://localhost:8080/static/search/FlexSearch.html"/>
<stringAttribute key="com.adobe.flexbuilder.debug.ATTR_PROFILE_URL" value="http://localhost:8080/static/search/FlexSearch.html"/>
<stringAttribute key="com.adobe.flexbuilder.debug.ATTR_PROJECT" value="FlexSearch"/>
<stringAttribute key="com.adobe.flexbuilder.debug.ATTR_RUN_URL" value="http://localhost:8080/static/search/FlexSearch.html"/>
<booleanAttribute key="com.adobe.flexbuilder.debug.ATTR_USE_DEFAULT_URLS" value="false"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/FlexSearch"/>
<listEntry value="/FlexSearch/src/FlexSearch.mxml"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
<listEntry value="1"/>
</listAttribute>
</launchConfiguration>

4. Resources

Conclusion

I feel that I have learned a lot about Python and Flex, thus the effort was worth it in the end. The Google App Engine approach, in spite of, being different on the surface felt very familiar. I am very grateful to whoever submitted my previous article “Sharpen the vim saw” to Reddit and for the comments, tips and pingbacks as well. Hopefully you will find this howto as interesting. My next post will be about another mad experiment with Flex and GAE. This was meant as an introductory article, hence I promise to give more technical details next time.

Series Navigation
0saves
If you enjoyed this post, please consider leaving a comment or subscribing to the RSS feed to have future articles delivered to your feed reader.
Share
This entry was posted in programming and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">