We currently have a form with the standard multi-select functionality of "here are the available options, here are the selected options, here are some buttons to move stuff back and forth." However, the client now wants the ability to not just select certain items, but to also categorize them. For example, given a list of books, they want to not just select the ones they own, but also the ones they've read, the ones they would like to read, and the ones they've heard about. (All examples fictional.) Thankfully, a selected item can only be in one category at a time.
I can find many examples of moving items between listboxes, but not a single one for moving items between multiple listboxes. To add to the complication, the form needs to have two sets of list+categories, e.g. a list of movies that need to be categorized in addition to the aforementioned books.
EDIT: Having now actually sat down to try to code the non-javascripty bits, I need to revise my question, because I realized that multiple select lists won't really work from the "how do I inform the server about all this lovely new information" standpoint. So the html code is now a pseudo-listbox, i.e. an unordered list (<ul>
) displayed in a box with a scrollbar, and each list item (<li>
) has a set of five radio buttons (unselected/own/read/like/heard).
My task is still roughly the same: how to take this one list and make it easy to categorize the items, in such a way that the user can tell at a glance what is in what category. (The pseudo-listbox has some of the same disadvantages as a multi-select listbox, namely it's hard to tell what's selected if the list is long enough to scroll.) The dream solution would be a drag-and-drop type thing, but at this point even buttons would be OK.
Another modification (a good one) is that the client has revised the lists, so the longest is now "only" 62 items long (instead of the many hundreds they had before). The categories will still mostly contain zero, one, or two selected items, possibly a couple more if the user was overzealous.
As far as OS and stuff, the site is in classic asp (quit snickering!),开发者_如何学Python the server-side code is VBScript, and so far we've avoided the various Javascript libraries by the simple expedient of almost never using client-side scripting. This one form for this one client is currently the big exception. Give 'em an inch and they want a mile...
Oh, and I have to add: I suck at Javascript, or really at any C-descendant language. Curly braces give me hives. I'd really, really like something I can just copy & paste into my page, maybe tweak some variable names, and never look at it again. A girl can dream, can't she? :)
[existing code deleted because it's largely irrelevant.]
Funny, I also, just yesterday googled "moving items between multiple listboxes" and your question pop up.
I didnt read your entire post so Im not sure if I can help out. But this solved my problem. I downloaded this solution. And then made the following changes...
- Add one extra hidenfield in the html per (extra) listbox.
- Modified like below, you can compare what changes I made...
//...
public partial class ErrorActions : System.Web.UI.Page { private XmlDocument _xmlDocument = new XmlDocument(); public ListBox FromListBox { get { return lstFrom; } }
public ListBox AbortListBox
{
get
{
return lstToAbort;
}
}
public ListBox ClearingListBox
{
get
{
return lstToClearing;
}
}
protected void Page_Load(object sender, EventArgs e)
{
Page.ClientScript.RegisterClientScriptInclude("listboxjs", "/JavaScripts/listbox.js");
if (!IsPostBack)
{
string movejs = "move('{0}','{1}','{2}')";
string unselectjs = "unselect('{0}')";
lstFrom.Attributes["onclick"] = String.Format(unselectjs, lstToAbort.ClientID);
lstFrom.Attributes["onclick"] = String.Format(unselectjs, lstToClearing.ClientID);
lstToAbort.Attributes["onclick"] = String.Format(unselectjs, lstFrom.ClientID);
lstToAbort.Attributes["onclick"] = String.Format(unselectjs, lstToClearing.ClientID);
lstToClearing.Attributes["onclick"] = String.Format(unselectjs, lstFrom.ClientID);
lstToClearing.Attributes["onclick"] = String.Format(unselectjs, lstToAbort.ClientID);
btnToAbort.Attributes["onclick"] = String.Format(movejs, lstFrom.ClientID, lstToAbort.ClientID, hdnDropdownsAbort.ClientID);
btnFromAbort.Attributes["onclick"] = String.Format(movejs, lstToAbort.ClientID, lstFrom.ClientID, hdnDropdownsAbort.ClientID);
btnToClearing.Attributes["onclick"] = String.Format(movejs, lstFrom.ClientID, lstToClearing.ClientID, hdnDropdownsClearing.ClientID);
btnFromClearing.Attributes["onclick"] = String.Format(movejs, lstToClearing.ClientID, lstFrom.ClientID, hdnDropdownsClearing.ClientID);
}
else
{
//if (!(String.IsNullOrEmpty(hdnDropdowns.Value)))
//{
// PopulateListBoxes();
//}
if (!(String.IsNullOrEmpty(hdnDropdownsAbort.Value)))
{
PopulateAbortListBox();
}
if (!(String.IsNullOrEmpty(hdnDropdownsClearing.Value)))
{
PopulateClearingListBox();
}
}
}
private void PopulateListBox(ListBox listBox)
{
listBox.Items.Clear();
XmlNodeList nodes = _xmlDocument.SelectNodes("listboxes/" + listBox.ClientID + "/option");
foreach (XmlNode node in nodes)
{
listBox.Items.Add(new ListItem(node["key"].InnerText, node["value"].InnerText));
}
}
//private void PopulateListBoxes()
//{
// _xmlDocument.LoadXml(HttpUtility.UrlDecode(hdnDropdownsAbort.Value));
// //PopulateListBox(lstFrom);
// PopulateListBox(lstToAbort);
// PopulateListBox(lstToClearing);
//}
private void PopulateAbortListBox()
{
_xmlDocument.LoadXml(HttpUtility.UrlDecode(hdnDropdownsAbort.Value));
PopulateListBox(lstToAbort);
}
private void PopulateClearingListBox()
{
_xmlDocument.LoadXml(HttpUtility.UrlDecode(hdnDropdownsClearing.Value));
PopulateListBox(lstToClearing);
}
protected void btnDoIt_Click(object sender, EventArgs e)
{
MissionErrorCodeDB db = new MissionErrorCodeDB();
db.DeleteErrorCodeActions(ErrorAction.AbortMission);
db.DeleteErrorCodeActions(ErrorAction.GoToClearingStation);
foreach (ListItem item in lstToAbort.Items)
{
db.AddErrorCodeAction(Convert.ToInt32(item.Value), ErrorAction.AbortMission);
}
foreach (ListItem item in lstToClearing.Items)
{
db.AddErrorCodeAction(Convert.ToInt32(item.Value), ErrorAction.GoToClearingStation);
}
}
protected override void OnPreRender(EventArgs e)
{
MissionErrorCodeDB db = new MissionErrorCodeDB();
List<MissionErrorCode> aborts = db.GetAll(ErrorAction.AbortMission);
List<MissionErrorCode> clearing = db.GetAll(ErrorAction.GoToClearingStation);
List<MissionErrorCode> all = db.GetAll();
all.RemoveAll(delegate(MissionErrorCode mec)
{
foreach (MissionErrorCode item in aborts)
{
if( mec.ErrorCode == item.ErrorCode )
return true;
}
return false;
});
all.RemoveAll(delegate(MissionErrorCode mec)
{
foreach (MissionErrorCode item in clearing)
{
if (mec.ErrorCode == item.ErrorCode)
return true;
}
return false;
});
populateBoxFromDatabase(AbortListBox, aborts);
populateBoxFromDatabase(ClearingListBox, clearing);
populateBoxFromDatabase(FromListBox, all);
base.OnPreRender(e);
}
private void populateBoxFromDatabase(ListBox listBox, List<MissionErrorCode> errorCodes)
{
string text;
int textLength = 46;
listBox.Items.Clear();
foreach (MissionErrorCode item in errorCodes)
{
if (item.ErrorText.Length < textLength)
{
text = item.ErrorCode + " - " + item.ErrorText;
}
else
{
text = item.ErrorCode + " - " + item.ErrorText.Substring(0, textLength - 1) + "...";
}
listBox.Items.Add(new ListItem(text, item.ErrorCode.ToString()));
}
}
}
//...
To avoid big chunks of Javascript I suggest you to do the work with a couple a Ajax calls
- Add several buttons next to the item (Already have it, Want it, etc). Each button should call a page in the server that sets the item to the correct category.
- In the success callback, call other ajax function that refresh only the category list affected.
Use jQuery and you will see that making the calls are pretty simple.
Good luck.
Well, nobody seems to want to do my work for me, so here's what we ended up doing. (It's not entirely done yet; when it is, I might post the code just for completeness' sake.)
We've taken the plunge and downloaded JQuery, specifically the JQuery UI "Sortable" functions. Like I said, the main dropdown is now a pseudo-listbox, i.e. a ul
with restricted height and overflow:auto
. Each item has five radio buttons next to it, which are hidden with Javascript. (That is, if Javascript isn't available, the user just sees a list with radio buttons.) When you drag items from the main list into the (script-generated) category lists, the appropriate radio button is marked. There will also be some code that runs on page load to move already-marked options from the main list to the appropriate category (i.e. for editing capability).
精彩评论