Move options between multiple lists - javascript

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!), 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).

Related

How do I display a Dialog Box that Waits for User Input?

I'm accustomed to Windows Forms, but the standard MessageBox.Show() is not available in ASP.NET project. Can this be done in an ASP.NET WebForm project?
Say I have a couple of routines:
DatabaseRoutine();
GotoAnotherPageRoutine();
The DatabaseRoutine() makes a call to the database and has a TRY/CATCH block:
public void DatabaseRoutine()
{
try {
// code here that may fail
} catch (Exception error)
{
var scriptManager = Page.ClientScript;
var cstype = this.GetType();
var csname1 = "DatabaseRoutine";
if (!scriptManager.IsStartupScriptRegistered(cstype, csname1)) {
var script = String.Format("<script runat=\"server\">alert('{0}: {1}');</script>", csname1, message);
scriptManager.RegisterStartupScript(cstype, csname1, script, true);
}
}
}
The catch routine above would be extracted to an error handling method that could be called by other sections of the code that could have errors.
The issue currently is that the next routine GotoAnotherPageRoutine() is going to be called immediately after the ScriptManager calls the Javascript Alert, so it will not ever be displayed to the end-user.
What else is at our disposal to display a message to the end-user?
Adding the redirect URL to the Alert box is not recommended for a couple of reasons (the redirect is not always the same and the redirects are often nested in other decision-making tasks based on the database entries).
Maybe can you try with something like this
try {
DatabaseRoutine();
GotoAnotherPageRoutine();
} catch (Exception ex) { ShowModalError() ... }
public void DatabaseRoutine()
{
.. do some stuff without any try catch
}
or
try {
DatabaseRoutine();
GotoAnotherPageRoutine();
} catch (Exception ex) { ShowModalError() ... }
public void DatabaseRoutine()
{
try {
.. do some stuff
}
catch (Execpetion ex)
{
..log exception on db ..
throw ex // rethrow exception
}
}
for a better popup you can use ajaxcontroltoolkit and modalpopupextender control
So, can you setup and have text for the prompt from code behind (server side)?
Yes, you can. But you have to keep SEVERAL things in mind as to how this can (or will work).
First up, like humans breathing air? Well, the most important concept here is to keep in mind how a asp.net page "life cycle" works. Without just a BIT of considering of this issue, then most attempts at this will fail.
The next issue - and a HUGE one?
most web page code (client side) has now forced upon developers to NOT be allowed to write what we call blocking code. In other words, if you say popup a ajax popup, or a jQuery popup? They do NOT cause the code to HALT.
About the ONLY blocking option left in browsers of course is the JavaScript alert(), and of js prompt(); Both of these DO BLOCK (halt code), but after that? Then you have to wire up EXTRA code that runs WHEN the user hits ok, or cancel.
So, for REALLY quick and dirty? Say drop a asp.net button to delete somthing on a form.
With this :
<asp:Button ID="btnDialogTest"
OnClientClick="return askdelete();"
runat="server" Height="36px" Text="Prompt test" Width="100px" />
<script>
function askdelete() {
return confirm('delete this');
}
</script>
So, above will pop a dialog. And if the user hits cancel, then the button event code will not run. Above is REALLY short - and MUCH less then wireing up ajax toolkit dialog for a SUPER simple yes/no prompt.
And yes, I even use jQuery toast messages with text from the server. But lets stick to our goal here.
So, what about our server side prompt yes/no idea?
The most simple approach of course is thus to use option 1 or 2 above (alert() or prompt()). Since those two buttons DO BLOCK code, then we can do this for a prompt:
(but no server side supply of text).
If the user clicks ok, then our button code runs. If they click cancel, then the server side event code stub does NOT run.
However, the above would not get us server side supply of text.
So, how could we supply server side text, and then run code based on that choice?
Well, we could drop in this script function into the given page:
Well, say I have this server side button code:
Protected Sub btnServerCode_Click(sender As Object, e As EventArgs) Handles btnServerCode.Click
Dim sTitle As String = "This is a server side TITLE suppled prompt text"
Dim sOkButton As String = "btnOk"
Dim sCancelButton As String = "btnCancel"
Dim sBody As String = "this is SERVER text in the box"
Call MyDialog2("popdialog2", sTitle, sBody, sOkButton, sCancelButton)
End Sub
so, we dropped 3 buttons onto the form, we thus have this:
So, when I run the code, I get this:
So some suggests and tricks as to how the above works.
I dropped 3 buttons. The first button was the simple event - code behind that run above code.
I THEN dropped two more buttons. The "ok" one, and the "cancel" one.
So, we can't BLOCK the code or HALT the code in the server side code.
I mean, the user clicks a button - the web page travels up to server, THEN code behind runs, and THEN the page makes the trip down back to the browser, and THEN our dialog box displays. So this round trip process is important.
As for the dialog? Well in this case I used jQuery.UI.
You CAN do much the same if you want to use the ajaxtoolkit and their dialogs box. Both quite much can do the same thing. About the only difference is that the ajax ones tend to allow you to write LESS js. However, while I did use the ajaxtoolkit dialogs, I found the jQuery.ui ones better. And ONE BIG reason is that you can have the jQuery.ui dialog LOAD a whole new other pages for the content of the dialog. It is quite easy.
Note also how I included a text box, and a check box. When your button "ok" or "cancel" code runs, you have full use (code behind) of the values the user entered into that text box, and also full use of the value of the check box. Now one could just dump/drop/not have the check box and the text box - I just included them to so this is possible.
And just like the ajax toolkit, the idea here is you create a "div" with the popup content, and of course set style=none to hide.
So, the popup div specified in above - in the same web page looks like this:
<div id="popdialog2" runat="server" style="display:none">
<h2 id="popdialog2body" runat="server">My cool pop dialog</h2>
<asp:TextBox ID="TextBox2" runat="server"></asp:TextBox>
<br />
<asp:CheckBox ID="CheckBox1" runat="server" Text="A nice check box" />
</div>
Note how I put runat server tags - I wanted to be able to "replace" or "set" the "my cool pop dialog" text with anything else in the "h2" (larger heading) part.
Also, to save a LOT of work to wire up the events? Well as noted, I drop two plane jane asp buttons on the form. I then double click on the button, and thus write/wire up the code I want. For this sample, I had/have this:
Protected Sub btnOk_Click(sender As Object, e As EventArgs) Handles btnOk.Click
Debug.Print("ok button click")
End Sub
Protected Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
Debug.Print("cancel button click")
End Sub
And of course, often we might not want cancel. I could of course just do a js postback and pass up some values to be caught in the page-load event. But by dropping in buttons for what we want to occur for ok, or cancel, then we are quite much free (with greater ease) to wire up two events (one for ok, and the other for cancel).
Of course once I have those two buttons code working? Well, of course I don't need to display them - the dialog box "trick" I use here is to have the popped dialog box execute the "click" method of either button. So, in theory, then I should/would/could/will hide those two buttons with style=dispaly:none.
So, now our page has just this:
<asp:Button ID="btnServerCode" runat="server" Text="Server Prompt" />
<br />
<asp:Button ID="btnOk" runat="server" Text="Ok" Width="63px" ClientIDMode="Static"
style="display:none"/>
<asp:Button ID="btnCancel" runat="server" Text="Cancel" ClientIDMode="Static"
style="display:none" />
<br />
so that button "click" trick saves a LOT of js and having to wire up a event for the ok button, and the cancel button.
Note also that I did/do use clientIDMode=static. This just allows getElementById, or in this case the jQuery selector to with ease to pick up the specified control. If you don't, then you have to tweak the $ selector code in above.
The jScript routine I use is this:
function myaskcool(myDiv, sPrompt, sOk, sCancel) {
var mydiv = $(myDiv);
mydiv.dialog({
autoOpen: false, modal: true, title: sPrompt, width: '25%',
position: { my: 'top', at: 'top+150' },
buttons: {
'ok': function () {
vbtn = $(sOk);
vbtn.click();
},
'cancel': function () {
vbtn = $(sCancel);
vbtn.click();
}
}
});
// Open the dialog
mydiv.dialog('open');
}
So the above needs jQuery, and also needs jQuery.ui.
As noted, you could change the above to work with the toolkit dialogs. (you can pop a toolkit dialog with js - and I recommend you do this - Edit: what I mean here is to NOT use the options in toolkit to automatic pop the dialog based on a associated control - but just use a wee bit of js to pop that toolkit dialog).
A few more things:
The dialog is actually dismissed DUE TO THE POST BACK. If you going to run client side code as a result of the dialog, then BOTH the ok, and the cancel js has to dismiss the dialog. But, since we do a post back on either choice - then the dialog is dismissed for you.
In summary:
I do recommend jQuery.ui (and jQuery) for the dialogs.
You can consider the ajaxtool kit dialog (both jQuery.ui and toolkit do operate on a "div" that you place in the page.
I do suggest the button trick.
you can't HALT code to wait for a dialog. So of course our super long time coding habit of:
if msgbox("do you want to delete") = vb.ok then
bla bla bla
Now has to be broken up into two (or 3 if you want cancel code)
Thus:
code will pop the dialog
user ok = run some button stub code - button clicked by js
user cancel = run some button stub code - button clicked by js
So, we wind up in place of that loveable and simple msgbox command?
Well, you now have to write at least one more event stub for the ok.
But, by using the "click()" method trick above, then we at least don't have to wire up complex ajax calls, and we get a button click event (and nice code behind stub) to run for the above approach.
Also keep in mind that when we inject the js into the web page, it BETTER be in most cases the LAST code that runs in the code behind - so right before the sub exit.
So our LAST routine is this one:
Dim sTitle As String = "This is a server side TITLE suppled prompt text"
Dim sOkButton As String = "btnOk"
Dim sCancelButton As String = "btnCancel"
Dim sBody As String = "this is SERVER text in the box"
Call MyDialog2("popdialog2", sTitle, sBody, sOkButton, sCancelButton)
And call MyDialog2 is the code that "injects" the js to then call our script above in the page:
That code is thus this:
Sub MyDialog2(sDiv As String, sTitle As String, sBody As String, sOk As String, sCancel As String)
Me.popdialog2body.InnerText = sBody
Dim jScript = "myaskcool('##Div','#Title','##Ok','##Cancel');"
jScript = jScript.Replace("#Div", sDiv)
jScript = jScript.Replace("#Title", sTitle)
jScript = jScript.Replace("#Ok", sOk)
jScript = jScript.Replace("#Cancel", sCancel)
ScriptManager.RegisterStartupScript(Me.Page, Me.GetType(), "mycoolasker", jScript, True)
End Sub
Now of course, we could add to above routine the js "myaskcool" to above.
This would then mean we don't have to bother "adding" that myaskcool() js routine to each page.
As noted, above makes use of jQuery AND ALSO jQuery.UI for the dialog prompt.
And as noted, in that hidden div, you can add check box, text box or whatever. Since the ok or cancel does a post back (the click() trick), then all of the values inside that div are 100% able to be freely used by the code behind. So, check box, text box, combo box, or whaterver can be dropped into that dialog.
ONE BIG rule however:
You can't have those controls inside of that dialog do a postback. Well, ok you can, but such postbacks WILL dismiss the dialog. But, say in the case of a dropdown, radio button list, or whatever? Often if you set auto-postback "true", then it often rather nice to just popup that dialog - let the user select the combo box, and BECUASE the dropdown/combo has auto-post back, then you don't care about the ok, cancel buttons - but just want a user selection and the "all easy" important post-back + code behind stub for that control to run. So you can have post backs in the "div" controls - but just keep in mind if you do that - then the dialog will be dismissed.
For now, I have found a way to insert the System.Windows.Forms.MessageBox.
public void DebugLogHandler(String module, String method, String message) {
if (!String.IsNullOrEmpty(message)) {
var lc = message.ToLower();
if (-1 < lc.IndexOf("error")) {
var scriptManager = Page.ClientScript;
var cstype = this.GetType();
var csname1 = String.Format("{0}.{1}", module, method);
if (MessageBox.Show(message, csname1, MessageBoxButtons.OK) != DialogResult.OK) {
if (!scriptManager.IsStartupScriptRegistered(cstype, csname1)) {
var script = String.Format("<script runat=\"server\">alert('{0}: {1}');</script>", csname1, message);
scriptManager.RegisterStartupScript(cstype, csname1, script, true);
}
}
}
}
}
If anyone comes up with something better, please post up.
Here is the hack I did, and it works!
In the Business Objects solution of my project, I created a Message Handling delegate and put an instance of that delegate in my class:
namespace BusinessObjects.Utilities
{
public delegate void MessageHandler(String module, String method, String message);
public class ExtensionLogHelper
{
public static MessageHandler OnMessage;
public static void LogDetails(Guid extensionLogGuid, bool IsSuccess, string logDetails)
{
// ...
// ... other code that logs to the database here
// ...
if (OnMessage != null) {
if (!String.IsNullOrEmpty(logDetails)) {
var logGuid = String.Format("{0}", extensionLogGuid);
OnMessage(logGuid, "BusinessObjects.Utilities.AddExtensionLogDetails", logDetails);
}
}
}
}
}
The methods are static, so as long as the OnMessage handler is set, messages will pump across.
In the Master.Page code-behind, I added:
a static instance of my Master.Page,
a static System.Collections.Queue,
assigned the MessageHandler OnMessage to my static Instance,
created a method to add new items to the Queue, and
created a way to display those messages.
Here are the basic requirements:
namespace Web.Master
{
public partial class MainLayout : System.Web.UI.MasterPage
{
public static MainLayout Instance;
private static Queue<PopMessage> queue;
protected void Page_Load(object sender, EventArgs e)
{
if (Instance == null) {
Instance = (MainLayout)Page.Master;
// a bug here. It only seems to fire once, not if the page is refreshed
Page.LoadComplete += new EventHandler(Page_LoadComplete);
// this is where the Master.Page is linked to the Debug Logger:
BusinessObjects.Utilities.ExtensionLogHelper.OnMessage = Instance.DebugLogHandler;
if (queue == null) {
queue = new Queue<PopMessage>();
}
}
// manually call this until I can get the Page.LoadComplete to fire it.
ProcessQueue();
}
As soon as I can get my Page.LoadComplete to set and fire consistently, I'll remove that last line of the Page_Load event. For details about that issue, I have it as a current question here:
Why does Page.LoadComplete throw HttpUnhandledException?
This is the public event that is called by my static Master.Page Instance:
public void DebugLogHandler(String module, String method, String message) {
queue.Enqueue(new PopMessage() {
Heading = module, MoreInfo = method, Details = message
});
}
protected void Page_LoadComplete(object sender, EventArgs e) {
ProcessQueue();
}
// remove messages from the queue, count any errors, and display to User
private void ProcessQueue() {
var errors = 0;
PopMessage msg = null;
do {
if (queue.Dequeue(ref msg)) {
if (msg.IsError) {
errors++;
}
}
} while (msg != null);
if (0 < errors) {
var responseMsg = String.Format("<script language='javascript'>alert('There were {0} error(s). Go to [Settings] > [System] > [Logs] for details.')</script>", errors);
Response.Write(responseMsg);
}
}
This is the class I use to save messages in my Queue:
class PopMessage {
public PopMessage() { }
public String Heading { get; set; }
public String MoreInfo { get; set; }
public String Details { get; set; }
public bool IsError
{
get
{
if (!String.IsNullOrEmpty(Details)) {
var lc = Details.ToLower();
if (-1 < lc.IndexOf("error")) {
return true;
}
}
return false;
}
}
public override string ToString() {
var result = IsError ? "Error: " : String.Empty;
if (!String.IsNullOrEmpty(Heading)) {
result += Heading;
} else {
result += "No Heading";
}
return result;
}
}
}
}
I can call several routines, load DataGrids, edit items in the GridViewRows, send all log messages to the Master.Page, and they can be handled as the last method of the Page_Load event.
This may not be the best architecture, but it provides a solution to let the customer know that errors exist without having to redesign a 10-year-old website.

Framework7 Single Searchbar for multiple Virtual Lists

The Task
Picture the typical search-bar and list of results. With framework7 in my Cordova app we can of course implement this easily even with a virtual list where elements aren't all being rendered. But what I'm trying to implement is a search-bar which will effect two virtual lists. The reason for this is as part of the app I'm creating there will be a list for new and refurbished equipment. Each list is on a different slider tab on the same page, so only one list will be seen at any one moment. But I need to use a singular search-bar to filter both.
What I've done so far
Currently the slider with two separate virtual lists is set up, the search bar is initiated. Is there any good way to allow the searchbar to apply to both lists without going crazy with the customSearch true parameter. If that is the only option how would I keep the default methods for filtering lists, I simply want to apply it twice.
I've also considered creating a second searchbar with display:none and having it copy input over from the seen searchbar. Then hidden searchbar could apply to one list while the seen one would apply to the other. But that would be really hacky and not neat at all.
Sorry If this is a bit unclear, I'm not sure how best to approach the challenge, thanks for any help
I initialise my two virtual lists, I make the search function accessable from outside of that. Then I initalise my searchbar with customSearch true, on search I use my virtual list (vl) array and search them individually using the filter and query functions available. It was a bit of a pain but this works perfectly fine. The vl[1] in this example is just a copy of vl[0] since I haven't actually set it up yet.
function virtualSearchAll(query,items){//search query
var foundItems = [];
for (var i = 0; i < items.length; i++) {
// Check if title contains query string
if (items[i].internal_descriptn.toLowerCase().indexOf(query.toLowerCase().trim()) >= 0) foundItems.push(i);
}
// Return array with indexes of matched items
return foundItems;
}
var vl = [];
vl[0] = myApp.virtualList('.vl0', {//initialise new products
items: selectProd,
template: '<li data-value="{{model_id}}" class="item-content"><div class="item-inner"><div class="item-title list-title">{{internal_descriptn}}</div></div></li>',
searchAll: function(query,items) {
return virtualSearchAll(query, items);
}
});
vl[1] = myApp.virtualList('.vl1', {//initialise test/referb products
items: [{internal_descriptn:"test desc",model_id:"wehayy"}],
template: '<li data-value="{{model_id}}" class="item-content"><div class="item-inner"><div class="item-title list-title">{{internal_descriptn}}</div></div></li>',
searchAll: function(query,items) {
return virtualSearchAll(query, items);
}
});
var mySearchbar = myApp.searchbar('.searchbar', {
customSearch: true,
searchIn: '.item-title',
onSearch: function(s) {
previousQuery = s.query;
for (let n in vl) {
let vlItems = virtualSearchAll(s.query,vl[n].items);
vl[n].filterItems(vlItems);
if(vlItems.length === 0) {//if the search has no results then show no results element else hide it
$('.vl'+n+'-not-found').show();
} else {
$('.vl'+n+'-not-found').hide();
}
}
highlightRows();
}
});
I should add that highlightRows() is part of a different functionality which needs to be refreshed on every search. You can ignore that

How to make selection on drop down list (DynamicForm) using JavaScript in SmartClient?

Here is a drop down list in SmartClient: http://www.smartclient.com/#dropdownGrid.
I want to make a selection using JavaScript. Like, I run some JavaScript in console, and the drop list will select a specific item.
I did some research, found a code snap to do this (the code is in Java, but I think there should be similar functions in JavaScript):
Record rec = perdomainGrid.getRecordList().find("domaine_id", domaine_id);
perdomainGrid.selectSingleRecord(rec);
If I want to make selection, first I need to obtain perdomainGrid object. In my above giving link, the drop down list id in GWT is exampleForm (can be seen in dropDownGrid.js tab). I try to get the object by:
var form = isc.DynamicForm.getById("exampleForm");
form does exist, but there is no getRecordList() function on it, there is selectSingleRecord() function on it though.
I try to check form's class by form.className, its value is normal. I don't know what does that mean.
I'm kind of confused now. Could somebody help me on this?
isc_SelectItem_5 has a function called pickValue(), it takes one parameter SKU. This function can be used to select item.
var itemName = "Letter Tray Front Load Tenex 200 Class Blk #23001";
var data = isc_SelectItem_5.optionDataSource.cacheData;
var targetSKU = data.find(function(e) {
if (e.itemName == itemName) {
return e;
}
}).SKU;
isc_SelectItem_5.pickValue(targetSKU);

Cannot switch between classes with jQuery

I've got a problem with switching between element classes - probably sth stupid, but I couldn't find the answer.
In my system I display a list of items. Now I want to be able to promote items, so that they appear at the top of the list. I created some backend infrastructure which works ok and added things to my frontend: a star (a span with star bg) next to every item's title and a jQuery script which is supposed to:
listen to 'click' event - when I click on a star
get some data- attributes from the span
post them to my controller
the controller checks if I'm allowed to promote items and replies 'true' or 'false'
if 'true' then I switch between 'gold-star' and 'silver-star' classes of the item
For some reason the classes don't switch - only when I refresh the page I can see the effect. I tried debugging with Firebug - it gets to the toggle line, but then nothing happens.
Here's the code:
<span class="silver-star promote">
$(".promote").bind("click", function() {
var $this = $(this);
var itemId = $this.attr("data-id"),
isPromoted = true;
if ($this.hasClass("gold-star")) {
isPromoted = false;
}
$.post('/promoteitems', { itemId: itemId, isPromoted: isPromoted }, function(allowPromotion) {
if (allowPromotion == true) {
$this.toggleClass("silver-star").toggleClass("gold-star");
}
});
});
Thanks in advance!
When you are getting a response back it might not recognise it as a boolean simple test would be to check response as string
From your comment on the question:
...the allowPromotion value is 'True' (with a capital T)...
That tell us it's a string, not a boolean. You don't want to just do if (allowPromotion), because that will toggle the classes even if you get back "False".
Instead:
if (allowPromotion == "True") { // Note the quotes and capital T
// ...toggle the classes
}
Or if you want to allow for possibly getting back something with a lower-case T at some point in the future:
if (/^\s*true\s*$/i.test(allowPromotion)) {
// ...toggle the classes
}
That's over-engineering it a bit (it'll work with "True", "true", " True " [note the spaces], and even an actual boolean true)...

Submitting form/getting HTML with JavaScript without iframe?

Context:
I work a student job transcribing paper reports in a webapp. It's old and we unfortunately can't change the source nor directly run a DB query.
It only checks if the unique ID exists once you submit the entire form, and you can't submit it unless it's entirely filled. Needless to say, it's a huge waste of time as you often transcribe the whole thing only to realise it's a duplicate.
Objective:
I made the userscript below that launches a search the search on the onblur of the unique ID's input(noReferenceDeclarant), checks if there are any matches (rows) and returns accordingly. Runs with Greasemonkey. The search form is in another page on the same domain. The search form does not take any URL arguments.
Can this be done without using an iframe (AJAX perhaps?)
This is a tool for my own productivity & to learn JS at the same time. As I'm still very much a beginner, any tips to make that code cleaner are welcome.
//Adding function to input's blur event
$(document).on ("blur", "#noReferenceDeclarant", isRefNumberExists);
//Vars
var noReferenceDeclarant = '';
var loadCode = 0;
var $searchForm;
//Fonctions
function isRefNumberExists ()
{
noReferenceDeclarant = $('#noReferenceDeclarant').val();
loadCode = 0;
//Make sure there's data in the input before proceeding
if (noReferenceDeclarant)
{
//Build search iframe
$searchForm = $('<iframe />', {
name: 'searchWindow',
src: 'rechercherGriIntranet.do?methode=presenterRechercher',
id: 'searchWindow',
width: 0,
height: 0
}).appendTo('body');
$searchForm.load(searchRefNumber);
}
}
function searchRefNumber()
{
var isExists = false;
//Check which "load" it is to avoid submit loops
if (loadCode === 0)
{
loadCode = 1;
//Filling search form with search term
$(this.contentDocument).find('#noReference').val(noReferenceDeclarant);
//Set search form preferences
$(this.contentDocument).find('#typeRapportAss').prop('checked', false);
$(this.contentDocument).find('#typeRapportAS').prop('checked', false);
$(this.contentDocument).find('#typeRapportSI').prop('checked', true);
//Submit the form
$(this.contentDocument).find('form:first').submit();
}
else if (loadCode === 1)
{
loadCode = 2;
//See if there are any tr in the result table. If there are no results, there a thead but no tr.
var foundReports = $(this.contentDocument).find('.resultatRecherche tr').length;
if (foundReports > 0)
{
if (confirm('A report matching this ID already exists. Do you want to display it?'))
{
//Modal window loading the report in an iframe. Not done yet but that's fairly straightforward.
}
else
{
//Close and return to the form.
}
}
}
//Reset variables/clean ressources
delete $searchForm;
$('#dateRedactionRapport').focus();
}
On the whole I've seen far, far worse code.
Ajax could do it, but then you'd just have to put the AJAX response into the DOM (as an iframe, most likely).
In this instance, I'd keep the approach you have. I think it is the sanest.j
Without the full context, there may be a way to clean up the loadCode -- but what you have is pretty same and works. A lot of folks would call it a semaphore, but that is just an issue of terminology.
The only thing I"d really clean up is recommend not calling the jQuery object so often..
// Many folks recommend that jQuery variables be named $<something>
var $doc = $(this.contentDocument);
doc.find('#typeRapportAss').prop('checked', false);
$doc.find('#typeRapportAS').prop('checked', false);
$doc.find('#typeRapportSI').prop('checked', true);
If you wanted to play with jQuery data structures, you could make a 'config' object that looks like this:
var formValues = {
typeRapportAs: false,
typeRapportAS: false,
typeRapportSI: true
};
then iterate over that to (using for ... in with .hasOwnProperty).
Not NEEDED for this project, what you are doing is fine, but it might make a learning exercise.

Categories