a few little web enhancements

This commit is contained in:
Adam D. Ruppe 2021-07-28 22:23:38 -04:00
parent ae0fbaa89f
commit 81dba0f46d
4 changed files with 318 additions and 102 deletions

103
cgi.d
View File

@ -3277,6 +3277,37 @@ mixin template GenericMain(alias fun, long maxContentLength = defaultMaxContentL
mixin CustomCgiMain!(Cgi, fun, maxContentLength); mixin CustomCgiMain!(Cgi, fun, maxContentLength);
} }
/++
Boilerplate mixin for a main function that uses the [dispatcher] function.
You can send `typeof(null)` as the `Presenter` argument to use a generic one.
History:
Added July 9, 2021
+/
mixin template DispatcherMain(Presenter, DispatcherArgs...) {
/++
Handler to the generated presenter you can use from your objects, etc.
+/
Presenter activePresenter;
/++
Request handler that creates the presenter then forwards to the [dispatcher] function.
Renders 404 if the dispatcher did not handle the request.
+/
void handler(Cgi cgi) {
auto presenter = new Presenter;
activePresenter = presenter;
scope(exit) activePresenter = null;
if(cgi.dispatcher!DispatcherArgs(presenter))
return;
presenter.renderBasicError(cgi, 404);
}
mixin GenericMain!handler;
}
private string simpleHtmlEncode(string s) { private string simpleHtmlEncode(string s) {
return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\n", "<br />\n"); return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\n", "<br />\n");
} }
@ -4068,6 +4099,17 @@ void handleCgiRequest(alias fun, CustomCgi = Cgi, long maxContentLength = defaul
+/ +/
/++
The stack size when a fiber is created. You can set this from your main or from a shared static constructor
to optimize your memory use if you know you don't need this much space. Be careful though, some functions use
more stack space than you realize and a recursive function (including ones like in dom.d) can easily grow fast!
History:
Added July 10, 2021. Previously, it used the druntime default of 16 KB.
+/
version(cgi_use_fiber)
__gshared size_t fiberStackSize = 4096 * 100;
version(cgi_use_fiber) version(cgi_use_fiber)
class CgiFiber : Fiber { class CgiFiber : Fiber {
private void function(Socket) f_handler; private void function(Socket) f_handler;
@ -4081,8 +4123,7 @@ class CgiFiber : Fiber {
this(void delegate(Socket) handler) { this(void delegate(Socket) handler) {
this.handler = handler; this.handler = handler;
// FIXME: stack size super(&run, fiberStackSize);
super(&run);
} }
Socket connection; Socket connection;
@ -8106,8 +8147,34 @@ auto callFromCgi(alias method, T)(T dg, Cgi cgi) {
*what = T.init; *what = T.init;
return true; return true;
} else { } else {
// could be a child // could be a child. gonna allow either obj.field OR obj[field]
if(name[paramName.length] == '.') {
string afterName;
if(name[paramName.length] == '[') {
int count = 1;
auto idx = paramName.length + 1;
while(idx < name.length && count > 0) {
if(name[idx] == '[')
count++;
else if(name[idx] == ']') {
count--;
if(count == 0) break;
}
idx++;
}
if(idx == name.length)
return false; // malformed
auto insideBrackets = name[paramName.length + 1 .. idx];
afterName = name[idx + 1 .. $];
name = name[0 .. paramName.length];
paramName = insideBrackets;
} else if(name[paramName.length] == '.') {
paramName = name[paramName.length + 1 .. $]; paramName = name[paramName.length + 1 .. $];
name = paramName; name = paramName;
int p = 0; int p = 0;
@ -8117,19 +8184,25 @@ auto callFromCgi(alias method, T)(T dg, Cgi cgi) {
p++; p++;
} }
afterName = paramName[p .. $];
paramName = paramName[0 .. p];
} else {
return false;
}
if(paramName.length)
// set the child member // set the child member
switch(paramName) { switch(paramName) {
foreach(idx, memberName; __traits(allMembers, T)) foreach(idx, memberName; __traits(allMembers, T))
static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) { static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
// data member! // data member!
case memberName: case memberName:
return setVariable(name, paramName, &(__traits(getMember, *what, memberName)), value); return setVariable(name ~ afterName, paramName, &(__traits(getMember, *what, memberName)), value);
} }
default: default:
// ok, not a member // ok, not a member
} }
} }
}
return false; return false;
} else static if(isSomeString!T || isIntegral!T || isFloatingPoint!T) { } else static if(isSomeString!T || isIntegral!T || isFloatingPoint!T) {
@ -8539,13 +8612,13 @@ html", true, true);
} }
/// Multiple responses deconstruct the algebraic type and forward to the appropriate handler at runtime /// Multiple responses deconstruct the algebraic type and forward to the appropriate handler at runtime
void presentSuccessfulReturn(T : MultipleResponses!Types, Types...)(Cgi cgi, T ret, typeof(null) meta, string format) { void presentSuccessfulReturn(T : MultipleResponses!Types, Meta, Types...)(Cgi cgi, T ret, Meta meta, string format) {
bool outputted = false; bool outputted = false;
foreach(index, type; Types) { foreach(index, type; Types) {
if(ret.contains == index) { if(ret.contains == index) {
assert(!outputted); assert(!outputted);
outputted = true; outputted = true;
(cast(CRTP) this).presentSuccessfulReturnAsHtml(cgi, ret.payload[index], meta); (cast(CRTP) this).presentSuccessfulReturn(cgi, ret.payload[index], meta, format);
} }
} }
if(!outputted) if(!outputted)
@ -8655,7 +8728,19 @@ html", true, true);
auto div = Element.make("div"); auto div = Element.make("div");
div.addClass("form-field"); div.addClass("form-field");
static if(is(T == struct)) { static if(is(T == Cgi.UploadedFile)) {
Element lbl;
if(displayName !is null) {
lbl = div.addChild("label");
lbl.addChild("span", displayName, "label-text");
lbl.appendText(" ");
} else {
lbl = div;
}
auto i = lbl.addChild("input", name);
i.attrs.name = name;
i.attrs.type = "file";
} else static if(is(T == struct)) {
if(displayName !is null) if(displayName !is null)
div.addChild("span", displayName, "label-text"); div.addChild("span", displayName, "label-text");
auto fieldset = div.addChild("fieldset"); auto fieldset = div.addChild("fieldset");

99
dom.d
View File

@ -94,7 +94,10 @@ bool isConvenientAttribute(string name) {
/// The main document interface, including a html parser. /// The main document interface, including a html parser.
/// Group: core_functionality /// Group: core_functionality
class Document : FileResource { class Document : FileResource, DomParent {
inout(Document) asDocument() inout { return this; }
inout(Element) asElement() inout { return null; }
/// Convenience method for web scraping. Requires [arsd.http2] to be /// Convenience method for web scraping. Requires [arsd.http2] to be
/// included in the build as well as [arsd.characterencodings]. /// included in the build as well as [arsd.characterencodings].
static Document fromUrl()(string url, bool strictMode = false) { static Document fromUrl()(string url, bool strictMode = false) {
@ -1130,6 +1133,7 @@ class Document : FileResource {
} while (r.type != 0 || r.element.nodeType != 1); // we look past the xml prologue and doctype; root only begins on a regular node } while (r.type != 0 || r.element.nodeType != 1); // we look past the xml prologue and doctype; root only begins on a regular node
root = r.element; root = r.element;
root.parent_ = this;
if(!strict) // in strict mode, we'll just ignore stuff after the xml if(!strict) // in strict mode, we'll just ignore stuff after the xml
while(r.type != 4) { while(r.type != 4) {
@ -1353,7 +1357,6 @@ class Document : FileResource {
name = name.toLower(); name = name.toLower();
auto e = Element.make(name, null, null, selfClosedElements); auto e = Element.make(name, null, null, selfClosedElements);
e.parentDocument = this;
return e; return e;
@ -1475,9 +1478,17 @@ class Document : FileResource {
} }
} }
interface DomParent {
inout(Document) asDocument() inout;
inout(Element) asElement() inout;
}
/// This represents almost everything in the DOM. /// This represents almost everything in the DOM.
/// Group: core_functionality /// Group: core_functionality
class Element { class Element : DomParent {
inout(Document) asDocument() inout { return null; }
inout(Element) asElement() inout { return this; }
/// Returns a collection of elements by selector. /// Returns a collection of elements by selector.
/// See: [Document.opIndex] /// See: [Document.opIndex]
ElementCollection opIndex(string selector) { ElementCollection opIndex(string selector) {
@ -1926,32 +1937,51 @@ class Element {
/// Instead, this flag tells if it should be. It is based on the source document's notation and a html element list. /// Instead, this flag tells if it should be. It is based on the source document's notation and a html element list.
private bool selfClosed; private bool selfClosed;
private DomParent parent_;
/// Get the parent Document object that contains this element. /// Get the parent Document object that contains this element.
/// It may be null, so remember to check for that. /// It may be null, so remember to check for that.
Document parentDocument; @property inout(Document) parentDocument() inout {
if(this.parent_ is null)
return null;
auto p = cast() this.parent_.asElement;
auto prev = cast() this;
while(p) {
prev = p;
if(p.parent_ is null)
return null;
p = cast() p.parent_.asElement;
}
return cast(inout) prev.parent_.asDocument;
}
deprecated @property void parentDocument(Document doc) {
parent_ = doc;
}
///. ///.
inout(Element) parentNode() inout { inout(Element) parentNode() inout {
auto p = _parentNode; if(parent_ is null)
return null;
auto p = parent_.asElement;
if(cast(DocumentFragment) p) if(cast(DocumentFragment) p)
return p._parentNode; return p.parent_.asElement;
return p; return p;
} }
//protected //protected
Element parentNode(Element e) { Element parentNode(Element e) {
return _parentNode = e; parent_ = e;
return e;
} }
private Element _parentNode;
// the next few methods are for implementing interactive kind of things
private CssStyle _computedStyle;
// these are here for event handlers. Don't forget that this library never fires events. // these are here for event handlers. Don't forget that this library never fires events.
// (I'm thinking about putting this in a version statement so you don't have the baggage. The instance size of this class is 56 bytes right now.) // (I'm thinking about putting this in a version statement so you don't have the baggage. The instance size of this class is 56 bytes right now.)
version(dom_with_events) {
EventHandler[][string] bubblingEventHandlers; EventHandler[][string] bubblingEventHandlers;
EventHandler[][string] capturingEventHandlers; EventHandler[][string] capturingEventHandlers;
EventHandler[string] defaultEventHandlers; EventHandler[string] defaultEventHandlers;
@ -1965,6 +1995,7 @@ class Element {
else else
bubblingEventHandlers[event] ~= handler; bubblingEventHandlers[event] ~= handler;
} }
}
// and now methods // and now methods
@ -2091,7 +2122,6 @@ class Element {
/// Generally, you don't want to call this yourself - use Element.make or document.createElement instead. /// Generally, you don't want to call this yourself - use Element.make or document.createElement instead.
this(Document _parentDocument, string _tagName, string[string] _attributes = null, bool _selfClosed = false) { this(Document _parentDocument, string _tagName, string[string] _attributes = null, bool _selfClosed = false) {
parentDocument = _parentDocument;
tagName = _tagName; tagName = _tagName;
if(_attributes !is null) if(_attributes !is null)
attributes = _attributes; attributes = _attributes;
@ -2128,8 +2158,6 @@ class Element {
} }
private this(Document _parentDocument) { private this(Document _parentDocument) {
parentDocument = _parentDocument;
version(dom_node_indexes) version(dom_node_indexes)
this.dataset.nodeIndex = to!string(&(this.attributes)); this.dataset.nodeIndex = to!string(&(this.attributes));
} }
@ -2600,6 +2628,10 @@ class Element {
// if you change something here, it won't apply... FIXME const? but changing it would be nice if it applies to the style attribute too though you should use style there. // if you change something here, it won't apply... FIXME const? but changing it would be nice if it applies to the style attribute too though you should use style there.
// the next few methods are for implementing interactive kind of things
private CssStyle _computedStyle;
/// Don't use this. /// Don't use this.
@property CssStyle computedStyle() { @property CssStyle computedStyle() {
if(_computedStyle is null) { if(_computedStyle is null) {
@ -2713,12 +2745,16 @@ class Element {
selfClosed = false; selfClosed = false;
e.parentNode = this; e.parentNode = this;
e.parentDocument = this.parentDocument;
if(auto frag = cast(DocumentFragment) e) if(auto frag = cast(DocumentFragment) e)
children ~= frag.children; children ~= frag.children;
else else
children ~= e; children ~= e;
/+
foreach(item; e.tree)
item.parentDocument = this.parentDocument;
+/
sendObserverEvent(DomMutationOperations.appendChild, null, null, e); sendObserverEvent(DomMutationOperations.appendChild, null, null, e);
return e; return e;
@ -2746,7 +2782,6 @@ class Element {
children = children[0..i] ~ frag.children ~ children[i..$]; children = children[0..i] ~ frag.children ~ children[i..$];
else else
children = children[0..i] ~ what ~ children[i..$]; children = children[0..i] ~ what ~ children[i..$];
what.parentDocument = this.parentDocument;
what.parentNode = this; what.parentNode = this;
return what; return what;
} }
@ -2781,7 +2816,6 @@ class Element {
else else
children = children[0 .. i + 1] ~ what ~ children[i + 1 .. $]; children = children[0 .. i + 1] ~ what ~ children[i + 1 .. $];
what.parentNode = this; what.parentNode = this;
what.parentDocument = this.parentDocument;
return what; return what;
} }
} }
@ -2810,7 +2844,6 @@ class Element {
c.parentNode = null; c.parentNode = null;
c = replacement; c = replacement;
c.parentNode = this; c.parentNode = this;
c.parentDocument = this.parentDocument;
return child; return child;
} }
assert(0); assert(0);
@ -2888,7 +2921,6 @@ class Element {
else else
children = children[0..i] ~ child ~ children[i..$]; children = children[0..i] ~ child ~ children[i..$];
child.parentNode = this; child.parentNode = this;
child.parentDocument = this.parentDocument;
break; break;
} }
} }
@ -2920,7 +2952,6 @@ class Element {
do { do {
foreach(c; e.children) { foreach(c; e.children) {
c.parentNode = this; c.parentNode = this;
c.parentDocument = this.parentDocument;
} }
if(position is null) if(position is null)
children ~= e.children; children ~= e.children;
@ -2954,7 +2985,6 @@ class Element {
} }
do { do {
e.parentNode = this; e.parentNode = this;
e.parentDocument = this.parentDocument;
if(auto frag = cast(DocumentFragment) e) if(auto frag = cast(DocumentFragment) e)
children = e.children ~ children; children = e.children ~ children;
else else
@ -3000,13 +3030,10 @@ class Element {
doc.parseUtf8("<innerhtml>" ~ html ~ "</innerhtml>", strict, strict); // FIXME: this should preserve the strictness of the parent document doc.parseUtf8("<innerhtml>" ~ html ~ "</innerhtml>", strict, strict); // FIXME: this should preserve the strictness of the parent document
children = doc.root.children; children = doc.root.children;
foreach(c; children) { foreach(c; doc.root.tree) {
c.parentNode = this; c.parentNode = this;
c.parentDocument = this.parentDocument;
} }
reparentTreeDocuments();
doc.root.children = null; doc.root.children = null;
return this; return this;
@ -3017,11 +3044,6 @@ class Element {
return this.innerHTML = html.source; return this.innerHTML = html.source;
} }
private void reparentTreeDocuments() {
foreach(c; this.tree)
c.parentDocument = this.parentDocument;
}
/** /**
Replaces this node with the given html string, which is parsed Replaces this node with the given html string, which is parsed
@ -3037,13 +3059,8 @@ class Element {
children = doc.root.children; children = doc.root.children;
foreach(c; children) { foreach(c; children) {
c.parentNode = this; c.parentNode = this;
c.parentDocument = this.parentDocument;
} }
reparentTreeDocuments();
stripOut(); stripOut();
return doc.root.children; return doc.root.children;
@ -3093,7 +3110,6 @@ class Element {
replace.parentNode = this; replace.parentNode = this;
children[i].parentNode = null; children[i].parentNode = null;
children[i] = replace; children[i] = replace;
replace.parentDocument = this.parentDocument;
return replace; return replace;
} }
} }
@ -3132,7 +3148,6 @@ class Element {
children[i] = replace[0]; children[i] = replace[0];
foreach(e; replace) { foreach(e; replace) {
e.parentNode = this; e.parentNode = this;
e.parentDocument = this.parentDocument;
} }
children = .insertAfter(children, i, replace[1..$]); children = .insertAfter(children, i, replace[1..$]);
@ -3263,7 +3278,6 @@ class Element {
/// Clones the node. If deepClone is true, clone all inner tags too. If false, only do this tag (and its attributes), but it will have no contents. /// Clones the node. If deepClone is true, clone all inner tags too. If false, only do this tag (and its attributes), but it will have no contents.
Element cloneNode(bool deepClone) { Element cloneNode(bool deepClone) {
auto e = Element.make(this.tagName); auto e = Element.make(this.tagName);
e.parentDocument = this.parentDocument;
e.attributes = this.attributes.aadup; e.attributes = this.attributes.aadup;
e.selfClosed = this.selfClosed; e.selfClosed = this.selfClosed;
@ -3565,6 +3579,9 @@ class Element {
} }
} }
// computedStyle could argubaly be removed to bring size down
//pragma(msg, __traits(classInstanceSize, Element));
//pragma(msg, Element.tupleof);
// FIXME: since Document loosens the input requirements, it should probably be the sub class... // FIXME: since Document loosens the input requirements, it should probably be the sub class...
/// Specializes Document for handling generic XML. (always uses strict mode, uses xml mime type and file header) /// Specializes Document for handling generic XML. (always uses strict mode, uses xml mime type and file header)
@ -4053,7 +4070,7 @@ class DocumentFragment : Element {
} }
*/ */
override Element parentNode(Element p) { override Element parentNode(Element p) {
this._parentNode = p; this.parent_ = p;
foreach(child; children) foreach(child; children)
child.parentNode = p; child.parentNode = p;
return p; return p;
@ -8499,9 +8516,11 @@ private string[string] aadup(in string[string] arr) {
// dom event support, if you want to use it // dom event support, if you want to use it
/// used for DOM events /// used for DOM events
version(dom_with_events)
alias EventHandler = void delegate(Element handlerAttachedTo, Event event); alias EventHandler = void delegate(Element handlerAttachedTo, Event event);
/// This is a DOM event, like in javascript. Note that this library never fires events - it is only here for you to use if you want it. /// This is a DOM event, like in javascript. Note that this library never fires events - it is only here for you to use if you want it.
version(dom_with_events)
class Event { class Event {
this(string eventName, Element target) { this(string eventName, Element target) {
this.eventName = eventName; this.eventName = eventName;

View File

@ -693,6 +693,9 @@ struct var {
// so prewrapped stuff can be easily passed. // so prewrapped stuff can be easily passed.
this._type = Type.Object; this._type = Type.Object;
this._payload._object = t; this._payload._object = t;
} else static if(is(T == enum)) {
this._type = Type.String;
this._payload._string = to!string(t);
} else static if(isFloatingPoint!T) { } else static if(isFloatingPoint!T) {
this._type = Type.Floating; this._type = Type.Floating;
this._payload._floating = t; this._payload._floating = t;

View File

@ -20,6 +20,11 @@
there were no items. there were no items.
</or-else> </or-else>
<form>
<!-- new on July 17, 2021 (dub v10.3) -->
<hidden-form-data from="data_var" name="arg_name" />
</form>
<render-template file="partial.html" /> <render-template file="partial.html" />
</main> </main>
``` ```
@ -49,11 +54,10 @@ class TemplateException : Exception {
} }
} }
Document renderTemplate(string templateName, var context = var.emptyObject, var skeletonContext = var.emptyObject) { void addDefaultFunctions(var context) {
import std.file; import std.conv;
import arsd.cgi; // FIXME: I prolly want it to just set the prototype or something
try {
context.encodeURIComponent = function string(var f) { context.encodeURIComponent = function string(var f) {
import std.uri; import std.uri;
return encodeComponent(f.get!string); return encodeComponent(f.get!string);
@ -88,6 +92,21 @@ Document renderTemplate(string templateName, var context = var.emptyObject, var
return hour.to!string ~ (minutes < 10 ? ":0" : ":") ~ minutes.to!string ~ " " ~ am; return hour.to!string ~ (minutes < 10 ? ":0" : ":") ~ minutes.to!string ~ " " ~ am;
}; };
// don't want checking meta or data to be an error
if(context.meta == null)
context.meta = var.emptyObject;
if(context.data == null)
context.data = var.emptyObject;
}
Document renderTemplate(string templateName, var context = var.emptyObject, var skeletonContext = var.emptyObject) {
import std.file;
import arsd.cgi;
try {
addDefaultFunctions(context);
addDefaultFunctions(skeletonContext);
auto skeleton = new Document(readText("templates/skeleton.html"), true, true); auto skeleton = new Document(readText("templates/skeleton.html"), true, true);
auto document = new Document(); auto document = new Document();
document.parseSawAspCode = (string) => true; // enable adding <% %> to the dom document.parseSawAspCode = (string) => true; // enable adding <% %> to the dom
@ -212,6 +231,18 @@ void expandTemplate(Element root, var context) {
fragment.stealChildren(document.root); fragment.stealChildren(document.root);
debug fragment.appendChild(new HtmlComment(null, "end " ~ templateName)); debug fragment.appendChild(new HtmlComment(null, "end " ~ templateName));
ele.replaceWith(fragment);
} else if(ele.tagName == "hidden-form-data") {
auto from = interpret(ele.attrs.from, context);
auto name = ele.attrs.name;
auto form = new Form(null);
populateForm(form, from, name);
auto fragment = new DocumentFragment(null);
fragment.stealChildren(form);
ele.replaceWith(fragment); ele.replaceWith(fragment);
} else if(auto asp = cast(AspCode) ele) { } else if(auto asp = cast(AspCode) ele) {
auto code = asp.source[1 .. $-1]; auto code = asp.source[1 .. $-1];
@ -238,7 +269,7 @@ void expandTemplate(Element root, var context) {
if(root.hasAttribute("onrender")) { if(root.hasAttribute("onrender")) {
var nc = var.emptyObject(context); var nc = var.emptyObject(context);
nc["this"] = wrapNativeObject(root); nc["this"] = wrapNativeObject(root);
nc["this"]["populateFrom"]._function = delegate var(var this_, var[] args) { nc["this"]["populateFrom"] = delegate var(var this_, var[] args) {
auto form = cast(Form) root; auto form = cast(Form) root;
if(form is null) return this_; if(form is null) return this_;
foreach(k, v; args[0]) { foreach(k, v; args[0]) {
@ -256,9 +287,12 @@ void populateForm(Form form, var obj, string name) {
import std.string; import std.string;
if(obj.payloadType == var.Type.Object) { if(obj.payloadType == var.Type.Object) {
form.setValue(name, "");
foreach(k, v; obj) { foreach(k, v; obj) {
auto fn = name.replace("%", k.get!string); auto fn = name.replace("%", k.get!string);
// should I unify structs and assoctiavite arrays?
populateForm(form, v, fn ~ "["~k.get!string~"]"); populateForm(form, v, fn ~ "["~k.get!string~"]");
//populateForm(form, v, fn ~"."~k.get!string);
} }
} else { } else {
//import std.stdio; writeln("SET ", name, " ", obj, " ", obj.payloadType); //import std.stdio; writeln("SET ", name, " ", obj, " ", obj.payloadType);
@ -291,6 +325,18 @@ struct Template {
struct Skeleton { struct Skeleton {
string name; string name;
} }
/++
UDA to attach runtime metadata to a function. Will be available in the template.
History:
Added July 12, 2021
+/
struct meta {
string name;
string value;
}
/++ /++
Can be used as a return value from one of your own methods when rendering websites with [WebPresenterWithTemplateSupport]. Can be used as a return value from one of your own methods when rendering websites with [WebPresenterWithTemplateSupport].
+/ +/
@ -319,6 +365,8 @@ template WebPresenterWithTemplateSupport(CTRP) {
typeof(null) at; typeof(null) at;
string templateName; string templateName;
string skeletonName; string skeletonName;
string[string] meta;
Form function(WebPresenterWithTemplateSupport presenter) automaticForm;
alias at this; alias at this;
} }
template methodMeta(alias method) { template methodMeta(alias method) {
@ -332,6 +380,12 @@ template WebPresenterWithTemplateSupport(CTRP) {
ret.templateName = attr.name; ret.templateName = attr.name;
else static if(is(typeof(attr) == Skeleton)) else static if(is(typeof(attr) == Skeleton))
ret.skeletonName = attr.name; ret.skeletonName = attr.name;
else static if(is(typeof(attr) == .meta))
ret.meta[attr.name] = attr.value;
ret.automaticForm = function Form(WebPresenterWithTemplateSupport presenter) {
return presenter.createAutomaticFormForFunction!(method, typeof(&method))(null);
};
return ret; return ret;
} }
@ -351,11 +405,66 @@ template WebPresenterWithTemplateSupport(CTRP) {
void presentSuccessfulReturnAsHtml(T)(Cgi cgi, T ret, Meta meta) { void presentSuccessfulReturnAsHtml(T)(Cgi cgi, T ret, Meta meta) {
if(meta.templateName.length) { if(meta.templateName.length) {
var sobj = var.emptyObject;
var obj = var.emptyObject; var obj = var.emptyObject;
obj.data = ret; obj.data = ret;
presentSuccessfulReturnAsHtml(cgi, RenderTemplate(meta.templateName, obj), meta);
/+
sobj.meta = var.emptyObject;
foreach(k,v; meta.meta)
sobj.meta[k] = v;
+/
obj.meta = var.emptyObject;
foreach(k,v; meta.meta)
obj.meta[k] = v;
obj.meta.currentPath = cgi.pathInfo;
obj.meta.automaticForm = { return meta.automaticForm(this).toString; };
presentSuccessfulReturnAsHtml(cgi, RenderTemplate(meta.templateName, obj, sobj), meta);
} else } else
super.presentSuccessfulReturnAsHtml(cgi, ret, meta); super.presentSuccessfulReturnAsHtml(cgi, ret, meta);
} }
} }
} }
auto serveTemplateDirectory()(string urlPrefix, string directory = null, string skeleton = null, string extension = ".html") {
import arsd.cgi;
import std.file;
assert(urlPrefix[0] == '/');
assert(urlPrefix[$-1] == '/');
static struct DispatcherDetails {
string directory;
string skeleton;
string extension;
}
if(directory is null)
directory = urlPrefix[1 .. $];
assert(directory[$-1] == '/');
static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
auto file = cgi.pathInfo[urlPrefix.length .. $];
if(file.indexOf("/") != -1 || file.indexOf("\\") != -1)
return false;
auto fn = "templates/" ~ details.directory ~ file ~ details.extension;
if(std.file.exists(fn)) {
cgi.setCache(true);
auto doc = renderTemplate(fn["templates/".length.. $]);
cgi.gzipResponse = true;
cgi.write(doc.toString, true);
return true;
} else {
return false;
}
}
return DispatcherDefinition!(internalHandler, DispatcherDetails)(urlPrefix, false, DispatcherDetails(directory, skeleton, extension));
}