
On Monday, August 25, 2003, at 05:45 PM, Donovan Preston wrote:
On Monday, August 25, 2003, at 1:30 PM, Sean Gillies wrote:
Hi,
Am very new to Twisted but am quickly finding that I like it. All my questions and comments involve Twisted 1.0.6 in combo with Python 2.3 on OS X.
Great! Always glad to have more users giving me feedback.
If you are going to be using LivePage, you should probably track CVS instead of using a release, since LivePage is the one major area of woven I am still doing major work on.
First, I want to clarify some terminology. LivePage is designed to allow two things:
Client-To-Server Events: A JavaScript event handler in the browser is routed to the Twisted server and causes some code to run. This uses a channel I am calling the "InputConduit"
Server-To-Client Events: Some event happened on the server (For example, new mail arrived, another player entered the room) and the server wants to "Push" some new HTML to the client. This uses a channel I am calling the "OutputConduit"
I've run into a problem while making a rough .rpy prototype of a mapping application (ala MapQuest) using the U of Minnesota MapServer's Python interface.
I will need to have server-side handlers for javascript events to allow map zooming, panning, &c and so have been experimenting with LivePage. I intend that the code should render a map image, and upon clicking the image, we zoom into the map (changing model data), and redraw the map image. Unfortunately, browsers are unable to load the
mapserv.rpy/?woven_hookupOutputConduitToThisFrame=1
URL that is generated by my instance of LivePage, and so the map is never redrawn as it should.
There are actually two unrelated issues here. The first is that the current implementation of LivePage in the Twisted CVS is geared towards the NewReality web client (NewReality is a multiuser text environment (game) written in Twisted) and thus the ability for the server to send events to the client is pretty important. You are connected and logged in using the web client, and someone else enters the room -- you want the web browser to display a notice about someone entering the room without having to refresh the browser manually or set up continual reloading.
The output conduit is implemented in certain browsers (those lacking Flash and LiveConnect) by embedding an <iframe> tag whose src="?woven_hookupOutputConduitToThisFrame=1" in the page. When the browser attempts to load this iframe, it makes a request to the server, and twisted notices that the browser "wants to hook up the output conduit to this frame", and *never finishes sending this page to the client*. The Twisted server just holds this connection open forever, so that it can write data to the client on demand in response to events on the server, and this is why it appears your browser is unable to load this URL.
Since with your application it sounds like you only want client-to-server events, you need to do a little bit of hacking to prevent Woven from including the output conduit HTML in your pages. This will get easier in future releases of Twisted; I just haven't had time to enumerate the possible ways people will want to use LivePage and come up with an easy way for the programmer to specify the features they want.
If you look at the HTML fragment woven includes in your page when you specify the view directive "webConduitGlue" you will see it includes the following parts:
<div> <script src="WebConduit2_js" language="javascript"></script> <iframe src="input_html" style="width: 0; height: 0" id="woven_inputConduit"></iframe> <iframe src="?woven_hookupOutputConduitToThisFrame=1" style="width: 100%" id="woven_outputConduit"></iframe> </div>
Simply replace the view="webConduitGlue" node with the above fragment, sans the output conduit frame:
<div> <script src="WebConduit2_js" language="javascript"></script> <iframe src="input_html" style="width: 0; height: 0" id="woven_inputConduit"></iframe> </div>
After you have done this, your pages will actually appear to fully load and you should feel happier.
The model data does get changed successfully, and when I manually reload the page, I do see the zoomed map image as I expect.
The second issue is that you are not notifying your models properly to tell them that they have changed. When a client-to-server event is sent from the browser to the server, the current version of Woven only sends the portions of the page which have actually changed, not the entire page, back to the browser. Again, this is something I would like the programmer to be able to control, because sometimes the changes are extensive enough that it makes more sense to send the entire page back.
Your redirect hack is not a terrible solution, actually. I have used it before.
See below for an example of how you can make your current controller code notify the model properly.
What is this 'woven_hookupOutputConduitToThisFrame' all about? Do I need to set up another method to handle this URL?
I know that it's not recommended to put so much logic in the .rpy file, but as I said, a quick and dirty prototype is what I'm after. Am attaching the source of mapserv.rpy here:
-------------------------------------------------------------------- import os import time from twisted.web.woven import page, widgets, controller, model from mapscript import *
base_mapfile = 'navtech_std.map'
# Get map object from the session data if we can class MapModel(model.MethodModel): def wmfactory_mapobj(self, request): session_obj = request.getSession() try: session_mapobj = session_obj.mapobj except AttributeError: session_mapobj = mapObj(base_mapfile) session_obj.mapobj = session_mapobj return session_mapobj
# Widget for creating a temp map image and a tag linking to it class MapImage(widgets.Widget): def setUp(self, request, node, data): mapobj = data node.tagName = "img" imgobj = mapobj.draw() tmp_file = '%s_%d_%d.%s' \ % (mapobj.name, time.time(), os.getpid(), imgobj.format.extension ) imgobj.save(os.path.join('/tmp', tmp_file)) map_url = os.path.join('/tmp', tmp_file) node.setAttribute('src', map_url) node.setAttribute('height', str(mapobj.height)) node.setAttribute('width', str(mapobj.width)) node.setAttribute('border', '1')
# Widget for displaying the current scale of the map class MapScale(widgets.Widget): def setUp(self, request, node, data): text = 'Scale: 1:%d' % (data.scale) newNode = request.d.createTextNode(text) node.appendChild(newNode)
# Template, using the web conduit glue template = """<html> <body> <img model="mapobj" view="map_image" controller="map_ctrl"/> <p model="mapobj" view="map_scale" /> <div view="webConduitGlue" /> </body> </html> """
# Zoom into the map in the event of a javascript onclick class MyEventHandler(controller.Controller): def handle(self, request): self.view.addEventHandler("onclick", self.onClick)
def onClick(self, request, widget): # Get session map object (layers and spatial extents) session_obj = request.getSession() try: session_mapobj = session_obj.mapobj except AttributeError: session_mapobj = mapObj(base_mapfile) session_obj.mapobj = session_mapob
# zoom in on the map w = session_mapobj.width h = session_mapobj.height pt = pointObj() pt.x, pt.y = w/2, h/2 session_mapobj.zoomPoint(2, pt, w, h, session_mapobj.extent, None) print session_mapobj.extent.minx, session_mapobj.extent.maxx print self, "Zoomed!!!"
# Tell the browser that the model has changed, and # any widgets which rely on this model will have to be # rerendered, and the HTML sent to the browser widget.model.notify({'request': request})
# Delete the next three lines
# There is a better way to redraw the map, certainly request.redirect('http://localhost:8084/mapserv.rpy/') request.finish()
# Page class class MyPage(page.LivePage): def wcfactory_map_ctrl(self, request, node, model): return MyEventHandler(model)
# Resource resource = MyPage(MapModel(), template=template) resource.setSubviewFactory("map_image", MapImage) resource.setSubviewFactory("map_scale", MapScale) ---------------------------------------------------------------------- -
looking forward to any insights into LivePage and also looking forward to getting enough experience with Twisted that I can begin to contribute to the list discussions.
By the way, addEventHandler takes additional arguments which are javascript strings which will be evaluated within the context of the browser, and the results will be sent as additional arguments to the server-side event handler.
To take advantage of this, you have to know a bit about JavaScript, but for example, if you know about the IE global "event" object, you could use it to pass the current x, y position of the mouse to the server with something like this:
self.view.addEventHandler("onclick", self.onClick, 'event.x', 'event.y')
Then defining the event handler like this:
def addEventHandler(self, request, widget, x, y): print "x, y", x, y
Of course, doing this means knowing enough about javascript to do this properly, but those are problems with JavaScript and not Woven. I think Mozilla has different semantics for getting at the event object which may require changes in WebConduit2_js. I would consider it a bug if changes are required.
I hope this helps!
Donovan
Hi Donovan, Thanks for the reply. I can't wait to try out your recommendations. I had read in the docs about the extra args to addEventHandler, but appreciate seeing an example that's so close to my particular need. cheers, Sean -- Sean Gillies sgillies at frii dot com http://www.frii.com/~sgillies