Fan

 

abstract class

webapp::Widget

sys::Obj
  web::Weblet
    webapp::Widget
//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   18 Mar 08  Andy Frank  Creation
//

using web

**
** Widget is the base class for all web-based UI widgets.
**
** See `docLib::WebWidget`
**
@serializable
abstract class Widget : Weblet
{

  **
  ** Handle configuring the inital Widget pipeline.  To allow
  ** Widgets to be nested, we use two thread local Bufs to capture
  ** the output for the '<head>' and '<body>' tags separately.
  ** After the request has been serviced, we flush the Bufs to the
  ** actual output stream.
  **
  ** If this method is called again (on any instance) after the
  ** initial call, it short-circuits and simply calls the default
  ** `web::Weblet.service` implementation.
  **
  override Void service()
  {
    // if service has already been called on this thread
    // then just route to the default implementation
    if (Actor.locals["webapp.widget.head"] != null)
    {
      super.service
      return
    }

    try
    {
      // create bufs
      head := Buf(1024)
      body := Buf(8192)

      // add thread locals
      Actor.locals["webapp.widget.head"] = WebOutStream(head.out)
      Actor.locals["webapp.widget.body"] = WebOutStream(body.out)

      // write content
      res.headers["Content-Type"] = "text/html; charset=UTF-8"
      q := req.uri.query["webappWidgetCall"]
      if (q != null)
      {
        m := Slot.findMethod(q)
        if (!m.parent.fits(Widget#))
          throw ArgErr("Type not Widget: $m.parent")
        m.parent.make()->onCall(m)
      }
      else
      {
        startRes
        super.service
        finishRes
      }

      // flush streams
      if (!res.isCommitted)
      {
        res.headers["Content-Length"] = (head.size + body.size).toStr
        res.out.writeBuf(head.flip)
        res.out.writeBuf(body.flip)
      }
    }
    finally
    {
      // remove thread locals
      Actor.locals.remove("webapp.widget.head")
      Actor.locals.remove("webapp.widget.body")
    }
  }

  **
  ** Start the response.  For HTML pages, this method is
  ** responsible for creating the markup up to and including
  ** the starting '<head>' and '<body>' tags in their
  ** respective buffers.
  **
  virtual Void startRes()
  {
    if (req.method == "GET")
    {
      head.docType
      head.html
      head.head
      body.body
    }
  }

  **
  ** Finish the response.  For HTML pages, this method is
  ** responsible for writing the ending '<head>' and '<body>'
  ** tags in the respective buffers.
  **
  virtual Void finishRes()
  {
    if (req.method == "GET")
    {
      head.headEnd
      body.bodyEnd
      body.htmlEnd
    }
  }

  **
  ** The buffered WebOutStream for the <head> element.
  **
  once WebOutStream head()
  {
    buf := Actor.locals["webapp.widget.head"] as WebOutStream
    if (buf == null) throw Err("Widget.head not found")
    return buf
  }

  **
  ** The buffered WebOutStream for the <body> element.
  **
  once WebOutStream body()
  {
    buf := Actor.locals["webapp.widget.body"] as WebOutStream
    if (buf == null) throw Err("Widget.body not found")
    return buf
  }

  **
  ** Return a Uri that can be used to call a specific method
  ** on a Widget, bypassing the normal 'service' implementation.
  **
  Uri call(Method m, Uri uri := req.uri)
  {
    if (!m.parent.fits(Widget#))
      throw ArgErr("Type must be Widget: $m.parent")

    return uri.plusQuery(["webappWidgetCall":m.qname])
  }

  **
  ** Return a new Uri that removes the 'Widget.call' specifics
  ** from the given Uri.
  **
  Uri minusCall(Uri uri)
  {
    path  := uri.pathStr
    query := uri.query.rw
    query.remove("webappWidgetCall")
    return path.toUri.plusQuery(query)
  }

  **
  ** Handle invoking a method requested using a Uri from 'Widget.call'.
  **
  virtual Void onCall(Method m)
  {
    m.call(this)
  }

  **
  ** Return a name that is unique to this web request.  The name
  ** returned can be used as a valid HTML name or id attribute, as
  ** well as a JavaScript variable name.
  **
  Str unique()
  {
    Int last := Actor.locals.get("webapp.widget.unique", -1)
    Actor.locals["webapp.widget.unique"] = ++last
    return "n$last"
  }

}