mirror of https://github.com/adamdruppe/arsd.git
event methods
This commit is contained in:
parent
d6c3404ae6
commit
982ec91e13
161
dom.d
161
dom.d
|
@ -1,3 +1,23 @@
|
||||||
|
/**
|
||||||
|
This is an html DOM implementation, started with cloning
|
||||||
|
what the browser offers in Javascript, but going well beyond
|
||||||
|
it in convenience.
|
||||||
|
|
||||||
|
If you can do it in Javascript, you can probably do it with
|
||||||
|
this module.
|
||||||
|
|
||||||
|
And much more.
|
||||||
|
|
||||||
|
|
||||||
|
Note: some of the documentation here writes html with added
|
||||||
|
spaces. That's because ddoc doesn't bother encoding html output,
|
||||||
|
and adding spaces is easier than using LT macros everywhere.
|
||||||
|
|
||||||
|
|
||||||
|
BTW: this file depends on arsd.characterencodings, so help it
|
||||||
|
correctly read files from the internet. You should be able to
|
||||||
|
get characterencodings.d from the same place you got this file.
|
||||||
|
*/
|
||||||
module arsd.dom;
|
module arsd.dom;
|
||||||
|
|
||||||
// NOTE: do *NOT* override toString on Element subclasses. It won't work.
|
// NOTE: do *NOT* override toString on Element subclasses. It won't work.
|
||||||
|
@ -16,6 +36,7 @@ import std.string;
|
||||||
import std.exception;
|
import std.exception;
|
||||||
import std.uri;
|
import std.uri;
|
||||||
import std.array;
|
import std.array;
|
||||||
|
import std.range;
|
||||||
|
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
|
|
||||||
|
@ -28,8 +49,8 @@ import std.stdio;
|
||||||
/// Document implements this interface with type = text/html (see Document.contentType for more info)
|
/// Document implements this interface with type = text/html (see Document.contentType for more info)
|
||||||
/// and data = document.toString, so you can return Documents anywhere web.d expects FileResources.
|
/// and data = document.toString, so you can return Documents anywhere web.d expects FileResources.
|
||||||
interface FileResource {
|
interface FileResource {
|
||||||
string contentType() const;
|
string contentType() const; /// the content-type of the file. e.g. "text/html; charset=utf-8" or "image/png"
|
||||||
immutable(ubyte)[] getData() const;
|
immutable(ubyte)[] getData() const; /// the data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -166,6 +187,8 @@ body {
|
||||||
|
|
||||||
/// This represents almost everything in the DOM.
|
/// This represents almost everything in the DOM.
|
||||||
class Element {
|
class Element {
|
||||||
|
// putting all the members up front
|
||||||
|
|
||||||
// this ought to be private. don't use it directly.
|
// this ought to be private. don't use it directly.
|
||||||
Element[] children;
|
Element[] children;
|
||||||
|
|
||||||
|
@ -186,6 +209,27 @@ class Element {
|
||||||
///.
|
///.
|
||||||
Element parentNode;
|
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 .. $];
|
||||||
|
|
||||||
|
if(useCapture)
|
||||||
|
capturingEventHandlers[event] ~= handler;
|
||||||
|
else
|
||||||
|
bubblingEventHandlers[event] ~= handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// and now methods
|
||||||
|
|
||||||
/// Convenience function to try to do the right thing for HTML. This is the main
|
/// Convenience function to try to do the right thing for HTML. This is the main
|
||||||
/// way I create elements.
|
/// way I create elements.
|
||||||
|
@ -763,8 +807,6 @@ class Element {
|
||||||
return _computedStyle;
|
return _computedStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CssStyle _computedStyle;
|
|
||||||
|
|
||||||
/// These properties are useless in most cases, but if you write a layout engine on top of this lib, they may be good
|
/// These properties are useless in most cases, but if you write a layout engine on top of this lib, they may be good
|
||||||
version(browser) {
|
version(browser) {
|
||||||
void* expansionHook; ///ditto
|
void* expansionHook; ///ditto
|
||||||
|
@ -1230,7 +1272,7 @@ class Element {
|
||||||
/// This sets the inner content of the element *without* trying to parse it.
|
/// This sets the inner content of the element *without* trying to parse it.
|
||||||
/// You can inject any code in there; this serves as an escape hatch from the dom.
|
/// You can inject any code in there; this serves as an escape hatch from the dom.
|
||||||
///
|
///
|
||||||
/// The only times you might actually need it are for <style> and <script> tags in html.
|
/// The only times you might actually need it are for < style > and < script > tags in html.
|
||||||
/// Other than that, innerHTML and/or innerText should do the job.
|
/// Other than that, innerHTML and/or innerText should do the job.
|
||||||
@property void innerRawSource(string rawSource) {
|
@property void innerRawSource(string rawSource) {
|
||||||
children.length = 0;
|
children.length = 0;
|
||||||
|
@ -1405,8 +1447,8 @@ class Element {
|
||||||
/// Wraps this element inside the given element.
|
/// Wraps this element inside the given element.
|
||||||
/// It's like this.replaceWith(what); what.appendchild(this);
|
/// It's like this.replaceWith(what); what.appendchild(this);
|
||||||
///
|
///
|
||||||
/// Given: <b>cool</b>, if you call b.wrapIn(new Link("site.com", "my site is "));
|
/// Given: < b >cool</ b >, if you call b.wrapIn(new Link("site.com", "my site is "));
|
||||||
/// you'll end up with: <a href="site.com">my site is <b>cool</a>.
|
/// you'll end up with: < a href="site.com">my site is < b >cool< /b ></ a >.
|
||||||
Element wrapIn(Element what)
|
Element wrapIn(Element what)
|
||||||
in {
|
in {
|
||||||
assert(what !is null);
|
assert(what !is null);
|
||||||
|
@ -4554,6 +4596,111 @@ struct ElementCollection {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// dom event support, if you want to use it
|
||||||
|
|
||||||
|
/// used for DOM events
|
||||||
|
alias void delegate(Element, Event) EventHandler;
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
class Event {
|
||||||
|
this(string eventName, Element target) {
|
||||||
|
this.eventName = eventName;
|
||||||
|
this.srcElement = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prevents the default event handler (if there is one) from being called
|
||||||
|
void preventDefault() {
|
||||||
|
defaultPrevented = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stops the event propagation immediately.
|
||||||
|
void stopPropagation() {
|
||||||
|
propagationStopped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool defaultPrevented;
|
||||||
|
bool propagationStopped;
|
||||||
|
string eventName;
|
||||||
|
|
||||||
|
Element srcElement;
|
||||||
|
alias srcElement target;
|
||||||
|
|
||||||
|
Element relatedTarget;
|
||||||
|
|
||||||
|
int clientX;
|
||||||
|
int clientY;
|
||||||
|
|
||||||
|
int button;
|
||||||
|
|
||||||
|
bool isBubbling;
|
||||||
|
|
||||||
|
/// this sends it only to the target. If you want propagation, use dispatch() instead.
|
||||||
|
void send() {
|
||||||
|
if(srcElement is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto e = srcElement;
|
||||||
|
|
||||||
|
if(eventName in e.bubblingEventHandlers)
|
||||||
|
foreach(handler; e.bubblingEventHandlers[eventName])
|
||||||
|
handler(e, this);
|
||||||
|
|
||||||
|
if(!defaultPrevented)
|
||||||
|
if(eventName in e.defaultEventHandlers)
|
||||||
|
e.defaultEventHandlers[eventName](e, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// this dispatches the element using the capture -> target -> bubble process
|
||||||
|
void dispatch() {
|
||||||
|
if(srcElement is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// first capture, then bubble
|
||||||
|
|
||||||
|
Element[] chain;
|
||||||
|
Element curr = srcElement;
|
||||||
|
while(curr) {
|
||||||
|
auto l = curr;
|
||||||
|
chain ~= l;
|
||||||
|
curr = curr.parentNode;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
isBubbling = false;
|
||||||
|
|
||||||
|
foreach(e; chain.retro) {
|
||||||
|
if(eventName in e.capturingEventHandlers)
|
||||||
|
foreach(handler; e.capturingEventHandlers[eventName])
|
||||||
|
handler(e, this);
|
||||||
|
|
||||||
|
// the default on capture should really be to always do nothing
|
||||||
|
|
||||||
|
//if(!defaultPrevented)
|
||||||
|
// if(eventName in e.defaultEventHandlers)
|
||||||
|
// e.defaultEventHandlers[eventName](e.element, this);
|
||||||
|
|
||||||
|
if(propagationStopped)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
isBubbling = true;
|
||||||
|
if(!propagationStopped)
|
||||||
|
foreach(e; chain) {
|
||||||
|
if(eventName in e.bubblingEventHandlers)
|
||||||
|
foreach(handler; e.bubblingEventHandlers[eventName])
|
||||||
|
handler(e, this);
|
||||||
|
|
||||||
|
if(!defaultPrevented)
|
||||||
|
if(eventName in e.defaultEventHandlers)
|
||||||
|
e.defaultEventHandlers[eventName](e, this);
|
||||||
|
|
||||||
|
if(propagationStopped)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright: Adam D. Ruppe, 2010 - 2012
|
Copyright: Adam D. Ruppe, 2010 - 2012
|
||||||
License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
|
License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
|
||||||
|
|
Loading…
Reference in New Issue