mirror of https://github.com/adamdruppe/arsd.git
a few little web enhancements
This commit is contained in:
parent
ae0fbaa89f
commit
81dba0f46d
103
cgi.d
103
cgi.d
|
@ -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("&", "&").replace("<", "<").replace(">", ">").replace("\n", "<br />\n");
|
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)
|
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
99
dom.d
|
@ -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;
|
||||||
|
|
3
jsvar.d
3
jsvar.d
|
@ -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;
|
||||||
|
|
121
webtemplate.d
121
webtemplate.d
|
@ -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));
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue