1

There is a page with nested HTML select statements. Examples of how to bind selects in KnockoutJS assume the option values are in the view model. Here the options exist as HTML.

What syntax/functions would be used to bind the second select to the class name of the first select options so that they only appear in the second list when the corresponding class is selected? In other words, what would replace the space holder "whenStartAppGroupNSelected."

Markup :

<div class="apps">
    <select id="startapp" name="startapp" data-bind="value: startapp">
        <option value="value1" class="groupA">Some Val 1</option>
        <option value="value2" class="groupB">Some Val 2</option>
        <option value="value3" class="groupC">Some Val 3</option>
    </select>
</div>
<div class="pages">
     <select id="startpage" name="startpage" data-bind="value: startpage">
         <option data-bind="visible: whenStartAppGroupASelected" value="path1" class="groupA">Page 1</option>
         <option data-bind="visible: whenStartAppGroupBSelected" value="path2" class="groupB">Page 2</option>
         <option data-bind="visible: whenStartAppGroupCSelected" value="path3" class="groupC">Page 3</option>
         <option data-bind="visible: whenStartAppGroupCSelected" value="path4" class="groupC">Page 4</option>
     </select>
</div>

Model :

myModel = function() {
    var self = this;
    self.startapp = ko.observable("").extend({required: true});
    self.startpage = ko.observable("");
    self.selectedApp = ko.computed(function() {
        return self.startapp();
    });
};
0

1 Answer 1

2

I think what you have here is generally against the grain when it comes to knockout.js. Your view model(s) should be constructed in such a way that they provide an accurate representation of your data.

Instead, I would create a separate StartOption view model object to act as the "values" of the two drop-downs. Each StartOption would have an observable array for the sub-options, and then your main view model would have a list of StartOptions for the first-level objects, and each of those would have a list of the level-two items.

Something like this:

<div class="apps">
    <select id="startapp" name="startapp" 
        data-bind="options: startApps, optionsText: 'text', value: selectedStartApp, optionsCaption: 'Choose...'">
    </select>
</div>
<div class="pages" data-bind="with: selectedStartApp">
     <select id="startpage" name="startpage" 
         data-bind="options: subOptions, optionsText: 'text', value: $root.selectedStartPage">
     </select>
</div>

And then the JS:

var StartOption = function(value, text, options) {
    var self = this;
    self.value = ko.observable(value);
    self.text = ko.observable(text);
    self.subOptions = ko.observableArray(options || []);
};

var ViewModel = function() {
    var self = this; 

    self.startApps = ko.observableArray([
        new StartOption('value1', 'Some Val 1', [
            new StartOption('path1', 'Page 1')
        ]),
        new StartOption('value2', 'Some Val 2', [
            new StartOption('path2', 'Page 2')
        ]),
        new StartOption('value3', 'Some Val 3', [
            new StartOption('path3', 'Page 3'),
            new StartOption('path4', 'Page 4')
        ])
    ]);

    self.selectedStartApp = ko.observable();
    self.selectedStartPage = ko.observable();
};

ko.applyBindings(new ViewModel());

Working Demo of Above

Or perhaps you want to load the options from the server via AJAX, and your JSON response looks like:

var sampleData = [
    { 
        value: 'value1', 
        text: 'Some Val 1', 
        subOptions: [
            { value: 'path1', text: 'Page 1' }
        ]
    },
    { 
        value: 'value2', 
        text: 'Some Val 2', 
        subOptions: [
            { value: 'path2', text: 'Page 2' }
        ]
    },
    { 
        value: 'value3', 
        text: 'Some Val 3', 
        subOptions: [
            { value: 'path3', text: 'Page 3' },
            { value: 'path4', text: 'Page 4' }
         ]
     }
];

You could use the knockout-mapping plug-in to do some of the heavy lifting for you so you can simplify your view models:

var StartOption = function(data) {
    var self = this;
    // map the properties of the data object as observables
    // on this object; if we encounter "subOptions", make them
    // instances of this object as well
    ko.mapping.fromJS(data || {}, {
        'subOptions': {
            create: function(options) {
                return new StartOption(options.data);   
            }
        }
    }, self);
};

var ViewModel = function() {
    var self = this; 

    self.startApps = ko.observableArray();

    self.load = function(data) {
        self.startApps(ko.utils.arrayMap(data || [], function(option) {
            return new StartOption(option);
        }));
    };

    self.selectedStartApp = ko.observable();
    self.selectedStartPage = ko.observable();
};

var viewModel = new ViewModel();
viewModel.load(sampleData); 

ko.applyBindings(viewModel);

Working Demo of Above

Sign up to request clarification or add additional context in comments.

4 Comments

I can't thank you enough for pointing me in the correct direction. This is very instructional. The working examples are above and beyond.
You're welcome :) I'm nuts about knockout, so I had fun doing it.
I have tried this and i need to get the value of selectedStartApp as well, console.log(self.selectedStartPage()); works fine but console.log(self.selectedStartApp ()); brings back the StartOption object and i cant figure out how to retrieve it even though its in the Symbol(_latestValue): StartOption {value: ƒ, text: ƒ, subOptions: ƒ} subOptions: ƒ () text: ƒ () value: ƒ () S: {change: Array(0)} rc: 1 Symbol(_latestValue): "7970ecf2-3c99-49b0-914f-ab79083967bf" ...
In other words, value is nor being populated on the first dropdown, adding optionsValue: 'value' ReferenceError: give the following Unable to process binding "options: function(){return subOptions }" Message: subOptions is not defined

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.