# AjaxPage.py # # Author: John Dickinson # with help from nevow.org and Apple developer code # see also ajax.js import SitePage as ParentClass import StringIO,traceback,time,random RESPONSE_TIMEOUT = 100 # PyJavascript and quote_js based on ideas from nevow 0.4.1 (www.nevow.org) def quote_js(what): if isinstance(what, bool): ret = str(what).lower() elif isinstance(what,(int,long,float,PyJavascript)): ret = str(what) else: ret = "'%s'" % str(what).replace('\\','\\\\').replace('\'','\\\'').replace('\n','\\n') return ret class PyJavascript(object): def __init__(self, name): self.__name = name def __getattr__(self,aname): return self.__class__('%s.%s'%(self,aname)) def __str__(self): return self.__name def __call__(self,*a,**kw): args = ','.join(quote_js(i) for i in a) kwargs = ','.join('%s=%s'%(k,quote_js(v)) for k,v in kw.items()) if args and kwargs: allargs = '%s,%s'%(args,kwargs) elif not kwargs: allargs = args elif not args: allargs = kwargs return self.__class__('%s(%s)'%(self,allargs)) def __getitem__(self,index): return self.__class__('%s[%s]'%(self,quote_js(index))) def __repr__(self): return self.__str__() class AjaxPage(ParentClass): '''a super class to make coding XMLHttpRequest() applications easier''' # class-level variables to help make client code simpler document = PyJavascript('document') setTag = PyJavascript('ajax_setTag') setClass = PyJavascript('ajax_setClass') setValue = PyJavascript('ajax_setValue') setReadonly = PyJavascript('ajax_setReadonly') alert = PyJavascript('alert') generic_ajax = PyJavascript('generic_ajax') generic_ajax_form = PyJavascript('generic_ajax_form') this = PyJavascript('this') _responseBucket = {} def actions(self): return ParentClass.actions(self) + ['ajax_controller','ajax_response'] def ajax_allowed(self): return [] def ajax_clientPollingInterval(self): return random.choice(range(3,8)) # make it random to avoid syncronization def ajax_response(self): wait = self.ajax_clientPollingInterval() if wait is None: # turn off polling cmd = 'dying = true;' # tell the client that we are done polling else: who = self.session().identifier() # wait will be the timeout until the next time this function is called by the client cmd = 'wait = %s;' % wait # set wait variable here if self._responseBucket.get(who,[]): cmd += ';'.join(str(val) for req_number,val in self._responseBucket[who]) # add in other commands self._responseBucket[who] = [] self.write(cmd) # write out al least the wait variable def ajax_controller(self): fields = self.request().fields() func = fields.get('f') args = fields.get('a',[]) if type(args) != type([]): args = [args] req_number = args[-1] start_time = time.time() val = self.alert('There was some problem') if func in self.ajax_allowed(): try: func_obj = getattr(self,func) except AttributeError: val = self.alert('%s, although an approved function, was not found' % func) else: try: val = str(func_obj(*args[:-1])) # pull off sequence number added to "fix" IE except: err = StringIO.StringIO() traceback.print_exc(file=err) e = err.getvalue() val = self.alert('%s was called, but encountered an error: %s'%(func,e)) err.close() else: val = self.alert('%s is not an approved function' % func) if (time.time()-start_time) < RESPONSE_TIMEOUT: self.write(val) else: who = self.session().identifier() if not self._responseBucket.has_key(who): self._responseBucket[who] = [(req_number,val)] else: self._responseBucket[who].append((req_number,val)) def ajax_cmdToClient(self,cmd): who = self.session().identifier() if not self._responseBucket.has_key(who): self._responseBucket[who] = [] self._responseBucket[who].append((None,cmd)) def preAction(self, action_name): if action_name.startswith('ajax_'): pass else: ParentClass.preAction(self,action_name) def postAction(self, action_name): if action_name.startswith('ajax_'): pass else: ParentClass.postAction(self,action_name) def writeJavaScript(self): ParentClass.writeJavaScript(self) self.writeln('') if self.ajax_clientPollingInterval() is not None: self.writeln('')