You are viewing limited content. For full access, please sign in.

Question

Question

How to prevent multiple selections of the same value in a drop-down in a table

asked on July 24, 2015

Hi,

I would like to disable a list of options in a drop-down based on what the user has previously selected. The Drop-down is in a table and is linked to a lookup rule. If the user already selected a stock code, then they should not be allowed to select it again in additional rows, thereby creating duplicate lines.

Below is the field called "Stock Code" with class name ".stockCode". This is the end result of my current script, which is also pasted below. As you can see, the first row did not perform the lookup as expected. I was able to select from the list, which then triggered my script. You cannot see very easily on the screenshot, but the entire Stock Code drop down list is now disabled for the first row after the script runs. If I add some rows, then those rows perform the lookup as expected and their drop-down options are not disabled at all. Clearly, something isn't right with my script and I could use some help getting it to work.

Here is my script:

$("select").change(function()
 {
     var tr = $(this).closest("tr");
        tr.find('.stockCode option').attr("disabled",""); //enable everything

     //collect the values from selected;
     var  arr = $.map
     (
        tr.find('.stockCode option:selected'), function(n)
         {
              return n.value;
          }
      );

    //disable elements
    tr.find('.stockCode option').filter(function()
    {

        return $.inArray($(this).val(),arr)>-1; //if value is in the array of selected values
     }).attr("disabled","disabled");   

});

I got this script from here: http://stackoverflow.com/questions/27490401/prevent-multiple-selections-of-same-value-for-each-table-row

Although the script appears to work if I plug it into JSFiddle to test, I cannot modify it to get it to work on my own table in Forms.

Any ideas?

0 0

Replies

replied on July 25, 2015 Show version history

Hi Sheldon,

There are a variety of issues with the code you found on StackOverflow. Put simply, it's designed for static tables where the entire table gets generated on the server and then is served to the client, whereas Laserfiche Forms has dynamic tables where users can add more rows to the table. Every row that is added gets a "fresh" dropdown field, whose values will not be initially disabled like you would want it to.

This is also why the code you have works only for the first row. When you do:

$('select').change()

 

The change event handler gets attached only to the dropdown fields that are currently on the form. New dropdown fields, i.e. those added by the user, don't get the event handler attached.

JQuery provides a mechanism to attach event handlers to dynamic elements using a method called delegation. The trick is to attach the handler to a static parent element. For example, if your table id is q5, you can do this:

$('#q5').on('change', 'select', function() {
    ...
});

 

Since the table itself is static, it delegates the event to its children elements, even if those elements are added dynamically.

The reason your entire dropdown field is getting disabled is because of this line:

tr.find('.stockCode option').attr("disabled","");

 

If you want to enable a field, you need to explicitly set the disabled attribute to false:

tr.find('.stockCode option').attr("disabled","false");

 

Lastly, the reason your lookup doesn't work is because, as per W3C's HTML form specification, disabled fields don't get their values submitted. This prevents lookups from being performed. There are various workarounds for this, but for Laserfiche Forms, the easiest is to add a short (i.e. 10 ms) delay before disabling the field:

setTimeout(function() {
   $("select option").filter(function() {
     return $.inArray($(this).val(),arr)>-1;
   }).attr("disabled",true);
 }, 10);

 

In any case, even with the above changes, that code snippet won't work well:

  1. Dropdown fields in newly added rows have all values initially enabled, allowing the user to select them.
  2. Removing a table row does not enable that row's selection in other rows' dropdowns.

I wrote an alternative. See below:

$(document).ready(function() {
    
  var masterList = [];
  var selectedList = [];
  
  //this function taken from http://stackoverflow.com/questions/7837456/comparing-two-arrays-in-javascript
  Array.prototype.equals = function (array) {
    // if the other array is a falsy value, return
    if (!array)
        return false;

    // compare lengths - can save a lot of time 
    if (this.length != array.length)
        return false;

    for (var i = 0, l=this.length; i < l; i++) {
        // Check if we have nested arrays
        if (this[i] instanceof Array && array[i] instanceof Array) {
            // recurse into the nested arrays
            if (!this[i].equals(array[i]))
                return false;       
        }           
        else if (this[i] != array[i]) { 
            // Warning - two different object instances will never be equal: {x:20} != {x:20}
            return false;   
        }           
    }       
    return true;
  }  
  
  function createMasterList() {
    masterList = [];
    $('#Field16\\(1\\)').children('option').each(function() {
      masterList.push($(this).val());
    });
    masterList.shift(); //remove blank value
  }
  
  createMasterList(); //used to check if all dropdown values have been selected
  
  function updateSelectedList() {
    selectedList = [];
    var selectedValue;
    $('.stockCode').each(function() {
      selectedValue = $(this).find('option:selected').text();
      if (selectedValue != "" && $.inArray(selectedValue, selectedList) == "-1") {
      	selectedList.push(selectedValue);
      }
    });
  }
  
  //disable the dropdown items that have already been selected
  function disableAlreadySelected() {
    $('option').each(function() {
      if ($.inArray(this.value, selectedList) != "-1") {
        $(this).attr("disabled", true);
      } else {
        $(this).attr("disabled", false);
      }
    });
  }
  
  //If all values have been selected, don't let the user add more rows
  function hideAddButtonIfDone() {
    masterList.sort();
    selectedList.sort();
    if (masterList.equals(selectedList)) {
      console.log("lists equal, hiding add button");
      $('#q5 .cf-table-add-row').hide();
    }
    else {
      console.log("lists not equal, showing add button");
      $('#q5 .cf-table-add-row').show();
    }
  }
  
  $('#q5').on('change', '.stockCode', function() {
    setTimeout(function() {
      updateSelectedList();
      disableAlreadySelected();
      hideAddButtonIfDone();
    }, 10);
  });
  
  //when a new table row is added, disable the dropdown options that have already been selected
  $('#q5 .cf-table-add-row').on('click', disableAlreadySelected);
  
  //when a table row is removed, update all dropdowns (the removed row's dropdown option will be re-enabled
  //in remaining dropdowns
  $('#q5').on('DOMNodeRemoved', '.kx-repeatable > tr', function() {
    updateSelectedList();
    disableAlreadySelected();
    hideAddButtonIfDone();
  });
  
});

Paste that into your form's JavaScript section and:

  1. Change #q5 on lines 69, 73, 77, 86 and 90 to the id of your table
  2. Change #Field16\\(1\\) on line 33 to the id of the very first dropdown on the form (the one that's there when the form loads). You'll need to escape the parentheses using two backslashes.

That should do the trick.

2 0
replied on July 30, 2015 Show version history

Hi Ege,

Thank you very much for this script. It works beautifully!

I do, however, have one new problem. If I submit the form, the .stockCode fields are all blank on the submitted form. So it does not seem to retain the field/variable data, it saves it as blank.

If you can please assist me with fixing that, then I can mark this answer.

Thanks and regards,

Sheldon

0 0
replied on August 2, 2015 Show version history

Gotcha. I forgot that disabled values don't get submitted. laugh

Do you need to be able to change stock codes after they are submitted? If not, try this solution...

First, add another column in your table right next to stock code, and give it class 'hiddenStockCode'. Its title should be the same.

Then, replace the snippet between lines 77 and 83 with this one:

$('#q5').on('change', '.stockCode', function() {
    var hiddenField = $(this).siblings('.hiddenStockCode').find('input');
    hiddenField.val($(this).find('option:selected').text());
    setTimeout(function() {
      updateSelectedList();
      disableAlreadySelected();
      hideAddButtonIfDone();
    }, 10);
  });

This copies the selected value in the dropdown to another field to the new field you just created. Note that it has to be a single line field in this case (but it's not too difficult to make it work with a dropdown field if you need that).

After that, create a second form and use field variables to make it look identical to the first form.

Now, on the first form you'll hide the hiddenStockCode field and it's column:

.hiddenStockCode {
  display: none;
}

#q3 {
  display: none;
}

q3 is the hidden stock code column's id on my form. Yours may be different.

On the second form, you'll hide the original stock code field and its column:

.stockCode {
  display: none;
}

#q1 {
  display: none;
}

What's happening here is that when the user selects a stock code, we're copying the value to the second, hidden stock code field before we disable the selection. When the submitted form is opened (either as a user task, or when stored in the repository), the hidden form is now displayed, but the dropdown is hidden.

This serves the purpose of disabling dropdown items while still allowing their values to be submitted.

Give it a try and let me know the result. :)

0 0
replied on August 3, 2015

Hi Ege,

I believe your solution will work, however, I'm using this data that was submitted to save into a SQL database, and this will obviously require changes to my WF design. I can do that.

Another challenge is that the users are currently able to make changes to the stock codes that were submitted, so there are lookup rules that are designed for the other forms that use the same data. "Disabling" or preventing this functionality would change the way the users currently work and I would like to try and avoid that if possible.

Is there another way we can approach this?

Thanks

Sheldon

0 0
replied on August 3, 2015

You can change the hidden stock code field to a dropdown that has the same options, and hook up an identical lookup rule to it. That should allow users to change the submitted stock codes.

If you also want them to not be able to select duplicate codes, you can modify the code I originally provided to also work with .hiddenStockCode.

0 0
replied on August 20, 2015 Show version history

Ege,

I'm attempting to do this as well, but not getting success in exporting the value to Laserfiche.  The main difference is that I'm using a collection, not table and I'm guessing that is where I'm going wrong.  Below is my code and the information is not passing to the repository (I'm trying to apply the 'job' field to a field).  

// Start Disable Duplicate Jobs Selected
$(document).ready(function() {
    
  var masterList = [];
  var selectedList = [];
  
  //this function taken from http://stackoverflow.com/questions/7837456/comparing-two-arrays-in-javascript
  Array.prototype.equals = function (array) {
    // if the other array is a falsy value, return
    if (!array)
        return false;

    // compare lengths - can save a lot of time 
    if (this.length != array.length)
        return false;

    for (var i = 0, l=this.length; i < l; i++) {
        // Check if we have nested arrays
        if (this[i] instanceof Array && array[i] instanceof Array) {
            // recurse into the nested arrays
            if (!this[i].equals(array[i]))
                return false;       
        }           
        else if (this[i] != array[i]) { 
            // Warning - two different object instances will never be equal: {x:20} != {x:20}
            return false;   
        }           
    }       
    return true;
  }  
  
  function createMasterList() {
    masterList = [];
    $('#Field5\\(1\\)').children('option').each(function() {
      masterList.push($(this).val());
    });
    masterList.shift(); //remove blank value
  }
  
  createMasterList(); //used to check if all dropdown values have been selected
  
  function updateSelectedList() {
    selectedList = [];
    var selectedValue;
    $('.job').each(function() {
      selectedValue = $(this).find('option:selected').text();
      if (selectedValue != "" && $.inArray(selectedValue, selectedList) == "-1") {
      	selectedList.push(selectedValue);
      }
    });
  }
  //disable the dropdown items that have already been selected
  function disableAlreadySelected() {
    $('option').each(function() {
      if ($.inArray(this.value, selectedList) != "-1") {
        $(this).attr("disabled", true);
      } else {
        $(this).attr("disabled", false);
      }
    });
  }
  
  //If all values have been selected, don't let the user add more rows
  function hideAddButtonIfDone() {
    masterList.sort();
    selectedList.sort();
    if (masterList.equals(selectedList)) {
      console.log("lists equal, hiding add button");
      $('#q3 .cf-collection-append').hide();
    }
    else {
      console.log("lists not equal, showing add button");
      $('#q3 .cf-collection-append').show();
    }
  }
  
$('#q3').on('change', '.job', function() {
    var hiddenField = $(this).siblings('.hiddenjob').find('input');
    hiddenField.val($(this).find('option:selected').text());
    setTimeout(function() {
      updateSelectedList();
      disableAlreadySelected();
      hideAddButtonIfDone();
    }, 10);
  });
  
  //when a new table row is added, disable the dropdown options that have already been selected
  $('#q3 .cf-collection-append').on('click', disableAlreadySelected);
  
  //when a table row is removed, update all dropdowns (the removed row's dropdown option will be re-enabled
  //in remaining dropdowns
  $('#q3').on('DOMNodeRemoved', '.kx-repeatable > tr', function() {
    updateSelectedList();
    disableAlreadySelected();
    hideAddButtonIfDone();
  });
  
});
// End Disable duplicate jobs selected

Thanks,

Nate

0 0
replied on October 1, 2015 Show version history

A little different take on the same solution. Little less code.

function setDropOptions() {
   		var selects=$('li[attr=vendorcontacts] td select.cf-medium.form-control'), svals = selects.find('option:disabled').prop('disabled',false).end()
   			.find('option:selected').map(function() {return this.value;}).get();
   		for(var i=0;i<svals.length;i++){
   			selects.find('option[value='+svals[i]+']').prop('disabled',true);
   		}
   	}

$(document).on('change click','li[attr=vendorcontacts] select.cf-medium.form-control,span.form-del-field', function(e) {
        	setDropOptions();
})

You may need to play around with the classnames to find your selects but it works.

1 0
replied on October 17, 2017

How could this be adapted to solve this problem? Thank you.

1 0
You are not allowed to follow up in this post.

Sign in to reply to this post.