ほぼ同じ動作のアプリケーションがあり、同じ手法を選択しました。ただし、ViewModel には List1 と List2 の両方が含まれていました。
エディター テンプレートの呼び出しは次のように行いました。
<div id="SoftwarePanel" class="collapsiblePanel">
<h2>Request Software</h2>
@Html.EditorFor(m => m.Software)
</div>
この場合の「ソフトウェア」は、「AvailableItems」と「RequestedItems」のプロパティを含む「RequestableList」クラスのインスタンスです。エディター テンプレートは、両方のリストを互いに隣接させてレンダリングし、リストボックス間で項目を前後に移動するための適切なボタンを配置し、javascript を結び付けました。
<table>
<tr>
<td>
<input type="text" id="avail@{@name}Filter" />
<button type="button" id="avail@{@name}Clear" class="DualList_availClear dualListButton"">
X</button><br />
@Html.ListBoxFor(m => m.AvailableItems, new MultiSelectList((from i in Model.AvailableItems where !Model.RequestedItems.Contains(i.Key) select i), "Key", "Value"), new { @class = "dualList" })
<br />
<span id="avail@{@name}Counter" class="countLabel"></span>
<select id="avail@{@name}Storage">
</select>
</td>
<td>
<button id="to2@{@name}" type="button" class="dualListButton">
></button><br />
<button id="allTo2@{@name}" type="button" class="dualListButton">
>></button><br />
<button id="allTo1@{@name}" type="button" class="DualList_Allto1 dualListButton">
<<</button><br />
<button id="to1@{@name}" type="button" class="dualListButton">
<</button><br />
</td>
<td>
<input type="text" id="@{@name}RequestedFilter" />
<button type="button" id="@{@name}RequestedClear" class="DualList_requestClear dualListButton">
X</button><br />
@Html.ListBoxFor(m => m.RequestedItems, new MultiSelectList((from i in Model.RequestedItems select new { key = i, value = Model.AvailableItems[i] }).AsEnumerable(), "key", "value"), new { @class = "dualList" })
<br />
<span id="@{@name}RequestedCounter" class="countLabel"></span>
<select id="@{@name}RequestedStorage">
</select>
</td>
</tr>
</table>
更新: Justin Mead によって開発された DualListBox プラグインを使用しました。 http://www.meadmiracle.com/dlb/DLBDocumentation.aspx
<script type="text/javascript">
$(function () {
$.configureBoxes({
box1View: '@ViewData.TemplateInfo.GetFullHtmlFieldId("AvailableItems")',
box1Storage: 'avail@{@name}Storage',
box1Filter: 'avail@{@name}Filter',
box1Clear: 'avail@{@name}Clear',
box1Counter: 'avail@{@name}Counter',
box2View: '@ViewData.TemplateInfo.GetFullHtmlFieldId("RequestedItems")',
box2Storage: '@{@name}RequestedStorage',
box2Filter: '@{@name}RequestedFilter',
box2Clear: '@{@name}RequestedClear',
box2Counter: '@{@name}RequestedCounter',
to1: 'to1@{@name}',
to2: 'to2@{@name}',
allTo1: 'allTo1@{@name}',
allTo2: 'allTo2@{@name}',
onItemsChanged: function () {
var opts = $('#@ViewData.TemplateInfo.GetFullHtmlFieldId("RequestedItems") option');
var optIds = $.map(opts, function(e) { return $(e).val(); });
}
});
});
</script>