Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds remove method to layer (#63). #103

Closed
3 changes: 1 addition & 2 deletions examples/scripts/bar-chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ d3.chart("BarChart", {

function onExitTrans() {
this.duration(1000)
.attr("x", function(d, i) { return chart.x(i - 1) - .5; })
.remove();
.attr("x", function(d, i) { return chart.x(i - 1) - .5; });
}

function dataBind(data) {
Expand Down
5 changes: 4 additions & 1 deletion src/layer-extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ d3.selection.prototype.layer = function(options) {
var layer = new Layer(this);
var eventName;

// Set layer methods (required)
// Set layer methods (databind/insert required, remove optional)
layer.dataBind = options.dataBind;
layer.insert = options.insert;
if (options.remove) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a test for this code path? For that test, a SinonJS spy should be appropriate.

layer.remove = options.remove;
}

// Bind events (optional)
if ("events" in options) {
Expand Down
20 changes: 17 additions & 3 deletions src/layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ Layer.prototype.insert = function() {
d3cAssert(false, "Layers must specify an `insert` method.");
};

/**
* Invoked by {@link Layer#draw} in order to remove exiting DOM nodes from
* this layer's `base`. This default implementation may be overridden by
* Layer instances.
*/
Layer.prototype.remove = function() {
this.remove();
};

/**
* Subscribe a handler to a "lifecycle event". These events (and only these
* events) are triggered when {@link Layer#draw} is invoked--see that method
Expand Down Expand Up @@ -113,9 +122,12 @@ Layer.prototype.off = function(eventName, handler) {

/**
* Render the layer according to the input data: Bind the data to the layer
* (according to {@link Layer#dataBind}, insert new elements (according to
* {@link Layer#insert}, make lifecycle selections, and invoke all relevant
* handlers (as attached via {@link Layer#on}) with the lifecycle selections.
* (according to {@link Layer#dataBind}), insert new elements (according to
* {@link Layer#insert}), make lifecycle selections, invoke all relevant
* handlers (as attached via {@link Layer#on}) with the lifecycle selections,
* then remove exiting elements (according to {@link Layer#remove}).
*
* The lifecycle selections are:
*
* - update
* - update:transition
Expand Down Expand Up @@ -216,4 +228,6 @@ Layer.prototype.draw = function(data) {
}
}
}

this.remove.call(selection);
};
36 changes: 33 additions & 3 deletions test/tests/layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,18 @@ suite("d3.layer", function() {
sinon.spy(entering, "transition");
return entering;
});
var remove = this.remove = sinon.spy();
var base = this.base = d3.select("#test").append("svg");

this.layer = base.layer({
dataBind: dataBind,
insert: insert
insert: insert,
});

this.layerWithRemove = this.base.append('g').layer({
dataBind: dataBind,
insert: insert,
remove: remove
});
});

Expand Down Expand Up @@ -82,7 +89,24 @@ suite("d3.layer", function() {
this.layer.draw([]);
assert(this.insert.calledOn(this.dataBind.returnValues[0].enter.returnValues[0]));
});

test("by default removes exiting nodes from the DOM", function() {
this.layer.draw([1]);
assert.equal(this.layer.selectAll('g').size(), 1);
this.layer.draw([]);
assert.equal(this.layer.selectAll('g').size(), 0);
});
test("invokes the provided `remove` method in the context of the layer's bound 'exit' selection", function() {
var updating, exiting;

this.layerWithRemove.draw([1, 2, 3]);
this.layerWithRemove.draw([]);

updating = this.dataBind.returnValues[1];
exiting = updating.exit.returnValues[0];

assert(this.remove.calledOn(exiting));
});

suite("event triggering", function() {
test("invokes event handlers with the correct selection", function() {
var layer = this.base.append("g").layer({
Expand Down Expand Up @@ -137,19 +161,25 @@ suite("d3.layer", function() {
},
insert: function() {
return this.append("g");
}
},
remove: function() {
return this.remove();
}
});
var enterSpy = sinon.spy();
var updateSpy = sinon.spy();
var exitSpy = sinon.spy();
layer.draw([1]);

layer.on("enter", enterSpy);
layer.on("update", updateSpy);
layer.on("exit", exitSpy);

layer.draw([1]);

sinon.assert.callCount(enterSpy, 0);
sinon.assert.callCount(updateSpy, 1);
sinon.assert.callCount(exitSpy, 0);
});

suite("Layer#off", function() {
Expand Down