Friday, December 17, 2010

Look ma, no server



About a month ago I had a series of posts about setting up a simple web server (first one here).

I can see a lot of potential for a form-based system which would let command-line-phobic users set the preferences for scripts through what looks like a web page, but doesn't actually go through the server. Instead the request would come to us (or rather our PyObjC-based application), and the app would execute the script, display (and save) the results as desired.

I have a small example of form-processing here. It uses the WebKit, which looks a little formidable at first, but can be used in a pretty simple fashion.

There is only one class that does anything: MyWebViewController.py. It has myWebView as an outlet. In Interface Builder I just dragged a Web View onto the window, and an NSObject cube onto the .. (whatever it's called that holds the cubes). The class for the object is set to be MyWebViewController, and since the outlet had already been specified in the file, IB let me drag from the cube to the view and set the outlet.

We have a standard web form as shown in the screenshot, form.html looks like this:

<Content-type: text/html>

<form name="input" action="nuthin" method="get">

First name: <input type="text" name="firstname" /><br />
Last name: <input type="text" name="lastname" /><br /><br />
<input type="radio" name="sex" value="male" /> Male<br />
<input type="radio" name="sex" value="female" /> Female<br /><br />
<input type="checkbox" name="vehicle" value="Bike" /> I have a bike<br />
<input type="checkbox" name="vehicle" value="Car" /> I have a car<br /><br />

<input type="file" name="datafile" size="40">
<input type="submit" value="Submit" />
</form>
</html>

The form was added to the project under Resources. In the code below, the controller class sets itself as each of three different delegates for the Web View, and then loads the form. We implement a single method for each delegate. The UIDelegate method allows us to run an openPanel and set the file name the user chooses (subject to it being the right type).

In the methods we do some print statements that show we have access to all the form data.

The sequence of events was that I filled in the form and chose the file, and then hit submit. I marked the end of the file dialog process with some dashes.

If you look closely you can see that we don't actually need to be the resourceLoadDelegate. The output marked with 'provisionalLoad..' which comes to us as the frameLoadDelegate, is all that's necessary. If you scroll out on the second of those, you'll see we have the data including the file name.

I think it's pretty slick, and it took all of an hour, since I had a working Web View from another project. :)

What I haven't got working yet is a POST request with a bigger data form: textarea. If you know how, please speak up.

provisionalLoad..
file:///Users/telliott_admin/Desktop/FormBrowser/build/Debug/FormBrowser.app/Contents/Resources/myform.html
None
identifier..
<NSURLRequest /Users/telliott_admin/Desktop/FormBrowser/build/Debug/FormBrowser.app/Contents/Resources/myform.html> <WebDataSource: 0x3629c20>
2010-12-17 10:42:45.555 FormBrowser[7308:a0f] Application did finish launching.
runOpenPanel..
------------------------------
provisionalLoad..
file:///Users/telliott_admin/Desktop/FormBrowser/build/Debug/FormBrowser.app/Contents/Resources/nuthin?firstname=Tom&lastname=Elliott&sex=male&vehicle=Bike&vehicle=Car&datafile=x.txt
None
identifier..
<NSMutableURLRequest file:///Users/telliott_admin/Desktop/FormBrowser/build/Debug/FormBrowser.app/Contents/Resources/nuthin?firstname=Tom&lastname=Elliott&sex=male&vehicle=Bike&vehicle=Car&datafile=x.txt> <WebDataSource: 0x30dfa10>



from Foundation import *
from AppKit import *
from WebKit import *
from objc import *

class MyWebViewController(NSObject):

myWebView = objc.IBOutlet()

def applicationDidFinishLaunching_(self, sender):
NSLog("WVC Application did finish launching.")

def awakeFromNib(self):
self.myWebView.setFrameLoadDelegate_(self)
self.myWebView.setResourceLoadDelegate_(self)
self.myWebView.setUIDelegate_(self)
self.loadWebForm()

def loadWebForm(self):
path = NSBundle.mainBundle().pathForResource_ofType_(
'myform','html')
url = NSURL.URLWithString_(path)
req = NSURLRequest.requestWithURL_(url)
wf = self.myWebView.mainFrame()
wf.loadRequest_(req)

def webView_didStartProvisionalLoadForFrame_(self,wv,wf):
print 'provisionalLoad..'
print wv.mainFrameURL()
print wv.mainFrame().provisionalDataSource().data()

def webView_identifierForInitialRequest_fromDataSource_(
self,wv,identifier,dataSource):
print 'identifier..'
print identifier, dataSource

def webView_runOpenPanelForFileButtonWithResultListener_(
self,wv,listener):
print 'runOpenPanel..'
panel = NSOpenPanel.openPanel()
panel.setCanChooseDirectories_(True)
panel.setCanChooseFiles_(True)
fileTypes = ['txt']
result = panel.runModalForDirectory_file_types_(
NSHomeDirectory() + '/Desktop',None,fileTypes)
if not result == NSOKButton:
return
path = panel.filename()
if not path: return
print '-'*30
listener.chooseFilename_(path)