text: # Copyright (c) 2008 Divmod.  See LICENSE for details.
  
  """
  Demonstration of an Athena Widget which accepts input from the browser and
  sends back responses.
  """
  
  import sys
  
  from twisted.python.filepath import FilePath
  from twisted.python import log
  from twisted.internet import reactor
  
  from nevow.athena import LivePage, LiveElement, expose
  from nevow.loaders import xmlfile
  from nevow.appserver import NevowSite
  
  # Handy helper for finding external resources nearby.
  sibling = FilePath(__file__).sibling
  
  
  class Calculator(object):
      """
      The model object for the calculator demo.  This is the object which
      actually knows how to perform calculations.
  
      @ivar expression: A C{str} giving the current expression which has been
          entered into the calculator.  For example, if the buttons '3', '5', and
          '+' have been pressed (in that order), C{expression} will be C{'35+'}.
      """
      defaultExpression = '0'
      errorExpression = 'E'
  
      def __init__(self):
          self.expression = self.defaultExpression
  
  
      def buttonClicked(self, symbol):
          """
          Change the current expression by performing the operation indicated by
          C{symbol} (clearing it or computing it) or by extending it (with a
          digit or operator).
  
          @param symbol: C{'C'} to clear the expression, C{'='} to evaluate the
              expression, or one of C{'0'}-C{'9'}.
  
          @rtype: C{unicode}
          @return: The expression after interpreting the new symbol.
          """
          # Clear
          if symbol == 'C':
              self.expression = self.defaultExpression
              return self.expression
          # Check the expression is currently valid
          if self.expression == self.errorExpression:
              return self.expression
          # Evaluate the expression
          if symbol == '=':
              try:
                  self.expression = str(eval(self.expression))
              except ZeroDivisionError:
                  self.expression = self.errorExpression
              return self.expression
          # Replace of add to the expression
          if self.expression == self.defaultExpression:
              self.expression = symbol
          else:
              self.expression += symbol
          return self.expression
  
  
  
  class CalculatorElement(LiveElement):
      """
      A "live" calculator.
  
      All buttons presses in the browser are sent to the server. The server
      evaluates the expression and sets the output in the browser.
  
      @ivar validSymbols: A C{str} giving all of the symbols which the browser is
          allowed to submit to us.  Input is checked against this before being
          submitted to the model.
  
      @ivar calc: A L{Calculator} which will be used to handle all inputs and
          generate computed outputs.
      """
      docFactory = xmlfile(sibling('calculator.html').path, 'CalculatorPattern')
  
      jsClass = "CalculatorDemo.Calculator"
  
      validSymbols = '0123456789/*-=+.C'
  
      def __init__(self, calc):
          LiveElement.__init__(self)
          self.calc = calc
  
  
      def buttonClicked(self, symbol):
          """
          Accept a symbol from the browser, perform input validation on it,
          provide it to the underlying L{Calculator} if appropriate, and return
          the result.
  
          @type symbol: C{unicode}
          @rtype: C{unicode}
          """
          # Remember ... never trust a browser
          if symbol not in self.validSymbols:
              raise ValueError('Invalid symbol')
          return self.calc.buttonClicked(symbol)
      expose(buttonClicked)
  
  
  
  class CalculatorParentPage(LivePage):
      """
      A "live" container page for L{CalculatorElement}.
      """
      docFactory = xmlfile(sibling('calculator.html').path)
  
      def __init__(self, *a, **kw):
          LivePage.__init__(self)
          # Update the mapping of known JavaScript modules so that the
          # client-side code for this example can be found and served to the
          # browser.
          self.jsModules.mapping['CalculatorDemo'] = sibling(
              'calculator.js').path
  
  
      def render_calculator(self, ctx, data):
          """
          Replace the tag with a new L{CalculatorElement}.
          """
          c = CalculatorElement(Calculator())
          c.setFragmentParent(self)
          return c
  
  
  
  def main():
      log.startLogging(sys.stdout)
      site = NevowSite(CalculatorParentPage(calc=Calculator()))
      reactor.listenTCP(8080, site)
      reactor.run()
  
  
  
  if __name__ == '__main__':
      main()
  