Skip to content

Commit

Permalink
Multi-value map support for on.
Browse files Browse the repository at this point in the history
  • Loading branch information
mbostock committed Aug 5, 2012
1 parent 74596a9 commit 07f72be
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 47 deletions.
44 changes: 29 additions & 15 deletions d3.v2.js
Expand Up @@ -1705,21 +1705,34 @@
};
}
d3_selectionPrototype.on = function(type, listener, capture) {
if (arguments.length < 3) capture = false;
var n = arguments.length;
if (n < 3) {
if (typeof type !== "string") {
if (n < 2) listener = false;
for (capture in type) this.each(d3_selection_on(capture, type[capture], listener));
return this;
}
if (n < 2) return (n = this.node()["__on" + type]) && n._;
capture = false;
}
return this.each(d3_selection_on(type, listener, capture));
};
function d3_selection_on(type, listener, capture) {
var name = "__on" + type, i = type.indexOf(".");
if (i > 0) type = type.substring(0, i);
if (arguments.length < 2) return (i = this.node()[name]) && i._;
return this.each(function() {
var node = this, args = arguments, o = node[name];
if (o) {
node.removeEventListener(type, o, o.$);
delete node[name];
}
if (listener) {
node.addEventListener(type, node[name] = l, l.$ = capture);
l._ = listener;
}
function l(e) {
function onRemove() {
var wrapper = this[name];
if (wrapper) {
this.removeEventListener(type, wrapper, wrapper.$);
delete this[name];
}
}
function onAdd() {
var node = this, args = arguments;
onRemove.call(this);
this.addEventListener(type, this[name] = wrapper, wrapper.$ = capture);
wrapper._ = listener;
function wrapper(e) {
var o = d3.event;
d3.event = e;
args[0] = node.__data__;
Expand All @@ -1729,8 +1742,9 @@
d3.event = o;
}
}
});
};
}
return listener ? onAdd : onRemove;
}
d3_selectionPrototype.each = function(callback) {
return d3_selection_each(this, function(node, i, j) {
callback.call(node, node.__data__, i, j);
Expand Down
8 changes: 4 additions & 4 deletions d3.v2.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/core/selection-attr.js
Expand Up @@ -12,7 +12,7 @@ d3_selectionPrototype.attr = function(name, value) {

// For attr(object), the object specifies the names and values of the
// attributes to set or remove. The values may be functions that are
// evaluated for each element, or nulls, or strings.
// evaluated for each element.
for (value in name) this.each(d3_selection_attr(value, name[value]));
return this;
}
Expand Down
65 changes: 40 additions & 25 deletions src/core/selection-on.js
@@ -1,35 +1,48 @@
// type can be namespaced, e.g., "click.foo"
// listener can be null for removal
d3_selectionPrototype.on = function(type, listener, capture) {
if (arguments.length < 3) capture = false;
var n = arguments.length;
if (n < 3) {

// parse the type specifier
// For on(object) or on(object, boolean), the object specifies the event
// types and listeners to add or remove. The optional boolean specifies
// whether the listener captures events.
if (typeof type !== "string") {
if (n < 2) listener = false;
for (capture in type) this.each(d3_selection_on(capture, type[capture], listener));
return this;
}

// For on(string), return the listener for the first node.
if (n < 2) return (n = this.node()["__on" + type]) && n._;

// For on(string, function), use the default capture.
capture = false;
}

// Otherwise, a type, listener and capture are specified, and handled as below.
return this.each(d3_selection_on(type, listener, capture));
};

function d3_selection_on(type, listener, capture) {
var name = "__on" + type, i = type.indexOf(".");
if (i > 0) type = type.substring(0, i);

// if called with only one argument, return the current listener
if (arguments.length < 2) return (i = this.node()[name]) && i._;
function onRemove() {
var wrapper = this[name];
if (wrapper) {
this.removeEventListener(type, wrapper, wrapper.$);
delete this[name];
}
}

// remove the old event listener, and add the new event listener
return this.each(function() {
function onAdd() {
var node = this,
args = arguments,
o = node[name];

// remove the old listener, if any (using the previously-set capture)
if (o) {
node.removeEventListener(type, o, o.$);
delete node[name];
}
args = arguments;

// add the new listener, if any (remembering the capture flag)
if (listener) {
node.addEventListener(type, node[name] = l, l.$ = capture);
l._ = listener; // stash the unwrapped listener for get
}
onRemove.call(this);
this.addEventListener(type, this[name] = wrapper, wrapper.$ = capture);
wrapper._ = listener;

// wrapped event listener that propagates data changes
function l(e) {
function wrapper(e) {
var o = d3.event; // Events can be reentrant (e.g., focus).
d3.event = e;
args[0] = node.__data__;
Expand All @@ -39,5 +52,7 @@ d3_selectionPrototype.on = function(type, listener, capture) {
d3.event = o;
}
}
});
};
}

return listener ? onAdd : onRemove;
}
3 changes: 2 additions & 1 deletion src/core/selection-style.js
Expand Up @@ -4,7 +4,8 @@ d3_selectionPrototype.style = function(name, value, priority) {

// For style(object) or style(object, string), the object specifies the
// names and values of the attributes to set or remove. The values may be
// functions that are evaluated for each element.
// functions that are evaluated for each element. The optional string
// specifies the priority.
if (typeof name !== "string") {
if (n < 2) value = "";
for (priority in name) this.each(d3_selection_style(priority, name[priority], value));
Expand Down
12 changes: 11 additions & 1 deletion test/core/selection-on-test.js
Expand Up @@ -48,11 +48,21 @@ suite.addBatch({
"observes the specified namespace": function(body) {
var form = body.append("form"), foo = 0, bar = 0;
form.on("submit.foo", function() { ++foo; });
form.on("submit.bar", function() { ++bar; });
form.on({"submit.bar": function() { ++bar; }});
form.append("input").attr("type", "submit").node().click();
assert.equal(foo, 1);
assert.equal(bar, 1);
},
"can register listeners as a map": function(body) {
var form = body.append("form"), count = 0, fail = 0;
form.on({submit: function() { ++fail; }});
form.on({submit: function() { ++count; }});
form.append("input").attr("type", "submit").node().click();
assert.equal(count, 1);
assert.equal(fail, 0);
form.on({submit: null});
assert.isUndefined(form.on("submit"));
},
/* Not really sure how to test this one…
"observes the specified capture flag": function(body) {
},
Expand Down

0 comments on commit 07f72be

Please sign in to comment.