Commits

Robert Clipsham committed f7d4620

Added a crude and fairly useless implementation of Layouts to handle the
display of multiple controllers per request.
Improved documentation in Response and made a few other tweaks.

Comments (0)

Files changed (8)

 controllers.d
-models.d
+layouts.d
 lib/*
 .obj/*
 bin/*
 import serenity.database.Sqlite;
 
 import controllers;
+import layouts;
 
 import tango.core.tools.TraceExceptions;
 
 
 echo '// Automatically generated, do not edit by hand' > controllers.d
 echo 'module controllers;' >> controllers.d
+echo '// Automatically generated, do not edit by hand' > layouts.d
+echo 'module layouts;' >> layouts.d
 
 rm -f lib/libserenity-example.a
 for f in $(find example/controllers/ -name \*.d); do
     m=$(basename $f .d | perl -pe 's|/|.|g')
     echo "import example.controllers.$m;" >> controllers.d
 done
+for f in $(find example/layouts/ -name \*.d); do
+    $DC $ARGS $f $OD.obj -op
+    m=$(basename $f .d | perl -pe 's|/|.|g')
+    echo "import example.layouts.$m;" >> controllers.d
+done
 for f in $(find example/models/ -name \*.d); do
     $DC $ARGS $f $OD.obj -op || exit 1
 done
 ar rs lib/libserenity-example.a $(find .obj -name \*.o) || exit 1
 
-$DC -unittest $(echo $ARGS | perl -pe 's/-c//g') bootstrap.d controllers.d $OD.obj ${OF}bin/serenity.fcgi -L-Llib -L-lserenity-example -L-lserenity -L-lfcgid -L-lfcgi -Ifcgi || exit 1
+$DC -unittest $(echo $ARGS | perl -pe 's/-c//g') bootstrap.d controllers.d layouts.d $OD.obj ${OF}bin/serenity.fcgi -L-Llib -L-lserenity-example -L-lserenity -L-lfcgid -L-lfcgi -Ifcgi || exit 1
 # Speed up symbol look up for actions when not debugging
 if (( $DEBUG == 0 )); then
     strip -w '-K_D*controllers*view*MFC8serenity7Request7RequestAAaZC8serenity12HtmlDocument12HtmlDocument' bin/serenity.fcgi

example/layouts/Default.d

+/**
+ * Serenity Web Framework Example Plugin
+ *
+ * layouts/Default.d: Default layout for the Example Plugin
+ *
+ * Authors: Robert Clipsham <robert@octarineparrot.com>
+ * Copyright: Copyright (c) 2011, Robert Clipsham <robert@octarineparrot.com> 
+ * License: New BSD License, see COPYING
+ */
+module example.layouts.Default;
+
+import serenity.Layout;
+import serenity.HtmlDocument;
+
+class Default : Layout
+{
+    mixin registerLayout!(Default);
+
+    private HtmlDocument mDoc;
+
+    this()
+    {
+        mDoc = new HtmlDocument;
+    }
+
+    public HtmlDocument layout(Controller main, Document doc)
+    {
+        return cast(HtmlDocument)doc;
+    }
+}

serenity/Controller.d

  */
 module serenity.Controller;
 
+import serenity.Layout;
 import serenity.Log;
 import serenity.Model;
 import serenity.Response : Headers;
     private static Registered[char[]] mControllers;
     private char[][] mArguments;
     private Headers mHeaders;
+    private char[] mLayout = "Default";
+    private char[] mPlugin;
     private Logger mLog;
     private ushort mResponseCode = 200;
     private char[] mViewMethod = "viewdefault";
         controller.mArguments = args;
         controller.mHeaders = new Headers;
         controller.mHeaders["Content-Type"] = "text/html; charset=utf-8";
+        controller.mPlugin = plugin;
         controller.mViewMethod = "view" ~ toLower(action);
         controller.setResponseCode(code);
         return controller;
     }
 
     /**
+     * Get the Layout to be used for this request
+     *
+     * Returns:
+     *  Layout to be used
+     */
+    final public Layout getLayout()
+    {
+        return Layout.create(mPlugin, mLayout);
+    }
+
+    /**
+     * Set the layout type to use
+     *
+     * Params:
+     *  name = Name of the layout to use
+     */
+    final protected void setLayout(char[] name)
+    {
+        mLayout = name;
+    }
+
+    /**
      * Return a list of headers set by this controller
      *
      * Returns:

serenity/Dispatcher.d

  * Dispatcher.d: Dispatch a request
  *
  * Authors: Robert Clipsham <robert@octarineparrot.com>
- * Copyright: Copyright (c) 2010, Robert Clipsham <robert@octarineparrot.com> 
+ * Copyright: Copyright (c) 2010, 2011, Robert Clipsham <robert@octarineparrot.com> 
  * License: New BSD License, see COPYING
  */
 module serenity.Dispatcher;
         {
             auto controller = Router.match(request.getArg("PATH_INFO"));
             auto doc = controller.view(request);
+            auto layout = controller.getLayout();
+            doc = layout.layout(controller, doc);
             return new Response(request, controller.getHeaders(), doc, controller.getResponseCode());
         }
         catch (SerenityBaseException e)
         {
             auto controller = Router.getErrorController(e.getCode(), e.msg);
             auto doc = controller.view(request);
+            auto layout = controller.getLayout();
+            doc = layout.layout(controller, doc);
             return new Response(request, controller.getHeaders(), doc, controller.getResponseCode());
         }
         catch (Exception e)
             e.writeOut((char[] str) { err ~= str; });
             auto controller = Router.getErrorController(500, err);
             auto doc = controller.view(request);
+            auto layout = controller.getLayout();
+            doc = layout.layout(controller, doc);
             return new Response(request, controller.getHeaders(), doc, controller.getResponseCode());
         }
     }

serenity/Layout.d

+/**
+ * Serenity Web Framework
+ *
+ * Layout.d: Wrap a controllers output in a layout
+ *
+ * Authors: Robert Clipsham <robert@octarineparrot.com>
+ * Copyright: Copyright (c) 2011, Robert Clipsham <robert@octarineparrot.com> 
+ * License: New BSD License, see COPYING
+ */
+module serenity.Layout;
+
+public import serenity.Controller;
+public import serenity.Document;
+import serenity.Log;
+import serenity.Util;
+
+import tango.util.container.HashMap;
+
+template registerLayout(T)
+{
+    static this()
+    {
+        // TODO: This should be static, blame dmd >.>
+        // See: http://d.puremagic.com/issues/show_bug.cgi?id=4033
+        assert(isDerived(T.classinfo, Layout.classinfo));
+        Layout.register(T.classinfo);
+    }
+}
+
+mixin SerenityException!("InvalidLayout");
+
+class Layout
+{
+    private alias HashMap!(char[], ClassInfo) Registry;
+    private static Registry mLayouts;
+
+    static this()
+    {
+        mLayouts = new Registry;
+    }
+
+    /**
+     * Layout the given controller and document
+     *
+     * Params:
+     *  main = The main controller, as specified by the router
+     *  doc  = The document returned from main.view()
+     * Returns:
+     *  The resulting document, with a layout specified in the subclass
+     */
+    abstract public Document layout(Controller main, Document doc);
+
+    /**
+     * Create a layout from the given plugin with the given name
+     *
+     * Params:
+     *  plugin = Name of the plugin in which the Layout resides
+     *  name   = Name of the layout
+     * Throws:
+     *  InvalidLayoutException when the given Layout does not exist
+     * Returns:
+     *  The specified layout
+     */
+    public static Layout create(char[] plugin, char[] name)
+    {
+        auto cname = plugin ~ ".layouts." ~ name ~ "." ~ name;
+        auto registered = cname in mLayouts;
+        if (registered is null)
+        {
+            throw new InvalidLayoutException("Invalid layout: " ~ cname);
+        }
+        return cast(typeof(this))registered.create();
+    }
+
+    /**
+     * Register the given layout
+     *
+     * This should not be called directly, this is handled by registerLayout!(T)
+     * Params:
+     *  ci = ClassInfo of the layout to register
+     * Throws:
+     *  InvalidLayoutException when the given layout does not extend
+     *  Layout
+     */
+    public static void register(ClassInfo ci)
+    {
+        if (!isDerived(ci, this.classinfo))
+        {
+            throw new InvalidLayoutException("Invalid layout: " ~ ci.name);
+        }
+        mLayouts[ci.name] = ci;
+    }
+}

serenity/Response.d

  * Response.d: Represents a response to a request
  *
  * Authors: Robert Clipsham <robert@octarineparrot.com>
- * Copyright: Copyright (c) 2010, Robert Clipsham <robert@octarineparrot.com> 
+ * Copyright: Copyright (c) 2010, 2011, Robert Clipsham <robert@octarineparrot.com> 
  * License: New BSD License, see COPYING
  */
 module serenity.Response;
 import serenity.HtmlDocument;
 import serenity.HtmlPrinter;
 import serenity.Request;
+import serenity.Util;
 
 import tango.io.stream.Format;
 import tango.util.container.HashMap;
 
-class ResponseException : Exception
-{
-    this(char[] msg)
-    {
-        super(msg);
-    }
-}
+mixin SerenityException!("Response");
 
 private char[][ushort] statusCodes;
 
 public alias HashMap!(char[], char[]) Headers;
 class Response
 {
-    private Request mRequest;
-    private Headers mHeaders;
+    private Request  mRequest;
+    private Headers  mHeaders;
     private Document mDocument;
-    private ushort mCode;
+    private ushort   mCode;
 
     this(Request req, Headers headers, Document doc, ushort code)
     {
         mCode = code;
     }
 
+    /**
+     * Send the HTTP response code to the given FormatOutput!(char)
+     *
+     * Params:
+     *  stdout = The place to send the response
+     */
     private void sendStatus(FormatOutput!(char) stdout)
     {
         auto code = mCode in statusCodes;
         stdout("Status: ")(*code)("\r\n");
     }
 
+    /**
+     * Send the response to the given FormatOutput!(char)
+     *
+     * Params:
+     *  stdout = The place to send the response
+     */
     public void send(FormatOutput!(char) stdout)
     {
        auto printer = mRequest.protocol() & Request.Protocol.Cli ? new HtmlPrinter()  : new HtmlPrinter();