import arsd.web; import arsd.html; class Test : ServerSideDocument { mixin ServerSideDocumentEventStuff!(); override Document createNewDocumentSession() { auto document = new Document(""); auto a = document.mainBody.addChild("a", "this is a link", "404.html"); document.mainBody.addEventListener("click", (Element thislol, Event event) { event.target.style.color = "red"; event.target.appendText(" clicked! "); event.preventDefault(); }); return document; } } template ServerSideDocumentEventStuff() { alias super.cancelableEvent cancelableEvent; alias super.uncancelableEvent uncancelableEvent; } private __gshared Document[string] documents; class ServerSideDocument : ApiProvider { Document document; /// override this abstract Document createNewDocumentSession(); /// if you override this, be sure you call the super version first. void _initializePerCall() { if(session.sessionId in documents) document = documents[session.sessionId]; else { document = createNewDocumentSession(); documents[session.sessionId] = document; auto head = document.requireSelector("head"); head.addChild("script").src = "sse.js"; head.addChild("script").src = "functions.js"; } } Document _defaultPage() { return document; } DomMutation[]* changes; void _handleDomEvent(DomMutationEvent e) { DomMutation m; m.operation = e.operation; m.reference = e.target.dataset.nodeIndex; final switch(m.operation) { case DomMutationOperations.setAttribute: m.name = e.relatedString; m.content = e.relatedString2; break; case DomMutationOperations.removeAttribute: m.name = e.relatedString; break; case DomMutationOperations.insertBefore: m.reference2 = e.related.dataset.nodeIndex; goto case; case DomMutationOperations.appendChild: // tagname, attributes[], innerHTML m.name = e.related.tagName; m.attributes = e.related.attributes; if(e.related.nodeType == NodeType.Text) m.content = e.related.nodeValue(); else m.content = e.related.innerHTML; break; case DomMutationOperations.truncateChildren: case DomMutationOperations.removeChild: break; case DomMutationOperations.appendHtml: case DomMutationOperations.replaceHtml: case DomMutationOperations.appendText: case DomMutationOperations.replaceText: case DomMutationOperations.replaceTextOnly: m.content = e.relatedString; } if(changes !is null) { (*changes) ~= m; } else { // dispatch via EventSource } } EventResponse cancelableEvent(string type, string targetNodeIndex) { EventResponse r; auto target = document.requireSelector("[data-node-index=\""~targetNodeIndex~"\"]"); auto event = new Event(type, target); document.eventObservers ~= &_handleDomEvent; changes = &(r.mutations); event.dispatch(); changes = null; document.eventObservers = null; // FIXME r.cancelled = event.defaultPrevented; return r; } EventResponse uncancelableEvent(string type, string targetNodeIndex) { EventResponse r; return r; } override FileResource _catchAll(string path) { if(path == "sse.js") { auto expander = new JavascriptMacroExpander(); string src = import("sse.js"); return new DataFile("text/javascript", expander.expand(src)); } return super._catchAll(path); } } struct DomMutation { DomMutationOperations operation; string reference; string reference2; string name; string[string] attributes; string content; } struct EventResponse { bool cancelled; bool propagationStopped; // don't use this yet DomMutation[] mutations; } mixin FancyMain!(Test);