mirror of https://github.com/adamdruppe/arsd.git
a few little web enhancements
This commit is contained in:
parent
ae0fbaa89f
commit
81dba0f46d
119
cgi.d
119
cgi.d
|
@ -3277,6 +3277,37 @@ mixin template GenericMain(alias fun, long maxContentLength = defaultMaxContentL
|
|||
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) {
|
||||
return s.replace("&", "&").replace("<", "<").replace(">", ">").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)
|
||||
class CgiFiber : Fiber {
|
||||
private void function(Socket) f_handler;
|
||||
|
@ -4081,8 +4123,7 @@ class CgiFiber : Fiber {
|
|||
|
||||
this(void delegate(Socket) handler) {
|
||||
this.handler = handler;
|
||||
// FIXME: stack size
|
||||
super(&run);
|
||||
super(&run, fiberStackSize);
|
||||
}
|
||||
|
||||
Socket connection;
|
||||
|
@ -8106,8 +8147,34 @@ auto callFromCgi(alias method, T)(T dg, Cgi cgi) {
|
|||
*what = T.init;
|
||||
return true;
|
||||
} else {
|
||||
// could be a child
|
||||
if(name[paramName.length] == '.') {
|
||||
// could be a child. gonna allow either obj.field OR obj[field]
|
||||
|
||||
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 .. $];
|
||||
name = paramName;
|
||||
int p = 0;
|
||||
|
@ -8117,17 +8184,23 @@ auto callFromCgi(alias method, T)(T dg, Cgi cgi) {
|
|||
p++;
|
||||
}
|
||||
|
||||
// set the child member
|
||||
switch(paramName) {
|
||||
foreach(idx, memberName; __traits(allMembers, T))
|
||||
static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
|
||||
// data member!
|
||||
case memberName:
|
||||
return setVariable(name, paramName, &(__traits(getMember, *what, memberName)), value);
|
||||
}
|
||||
default:
|
||||
// ok, not a member
|
||||
afterName = paramName[p .. $];
|
||||
paramName = paramName[0 .. p];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(paramName.length)
|
||||
// set the child member
|
||||
switch(paramName) {
|
||||
foreach(idx, memberName; __traits(allMembers, T))
|
||||
static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
|
||||
// data member!
|
||||
case memberName:
|
||||
return setVariable(name ~ afterName, paramName, &(__traits(getMember, *what, memberName)), value);
|
||||
}
|
||||
default:
|
||||
// ok, not a member
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8539,13 +8612,13 @@ html", true, true);
|
|||
}
|
||||
|
||||
/// 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;
|
||||
foreach(index, type; Types) {
|
||||
if(ret.contains == index) {
|
||||
assert(!outputted);
|
||||
outputted = true;
|
||||
(cast(CRTP) this).presentSuccessfulReturnAsHtml(cgi, ret.payload[index], meta);
|
||||
(cast(CRTP) this).presentSuccessfulReturn(cgi, ret.payload[index], meta, format);
|
||||
}
|
||||
}
|
||||
if(!outputted)
|
||||
|
@ -8655,7 +8728,19 @@ html", true, true);
|
|||
auto div = Element.make("div");
|
||||
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)
|
||||
div.addChild("span", displayName, "label-text");
|
||||
auto fieldset = div.addChild("fieldset");
|
||||
|
|
119
dom.d
119
dom.d
|
@ -94,7 +94,10 @@ bool isConvenientAttribute(string name) {
|
|||
|
||||
/// The main document interface, including a html parser.
|
||||
/// 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
|
||||
/// included in the build as well as [arsd.characterencodings].
|
||||
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
|
||||
|
||||
root = r.element;
|
||||
root.parent_ = this;
|
||||
|
||||
if(!strict) // in strict mode, we'll just ignore stuff after the xml
|
||||
while(r.type != 4) {
|
||||
|
@ -1353,7 +1357,6 @@ class Document : FileResource {
|
|||
name = name.toLower();
|
||||
|
||||
auto e = Element.make(name, null, null, selfClosedElements);
|
||||
e.parentDocument = this;
|
||||
|
||||
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.
|
||||
/// 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.
|
||||
/// See: [Document.opIndex]
|
||||
ElementCollection opIndex(string selector) {
|
||||
|
@ -1926,44 +1937,64 @@ class Element {
|
|||
/// 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 DomParent parent_;
|
||||
|
||||
/// Get the parent Document object that contains this element.
|
||||
/// 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 {
|
||||
auto p = _parentNode;
|
||||
if(parent_ is null)
|
||||
return null;
|
||||
|
||||
auto p = parent_.asElement;
|
||||
|
||||
if(cast(DocumentFragment) p)
|
||||
return p._parentNode;
|
||||
return p.parent_.asElement;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
//protected
|
||||
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.
|
||||
// (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.)
|
||||
EventHandler[][string] bubblingEventHandlers;
|
||||
EventHandler[][string] capturingEventHandlers;
|
||||
EventHandler[string] defaultEventHandlers;
|
||||
|
||||
void addEventListener(string event, EventHandler handler, bool useCapture = false) {
|
||||
if(event.length > 2 && event[0..2] == "on")
|
||||
event = event[2 .. $];
|
||||
version(dom_with_events) {
|
||||
EventHandler[][string] bubblingEventHandlers;
|
||||
EventHandler[][string] capturingEventHandlers;
|
||||
EventHandler[string] defaultEventHandlers;
|
||||
|
||||
if(useCapture)
|
||||
capturingEventHandlers[event] ~= handler;
|
||||
else
|
||||
bubblingEventHandlers[event] ~= handler;
|
||||
void addEventListener(string event, EventHandler handler, bool useCapture = false) {
|
||||
if(event.length > 2 && event[0..2] == "on")
|
||||
event = event[2 .. $];
|
||||
|
||||
if(useCapture)
|
||||
capturingEventHandlers[event] ~= handler;
|
||||
else
|
||||
bubblingEventHandlers[event] ~= handler;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -2091,7 +2122,6 @@ class Element {
|
|||
|
||||
/// 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) {
|
||||
parentDocument = _parentDocument;
|
||||
tagName = _tagName;
|
||||
if(_attributes !is null)
|
||||
attributes = _attributes;
|
||||
|
@ -2128,8 +2158,6 @@ class Element {
|
|||
}
|
||||
|
||||
private this(Document _parentDocument) {
|
||||
parentDocument = _parentDocument;
|
||||
|
||||
version(dom_node_indexes)
|
||||
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.
|
||||
|
||||
// the next few methods are for implementing interactive kind of things
|
||||
private CssStyle _computedStyle;
|
||||
|
||||
/// Don't use this.
|
||||
@property CssStyle computedStyle() {
|
||||
if(_computedStyle is null) {
|
||||
|
@ -2713,12 +2745,16 @@ class Element {
|
|||
|
||||
selfClosed = false;
|
||||
e.parentNode = this;
|
||||
e.parentDocument = this.parentDocument;
|
||||
if(auto frag = cast(DocumentFragment) e)
|
||||
children ~= frag.children;
|
||||
else
|
||||
children ~= e;
|
||||
|
||||
/+
|
||||
foreach(item; e.tree)
|
||||
item.parentDocument = this.parentDocument;
|
||||
+/
|
||||
|
||||
sendObserverEvent(DomMutationOperations.appendChild, null, null, e);
|
||||
|
||||
return e;
|
||||
|
@ -2746,7 +2782,6 @@ class Element {
|
|||
children = children[0..i] ~ frag.children ~ children[i..$];
|
||||
else
|
||||
children = children[0..i] ~ what ~ children[i..$];
|
||||
what.parentDocument = this.parentDocument;
|
||||
what.parentNode = this;
|
||||
return what;
|
||||
}
|
||||
|
@ -2781,7 +2816,6 @@ class Element {
|
|||
else
|
||||
children = children[0 .. i + 1] ~ what ~ children[i + 1 .. $];
|
||||
what.parentNode = this;
|
||||
what.parentDocument = this.parentDocument;
|
||||
return what;
|
||||
}
|
||||
}
|
||||
|
@ -2810,7 +2844,6 @@ class Element {
|
|||
c.parentNode = null;
|
||||
c = replacement;
|
||||
c.parentNode = this;
|
||||
c.parentDocument = this.parentDocument;
|
||||
return child;
|
||||
}
|
||||
assert(0);
|
||||
|
@ -2888,7 +2921,6 @@ class Element {
|
|||
else
|
||||
children = children[0..i] ~ child ~ children[i..$];
|
||||
child.parentNode = this;
|
||||
child.parentDocument = this.parentDocument;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -2920,7 +2952,6 @@ class Element {
|
|||
do {
|
||||
foreach(c; e.children) {
|
||||
c.parentNode = this;
|
||||
c.parentDocument = this.parentDocument;
|
||||
}
|
||||
if(position is null)
|
||||
children ~= e.children;
|
||||
|
@ -2954,7 +2985,6 @@ class Element {
|
|||
}
|
||||
do {
|
||||
e.parentNode = this;
|
||||
e.parentDocument = this.parentDocument;
|
||||
if(auto frag = cast(DocumentFragment) e)
|
||||
children = e.children ~ children;
|
||||
else
|
||||
|
@ -3000,13 +3030,10 @@ class Element {
|
|||
doc.parseUtf8("<innerhtml>" ~ html ~ "</innerhtml>", strict, strict); // FIXME: this should preserve the strictness of the parent document
|
||||
|
||||
children = doc.root.children;
|
||||
foreach(c; children) {
|
||||
foreach(c; doc.root.tree) {
|
||||
c.parentNode = this;
|
||||
c.parentDocument = this.parentDocument;
|
||||
}
|
||||
|
||||
reparentTreeDocuments();
|
||||
|
||||
doc.root.children = null;
|
||||
|
||||
return this;
|
||||
|
@ -3017,11 +3044,6 @@ class Element {
|
|||
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
|
||||
|
||||
|
@ -3037,13 +3059,8 @@ class Element {
|
|||
children = doc.root.children;
|
||||
foreach(c; children) {
|
||||
c.parentNode = this;
|
||||
c.parentDocument = this.parentDocument;
|
||||
}
|
||||
|
||||
|
||||
reparentTreeDocuments();
|
||||
|
||||
|
||||
stripOut();
|
||||
|
||||
return doc.root.children;
|
||||
|
@ -3093,7 +3110,6 @@ class Element {
|
|||
replace.parentNode = this;
|
||||
children[i].parentNode = null;
|
||||
children[i] = replace;
|
||||
replace.parentDocument = this.parentDocument;
|
||||
return replace;
|
||||
}
|
||||
}
|
||||
|
@ -3132,7 +3148,6 @@ class Element {
|
|||
children[i] = replace[0];
|
||||
foreach(e; replace) {
|
||||
e.parentNode = this;
|
||||
e.parentDocument = this.parentDocument;
|
||||
}
|
||||
|
||||
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.
|
||||
Element cloneNode(bool deepClone) {
|
||||
auto e = Element.make(this.tagName);
|
||||
e.parentDocument = this.parentDocument;
|
||||
e.attributes = this.attributes.aadup;
|
||||
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...
|
||||
/// 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) {
|
||||
this._parentNode = p;
|
||||
this.parent_ = p;
|
||||
foreach(child; children)
|
||||
child.parentNode = p;
|
||||
return p;
|
||||
|
@ -8499,9 +8516,11 @@ private string[string] aadup(in string[string] arr) {
|
|||
// dom event support, if you want to use it
|
||||
|
||||
/// used for DOM events
|
||||
version(dom_with_events)
|
||||
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.
|
||||
version(dom_with_events)
|
||||
class Event {
|
||||
this(string eventName, Element target) {
|
||||
this.eventName = eventName;
|
||||
|
|
3
jsvar.d
3
jsvar.d
|
@ -693,6 +693,9 @@ struct var {
|
|||
// so prewrapped stuff can be easily passed.
|
||||
this._type = Type.Object;
|
||||
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) {
|
||||
this._type = Type.Floating;
|
||||
this._payload._floating = t;
|
||||
|
|
179
webtemplate.d
179
webtemplate.d
|
@ -20,6 +20,11 @@
|
|||
there were no items.
|
||||
</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" />
|
||||
</main>
|
||||
```
|
||||
|
@ -49,44 +54,58 @@ class TemplateException : Exception {
|
|||
}
|
||||
}
|
||||
|
||||
void addDefaultFunctions(var context) {
|
||||
import std.conv;
|
||||
// FIXME: I prolly want it to just set the prototype or something
|
||||
|
||||
context.encodeURIComponent = function string(var f) {
|
||||
import std.uri;
|
||||
return encodeComponent(f.get!string);
|
||||
};
|
||||
|
||||
context.formatDate = function string(string s) {
|
||||
if(s.length < 10)
|
||||
return s;
|
||||
auto year = s[0 .. 4];
|
||||
auto month = s[5 .. 7];
|
||||
auto day = s[8 .. 10];
|
||||
|
||||
return month ~ "/" ~ day ~ "/" ~ year;
|
||||
};
|
||||
|
||||
context.dayOfWeek = function string(string s) {
|
||||
import std.datetime;
|
||||
return daysOfWeekFullNames[Date.fromISOExtString(s[0 .. 10]).dayOfWeek];
|
||||
};
|
||||
|
||||
context.formatTime = function string(string s) {
|
||||
if(s.length < 20)
|
||||
return s;
|
||||
auto hour = s[11 .. 13].to!int;
|
||||
auto minutes = s[14 .. 16].to!int;
|
||||
auto seconds = s[17 .. 19].to!int;
|
||||
|
||||
auto am = (hour >= 12) ? "PM" : "AM";
|
||||
if(hour > 12)
|
||||
hour -= 12;
|
||||
|
||||
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 {
|
||||
context.encodeURIComponent = function string(var f) {
|
||||
import std.uri;
|
||||
return encodeComponent(f.get!string);
|
||||
};
|
||||
|
||||
context.formatDate = function string(string s) {
|
||||
if(s.length < 10)
|
||||
return s;
|
||||
auto year = s[0 .. 4];
|
||||
auto month = s[5 .. 7];
|
||||
auto day = s[8 .. 10];
|
||||
|
||||
return month ~ "/" ~ day ~ "/" ~ year;
|
||||
};
|
||||
|
||||
context.dayOfWeek = function string(string s) {
|
||||
import std.datetime;
|
||||
return daysOfWeekFullNames[Date.fromISOExtString(s[0 .. 10]).dayOfWeek];
|
||||
};
|
||||
|
||||
context.formatTime = function string(string s) {
|
||||
if(s.length < 20)
|
||||
return s;
|
||||
auto hour = s[11 .. 13].to!int;
|
||||
auto minutes = s[14 .. 16].to!int;
|
||||
auto seconds = s[17 .. 19].to!int;
|
||||
|
||||
auto am = (hour >= 12) ? "PM" : "AM";
|
||||
if(hour > 12)
|
||||
hour -= 12;
|
||||
|
||||
return hour.to!string ~ (minutes < 10 ? ":0" : ":") ~ minutes.to!string ~ " " ~ am;
|
||||
};
|
||||
addDefaultFunctions(context);
|
||||
addDefaultFunctions(skeletonContext);
|
||||
|
||||
auto skeleton = new Document(readText("templates/skeleton.html"), true, true);
|
||||
auto document = new Document();
|
||||
|
@ -212,6 +231,18 @@ void expandTemplate(Element root, var context) {
|
|||
fragment.stealChildren(document.root);
|
||||
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);
|
||||
} else if(auto asp = cast(AspCode) ele) {
|
||||
auto code = asp.source[1 .. $-1];
|
||||
|
@ -238,7 +269,7 @@ void expandTemplate(Element root, var context) {
|
|||
if(root.hasAttribute("onrender")) {
|
||||
var nc = var.emptyObject(context);
|
||||
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;
|
||||
if(form is null) return this_;
|
||||
foreach(k, v; args[0]) {
|
||||
|
@ -256,9 +287,12 @@ void populateForm(Form form, var obj, string name) {
|
|||
import std.string;
|
||||
|
||||
if(obj.payloadType == var.Type.Object) {
|
||||
form.setValue(name, "");
|
||||
foreach(k, v; obj) {
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
//import std.stdio; writeln("SET ", name, " ", obj, " ", obj.payloadType);
|
||||
|
@ -291,6 +325,18 @@ struct Template {
|
|||
struct Skeleton {
|
||||
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].
|
||||
+/
|
||||
|
@ -319,6 +365,8 @@ template WebPresenterWithTemplateSupport(CTRP) {
|
|||
typeof(null) at;
|
||||
string templateName;
|
||||
string skeletonName;
|
||||
string[string] meta;
|
||||
Form function(WebPresenterWithTemplateSupport presenter) automaticForm;
|
||||
alias at this;
|
||||
}
|
||||
template methodMeta(alias method) {
|
||||
|
@ -332,6 +380,12 @@ template WebPresenterWithTemplateSupport(CTRP) {
|
|||
ret.templateName = attr.name;
|
||||
else static if(is(typeof(attr) == Skeleton))
|
||||
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;
|
||||
}
|
||||
|
@ -351,11 +405,66 @@ template WebPresenterWithTemplateSupport(CTRP) {
|
|||
|
||||
void presentSuccessfulReturnAsHtml(T)(Cgi cgi, T ret, Meta meta) {
|
||||
if(meta.templateName.length) {
|
||||
var sobj = var.emptyObject;
|
||||
|
||||
var obj = var.emptyObject;
|
||||
|
||||
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
|
||||
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));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue