2

I am trying to get four cascading dropdowns using knockout.js:

  1. Search Criteria
  2. Sub Criteria
  3. Value
  4. State

I was able to get the first cascade going (but not the others due to databinding issues) by using code from the following link:

http://blogs.msdn.com/b/thebeebs/archive/2011/12/01/price-calculator.aspx

The data for these dropdowns is being returned to my razor viewpage as an IEnumrable of SearchCriterion from an MVC view using ViewBag.CriteriaData variable. The code for my classes is as follows:

public class SearchCriterion
{
    public string Text { get; set; }

    public string Value { get; set; }

    public List<SubCriterion> SubCriteria { get; set; }
} 

public class SubCriterion
{
    public string SearchCriterionValue { get; set; }

    public string Text { get; set; }

    public string Value { get; set; }

    public List<ColumnValue> ColumnValues { get; set; }
}

public class ColumnValue
{
    public string SearchCriterionValue { get; set; }

    public string SubCriterionValue { get; set; }

    public string Text { get; set; }

    public string Value { get; set; }

    public IEnumerable<StateValue> StateValues { get; set; }
}

public class StateValue
{   
    public string SearchCriterionValue { get; set; }

    public string SubCriterionValue { get; set; }

    public string ColumnValue { get; set; }

    public IEnumerable<int> InputStateIds { get; set; }

    public IEnumerable<int> OutputStateIds { get; set; }

    public int SelectedInputStateId { get; set; }

    public int SelectedOutputStateId { get; set; }

    public string Text { get; set; }

    public string Value { get; set; }    
}

The issues I am facing are in the following portions of the .cshtml code:

  1. What do I specify in this template for the other two dropdowns. e.g. the third dropdown needs to be bound to ColumnValue.Value (ColumnValue is part of SubCriterion)

    <script id='criteriaRowTemplate' type='text/html'>
        <tr>
            <td><select  data-bind='options: criteriaData, optionsText: "Text", optionsCaption: "Search Criterion", value: SearchCriterion' /></td>
            <td><select data-bind='visible: SearchCriterion, options: SearchCriterion() ? SearchCriterion().SubCriteria : null, optionsText: "Text", optionsCaption: "Sub Criterion", value: SubCriterion' /></td>
            <td><select data-bind='visible: SubCriterion, options: SubCriterion() ? SubCriterion().ColumnValues : null, optionsText: "Text", optionsCaption: "Column Value", value: ColumnValue'/></td>
            <td><select data-bind='visible: ColumnValue, options: ColumnValue() ? ColumnValue().StateValues : null, optionsText: "Text", optionsCaption: "State", value: StateValue'/></td>                
            <td><button data-bind='click: function() { viewModel.removeLine($data) }'>Remove</button></td>
        </tr>
    </script>
    
  2. Is this correct?

    var CriteriaLine = function() {
    this.SearchCriterion = ko.observable();
    this.SubCriterion = ko.observable();
    this.ColumnValue = ko.observable();
    this.StateValue = ko.observable();     
    
    // Whenever the Search Criteria changes, reset the Sub Criteria selection
    this.SearchCriterion.subscribe(function() { this.SubCriterion(undefined); }.bind(this));
    this.SubCriterion.subscribe(function() { this.ColumnValue(undefined); }.bind(this));
    this.ColumnValue.subscribe(function() { this.StateValue(undefined); }.bind(this));
    

    };

  3. How do I map the complete C# object with the Javascript object? It works if we just have the first two dropdowns:

    // Create a Javascript object object with the same property names as the C# object
    var dataToSearch = $.map(this.lines(), function (line) { return line.StateValue() ? line.StateValue() : undefined; });
    
        var SearchObject = new function () {
                this.StateValues = dataToSearch;
        };
    
        // Convert the object to JSON
        var searchCriteria = JSON.stringify(SearchObject);
    
  4. Does anything need to change here for the binding?

    // Apply the data from the server to the variable
    
    var criteriaData = @Html.Raw(@Json.Encode(ViewBag.CriteriaData));
    
    var viewModel = new Criteria();
    
    ko.applyBindings(viewModel, document.getElementById("criteriaDiv"));
    

EDIT:

I am now able to populate the cascading dropdowns (updated code above). Now I have 4 columns, each column having one of the dropdowns. I also have 1...n number of rows being added dynamically by using Knockoutjs. So, the user can now select values from these dropdowns and add more rows of dropdowns if he wants. The only thing remaining is to return the values that the user selects for the dropdowns to the controller(point 3 above). I am not sure how I can do that. Any help would be appreciated.

EDIT 2:

Added working code for Item # 3 and modified the ColumnValue and StateValue classes.

Yasir
  • 1,595
  • 5
  • 23
  • 42
  • Your implementation looks scary since you have 4 separate drop downs, where each sub-drop down depends on the value of the parent (ie subcriteria depends on the value of criteria). So let's say you have 10 different criteria, and for each criteria, they can have 10 sub criteria, and each of those 10 values, and each of those 10 columns. That's 10 + 10^2 + 10^3 + 10^4 values, or a possible 11,110 combinations. Instead, you should get rid of the the List<> collections, and have each drop down fire an AJAX request to populate the child drop down. – Makotosan Feb 27 '12 at 22:02
  • I would be scared too if I had so many cases. But the dropdowns here don't just depend on the parent, they also depend on their grandparent. So total number of options is fairly limited. The search criteria dropdown will just have 6 values, the sub criteria 2-4 values at max, the value dropdown will have mostly 2-3 values but in some cases might have 10 or so values and the last dropdown will have just 2 values. I am passing all these back as a nested object based on my cache without any performance lags. So, I don't mind sending all the data if seomeone can help me with the binding syntax. – Yasir Feb 27 '12 at 22:29
  • OK, so from what I can tell, you are breaking down the C# data correctly into a JavaScript object. I use $.json.decode(someObject) (jQuery addon) but it's the same thing. As for the subscriptions and data changes, I'd go with something like I suggested below where you're actually binding to computed values. Then you can simplify your ViewModel rather than trying to keep track of everything by itself. – farina Feb 27 '12 at 22:32
  • Here is a very simple article on JSON Serialization and Deserialization. Personally I use the WCF style for all of my data transfer objects, then I just let .net do the magic. It's pretty much the same as this example...but hopefully this will get you started. http://blogs.msdn.com/b/rakkimk/archive/2009/01/30/asp-net-json-serialization-and-deserialization.aspx – farina Feb 29 '12 at 21:57

2 Answers2

0

I update the answer, Hope, it will help new Comers. Methods for Binding Hierarchical Dropdowns using Knockout JS in MVC

Here you can find the good example .

Sanjeev S
  • 626
  • 1
  • 8
  • 27
0

I'm not sure I fully understand your question, but I'm going to take a whack at it anyway :). I think you're looking for a way to "validate" if it is in fact time to allow the next drop down to be active?

If so, you could approach it from a standpoint of Computed Observables. Basically, you would bind each of your dropdowns to a computed value which is derived from the previous dependencies.

Let me write fiddle and I'll show you :)

OK, give this a shot...sorry for the delay...http://jsfiddle.net/farina/ZNBcM/3/

farina
  • 3,486
  • 6
  • 32
  • 44
  • Thanks farina for replying. I have now added specific questions and pieces of code that I am having an issue with. In general my issue is with the binding syntax for nested objects like the one I have. – Yasir Feb 27 '12 at 21:47
  • Ahh, just saw your response...let me check that out. – farina Feb 27 '12 at 21:55
  • I updated my example...hopefully that is more aligned with what you need. – farina Feb 27 '12 at 22:55
  • Did that help, or answer your question? I'm trying to keep an eye on this one cause I'm curious. – farina Feb 29 '12 at 17:20
  • Thanks for your answer. I was able to get the cascading dropdowns (and dynamic row addition and deletion) working by modifying the criteriaRowTemplate (have updated the code above). Now, all I need to do is send the data back to the controller action method to save it (i.e only point 3 is left). I might still restructure my code to match your example if I am unable to do this, but will like to first give a shot to just modifying my code instead of replacing it. – Yasir Feb 29 '12 at 21:28
  • Cool, good luck! I know I did some similar stuff with Knockout and after a short while I realized I wasn't using Knockout to it's potential. It's better to use Knockout for what it does best and sometimes restructure than to try to write it into your app for small gains :). – farina Feb 29 '12 at 21:52
  • Thanks @farina for all your help. I was able to get this working. While, my solution (updated in the question) is probably a little different from your solution, I will mark your response as the answer. – Yasir Jun 26 '12 at 20:24