on master help sign in
Switch to a custom way of attaching event listeners that makes it easier to detach them if necessary.
90cf421evancz
libraries/Native/Signal/Keyboard.js+14−25
@@ -10,31 +10,20 @@ Elm.Native.Keyboard = function(elm) {
10 var keysDown = Signal.constant(NList.Nil);
10 var keysDown = Signal.constant(NList.Nil);
11 var lastKey = Signal.constant('\0');
11 var lastKey = Signal.constant('\0');
12
12
13− function down(e) {
13+ elm.addListener([keysDown.id], document, 'keydown', function down(e) {
14− if (NList.member(e.keyCode)(keysDown.value)) return;
14+ if (NList.member(e.keyCode)(keysDown.value)) return;
15− var list = NList.Cons(e.keyCode, keysDown.value);
15+ elm.notify(keysDown.id, NList.Cons(e.keyCode, keysDown.value));
16− var hasListener = elm.notify(keysDown.id, list);
16+ });
17− if (!hasListener) document.removeEventListener('keydown', down);
17+ elm.addListener([keysDown.id], document, 'keyup', function up(e) {
18− }
19− function up(e) {
20− function notEq(kc) { return kc !== e.keyCode; }
18+ function notEq(kc) { return kc !== e.keyCode; }
21− var codes = NList.filter(notEq)(keysDown.value);
19+ elm.notify(keysDown.id, NList.filter(notEq)(keysDown.value));
22− var hasListener = elm.notify(keysDown.id, codes);
20+ });
23− if (!hasListener) document.removeEventListener('keyup', up);
24− }
25− function blur(e) {
21+ elm.addListener([keysDown.id], document, 'blur', function blur(e) {
26− var hasListener = elm.notify(keysDown.id, NList.Nil);
22+ elm.notify(keysDown.id, NList.Nil);
27− if (!hasListener) document.removeEventListener('blur', blur);
23+ });
28− }
29− function press(e) {
24+ elm.addListener([lastKey.id], document, 'keypress', function press(e) {
30− var hasListener = elm.notify(lastKey.id, e.charCode || e.keyCode);
25+ elm.notify(lastKey.id, e.charCode || e.keyCode);
31− if (!hasListener) document.removeEventListener('keypress', press);
32− }
26+ });
33−
34− document.addEventListener('keydown' , down );
35− document.addEventListener('keyup' , up );
36− document.addEventListener('blur' , blur );
37− document.addEventListener('keypress', press);
38
27
39 function keySignal(f) {
28 function keySignal(f) {
40 var signal = A2( Signal.lift, f, keysDown );
29 var signal = A2( Signal.lift, f, keysDown );
libraries/Native/Signal/Mouse.js+14−27
@@ -40,33 +40,20 @@ Elm.Native.Mouse = function(elm) {
40
40
41 var node = elm.display === ElmRuntime.Display.FULLSCREEN ? document : elm.node;
41 var node = elm.display === ElmRuntime.Display.FULLSCREEN ? document : elm.node;
42
42
43− function click(e) {
43+ elm.addListener([isClicked.id, clicks.id], node, 'click', function click() {
44− var hasListener1 = elm.notify(isClicked.id, true);
44+ elm.notify(isClicked.id, true);
45− var hasListener2 = elm.notify(clicks.id, Utils.Tuple0);
45+ elm.notify(clicks.id, Utils.Tuple0);
46− elm.notify(isClicked.id, false);
46+ elm.notify(isClicked.id, false);
47− if (!hasListener1 && !hasListener2)
47+ });
48− node.removeEventListener('click', click);
49− }
50−
51− function down(e) {
48+ elm.addListener([isDown.id], node, 'mousedown', function down() {
52− var hasListener = elm.notify(isDown.id, true);
49+ elm.notify(isDown.id, true);
53− if (!hasListener) node.removeEventListener('mousedown', down);
50+ });
54− }
51+ elm.addListener([isDown.id], node, 'mouseup', function up() {
55−
56− function up(e) {
57− var hasListener = elm.notify(isDown.id, false);
52+ elm.notify(isDown.id, false);
58− if (!hasListener) node.removeEventListener('mouseup', up);
53+ });
59− }
60−
61− function move(e) {
54+ elm.addListener([position.id], node, 'mousemove', function move(e) {
62− var hasListener = elm.notify(position.id, getXY(e));
55+ elm.notify(position.id, getXY(e));
63− if (!hasListener) node.removeEventListener('mousemove', move);
64− }
56+ });
65−
66− node.addEventListener('click' , click);
67− node.addEventListener('mousedown', down);
68− node.addEventListener('mouseup' , up);
69− node.addEventListener('mousemove', move);
70
57
71 return elm.Native.Mouse = {
58 return elm.Native.Mouse = {
72 position: position,
59 position: position,
libraries/Native/Signal/Touch.js+5−6
@@ -74,11 +74,10 @@ Elm.Native.Touch = function(elm) {
74 for (var i = e.changedTouches.length; i--; ) { f(e.changedTouches[i]); }
74 for (var i = e.changedTouches.length; i--; ) { f(e.changedTouches[i]); }
75 var ts = new Array(e.touches.length);
75 var ts = new Array(e.touches.length);
76 for (var i = e.touches.length; i--; ) { ts[i] = touch(e.touches[i]); }
76 for (var i = e.touches.length; i--; ) { ts[i] = touch(e.touches[i]); }
77− var hasListener = elm.notify(root.id, ts);
77+ elm.notify(root.id, ts);
78− if (!hasListener) return node.removeEventListener(name, update);
79 e.preventDefault();
78 e.preventDefault();
80 }
79 }
81− node.addEventListener(name, update);
80+ elm.addListener([root.id], node, name, update);
82 }
81 }
83
82
84 listen("touchstart", start);
83 listen("touchstart", start);
@@ -98,14 +97,14 @@ Elm.Native.Touch = function(elm) {
98 }
97 }
99 }
98 }
100 }
99 }
101− node.addEventListener("mousedown", function(e) {
100+ elm.addListener([root.id], node, "mousedown", function down(e) {
102 node.addEventListener("mousemove", move);
101 node.addEventListener("mousemove", move);
103 e.identifier = mouseID;
102 e.identifier = mouseID;
104 start(e);
103 start(e);
105 root.value.push(touch(e));
104 root.value.push(touch(e));
106 elm.notify(root.id, root.value);
105 elm.notify(root.id, root.value);
107 });
106 });
108− node.addEventListener("mouseup", function(e) {
107+ elm.addListener([root.id], node, "mouseup", function up(e) {
109 node.removeEventListener("mousemove", move);
108 node.removeEventListener("mousemove", move);
110 e.identifier = mouseID;
109 e.identifier = mouseID;
111 end(e);
110 end(e);
@@ -118,7 +117,7 @@ Elm.Native.Touch = function(elm) {
118 }
117 }
119 elm.notify(root.id, root.value);
118 elm.notify(root.id, root.value);
120 });
119 });
121− node.addEventListener("blur", function() {
120+ elm.addListener([root.id], node, "blur", function blur(e) {
122 node.removeEventListener("mousemove", move);
121 node.removeEventListener("mousemove", move);
123 if (root.values.length > 0) {
122 if (root.values.length > 0) {
124 elm.notify(root.id, []);
123 elm.notify(root.id, []);
libraries/Native/Signal/Window.js+2−3
@@ -31,10 +31,9 @@ Elm.Native.Window = function(elm) {
31 var w = getWidth();
31 var w = getWidth();
32 var h = getHeight();
32 var h = getHeight();
33 if (dimensions.value._0 === w && dimensions.value._1 === h) return;
33 if (dimensions.value._0 === w && dimensions.value._1 === h) return;
34− var hasListener = elm.notify(dimensions.id, Tuple2(w,h));
34+ elm.notify(dimensions.id, Tuple2(w,h));
35− if (!hasListener) window.removeEventListener('resize', resizeIfNeeded);
36 }
35 }
37− window.addEventListener('resize', resizeIfNeeded);
36+ elm.addListener([dimensions.id], window, 'resize', resizeIfNeeded);
38
37
39 return elm.Native.Window = {
38 return elm.Native.Window = {
40 dimensions:dimensions,
39 dimensions:dimensions,
libraries/Native/Utils.js+1−1
@@ -135,7 +135,7 @@ Elm.Native.Utils = function(elm) {
135 }
135 }
136
136
137 if (elm.display === ElmRuntime.Display.COMPONENT) {
137 if (elm.display === ElmRuntime.Display.COMPONENT) {
138− elm.node.addEventListener('mouseover', adjustOffset);
138+ elm.addListener(elm.inputs, elm.node, 'mouseover', adjustOffset);
139 }
139 }
140
140
141 return elm.Native.Utils = {
141 return elm.Native.Utils = {
runtime/HotSwap.js+1−3
@@ -15,9 +15,7 @@ ElmRuntime.swap = function(from, to) {
15 }
15 }
16 var canSwap = depthFirstTraversals(similar, from.inputs, to.inputs);
16 var canSwap = depthFirstTraversals(similar, from.inputs, to.inputs);
17 if (canSwap) { depthFirstTraversals(swap, from.inputs, to.inputs); }
17 if (canSwap) { depthFirstTraversals(swap, from.inputs, to.inputs); }
18− ElmRuntime.draw(function() {
19− from.node.parentNode.replaceChild(to.node, from.node)
18+ from.node.parentNode.replaceChild(to.node, from.node);
20− });
21 return canSwap;
19 return canSwap;
22}
20}
23
21
runtime/Init.js+49−20
@@ -13,14 +13,12 @@ Elm.fullscreen = function(module) {
13 return init(ElmRuntime.Display.FULLSCREEN, container, module);
13 return init(ElmRuntime.Display.FULLSCREEN, container, module);
14};
14};
15
15
16−Elm.byId = function(id, module) {
16+Elm.domNode = function(container, module) {
17− var container = document.getElementById(id);
18 var tag = container.tagName;
17 var tag = container.tagName;
19 if (tag !== 'DIV') {
18 if (tag !== 'DIV') {
20− throw new Error('Elm.byId must be given a div, not a ' + tag + '.');
19+ throw new Error('Elm.node must be given a DIV, not a ' + tag + '.');
21− }
22− while (container.hasChildNodes()) {
20+ } else if (container.hasChildNodes()) {
23− container.removeChild(container.lastChild);
21+ throw new Error('Elm.node must be given an empty DIV. No children allowed!');
24 }
22 }
25 return init(ElmRuntime.Display.COMPONENT, container, module);
23 return init(ElmRuntime.Display.COMPONENT, container, module);
26};
24};
@@ -34,26 +32,36 @@ function init(display, container, module, moduleToReplace) {
34 var inputs = [];
32 var inputs = [];
35
33
36 function notify(id, v) {
34 function notify(id, v) {
37− var timestep = Date.now();
35+ var timestep = Date.now();
38− var hasListener = false;
39− for (var i = inputs.length; i--; ) {
36+ for (var i = inputs.length; i--; ) {
40− // must maintain the order of this stmt to avoid having the ||
41− // short-circuiting the necessary work of recv
42− hasListener = inputs[i].recv(timestep, id, v) || hasListener;
37+ inputs[i].recv(timestep, id, v);
43− }
38+ }
44− return hasListener;
45 }
39 }
46
40
47 container.offsetX = 0;
41 container.offsetX = 0;
48 container.offsetY = 0;
42 container.offsetY = 0;
49
43
44+ var listeners = [];
45+ function addListener(relevantInputs, domNode, eventName, func) {
46+ domNode.addEventListener(eventName, func);
47+ var listener = {
48+ relevantInputs: relevantInputs,
49+ domNode: domNode,
50+ eventName: eventName,
51+ func: func
52+ };
53+ listeners.push(listener);
54+ }
55+
50 // create the actual RTS. Any impure modules will attach themselves to this
56 // create the actual RTS. Any impure modules will attach themselves to this
51 // object. This permits many Elm programs to be embedded per document.
57 // object. This permits many Elm programs to be embedded per document.
58+ var elm = {
52− var elm = { notify:notify,
59+ notify:notify,
53− node:container,
60+ node:container,
54− display:display,
61+ display:display,
55− id:ElmRuntime.guid(),
62+ id:ElmRuntime.guid(),
63+ addListener:addListener,
56− inputs:inputs
64+ inputs:inputs
57 };
65 };
58
66
59 // Set up methods to communicate with Elm program from JS.
67 // Set up methods to communicate with Elm program from JS.
@@ -75,8 +83,8 @@ function init(display, container, module, moduleToReplace) {
75 });
83 });
76
84
77 function swap(newModule) {
85 function swap(newModule) {
86+ removeListeners(listeners);
78 var div = document.createElement('div');
87 var div = document.createElement('div');
79− if (container.id) { div.id = container.id; }
80 var newElm = init(display, div, newModule, elm);
88 var newElm = init(display, div, newModule, elm);
81 // elm.send = newElm.send;
89 // elm.send = newElm.send;
82 // elm.recv = newElm.recv;
90 // elm.recv = newElm.recv;
@@ -86,6 +94,7 @@ function init(display, container, module, moduleToReplace) {
86
94
87 var Module = module(elm);
95 var Module = module(elm);
88 inputs = ElmRuntime.filterDeadInputs(inputs);
96 inputs = ElmRuntime.filterDeadInputs(inputs);
97+ filterListeners(inputs, listeners);
89 if (display !== ElmRuntime.Display.NONE) {
98 if (display !== ElmRuntime.Display.NONE) {
90 var graphicsNode = initGraphics(elm, Module);
99 var graphicsNode = initGraphics(elm, Module);
91 }
100 }
@@ -102,6 +111,26 @@ function init(display, container, module, moduleToReplace) {
102 return { send:send, recv:recv, swap:swap };
111 return { send:send, recv:recv, swap:swap };
103};
112};
104
113
114+function filterListeners(inputs, listeners) {
115+ loop:
116+ for (var i = listeners.length; i--; ) {
117+ var listener = listeners[i];
118+ for (var j = inputs.length; j--; ) {
119+ if (listener.relevantInputs.indexOf(inputs[j].id) >= 0) {
120+ continue loop;
121+ }
122+ }
123+ listener.domNode.removeEventListener(listener.eventName, listener.func);
124+ }
125+}
126+
127+function removeListeners(listeners) {
128+ for (var i = listeners.length; i--; ) {
129+ var listener = listeners[i];
130+ listener.domNode.removeEventListener(listener.eventName, listener.func);
131+ }
132+}
133+
105function initGraphics(elm, Module) {
134function initGraphics(elm, Module) {
106 if (!('main' in Module))
135 if (!('main' in Module))
107 throw new Error("'main' is missing! What do I display?!");
136 throw new Error("'main' is missing! What do I display?!");