Skip to content

Instantly share code, notes, and snippets.

@froop
Created September 19, 2011 21:55
Show Gist options
  • Save froop/1227730 to your computer and use it in GitHub Desktop.
Save froop/1227730 to your computer and use it in GitHub Desktop.
[JavaScript] MultiSelectBox with QUnit test
/*global document, Option */
var MSELECT = {};
MSELECT.MultiSelectBox = function (parentElem) {
"use strict";
if (!parentElem || !parentElem.appendChild) {
throw new Error("Invalid parameter of 'parentElem'");
}
var tableElem = document.createElement("table"),
srcTitle = document.createElement("th"),
destTitle = document.createElement("th"),
srcSelectBox = document.createElement("select"),
destSelectBox = document.createElement("select"),
selectButton = document.createElement("input"),
removeButton = document.createElement("input"),
insertMode = false,
GUARD = "-";
// Setup public methods.
this.setSrcList = function (options) {
if (!options || !(options instanceof Array)) {
throw new Error("Invalid parameter of 'options'");
}
MSELECT.setSelectBoxOptions(srcSelectBox, options);
return this;
};
this.getDestListValues = function () {
var result = [], idx, max = destSelectBox.options.length, option;
for (idx = 0; idx < max; idx += 1) {
option = destSelectBox.options[idx];
if (option.value !== GUARD) {
result.push(option.value);
}
}
return result.join(",");
};
this.addSelected = function () {
var findInOptions, findFirstSelected, mergeOptions, sortOptions,
newDestOptions;
findInOptions = function (options, value) {
var idx, max = options.length;
for (idx = 0; idx < max; idx += 1) {
if (options[idx].value === value) {
return idx;
}
}
return -1;
};
findFirstSelected = function (options) {
var idx, max = options.length;
for (idx = 0; idx < max; idx += 1) {
if (options[idx].selected || options[idx].value === GUARD) {
break;
}
}
return idx;
};
mergeOptions = function (options1, options2) {
var result, idx, delIdx;
result = options1.slice(0); // Array copy
for (idx = options2.length - 1; idx >= 0; idx -= 1) {
delIdx = findInOptions(result, options2[idx].value);
if (delIdx >= 0) {
result.splice(delIdx, 1);
}
result.splice(findFirstSelected(result), 0, options2[idx]);
}
return result;
};
sortOptions = function (rule, target) {
var result = [], ruleIdx, max = rule.length, targetIdx;
for (ruleIdx = 0; ruleIdx < max; ruleIdx += 1) {
targetIdx = findInOptions(target, rule[ruleIdx].value);
if (targetIdx >= 0) {
result.push(target[targetIdx]);
}
}
return result;
};
(function () {
var baseOptions = MSELECT.copySelectBoxOptions(destSelectBox, false),
addOptions = MSELECT.copySelectBoxOptions(srcSelectBox, true);
newDestOptions = mergeOptions(baseOptions, addOptions);
if (insertMode) {
MSELECT.clearSelected(addOptions);
} else {
MSELECT.clearSelected(baseOptions);
newDestOptions = sortOptions(srcSelectBox.options, newDestOptions);
}
MSELECT.setSelectBoxOptions(destSelectBox, newDestOptions);
MSELECT.clearSelected(srcSelectBox.options);
}());
return this;
};
this.removeSelected = function () {
var idx, item, options = destSelectBox.options;
for (idx = options.length - 1; idx >= 0; idx -= 1) {
item = options[idx];
if (item.value === GUARD) {
options[idx].selected = false;
} else if (item.selected) {
options[idx] = null;
}
}
return this;
};
this.selectItem = function (value) {
var options = srcSelectBox.options, optMax = options.length, optIdx;
for (optIdx = 0; optIdx < optMax; optIdx += 1) {
if (options[optIdx].value === value) {
options[optIdx].selected = true;
break;
}
}
};
this.addSelectedAuto = function (valueStr) {
var values = valueStr.split(","), valMax = values.length, valIdx,
scrollToTop = function (selectBox) {
var options = srcSelectBox.options;
if (options.length > 0) {
options[0].selected = true;
options[0].selected = false;
}
};
MSELECT.clearSelected(srcSelectBox.options);
for (valIdx = 0; valIdx < valMax; valIdx += 1) {
this.selectItem(values[valIdx]);
}
this.addSelected();
MSELECT.clearSelected(srcSelectBox.options);
MSELECT.clearSelected(destSelectBox.options);
scrollToTop(srcSelectBox);
scrollToTop(destSelectBox);
return this;
};
this.setSrcTitle = function (html) {
srcTitle.innerHTML = html;
return this;
};
this.setDestTitle = function (html) {
destTitle.innerHTML = html;
return this;
};
this.setSelectButtonText = function (text) {
selectButton.value = text;
return this;
};
this.setRemoveButtonText = function (text) {
removeButton.value = text;
return this;
};
this.setInsertMode = function () {
insertMode = true;
destSelectBox.options[0] = new Option("- - - - - - - - - -", GUARD);
return this;
};
// for test.
this.getSrcSelectBox = function () {
return srcSelectBox;
};
this.getDestSelectBox = function () {
return destSelectBox;
};
// Setup DOM elements.
srcTitle.className = destTitle.className = "title";
srcSelectBox.multiple = destSelectBox.multiple = "multiple";
selectButton.type = removeButton.type = "button";
MSELECT.addEventListener(selectButton, (function (that) {
return function () {
that.addSelected();
};
}(this)));
MSELECT.addEventListener(removeButton, (function (that) {
return function () {
that.removeSelected();
};
}(this)));
// Build DOM elements.
(function createTableHeader() {
var theadElem = document.createElement("thead"),
trElem = document.createElement("tr");
trElem.appendChild(srcTitle);
trElem.appendChild(document.createElement("th"));
trElem.appendChild(destTitle);
theadElem.appendChild(trElem);
tableElem.appendChild(theadElem);
}());
(function createTableBody() {
var tbodyElem = document.createElement("tbody"),
trElem = document.createElement("tr"),
srcBody = document.createElement("td"),
buttonsBody = document.createElement("td"),
destBody = document.createElement("td");
srcBody.appendChild(srcSelectBox);
buttonsBody.appendChild(selectButton);
buttonsBody.appendChild(document.createElement("br"));
buttonsBody.appendChild(removeButton);
destBody.appendChild(destSelectBox);
trElem.appendChild(srcBody);
trElem.appendChild(buttonsBody);
trElem.appendChild(destBody);
tbodyElem.appendChild(trElem);
tableElem.appendChild(tbodyElem);
}());
parentElem.appendChild(tableElem);
};
// Utilities
MSELECT.setSelectBoxOptions = function (selectBox, options) {
"use strict";
if (!options || !(options instanceof Array)) {
throw new Error("Invalid parameter of 'options'");
}
var idx, max = options.length;
selectBox.options.length = 0;
for (idx = 0; idx < max; idx += 1) {
try {
selectBox.options[idx] = options[idx];
} catch (e) {
throw new Error("Invalid parameter of 'options[" + idx + "]'");
}
}
};
MSELECT.copySelectBoxOptions = function (selectBox, selectedOnly) {
"use strict";
var result = [], idx, max = selectBox.options.length, orgOpt, copyOpt;
for (idx = 0; idx < max; idx += 1) {
orgOpt = selectBox.options[idx];
if (!selectedOnly || orgOpt.selected) {
copyOpt = new Option(orgOpt.text, orgOpt.value);
copyOpt.selected = orgOpt.selected;
result.push(copyOpt);
}
}
return result;
};
MSELECT.clearSelected = function (options) {
"use strict";
var idx, max = options.length;
for (idx = 0; idx < max; idx += 1) {
options[idx].selected = false;
}
};
MSELECT.addEventListener = function (element, eventListner) {
"use strict";
if (element.addEventListener) { // W3C
element.addEventListener("click", eventListner, false);
} else if (element.attachEvent) { // IE
element.attachEvent("onclick", eventListner);
} else {
element.onclick = eventListner;
}
};
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-31j">
<style type="text/css">
select {
width: 120px;
height: 120px;
}
input[type="button"] {
width: 70px;
}
table {
border-spacing: 0;
}
table .title {
color: white;
background-color: blue;
}
</style>
<script type="text/javascript" src="MultiSelectBox.js"></script>
<script type="text/javascript">
var msBox, msBoxInsert;
window.onload = function () {
var options = [
new Option("0000000000", "00"),
new Option("0101010101", "01"),
new Option("0202020202", "02"),
new Option("0303030303", "03"),
new Option("0404040404", "04"),
new Option("0505050505", "05"),
new Option("0606060606", "06"),
new Option("0707070707", "07"),
new Option("0808080808", "08"),
new Option("0909090909", "09"),
new Option("12345678901234567890", "10")
],
optionsInsert = [
new Option("0000000000", "00"),
new Option("1111111111", "01"),
new Option("2222222222", "02"),
new Option("3333333333", "03"),
new Option("4444444444", "04"),
new Option("5555555555", "05"),
new Option("6666666666", "06"),
new Option("7777777777", "07"),
new Option("8888888888", "08"),
new Option("9999999999", "09")
];
msBox = new MSELECT.MultiSelectBox(document.getElementById("multi_select"))
.setSrcList(options).setSrcTitle("src").setDestTitle("dest")
.setSelectButtonText("select>>").setRemoveButtonText("<<remove")
.addSelectedAuto("00,01");
msBoxInsert = new MSELECT.MultiSelectBox(document.getElementById("multi_select_insert"))
.setSrcList(optionsInsert).setSrcTitle("src (insert)").setDestTitle("dest (insert)")
.setSelectButtonText("select>>").setRemoveButtonText("<<remove").setInsertMode();
}
</script>
</head>
<body>
<div>
<div id="multi_select"></div>
<input type="button" value="test" onclick="alert(msBox.getDestListValues())"/>
</div>
<br/>
<div>
<div id="multi_select_insert"></div>
<input type="button" value="test" onclick="alert(msBoxInsert.getDestListValues())"/>
</div>
</body>
</html>
var target;
module("MultiSelectBox", {
setup: function () {
target = createDefaultTarget();
},
teardown: function () {
}
});
test("setSrcList", function () {
equals(target.getSrcSelectBox().options.length, 4);
});
test("setSrcListTwice", function () {
var options = [ new Option("text0b", "value0b") ];
target.setSrcList(options);
equals(target.getSrcSelectBox().options.length, 1);
});
test("setSrcListSelected", function () {
var option1 = new Option("text0", "value0"),
option2 = new Option("text1", "value1"),
options = [];
option1.selected = true;
options = [option1, option2];
target.setSrcList(options);
ok(target.getSrcSelectBox().options[0].selected);
ok(!target.getSrcSelectBox().options[1].selected);
});
test("getDestListValues", function () {
target.getSrcSelectBox().options[0].selected = true;
target.getSrcSelectBox().options[2].selected = true;
target.addSelected();
equals(target.getDestListValues(), "value0,value2");
});
test("getDestListValuesEmpty", function () {
equals(target.getDestListValues(), "");
});
test("addSelectedFirst", function () {
target.getSrcSelectBox().options[0].selected = true;
target.addSelected();
equals(target.getDestListValues(), "value0");
ok(!target.getSrcSelectBox().options[0].selected);
});
test("addSelectedLast", function () {
target.getSrcSelectBox().options[2].selected = true;
target.addSelected();
equals(target.getDestListValues(), "value2");
});
test("addSelectedMiddle", function () {
target.getSrcSelectBox().options[1].selected = true;
target.addSelected();
equals(target.getDestListValues(), "value1");
});
test("addSelectedMulti", function () {
target.getSrcSelectBox().options[0].selected = true;
target.getSrcSelectBox().options[1].selected = true;
target.getSrcSelectBox().options[2].selected = true;
target.addSelected();
equals(target.getDestListValues(), "value0,value1,value2");
});
test("addSelectedNone", function () {
target.addSelected();
equals(target.getDestListValues(), "");
});
test("addSelectedTwice", function () {
target.getSrcSelectBox().options[1].selected = true;
target.addSelected();
ok(target.getDestSelectBox().options[0].selected);
target.getSrcSelectBox().options[2].selected = true;
target.addSelected();
ok(target.getDestSelectBox().options[1].selected);
ok(!target.getDestSelectBox().options[0].selected);
equals(target.getDestListValues(), "value1,value2");
});
test("addSelectedDupli", function () {
target.getSrcSelectBox().options[1].selected = true;
target.addSelected();
target.getSrcSelectBox().options[1].selected = true;
target.addSelected();
equals(target.getDestListValues(), "value1");
ok(target.getDestSelectBox().options[0].selected);
});
test("addSelectedMulti", function () {
target.getSrcSelectBox().options[0].selected = true;
target.getSrcSelectBox().options[1].selected = true;
target.addSelected();
equals(target.getDestListValues(), "value0,value1");
ok(target.getDestSelectBox().options[0].selected);
ok(target.getDestSelectBox().options[1].selected);
});
test("addSelectedSort", function () {
var options = [ new Option("text2", "value2"),
new Option("text1", "value1"),
new Option("text0", "value0") ];
target.setSrcList(options);
target.getSrcSelectBox().options[0].selected = true;
target.addSelected();
target.getSrcSelectBox().options[1].selected = true;
target.addSelected();
target.getSrcSelectBox().options[2].selected = true;
target.addSelected();
equals(target.getDestListValues(), "value2,value1,value0");
});
test("addSelectedInsertNoSelect", function () {
target.setInsertMode();
target.getSrcSelectBox().options[1].selected = true;
target.addSelected();
target.getSrcSelectBox().options[0].selected = true;
target.addSelected();
equals(target.getDestListValues(), "value1,value0");
equals(target.getDestSelectBox().options[2].value, "-");
});
test("addSelectedInsert", function () {
target.setInsertMode();
target.getSrcSelectBox().options[0].selected = true;
target.getSrcSelectBox().options[1].selected = true;
target.addSelected();
target.getSrcSelectBox().options[2].selected = true;
target.getDestSelectBox().options[0].selected = true;
target.addSelected();
equals(target.getDestListValues(), "value2,value0,value1");
});
test("addSelectedInsertExist", function () {
target.setInsertMode();
target.getSrcSelectBox().options[0].selected = true;
target.getSrcSelectBox().options[1].selected = true;
target.addSelected();
target.getSrcSelectBox().options[1].selected = true;
target.getDestSelectBox().options[0].selected = true;
target.addSelected();
equals(target.getDestListValues(), "value1,value0");
});
test("addSelectedInsertExistMulti", function () {
target.setInsertMode();
target.getSrcSelectBox().options[0].selected = true;
target.getSrcSelectBox().options[1].selected = true;
target.addSelected();
target.getSrcSelectBox().options[0].selected = true;
target.getSrcSelectBox().options[1].selected = true;
target.addSelected();
equals(target.getDestListValues(), "value0,value1");
});
test("removeSelectedFirst", function () {
target.getSrcSelectBox().options[0].selected = true;
target.getSrcSelectBox().options[1].selected = true;
target.addSelected();
MSELECT.clearSelected(target.getDestSelectBox().options);
target.getDestSelectBox().options[0].selected = true;
equals("value0,value1", target.getDestListValues());
target.removeSelected();
equals(target.getDestListValues(), "value1");
});
test("removeSelectedLast", function () {
target.getSrcSelectBox().options[0].selected = true;
target.getSrcSelectBox().options[1].selected = true;
target.getSrcSelectBox().options[2].selected = true;
target.addSelected();
MSELECT.clearSelected(target.getDestSelectBox().options);
target.getDestSelectBox().options[2].selected = true;
equals("value0,value1,value2", target.getDestListValues());
target.removeSelected();
equals(target.getDestListValues(), "value0,value1");
});
test("removeSelectedMiddle", function () {
target.getSrcSelectBox().options[0].selected = true;
target.getSrcSelectBox().options[1].selected = true;
target.getSrcSelectBox().options[2].selected = true;
target.addSelected();
MSELECT.clearSelected(target.getDestSelectBox().options);
target.getDestSelectBox().options[1].selected = true;
target.removeSelected();
equals(target.getDestListValues(), "value0,value2");
});
test("removeSelectedMulti", function () {
target.getSrcSelectBox().options[0].selected = true;
target.getSrcSelectBox().options[1].selected = true;
target.getSrcSelectBox().options[2].selected = true;
target.addSelected();
MSELECT.clearSelected(target.getDestSelectBox().options);
target.getDestSelectBox().options[0].selected = true;
target.getDestSelectBox().options[1].selected = true;
target.removeSelected();
equals(target.getDestListValues(), "value2");
});
test("removeSelectedAll", function () {
target.getSrcSelectBox().options[0].selected = true;
target.addSelected();
target.getDestSelectBox().options[0].selected = true;
target.removeSelected();
equals(target.getDestListValues(), "");
});
test("removeSelectedNone", function () {
target.getSrcSelectBox().options[0].selected = true;
target.addSelected();
MSELECT.clearSelected(target.getDestSelectBox().options);
target.removeSelected();
equals(target.getDestListValues(), "value0");
});
test("removeSelectedInsertMode", function () {
target.setInsertMode();
target.getSrcSelectBox().options[0].selected = true;
target.addSelected();
target.getDestSelectBox().options[0].selected = true;
target.getDestSelectBox().options[1].selected = true;
target.removeSelected();
equals(target.getDestSelectBox().options[0].value, "-");
});
test("addSelectedAuto", function () {
target.getSrcSelectBox().options[3].selected = true;
target.addSelectedAuto("value1");
target.addSelectedAuto("value0,value2");
equals(target.getDestListValues(), "value0,value1,value2");
ok(!target.getSrcSelectBox().options[0].selected);
ok(!target.getDestSelectBox().options[0].selected);
});
test("addSelectedAutoInsertMode", function () {
target.setInsertMode();
target.addSelectedAuto("value1");
target.addSelectedAuto("value0,value2");
equals(target.getDestListValues(), "value1,value0,value2");
});
test("addSelectedAutoEmpty", function () {
target.addSelectedAuto("");
equals(target.getDestListValues(), "");
});
test("setInsertMode", function () {
target.setInsertMode();
equals(target.getDestSelectBox().options[0].value, "-");
});
var createDefaultTarget = function () {
var parent = document.createElement("div"),
target = new MSELECT.MultiSelectBox(parent),
options = [ new Option("text0", "value0"),
new Option("text1", "value1"),
new Option("text2", "value2"),
new Option("text3", "value3") ];
target.setSrcTitle("title1");
target.setDestTitle("title2");
target.setSelectButtonText("add");
target.setRemoveButtonText("remove");
target.setSrcList(options);
return target;
};
@froop
Copy link
Author

froop commented Sep 23, 2011

QUnitテストケースMultiSelectBoxTest.jsのequals関数の引数expectedとresultが逆なのに気づいて修正。
元々JsTestDriver用に作っていたのを転用したのだが、testとequalsの部分だけ置換で動くようになったので、
結果NG時の表示がおかしくなるだけで結果OK時には問題なかったので盲点だった。

@froop
Copy link
Author

froop commented Sep 28, 2011

Insert mode に対応。これまでは並び順固定だったのが任意の順番ができるように

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment