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
.
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
- Python
- Pydev
- Django
- Google App Engine
- Flex
- Flex 3 Component Explorer
- Flex Style Explorer
- as3corelib
- Flash Debug Player
- YAML
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.
More From ivanidris
ivanidris Recommends
- 4 Most Important PHP Security Measures (Server-Side Magazine)
- Bootstrap PHP Code (Server-Side Magazine)
