Multi-value map support for classed.

This commit is contained in:
Mike Bostock 2012-08-05 14:54:02 -07:00
Родитель a39ca24fb8
Коммит f2b45c132d
8 изменённых файлов: 241 добавлений и 135 удалений

84
d3.v2.js поставляемый
Просмотреть файл

@ -373,7 +373,7 @@
return d == null;
}
function d3_collapse(s) {
return s.replace(/^\s+|\s+$/g, "").replace(/\s+/g, " ");
return s.trim().replace(/\s+/g, " ");
}
d3.range = function(start, stop, step) {
if (arguments.length < 3) {
@ -1430,45 +1430,61 @@
return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant;
}
d3_selectionPrototype.classed = function(name, value) {
var names = d3_collapse(name).split(" "), n = names.length, i = -1;
if (arguments.length > 1) {
while (++i < n) d3_selection_classed.call(this, names[i], value);
return this;
} else {
while (++i < n) if (!d3_selection_classed.call(this, names[i])) return false;
return true;
}
};
function d3_selection_classed(name, value) {
var re = new RegExp("(^|\\s+)" + d3.requote(name) + "(\\s+|$)", "g");
if (arguments.length < 2) {
var node = this.node();
if (c = node.classList) return c.contains(name);
var c = node.className;
re.lastIndex = 0;
return re.test(c.baseVal != null ? c.baseVal : c);
}
function classedAdd() {
if (c = this.classList) return c.add(name);
var c = this.className, cb = c.baseVal != null, cv = cb ? c.baseVal : c;
re.lastIndex = 0;
if (!re.test(cv)) {
cv = d3_collapse(cv + " " + name);
if (cb) c.baseVal = cv; else this.className = cv;
if ((value = typeof name) === "function") {
return this.each(function() {
var x = name.apply(this, arguments);
for (value in x) d3_selection_classed(value, x[value]).apply(this, arguments);
});
}
}
function classedRemove() {
if (c = this.classList) return c.remove(name);
var c = this.className, cb = c.baseVal != null, cv = cb ? c.baseVal : c;
if (cv) {
cv = d3_collapse(cv.replace(re, " "));
if (cb) c.baseVal = cv; else this.className = cv;
if (value === "string") {
var node = this.node(), n = (name = name.trim().split(/^|\s+/g)).length, i = -1;
if (value = node.classList) {
while (++i < n) if (!value.contains(name[i])) return false;
} else {
value = node.className;
if (value.baseVal != null) value = value.baseVal;
while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false;
}
return true;
}
for (value in name) this.each(d3_selection_classed(value, name[value]));
return this;
}
return this.each(d3_selection_classed(name, value));
};
function d3_selection_classedRe(name) {
return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g");
}
function d3_selection_classed(name, value) {
name = name.trim().split(/\s+/).map(d3_selection_classedName);
var n = name.length;
function classedConstant() {
var i = -1;
while (++i < n) name[i](this, value);
}
function classedFunction() {
(value.apply(this, arguments) ? classedAdd : classedRemove).call(this);
var i = -1, x = value.apply(this, arguments);
while (++i < n) name[i](this, x);
}
return this.each(typeof value === "function" ? classedFunction : value ? classedAdd : classedRemove);
return typeof value === "function" ? classedFunction : classedConstant;
}
function d3_selection_classedName(name) {
var re = d3_selection_classedRe(name);
return function(node, value) {
if (c = node.classList) return value ? c.add(name) : c.remove(name);
var c = node.className, cb = c.baseVal != null, cv = cb ? c.baseVal : c;
if (value) {
re.lastIndex = 0;
if (!re.test(cv)) {
cv = d3_collapse(cv + " " + name);
if (cb) c.baseVal = cv; else node.className = cv;
}
} else if (cv) {
cv = d3_collapse(cv.replace(re, " "));
if (cb) c.baseVal = cv; else node.className = cv;
}
};
}
d3_selectionPrototype.style = function(name, value, priority) {
var n = arguments.length;

8
d3.v2.min.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -1,3 +1,3 @@
function d3_collapse(s) {
return s.replace(/^\s+|\s+$/g, "").replace(/\s+/g, " ");
return s.trim().replace(/\s+/g, " ");
}

Просмотреть файл

@ -3,7 +3,7 @@ d3_selectionPrototype.attr = function(name, value) {
// For attr(function), the function must return an object for each element,
// specifying the names and values of the attributes to set or remove. The
// values must be constants, not function.
// values must be constants, not functions.
if ((value = typeof name) === "function") {
return this.each(function() {
var x = name.apply(this, arguments);
@ -19,8 +19,8 @@ d3_selectionPrototype.attr = function(name, value) {
: value.getAttribute(name);
}
// Otherwise, for attr(object), the object specifies the names and values of
// the attributes to set or remove. The values may be functions that are
// 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.
for (value in name) this.each(d3_selection_attr(value, name[value]));
return this;

Просмотреть файл

@ -1,61 +1,87 @@
d3_selectionPrototype.classed = function(name, value) {
var names = d3_collapse(name).split(" "),
n = names.length,
i = -1;
if (arguments.length > 1) {
while (++i < n) d3_selection_classed.call(this, names[i], value);
if (arguments.length < 2) {
// For classed(function), the function must return an object for each
// element, specifying the names of classes to add or remove. The values
// must be constants, not functions.
if ((value = typeof name) === "function") {
return this.each(function() {
var x = name.apply(this, arguments);
for (value in x) d3_selection_classed(value, x[value]).apply(this, arguments);
});
}
// For classed(string), return true only if the first node has the specified
// class or classes. Note that even if the browser supports DOMTokenList, it
// probably doesn't support it on SVG elements (which can be animated).
if (value === "string") {
var node = this.node(),
n = (name = name.trim().split(/^|\s+/g)).length,
i = -1;
if (value = node.classList) {
while (++i < n) if (!value.contains(name[i])) return false;
} else {
value = node.className;
if (value.baseVal != null) value = value.baseVal;
while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false;
}
return true;
}
// For classed(object), the object specifies the names of classes to add or
// remove. The values may be functions that are evaluated for each element.
for (value in name) this.each(d3_selection_classed(value, name[value]));
return this;
} else {
while (++i < n) if (!d3_selection_classed.call(this, names[i])) return false;
return true;
}
// Otherwise, both a name and a value are specified, and are handled as below.
return this.each(d3_selection_classed(name, value));
};
function d3_selection_classedRe(name) {
return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g");
}
// Multiple class names are allowed (e.g., "foo bar").
function d3_selection_classed(name, value) {
var re = new RegExp("(^|\\s+)" + d3.requote(name) + "(\\s+|$)", "g");
name = name.trim().split(/\s+/).map(d3_selection_classedName);
var n = name.length;
// If no value is specified, return the first value.
if (arguments.length < 2) {
var node = this.node();
if (c = node.classList) return c.contains(name);
var c = node.className;
re.lastIndex = 0;
return re.test(c.baseVal != null ? c.baseVal : c);
function classedConstant() {
var i = -1;
while (++i < n) name[i](this, value);
}
function classedAdd() {
if (c = this.classList) return c.add(name);
var c = this.className,
cb = c.baseVal != null,
cv = cb ? c.baseVal : c;
re.lastIndex = 0;
if (!re.test(cv)) {
cv = d3_collapse(cv + " " + name);
if (cb) c.baseVal = cv;
else this.className = cv;
}
// When the value is a function, the function is still evaluated only once per
// element even if there are multiple class names.
function classedFunction() {
var i = -1, x = value.apply(this, arguments);
while (++i < n) name[i](this, x);
}
function classedRemove() {
if (c = this.classList) return c.remove(name);
var c = this.className,
return typeof value === "function"
? classedFunction
: classedConstant;
}
function d3_selection_classedName(name) {
var re = d3_selection_classedRe(name);
return function(node, value) {
if (c = node.classList) return value ? c.add(name) : c.remove(name);
var c = node.className,
cb = c.baseVal != null,
cv = cb ? c.baseVal : c;
if (cv) {
if (value) {
re.lastIndex = 0;
if (!re.test(cv)) {
cv = d3_collapse(cv + " " + name);
if (cb) c.baseVal = cv;
else node.className = cv;
}
} else if (cv) {
cv = d3_collapse(cv.replace(re, " "));
if (cb) c.baseVal = cv;
else this.className = cv;
else node.className = cv;
}
}
function classedFunction() {
(value.apply(this, arguments)
? classedAdd
: classedRemove).call(this);
}
return this.each(typeof value === "function"
? classedFunction : value
? classedAdd
: classedRemove);
};
}

Просмотреть файл

@ -3,7 +3,7 @@ d3_selectionPrototype.property = function(name, value) {
// For property(function), the function must return an object for each
// element, specifying the names and values of the properties to set or
// remove. The values must be constants, not function.
// remove. The values must be constants, not functions.
if ((value = typeof name) === "function") {
return this.each(function() {
var x = name.apply(this, arguments);
@ -14,9 +14,9 @@ d3_selectionPrototype.property = function(name, value) {
// For property(string), return the property value for the first node.
if (value === "string") return this.node()[name];
// Otherwise, for property(object), the object specifies the names and
// values of the properties to set or remove. The values may be functions
// that are evaluated for each element.
// For property(object), the object specifies the names and values of the
// properties to set or remove. The values may be functions that are
// evaluated for each element.
for (value in name) this.each(d3_selection_property(value, name[value]));
return this;
}

Просмотреть файл

@ -4,7 +4,7 @@ d3_selectionPrototype.style = function(name, value, priority) {
// For style(function) or style(function, priority), the function must
// return an object for each element, specifying the names and values of the
// styles to set or remove. The values must be constants, not function.
// styles to set or remove. The values must be constants, not functions.
if ((priority = typeof name) === "function") {
if (n < 2) value = "";
return this.each(function() {

Просмотреть файл

@ -17,13 +17,6 @@ suite.addBatch({
body.classed("bar", true);
assert.equal(document.body.className, "foo bar");
},
"adds a missing class as a function": function(body) {
body.attr("class", null);
body.classed("foo", function() { return true; });
assert.equal(document.body.className, "foo");
body.classed("bar", function() { return true; });
assert.equal(document.body.className, "foo bar");
},
"removes an existing class as false": function(body) {
body.attr("class", "bar foo");
body.classed("foo", false);
@ -31,13 +24,6 @@ suite.addBatch({
body.classed("bar", false);
assert.equal(document.body.className, "");
},
"removes an existing class as a function": function(body) {
body.attr("class", "bar foo");
body.classed("foo", function() { return false; });
assert.equal(document.body.className, "bar");
body.classed("bar", function() { return false; });
assert.equal(document.body.className, "");
},
"preserves an existing class as true": function(body) {
body.attr("class", "bar foo");
body.classed("foo", true);
@ -45,13 +31,6 @@ suite.addBatch({
body.classed("bar", true);
assert.equal(document.body.className, "bar foo");
},
"preserves an existing class as a function": function(body) {
body.attr("class", "bar foo");
body.classed("foo", function() { return true; });
assert.equal(document.body.className, "bar foo");
body.classed("bar", function() { return true; });
assert.equal(document.body.className, "bar foo");
},
"preserves a missing class as false": function(body) {
body.attr("class", "baz");
body.classed("foo", false);
@ -60,14 +39,6 @@ suite.addBatch({
body.classed("bar", false);
assert.equal(document.body.className, "");
},
"preserves a missing class as a function": function(body) {
body.attr("class", "baz");
body.classed("foo", function() { return false; });
assert.equal(document.body.className, "baz");
body.attr("class", null);
body.classed("bar", function() { return false; });
assert.equal(document.body.className, "");
},
"gets an existing class": function(body) {
body.attr("class", " foo\tbar baz");
assert.isTrue(body.classed("foo"));
@ -80,27 +51,120 @@ suite.addBatch({
assert.isFalse(body.classed("bare"));
assert.isFalse(body.classed("rbaz"));
},
"returns the current selection": function(body) {
assert.isTrue(body.classed("foo", true) === body);
"accepts a name with whitespace, collapsing it": function(body) {
body.attr("class", null);
body.classed(" foo\t", true);
assert.equal(document.body.className, "foo");
body.classed("\tfoo ", false);
assert.equal(document.body.className, "");
},
"adds missing classes as true": function(body) {
"accepts a name with multiple classes separated by whitespace": function(body) {
body.attr("class", null);
body.classed("foo bar", true);
assert.equal(document.body.className, "foo bar");
},
"gets existing classes": function(body) {
body.attr("class", " foo\tbar baz");
assert.isTrue(body.classed("foo"));
assert.isTrue(body.classed("foo bar"));
assert.isTrue(body.classed("bar baz"));
assert.isTrue(body.classed("foo bar baz"));
},
"does not get missing classes": function(body) {
body.attr("class", " foo\tbar baz");
assert.isFalse(body.classed("foob"));
assert.isTrue(body.classed("bar foo"));
assert.isFalse(body.classed("foo bar baz"));
assert.isFalse(body.classed("foob bar"));
assert.isFalse(body.classed("bar baz blah"));
assert.isFalse(body.classed("foo bar baz moo"));
body.classed("bar foo", false);
assert.equal(document.body.className, "");
},
"accepts a silly class name with unsafe characters": function(body) {
body.attr("class", null);
body.classed("foo.bar", true);
assert.equal(document.body.className, "foo.bar");
assert.isTrue(body.classed("foo.bar"));
assert.isFalse(body.classed("foo"));
assert.isFalse(body.classed("bar"));
body.classed("bar.foo", false);
assert.equal(document.body.className, "foo.bar");
body.classed("foo.bar", false);
assert.equal(document.body.className, "");
},
"accepts a name with duplicate classes, ignoring them": function(body) {
body.attr("class", null);
body.classed(" foo\tfoo ", true);
assert.equal(document.body.className, "foo");
body.classed("\tfoo foo ", false);
assert.equal(document.body.className, "");
},
"accepts a value function returning true or false": function(body) {
body.attr("class", null);
body.classed("foo", function() { return true; });
assert.equal(document.body.className, "foo");
body.classed("foo bar", function() { return true; });
assert.equal(document.body.className, "foo bar");
body.classed("foo", function() { return false; });
assert.equal(document.body.className, "bar");
},
"accepts a value function returning returning an object containing true or false": function(body) {
body.attr("class", null);
body.classed(function() { return {foo: true}; });
assert.equal(document.body.className, "foo");
body.classed(function() { return {foo: true, bar: true}; });
assert.equal(document.body.className, "foo bar");
body.classed(function() { return {bar: false, foo: false}; });
assert.equal(document.body.className, "");
},
"accepts a name object containing true or false": function(body) {
body.attr("class", null);
body.classed({foo: true});
assert.equal(document.body.className, "foo");
body.classed({bar: true, foo: false});
assert.equal(document.body.className, "bar");
},
"accepts a name object containing a function returning true or false": function(body) {
body.attr("class", null);
body.classed({foo: function() { return true; }});
assert.equal(document.body.className, "foo");
},
"accepts a name object containing a mix of functions and non-functions": function(body) {
body.attr("class", "foo");
body.classed({foo: false, bar: function() { return true; }});
assert.equal(document.body.className, "bar");
},
"the value may be truthy or falsey": function(body) {
body.attr("class", "foo");
body.classed({foo: null, bar: function() { return 1; }});
assert.equal(document.body.className, "bar");
},
"keys in the name object may contain whitespace": function(body) {
body.attr("class", null);
body.classed({" foo\t": function() { return true; }});
assert.equal(document.body.className, "foo");
body.attr("class", null);
body.classed(function() { return {"\tfoo ": true}; });
assert.equal(document.body.className, "foo");
},
"keys in the name object may reference multiple classes": function(body) {
body.attr("class", null);
body.classed({"foo bar": function() { return true; }});
assert.equal(document.body.className, "foo bar");
body.attr("class", null);
body.classed(function() { return {"foo bar": true}; });
assert.equal(document.body.className, "foo bar");
},
"keys in the name object may contain duplicates": function(body) {
body.attr("class", null);
body.classed({"foo foo": function() { return true; }});
assert.equal(document.body.className, "foo");
body.attr("class", null);
body.classed(function() { return {"foo foo": true}; });
assert.equal(document.body.className, "foo");
},
"value functions are only evaluated once when used for multiple classes": function(body) {
var count = 0;
body.attr("class", null);
body.classed({"foo bar": function() { return ++count; }});
assert.equal(document.body.className, "foo bar");
assert.equal(count, 1);
body.attr("class", null);
body.classed(function() { return {"foo bar": count--}; });
assert.equal(document.body.className, "foo bar");
assert.equal(count, 0);
},
"returns the current selection": function(body) {
assert.isTrue(body.classed("foo", true) === body);
}
}
});