4

I have a series options a user can choose, which I am able to track via an observable array. The array itself is fed from a Model which I bring in to knockout via the Knockout Mapping extension (ko.mapping.fromJS). Everything is working fine.

Explaining this will get a but wordy and may cause more confusion to see the diagram below:

Situation

Basically:

  • I have a web entry form (its a configurator of sorts)
  • The item list is pretty big, I'd say 10 or so possible items can be added to a configuration
  • When a user adds an item, I push a default "Item A" into the Array bound to the options and it is rendered just fine.
  • What I am attempting to do is remove Item A from being selectable after it has been added once. If it is deleted, it should be able to be re-added
  • The way all of this is happening is via KO observables - one to track the available options, and another to track the "Selected" options. As I said, everything is working fine, and I'm trying to tweak it based on a request.

Initially - I was thinking - I would just let the users add duplicates and handle dupes via validation - if this is the only option I'll likely fall back to it.

I discovered "Post-processing the generated options" but the example provided declares the array in-line, and I'm not sure how I can attach this type of call back to an observable array I auto map using the mapping extension.

In a nutshell, I'm wondering if anyone has an idea on how to disable a previous selection (remember ALL selections are in one observable array, and the SELECTED ones are in another one) - or whether this is not possible given the source of my data. So in the hot pink selected annotation in the image - I'd ideally like only "Item B and Item C" to show up - but if ITEM A can be disabled that would work too.

I don't know if jQuery manipulation of the DOM would be viable? It would have to occur after databinding, and may get messy.

Depending on the answer here - my next screen has TWO cascading dropdowns and I was thinking of applying this same unique selection approach - but to a combination.

Some code(simplified to protect the guilty)

public class ItemsModel
{
  public List<ItemTypes> ItemTypes{ get; set; }
  public List<SelectedItemTypes> SelectedItemTypes{ get; set; }
}

public class ItemTypes
{
   public int Id { get; set; }
   public string Description { get; set; }
}

public class SelectedItemTypes
{
    public int Id { get; set; }
    public decimal Amount { get; set; }
}

**Javascript/HTML (again snipped for pertinent parts) **

      self.worksheetitems = ko.mapping.fromJS(@Html.Raw(Model.ToJson()))
      /* Adds an Item */
         self.addItem= function () {
                self.worksheetitems
               .SelectedItemTypes.push({ 'Id': ko.observable(), 
               'Amount': ko.observable(0)});

Html Table that holds this stuff (notice the foreach through the selected items, and binding to the all items array):

    <!-- Items -->
<tbody data-bind=
      "visible: worksheetitems.SelectedItemTypes().length > 0, 
       foreach: worksheetitems.SelectedItemTypes">
          <tr>
              <td>
              <select data-bind=
                      "options: $root.worksheetitems.ItemTypes(), 
                      optionsText: 'Description', 
                      optionsValue: 'Id', value: Id"></select>
              </td>           
           <tr/>
    <!-- Snipped -->

    <button data-bind="click: $root.addItem">Add</button> 
    <!-- Add Another Item -->
Ta01
  • 31,040
  • 13
  • 70
  • 99
  • Well explained question, but it would help if you'd add some code to the question help us make a minimal reproducible scenario. What you want is certainly possible (without any jQuery) but the details will depend on your context. – Jeroen Aug 12 '13 at 19:33
  • sure I will add some code – Ta01 Aug 12 '13 at 19:33
  • I think you may be able to utilize the option to specify callbacks [for when mapping creates items](http://knockoutjs.com/documentation/plugins-mapping.html#customizing_object_construction_using_create) to tweak the generated observable. You could try to combine that with the [setOptionsDisable](http://knockoutjs.com/documentation/options-binding.html#note_2_postprocessing_the_generated_options) post processing you mentioned. Would that help? – Jeroen Aug 12 '13 at 20:28
  • @Jeroen thanks for your reply, let me try that – Ta01 Aug 12 '13 at 20:32

1 Answers1

1

Not sure if I understand correctly, but it sounds like you're looking for Computed Observables:

self.AvailableItemTypes = ko.computed(function() {
    var selectedTypeIds = this.SelectedItemTypes().map(function(el) {
        return el.Id; 
    });
    return this.ItemTypes().filter(function(el) {
        return selectedTypeIds.indexOf(el.Id) === -1;
    });
});
sroes
  • 14,663
  • 1
  • 53
  • 72
  • Well, I thought it worked, but some more tests I'm getting some strange behavior, I can see you are getting an array of selectedTypeIds and then you are filtering the ItemTypes - I'm not sure if that filter is right, shouldn't it be === 1 instead of -1, because you want the ItemTypes only that are not selected – Ta01 Aug 13 '13 at 18:01
  • Also, would indexOf return an array? – Ta01 Aug 13 '13 at 18:03
  • `indexOf` returns the index of the given element, if no element was found `-1` is returned. So that filter should return a new array with only the elements whos id is not in `selectedTypeIds` – sroes Aug 13 '13 at 18:32