Getting Started

Dependent Controls is a library for making dependent drop-downs quickly and easily. It works with controls when one list of data depends on another list. The child drop-down will be shown only when the value in the parent drop-down is selected.

The library is written in pure ES6 JavaScript and doesn't have any dependencies. It supports SELECTs, radio buttons, and any other custom HTML. Any CSS framework can be used for styling.


DropDown (SELECT) controls and Bootstrap CSS Framework

This basic example will demonstrate dependent SELECT dropdowns with the Bootstrap framework as CSS and the JavaScript object as a data source.

You can find other data source options here.


Get started with a minimal Bootstrap layout as described in Bootstrap 5 documentation:

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <title>Example</title>
  </head>
  <body>



    <!-- Bootstrap Bundle with Popper -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
  </body>
</html> 

You can use any CSS Framework or your custom HTML & CSS.



Add form element and three drop-down placeholders. Please note, that form should have data-jso-form attribute. All select elements should have data-jso-level attribute that defines nesting level. Also, each control should have a unique name.

<section class="py-5 container">
    <form action="/" method="get" autocomplete="off" data-jso-form>

        <select data-jso-level="1" 
                class="form-select m-2" 
                name="select-1"
                data-jso-disabled
                required></select>

        <select data-jso-level="2" 
                class="form-select m-2" 
                name="select-2"
                data-jso-disabled
                required></select>

        <select data-jso-level="3" 
                class="form-select m-2" 
                name="select-3"
                data-jso-disabled
                required></select>

        <div>
            <button type="submit" class="btn btn-primary my-2">Submit</button>
            <button type="reset" class="btn btn-secondary my-2">Reset</button>
        </div>
    </form>
</section>

Now define a template that describes the control's inner HTML. The template can contains macros {{value}}, {{text}}, {{id}}, and more.

<template id="option-template">
    <option value="{{value}}">{{text}}</option>
</template>

Place the <script> near the end of your pages, right before the closing </body> tag:

<script src="dependent-controls.min.js"></script>

Define sample data with three nested levels in the JavaScript object. This data can be declared directly on the page in a SCRIPT tag, but in this example, we'll use a separate JavaScript file, data.js.

var data = {
    "data": [
        {
            "value": "",
            "text": "Select Category"
        },
        {
            "value": "val-1",
            "text": "Value 1",
            "data": [
                {
                    "value": "",
                    "text": "Select Category"
                },
                {
                    "value": "val-1-1",
                    "text": "Value 1.1",
                    "data": [
                        {
                            "value": "",
                            "text": "Select Category"
                        },
                        {
                            "value": "val-1-1-1",
                            "text": "Value 1.1.1"
                        },
                        {
                            "value": "val-1-1-2",
                            "text": "Value 1.1.2"
                        }
                    ]
                },
                {
                    "value": "val-1-2",
                    "text": "Value 1.2",
                    "data": [
                        {
                            "value": "",
                            "text": "Select Category"
                        },
                        {
                            "value": "val-1-2-1",
                            "text": "Value 1.2.1"
                        },
                        {
                            "value": "val-1-2-2",
                            "text": "Value 1.2.2"
                        }
                    ]
                }
            ]
        },
        {
            "value": "val-2",
            "text": "Value 2",
            "data": [
                {
                    "value": "",
                    "text": "Select Category"
                },
                {
                    "value": "val-2-1",
                    "text": "Value 2.1",
                    "data": [
                        {
                            "value": "",
                            "text": "Select Category"
                        },
                        {
                            "value": "val-2-1-1",
                            "text": "Value 2.1.1"
                        },
                        {
                            "value": "val-2-1-2",
                            "text": "Value 2.1.2"
                        }
                    ]
                },
                {
                    "value": "val-2-2",
                    "text": "Value 2.2",
                    "data": [
                        {
                            "value": "",
                            "text": "Select Category"
                        },
                        {
                            "value": "val-2-2-1",
                            "text": "Value 2.2.1"
                        },
                        {
                            "value": "val-2-2-2",
                            "text": "Value 2.2.2"
                        }
                    ]
                }
            ]
        }
    ]
};

Add Dependent Controls library initialization inside the script tags:

jsoDControls({
    dataSource: data,
    template: '#option-template'
});

The final version:

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <title>Dependent Controls Demo Page</title>
  </head>
  <body>
    <section class="py-5 container">

        <form action="/" method="get" autocomplete="off" data-jso-form style="max-width: 500px; margin: auto;">

            <select data-jso-level="1" 
                    class="form-select m-2" 
                    name="select-1"
                    data-jso-disabled
                    required></select>

            <select data-jso-level="2" 
                    class="form-select m-2" 
                    name="select-2"
                    data-jso-disabled
                    required></select>

            <select data-jso-level="3" 
                    class="form-select m-2" 
                    name="select-3"
                    data-jso-disabled
                    required></select>

            <div class="text-center">
                <button type="submit" class="btn btn-primary my-2">Submit</button>
                <button type="reset" class="btn btn-secondary my-2">Reset</button>
            </div>
        </form>
    </section>

    <!-- form option template -->
    <template id="option-template">
        <option value="{{value}}">{{text}}</option>
    </template>

    <!-- Bootstrap Bundle with Popper -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>

    <!-- Dependent Controls Library -->
    <script src="dependent-controls.min.js"></script>
    <script src="data.js"></script>
    <script>
    jsoDControls({
        dataSource: data,
        template: '#option-template'
    });
    </script>
  </body>
</html> 

Radio button controls and Bootstrap CSS Framework

This example will demonstrate dependent radio button controls with the Bootstrap framework as CSS and the JSON file as a data source.

You can find other data source options here.


Get started with a minimal Bootstrap layout as described in Bootstrap 5 documentation:

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <title>Example</title>
  </head>
  <body>



    <!-- Bootstrap Bundle with Popper -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
  </body>
</html> 

You can use any CSS Framework or your custom HTML & CSS.



Add form element and three DIV placeholders. Please note, that form should have data-jso-form attribute. All placeholder elements should have data-jso-level attribute that defines nesting level. The data attribute data-jso-hide means that empty controls will be hidden.

<section class="py-5 container">
    <form action="/" method="get" autocomplete="off" data-jso-form>

        <!-- placeholders for radio buttons -->
        <div data-jso-level="1" class="border rounded mb-2 p-2" data-jso-hide></div>
        <div data-jso-level="2" class="border rounded mb-2 p-2" data-jso-hide></div>
        <div data-jso-level="3" class="border rounded mb-2 p-2" data-jso-hide></div>

        <div>
            <button type="submit" class="btn btn-primary my-2">Submit</button>
            <button type="reset" class="btn btn-secondary my-2">Reset</button>
        </div>
    </form>
</section>

Now define a template that describes the control's inner HTML. The template can contains macros {{value}}, {{text}}, {{id}}, and more.

<template id="radio-buttons-template">
    <div class="form-check form-check-inline">
        <input 
            class="form-check-input" 
            type="radio" 
            name="radio-{{level}}" 
            id="inline-radio-{{id}}" 
            value="{{value}}" 
            required
        />
        <label class="form-check-label" for="inline-radio-{{id}}">{{text}}</label>
    </div>
</template>

Place the <script> near the end of your pages, right before the closing </body> tag:

<script src="dependent-controls.min.js"></script>

Create a new data.json file with the following content:

{
    "data": [
        {
            "value": "val-1",
            "text": "Value 1",
            "data": [
                {
                    "value": "val-1-1",
                    "text": "Value 1.1",
                    "data": [
                        {
                            "value": "val-1-1-1",
                            "text": "Value 1.1.1"
                        },
                        {
                            "value": "val-1-1-2",
                            "text": "Value 1.1.2"
                        }
                    ]
                },
                {
                    "value": "val-1-2",
                    "text": "Value 1.2",
                    "data": [
                        {
                            "value": "val-1-2-1",
                            "text": "Value 1.2.1"
                        },
                        {
                            "value": "val-1-2-2",
                            "text": "Value 1.2.2"
                        }
                    ]
                }
            ]
        },
        {
            "value": "val-2",
            "text": "Value 2",
            "data": [
                {
                    "value": "val-2-1",
                    "text": "Value 2.1",
                    "data": [
                        {
                            "value": "val-2-1-1",
                            "text": "Value 2.1.1"
                        },
                        {
                            "value": "val-2-1-2",
                            "text": "Value 2.1.2"
                        }
                    ]
                },
                {
                    "value": "val-2-2",
                    "text": "Value 2.2",
                    "data": [
                        {
                            "value": "val-2-2-1",
                            "text": "Value 2.2.1"
                        },
                        {
                            "value": "val-2-2-2",
                            "text": "Value 2.2.2"
                        }
                    ]
                }
            ]
        }
    ]
}

Add Dependent Controls library initialization inside the script tags:

jsoDControls({
    dataSource: 'data.json',
    template: '#radio-buttons-template'
});

The final version:

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <title>Dependent Controls Demo Page</title>
  </head>
  <body>
    <section class="py-5 container">

        <form action="/" method="get" autocomplete="off" data-jso-form style="max-width: 500px; margin: auto;">

            <!-- placeholders for radio buttons -->
            <div data-jso-level="1" class="border rounded mb-2 p-2" data-jso-hide></div>
            <div data-jso-level="2" class="border rounded mb-2 p-2" data-jso-hide></div>
            <div data-jso-level="3" class="border rounded mb-2 p-2" data-jso-hide></div>

            <div class="text-center">
                <button type="submit" class="btn btn-primary my-2">Submit</button>
                <button type="reset" class="btn btn-secondary my-2">Reset</button>
            </div>
        </form>
    </section>

    <!-- form option template -->
    <template id="radio-buttons-template">
        <div class="form-check form-check-inline">
            <input 
                class="form-check-input" 
                type="radio" 
                name="radio-{{level}}" 
                id="inline-radio-{{id}}" 
                value="{{value}}" 
                required
            />
            <label class="form-check-label" for="inline-radio-{{id}}">{{text}}</label>
        </div>
    </template>

    <!-- Bootstrap Bundle with Popper -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>

    <!-- Dependent Controls Library -->
    <script src="dependent-controls.min.js"></script>
    <script>
    jsoDControls({
        dataSource: 'data.json',
        template: '#radio-buttons-template'
    });
    </script>
  </body>
</html> 

Switches, Bootstrap CSS, and JavaScript object as data source

This example will demonstrate dependent switches with the Bootstrap framework as CSS and the JavaScript object as a data source.

You can find other data source options here.


Get started with a minimal Bootstrap layout as described in Bootstrap 5 documentation:

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <title>Example</title>
  </head>
  <body>



    <!-- Bootstrap Bundle with Popper -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
  </body>
</html> 

You can use any CSS Framework or your custom HTML & CSS.



Add form element and three DIV placeholders. Please note, that form should have data-jso-form attribute. All placeholder elements should have data-jso-level attribute that defines nesting level. The data attribute data-jso-hide means that empty controls will be hidden.

<section class="py-5 container">
    <form action="/" method="get" autocomplete="off" data-jso-form>

        <!-- placeholder for level #1 -->
        <div 
            data-jso-level="1" 
            class="border rounded mb-2 p-2" 
            data-jso-hide></div>

        <!-- placeholder for level #2 -->
        <div    
            data-jso-level="2" 
            class="border rounded mb-2 p-2" 
            data-jso-hide></div>

        <!-- placeholder for level #3 -->    
        <div 
            data-jso-level="3" 
            class="border rounded mb-2 p-2" 
            data-jso-hide></div>

        <div>
            <button type="submit" class="btn btn-primary my-2">Submit</button>
            <button type="reset" class="btn btn-secondary my-2">Reset</button>
        </div>
    </form>
</section>

Now define a template that describes the control's inner HTML. The template can contains macros {{value}}, {{text}}, {{id}}, and more.

<template id="switch-template">
    <div class="form-check form-switch form-check-inline">
        <input 
            class="form-check-input" 
            type="radio" 
            name="switch-radio-{{level}}" 
            id="switch-radio-{{id}}" 
            value="{{value}}"
            required 
        />
        <label class="form-check-label" for="switch-radio-{{id}}">{{text}}</label>
    </div>
</template>

Place the <script> near the end of your pages, right before the closing </body> tag:

<script src="dependent-controls.min.js"></script>

Create a new data.js file with the following content:

var data = {
    "data": [
        {
            "value": "val-1",
            "text": "Value 1",
            "data": [
                {
                    "value": "val-1-1",
                    "text": "Value 1.1",
                    "data": [
                        {
                            "value": "val-1-1-1",
                            "text": "Value 1.1.1"
                        },
                        {
                            "value": "val-1-1-2",
                            "text": "Value 1.1.2"
                        }
                    ]
                },
                {
                    "value": "val-1-2",
                    "text": "Value 1.2",
                    "data": [
                        {
                            "value": "val-1-2-1",
                            "text": "Value 1.2.1"
                        },
                        {
                            "value": "val-1-2-2",
                            "text": "Value 1.2.2"
                        }
                    ]
                }
            ]
        },
        {
            "value": "val-2",
            "text": "Value 2",
            "data": [
                {
                    "value": "val-2-1",
                    "text": "Value 2.1",
                    "data": [
                        {
                            "value": "val-2-1-1",
                            "text": "Value 2.1.1"
                        },
                        {
                            "value": "val-2-1-2",
                            "text": "Value 2.1.2"
                        }
                    ]
                },
                {
                    "value": "val-2-2",
                    "text": "Value 2.2",
                    "data": [
                        {
                            "value": "val-2-2-1",
                            "text": "Value 2.2.1"
                        },
                        {
                            "value": "val-2-2-2",
                            "text": "Value 2.2.2"
                        }
                    ]
                }
            ]
        }
    ]
}

Add Dependent Controls library initialization inside the script tags:

jsoDControls({
    dataSource: data,
    template: '#switch-template'
});

The final version:

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <title>Dependent Controls Demo Page</title>
  </head>
  <body>
    <section class="py-5 container">

        <form action="/" method="get" autocomplete="off" data-jso-form style="max-width: 500px; margin: auto;">

            <!-- placeholders for radio buttons -->
            <div data-jso-level="1" class="border rounded mb-2 p-2" data-jso-hide></div>
            <div data-jso-level="2" class="border rounded mb-2 p-2" data-jso-hide></div>
            <div data-jso-level="3" class="border rounded mb-2 p-2" data-jso-hide></div>

            <div class="text-center">
                <button type="submit" class="btn btn-primary my-2">Submit</button>
                <button type="reset" class="btn btn-secondary my-2">Reset</button>
            </div>
        </form>
    </section>

    <!-- form option template -->
    <template id="switch-template">
        <div class="form-check form-switch form-check-inline">
            <input 
                class="form-check-input" 
                type="radio" 
                name="switch-radio-{{level}}" 
                id="switch-radio-{{id}}" 
                value="{{value}}"
                required 
            />
            <label class="form-check-label" for="switch-radio-{{id}}">{{text}}</label>
        </div>
    </template>

    <!-- Bootstrap Bundle with Popper -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>

    <!-- Dependent Controls Library -->
    <script src="dependent-controls.min.js"></script>
    <script src="data.js"></script>
    <script>
    jsoDControls({
        dataSource: data,
        template: '#switch-template'
    });
    </script>
  </body>
</html> 

Mixed Controls and Bootstrap CSS Framework

This example will demonstrate different control types on different nesting levels. The first level contains SELECT control, the second level has radio buttons, and the third level uses switches. In this example, we use nested templates.

You can find various data source options here.


Get started with a minimal Bootstrap layout as described in Bootstrap 5 documentation:

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <title>Example</title>
  </head>
  <body>



    <!-- Bootstrap Bundle with Popper -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
  </body>
</html> 

You can use any CSS Framework or your custom HTML & CSS.



Add form element and three placeholders. In this example we use nested templates:

<section class="py-5 container">
    <form action="/" method="get" autocomplete="off" data-jso-form>

        <!-- select placeholder -->
        <select data-jso-level="1" 
                class="form-select mb-2" 
                name="select-1"
                data-jso-hide
                required>
            <template>
                <option value="{{value}}">{{text}}</option>
            </template>
        </select>

        <!-- radio button placeholder -->
        <div data-jso-level="2" class="border rounded mb-2 p-2" data-jso-hide>
            <template>
                <div class="form-check form-check-inline">
                    <input 
                        class="form-check-input" 
                        type="radio" 
                        name="radio-{{level}}" 
                        id="inline-radio-{{id}}" 
                        value="{{value}}" 
                        required
                    />
                    <label class="form-check-label" for="inline-radio-{{id}}">{{text}}</label>
                </div>
            </template>
        </div>

        <!-- switch placeholder -->
        <div data-jso-level="3" class="border rounded mb-2 p-2" data-jso-hide>
            <template>
                <div class="form-check form-switch form-check-inline">
                    <input 
                        class="form-check-input" 
                        type="radio" 
                        name="switch-radio-{{level}}" 
                        id="switch-radio-{{id}}" 
                        value="{{value}}"
                        required 
                    />
                    <label class="form-check-label" for="switch-radio-{{id}}">{{text}}</label>
                </div>
            </template>
        </div>

        <div>
            <button type="submit" class="btn btn-primary my-2">Submit</button>
            <button type="reset" class="btn btn-secondary my-2">Reset</button>
        </div>
    </form>
</section>

Place the <script> near the end of your pages, right before the closing </body> tag:

<script src="dependent-controls.min.js"></script>

Create a new data.js file with the following content:

var data = {
    "data": [
        {
            "value": "",
            "text": "Select Category"
        },
        {
            "value": "val-1",
            "text": "Value 1",
            "data": [
                {
                    "value": "val-1-1",
                    "text": "Value 1.1",
                    "data": [
                        {
                            "value": "val-1-1-1",
                            "text": "Value 1.1.1"
                        },
                        {
                            "value": "val-1-1-2",
                            "text": "Value 1.1.2"
                        }
                    ]
                },
                {
                    "value": "val-1-2",
                    "text": "Value 1.2",
                    "data": [
                        {
                            "value": "val-1-2-1",
                            "text": "Value 1.2.1"
                        },
                        {
                            "value": "val-1-2-2",
                            "text": "Value 1.2.2"
                        }
                    ]
                }
            ]
        },
        {
            "value": "val-2",
            "text": "Value 2",
            "data": [
                {
                    "value": "val-2-1",
                    "text": "Value 2.1",
                    "data": [
                        {
                            "value": "val-2-1-1",
                            "text": "Value 2.1.1"
                        },
                        {
                            "value": "val-2-1-2",
                            "text": "Value 2.1.2"
                        }
                    ]
                },
                {
                    "value": "val-2-2",
                    "text": "Value 2.2",
                    "data": [
                        {
                            "value": "val-2-2-1",
                            "text": "Value 2.2.1"
                        },
                        {
                            "value": "val-2-2-2",
                            "text": "Value 2.2.2"
                        }
                    ]
                }
            ]
        }
    ]
}

Add Dependent Controls library initialization inside the script tags:

jsoDControls({
    dataSource: data
});

The final version:

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <title>Dependent Controls Demo Page</title>
  </head>
  <body>
    <section class="py-5 container">

        <form action="/" method="get" autocomplete="off" data-jso-form style="max-width: 500px; margin: auto;">

            <!-- select placeholder -->
            <select data-jso-level="1" 
                    class="form-select mb-2" 
                    name="select-1"
                    data-jso-hide
                    required>
                <template>
                    <option value="{{value}}">{{text}}</option>
                </template>
            </select>

            <!-- radio button placeholder -->
            <div data-jso-level="2" class="border rounded mb-2 p-2" data-jso-hide>
                <template>
                    <div class="form-check form-check-inline">
                        <input 
                            class="form-check-input" 
                            type="radio" 
                            name="radio-{{level}}" 
                            id="inline-radio-{{id}}" 
                            value="{{value}}" 
                            required
                        />
                        <label class="form-check-label" for="inline-radio-{{id}}">{{text}}</label>
                    </div>
                </template>
            </div>

            <!-- switch placeholder -->
            <div data-jso-level="3" class="border rounded mb-2 p-2" data-jso-hide>
                <template>
                    <div class="form-check form-switch form-check-inline">
                        <input 
                            class="form-check-input" 
                            type="radio" 
                            name="switch-radio-{{level}}" 
                            id="switch-radio-{{id}}" 
                            value="{{value}}"
                            required 
                        />
                        <label class="form-check-label" for="switch-radio-{{id}}">{{text}}</label>
                    </div>
                </template>
            </div>

            <div>
                <button type="submit" class="btn btn-primary my-2">Submit</button>
                <button type="reset" class="btn btn-secondary my-2">Reset</button>
            </div>
        </form>
    </section>

    <!-- Bootstrap Bundle with Popper -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>

    <!-- Dependent Controls Library -->
    <script src="dependent-controls.min.js"></script>
    <script src="data.js"></script>
    <script>
    jsoDControls({
        dataSource: data
    });
    </script>
  </body>
</html> 

SELECT controls and Foundation CSS Framework

This basic example will demonstrate dependent SELECT dropdowns with the Foundation framework as CSS and the JavaScript object as a data source.

You can find other data source options here.


Get started with a minimal Foundation layout as described in Foundation documentation:

<!doctype html>
<html class="no-js" lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Demo Page</title>

    <!-- Foundation CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/foundation-sites@6.6.3/dist/css/foundation.min.css" integrity="sha256-ogmFxjqiTMnZhxCqVmcqTvjfe1Y/ec4WaRj/aQPvn+I=" crossorigin="anonymous">

  </head>
  <body>


  </body>
</html>

You can use any CSS Framework or your custom HTML & CSS.



Add form element and three drop-down placeholders. Please note, that form should have data-jso-form attribute. In this example, we use nested templates - please read more about templates here.

All placeholders should have data-jso-level attribute that defines nesting level. Also, each SELECT should have a unique name.

<form style="max-width: 500px; margin: 2rem auto" action="/" method="get" autocomplete="off" data-jso-form>
    <label data-jso-level="1" 
            class="text-left"
            data-jso-disabled
            required>Select Menu Level 1
        <select name="select-1">
            <template>
                <option value="{{value}}">{{text}}</option>
            </template>
        </select>
    </label>

    <label data-jso-level="2"
            class="text-left"
            data-jso-disabled
            required>Select Menu Level 2
        <select name="select-2">
            <template>
                <option value="{{value}}">{{text}}</option>
            </template>
        </select>
    </label>

    <label data-jso-level="3"
            class="text-left"
            data-jso-disabled
            required>Select Menu  Level 3
        <select name="select-3">
            <template>
                <option value="{{value}}">{{text}}</option>
            </template>
        </select>
    </label>

    <div>
        <button type="submit" class="success button">Submit</button>
        <button type="reset" class="warning button">Reset</button>
    </div>
</form>

Place the <script> near the end of your pages, right before the closing </body> tag:

<script src="dependent-controls.min.js"></script>

Create a new data.js file with the following content:

var data = {
    "data": [
        {
            "value": "",
            "text": "Select Category"
        },
        {
            "value": "val-1",
            "text": "Value 1",
            "data": [
                {
                    "value": "",
                    "text": "Select Category"
                },
                {
                    "value": "val-1-1",
                    "text": "Value 1.1",
                    "data": [
                        {
                            "value": "",
                            "text": "Select Category"
                        },
                        {
                            "value": "val-1-1-1",
                            "text": "Value 1.1.1"
                        },
                        {
                            "value": "val-1-1-2",
                            "text": "Value 1.1.2"
                        }
                    ]
                },
                {
                    "value": "val-1-2",
                    "text": "Value 1.2",
                    "data": [
                        {
                            "value": "",
                            "text": "Select Category"
                        },
                        {
                            "value": "val-1-2-1",
                            "text": "Value 1.2.1"
                        },
                        {
                            "value": "val-1-2-2",
                            "text": "Value 1.2.2"
                        }
                    ]
                }
            ]
        },
        {
            "value": "val-2",
            "text": "Value 2",
            "data": [
                {
                    "value": "",
                    "text": "Select Category"
                },
                {
                    "value": "val-2-1",
                    "text": "Value 2.1",
                    "data": [
                        {
                            "value": "",
                            "text": "Select Category"
                        },
                        {
                            "value": "val-2-1-1",
                            "text": "Value 2.1.1"
                        },
                        {
                            "value": "val-2-1-2",
                            "text": "Value 2.1.2"
                        }
                    ]
                },
                {
                    "value": "val-2-2",
                    "text": "Value 2.2",
                    "data": [
                        {
                            "value": "",
                            "text": "Select Category"
                        },
                        {
                            "value": "val-2-2-1",
                            "text": "Value 2.2.1"
                        },
                        {
                            "value": "val-2-2-2",
                            "text": "Value 2.2.2"
                        }
                    ]
                }
            ]
        }
    ]
}

Add Dependent Controls library initialization inside the script tags:

jsoDControls({
    dataSource: data
});

The final version:

<!doctype html>
<html class="no-js" lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Demo Page</title>

    <!-- Foundation CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/foundation-sites@6.6.3/dist/css/foundation.min.css" integrity="sha256-ogmFxjqiTMnZhxCqVmcqTvjfe1Y/ec4WaRj/aQPvn+I=" crossorigin="anonymous">

  </head>
  <body>

    <div class="callout large primary">
      <div class="text-center">
        <h3>SELECT controls, Foundation CSS,<br /> and JSON file as data source</h3>

        <form style="max-width: 500px; margin: 2rem auto" action="/" method="get" autocomplete="off" data-jso-form>
            <label data-jso-level="1" 
                    class="text-left"
                    data-jso-disabled
                    required>Select Menu Level 1
                <select name="select-1">
                    <template>
                        <option value="{{value}}">{{text}}</option>
                    </template>
                </select>
            </label>

            <label data-jso-level="2"
                    class="text-left"
                    data-jso-disabled
                    required>Select Menu Level 2
                <select name="select-2">
                    <template>
                        <option value="{{value}}">{{text}}</option>
                    </template>
                </select>
            </label>

            <label data-jso-level="3"
                    class="text-left"
                    data-jso-disabled
                    required>Select Menu  Level 3
                <select name="select-3">
                    <template>
                        <option value="{{value}}">{{text}}</option>
                    </template>
                </select>
            </label>

            <div>
                <button type="submit" class="success button">Submit</button>
                <button type="reset" class="warning button">Reset</button>
            </div>
        </form>
      </div>
    </div>

    <!-- Dependent Control Library -->
    <script src="dependent-controls.min.js"></script>
    <script src="data.js"></script>
    <script>
        jsoDControls({
            dataSource: data
        });
    </script>
  </body>
</html>

SELECT controls, Tailwind CSS, and JSON file as data source

This basic example will demonstrate dependent SELECT dropdowns with the Tailwind CSS framework, and the JSON file as a data source.

You can find other data source options here.


Get started with a minimal Tailwind CSS layout as described in Tailwind CSS documentation. In this example, we will use the CDN approach for simplicity, but you can use another installation method.

<!doctype html>
<html>
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Demo Page</title>

  <!-- Tailwind CSS -->
  <link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">

</head>
<body>


</body>
</html>

You can use any CSS Framework or your custom HTML & CSS.



Add form element and three drop-down placeholders. Please note, that form should have data-jso-form attribute. All select elements should have data-jso-level attribute that defines nesting level. Also, each control should have a unique name.

<form 
    class="flex flex-col w-96 mx-auto"
    action="/" 
    method="get" 
    autocomplete="off" 
    data-jso-form>

    <select data-jso-level="1" 
            class="border mb-2 px-4 py-2 rounded disabled:bg-gray-300" 
            name="select-1"
            data-jso-disabled
            required></select>

    <select data-jso-level="2" 
            class="border mb-2 px-4 py-2 rounded disabled:bg-gray-300" 
            name="select-2"
            data-jso-disabled
            required></select>

    <select data-jso-level="3" 
            class="border mb-2 px-4 py-2 rounded disabled:bg-gray-300" 
            name="select-3"
            data-jso-disabled
            required></select>

    <div class="text-center mt-3">
        <button type="submit" class="px-4 py-2 bg-green-200 rounded">Submit</button>
        <button type="reset" class="px-4 py-2 bg-green-200 rounded">Reset</button>
    </div>
</form>

Now define a template that describes the control's inner HTML. The template can contains macros {{value}}, {{text}}, {{id}}, and more.

<template id="option-template">
    <option value="{{value}}">{{text}}</option>
</template>

Place the <script> near the end of your pages, right before the closing </body> tag:

<script src="dependent-controls.min.js"></script>

Create a new data.json file with the following content:

{
    "data": [
        {
            "value": "",
            "text": "Select Category"
        },
        {
            "value": "val-1",
            "text": "Value 1",
            "data": [
                {
                    "value": "",
                    "text": "Select Category"
                },
                {
                    "value": "val-1-1",
                    "text": "Value 1.1",
                    "data": [
                        {
                            "value": "",
                            "text": "Select Category"
                        },
                        {
                            "value": "val-1-1-1",
                            "text": "Value 1.1.1"
                        },
                        {
                            "value": "val-1-1-2",
                            "text": "Value 1.1.2"
                        }
                    ]
                },
                {
                    "value": "val-1-2",
                    "text": "Value 1.2",
                    "data": [
                        {
                            "value": "",
                            "text": "Select Category"
                        },
                        {
                            "value": "val-1-2-1",
                            "text": "Value 1.2.1"
                        },
                        {
                            "value": "val-1-2-2",
                            "text": "Value 1.2.2"
                        }
                    ]
                }
            ]
        },
        {
            "value": "val-2",
            "text": "Value 2",
            "data": [
                {
                    "value": "",
                    "text": "Select Category"
                },
                {
                    "value": "val-2-1",
                    "text": "Value 2.1",
                    "data": [
                        {
                            "value": "",
                            "text": "Select Category"
                        },
                        {
                            "value": "val-2-1-1",
                            "text": "Value 2.1.1"
                        },
                        {
                            "value": "val-2-1-2",
                            "text": "Value 2.1.2"
                        }
                    ]
                },
                {
                    "value": "val-2-2",
                    "text": "Value 2.2",
                    "data": [
                        {
                            "value": "",
                            "text": "Select Category"
                        },
                        {
                            "value": "val-2-2-1",
                            "text": "Value 2.2.1"
                        },
                        {
                            "value": "val-2-2-2",
                            "text": "Value 2.2.2"
                        }
                    ]
                }
            ]
        }
    ]
}

Add Dependent Controls library initialization inside the script tags:

jsoDControls({
    dataSource: 'data.json',
    template: '#option-template'
});

The final version:

<!doctype html>
<html>
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Demo Page</title>

  <!-- Tailwind CSS -->
  <link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">

  <style>
      [disabled]{
        background-color: rgb(232 232 232);
      }
  </style>
</head>
<body>
    <div class="container mx-auto my-6 flex flex-col text-center">
        <h1 class="mb-6 font-bold text-2xl">SELECT controls, Tailwind CSS,<br /> and JSON file as data source</h1>

        <form 
            class="flex flex-col w-96 mx-auto"
            action="/" 
            method="get" 
            autocomplete="off" 
            data-jso-form>

            <select data-jso-level="1" 
                    class="border mb-2 px-4 py-2 rounded disabled:bg-gray-300" 
                    name="select-1"
                    data-jso-disabled
                    required></select>

            <select data-jso-level="2" 
                    class="border mb-2 px-4 py-2 rounded disabled:bg-gray-300" 
                    name="select-2"
                    data-jso-disabled
                    required></select>

            <select data-jso-level="3" 
                    class="border mb-2 px-4 py-2 rounded disabled:bg-gray-300" 
                    name="select-3"
                    data-jso-disabled
                    required></select>

            <div class="text-center mt-3">
                <button type="submit" class="px-4 py-2 bg-green-200 rounded">Submit</button>
                <button type="reset" class="px-4 py-2 bg-green-200 rounded">Reset</button>
            </div>
        </form>

        <!-- form option template -->
        <template id="option-template">
            <option value="{{value}}">{{text}}</option>
        </template>
    </div>

    <!-- Dependent Controls Library -->
    <script src="dependent-controls.min.js"></script>
    <script>
    jsoDControls({
        dataSource: 'data.json',
        template: '#option-template'
    });
    </script>
</body>
</html>

SELECT controls and pure CSS without frameworks

This basic example will demonstrate dependent SELECT dropdowns without CSS frameworks and the JavaScript object as a data source.


You can find other data source options here.


Get started with a minimal HTML layout:

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Demo Page</title>

  </head>
  <body>


  </body>
</html> 

Add form element and three drop-down placeholders. Please note, that form should have data-jso-form attribute. All select elements should have data-jso-level attribute that defines nesting level. Also, each control should have a unique name.

<form action="/" method="get" autocomplete="off" data-jso-form>

    <select data-jso-level="1" 
            name="select-1"
            data-jso-disabled
            required></select>

    <select data-jso-level="2" 
            name="select-2"
            data-jso-disabled
            required></select>

    <select data-jso-level="3" 
            name="select-3"
            data-jso-disabled
            required></select>

    <div>
        <button type="submit">Submit</button>
        <button type="reset">Reset</button>
    </div>
</form>

Now define a template that describes the control's inner HTML. The template can contains macros {{value}}, {{text}}, {{id}}, and more.

<template id="option-template">
    <option value="{{value}}">{{text}}</option>
</template>

Add some styling to the HEAD section:

<style>
    form{
        display: flex;
        flex-direction: column;
        align-items: center;
        margin: 3rem auto;
    }

    select{
        border: 1px solid #ccc;
        padding: 0.5rem 1rem;
        margin: 0 0 1rem 0;
        width: 300px;
        max-width: 90%;
        border-radius: 0.5rem;
    }

    button{
        border: 1px solid #ccc;
        padding: 0.5rem 1rem;
        border-radius: 0.5rem;
        background-color: #eaf2ff;
    }

    :disabled{
        background-color: #efefef;
    }
</style>

Place the <script> near the end of your pages, right before the closing </body> tag:

<script src="dependent-controls.min.js"></script>

Create a new data.js file with the following content:

var data = {
    "data": [
        {
            "value": "",
            "text": "Select Category"
        },
        {
            "value": "val-1",
            "text": "Value 1",
            "data": [
                {
                    "value": "",
                    "text": "Select Category"
                },
                {
                    "value": "val-1-1",
                    "text": "Value 1.1",
                    "data": [
                        {
                            "value": "",
                            "text": "Select Category"
                        },
                        {
                            "value": "val-1-1-1",
                            "text": "Value 1.1.1"
                        },
                        {
                            "value": "val-1-1-2",
                            "text": "Value 1.1.2"
                        }
                    ]
                },
                {
                    "value": "val-1-2",
                    "text": "Value 1.2",
                    "data": [
                        {
                            "value": "",
                            "text": "Select Category"
                        },
                        {
                            "value": "val-1-2-1",
                            "text": "Value 1.2.1"
                        },
                        {
                            "value": "val-1-2-2",
                            "text": "Value 1.2.2"
                        }
                    ]
                }
            ]
        },
        {
            "value": "val-2",
            "text": "Value 2",
            "data": [
                {
                    "value": "",
                    "text": "Select Category"
                },
                {
                    "value": "val-2-1",
                    "text": "Value 2.1",
                    "data": [
                        {
                            "value": "",
                            "text": "Select Category"
                        },
                        {
                            "value": "val-2-1-1",
                            "text": "Value 2.1.1"
                        },
                        {
                            "value": "val-2-1-2",
                            "text": "Value 2.1.2"
                        }
                    ]
                },
                {
                    "value": "val-2-2",
                    "text": "Value 2.2",
                    "data": [
                        {
                            "value": "",
                            "text": "Select Category"
                        },
                        {
                            "value": "val-2-2-1",
                            "text": "Value 2.2.1"
                        },
                        {
                            "value": "val-2-2-2",
                            "text": "Value 2.2.2"
                        }
                    ]
                }
            ]
        }
    ]
}

Add Dependent Controls library initialization inside the script tags:

jsoDControls({
    dataSource: data,
    template: '#option-template'
});

The final version:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Demo Page</title>

    <style>
        form{
            display: flex;
            flex-direction: column;
            align-items: center;
            margin: 3rem auto;
        }

        select{
            border: 1px solid #ccc;
            padding: 0.5rem 1rem;
            margin: 0 0 1rem 0;
            width: 300px;
            max-width: 90%;
            border-radius: 0.5rem;
        }

        button{
            border: 1px solid #ccc;
            padding: 0.5rem 1rem;
            border-radius: 0.5rem;
            background-color: #eaf2ff;
        }

        :disabled{
            background-color: #efefef;
        }
    </style>
  </head>
  <body>

    <form action="/" method="get" autocomplete="off" data-jso-form>

        <select data-jso-level="1" 
                name="select-1"
                data-jso-disabled
                required></select>

        <select data-jso-level="2" 
                name="select-2"
                data-jso-disabled
                required></select>

        <select data-jso-level="3" 
                name="select-3"
                data-jso-disabled
                required></select>

        <div>
            <button type="submit">Submit</button>
            <button type="reset">Reset</button>
        </div>
    </form>

    <!-- form option template -->
    <template id="option-template">
        <option value="{{value}}">{{text}}</option>
    </template>

    <!-- Dependent Controls Library -->
    <script src="dependent-controls.min.js"></script>
    <script src="data.js"></script>
    <script>
    jsoDControls({
        dataSource: data,
        template: '#option-template'
    });
    </script>
  </body>
</html> 

SELECT controls, Bootstrap CSS, and custom function as data source + AJAX request to server-side

In this example, we'll see how to make an AJAX request a sample server and use a custom function data source to apply received JSON data to the SELECT controls.


You can find other data source options here.


Get started with a minimal Bootstrap layout as described in Bootstrap 5 documentation:

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <title>Example</title>
  </head>
  <body>



    <!-- Bootstrap Bundle with Popper -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
  </body>
</html> 

You can use any CSS Framework or your custom HTML & CSS.



Add form element and three drop-down placeholders. Please note, that form should have data-jso-form attribute. All select elements should have data-jso-level attribute that defines nesting level. Also, each control should have a unique name.

<form action="/" method="get" autocomplete="off" data-jso-form style="max-width: 500px; margin: auto;">

    <select data-jso-level="1" 
            class="form-select m-2" 
            name="select-1"
            data-jso-disabled
            required></select>

    <select data-jso-level="2" 
            class="form-select m-2" 
            name="select-2"
            data-jso-disabled
            required></select>

    <select data-jso-level="3" 
            class="form-select m-2" 
            name="select-3"
            data-jso-disabled
            required></select>

    <select data-jso-level="4" 
            class="form-select m-2" 
            name="select-4"
            data-jso-disabled
            required></select>

    <select data-jso-level="5" 
            class="form-select m-2" 
            name="select-5"
            data-jso-disabled
            required></select>

    <div class="text-center">
        <button type="submit" class="btn btn-primary my-2">Submit</button>
        <button type="reset" class="btn btn-secondary my-2">Reset</button>

        <!-- optional preloader -->
        <div data-jso-preloader>Loading...</div>
    </div>
</form>

The elements with data-jso-preloader data attribute will be used as preloaders. They may contain any HTML content (for example, CSS / image / SVG spinners).


Now define a template that describes the control's inner HTML. The template can contains macros {{value}}, {{text}}, {{id}}, and more.

<template id="option-template">
    <option value="{{value}}">{{text}}</option>
</template>

Place the <script> near the end of your pages, right before the closing </body> tag:

<script src="dependent-controls.min.js"></script>

Add Dependent Controls library initialization inside the script tags:

jsoDControls({
    template: '#option-template',
    dataSource: function (currentLevel, selectedValue, $control, callback) {
        fetch('https://dcontrols.jsocean.org/?level=' + currentLevel + '&value=' + encodeURIComponent(selectedValue))
        .then(function(response){
            return response.json();
        })
        .then(function(data){
            callback(data);
        });
    }
});

Here https://dcontrols.jsocean.org/ is a sample server used for demonstration purposes only.


Read more about custom function data source here.


The final version:

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <title>Dependent Controls Demo Page</title>
  </head>
  <body>
    <section class="py-5 container">
        <h1 class="text-center mb-5 fs-4 lh-base">SELECT controls, Bootstrap CSS,<br /> and AJAX reuqest to server</h1>

        <form action="/" method="get" autocomplete="off" data-jso-form style="max-width: 500px; margin: auto;">

            <select data-jso-level="1" 
                    class="form-select m-2" 
                    name="select-1"
                    data-jso-disabled
                    required></select>

            <select data-jso-level="2" 
                    class="form-select m-2" 
                    name="select-2"
                    data-jso-disabled
                    required></select>

            <select data-jso-level="3" 
                    class="form-select m-2" 
                    name="select-3"
                    data-jso-disabled
                    required></select>

            <select data-jso-level="4" 
                    class="form-select m-2" 
                    name="select-4"
                    data-jso-disabled
                    required></select>

            <select data-jso-level="5" 
                    class="form-select m-2" 
                    name="select-5"
                    data-jso-disabled
                    required></select>

            <div class="text-center">
                <button type="submit" class="btn btn-primary my-2">Submit</button>
                <button type="reset" class="btn btn-secondary my-2">Reset</button>

                <!-- optional preloader -->
                <div data-jso-preloader>Loading...</div>
            </div>
        </form>
    </section>

    <!-- form option template -->
    <template id="option-template">
        <option value="{{value}}">{{text}}</option>
    </template>

    <!-- Bootstrap Bundle with Popper -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>

    <!-- Dependent Controls Library -->
    <script src="dependent-controls.min.js"></script>
    <script>
    jsoDControls({
        template: '#option-template',
        dataSource: function (currentLevel, selectedValue, $control, callback) {
            fetch('https://dcontrols.jsocean.org/?level=' + currentLevel + '&value=' + encodeURIComponent(selectedValue))
            .then(function(response){
                return response.json();
            })
            .then(function(data){
                callback(data);
            });
        }
    });
    </script>
  </body>
</html> 

Data Sources

Data Structure

The data is a recursive structure with any number of nested items. The same pattern repeats in each level.

{
    "data": [
        {
            "value": "",
            "text": "Select Category"
        },
        {
            "value": "val-1",
            "text": "Value 1",
            "data": [
                // any number of nested items
                // ...
            ]
        },
        {
            "value": "val-2",
            "text": "Value 2",
            "data": [
                // any number of nested items
                // ...
            ]
        },
        // any number of items
        // ...
    ]
}
Property Name Values Description
value text value Form element value.
text text value Text that will be visible to the user.
selected text value Optional value of the selected form element.
id text value Optional form control id.
data object Optional nested data.


Data examples for server side implementation

Initial response

https://dcontrols.jsocean.org/?level=-1

{
    "data": [{
        "value": "",
        "text": "Select Category"
    }, {
        "value": "val-1",
        "text": "Value 1"
    }, {
        "value": "val-2",
        "text": "Value 2"
    }]
}

First level response example

https://dcontrols.jsocean.org/?level=0&value=val-1

{
    "value": "val-1",
    "text": "Value 1",
    "data": [{
        "value": "",
        "text": "Select Category"
    }, {
        "value": "val-1-1",
        "text": "Value 1.1"
    }, {
        "value": "val-1-2",
        "text": "Value 1.2"
    }]
}

Second level response example

https://dcontrols.jsocean.org/?level=1&value=val-1-5

{
    "value": "val-1-5",
    "text": "Value 1.5",
    "data": [{
        "value": "",
        "text": "Select Category"
    }, {
        "value": "val-1-5-1",
        "text": "Value 1.5.1"
    }, {
        "value": "val-1-5-2",
        "text": "Value 1.5.2"
    }]
}

The same pattern work for each nesting level.


JavaScript Object Data Source

This data source is the simplest one. Just create a JavaScript object and pass it to the library via the dataSource property.

    <script>
    var data = { ... };

    jsoDControls({
        dataSource: data,
        // ... other settings
    });
    </script>

JSON file Data Source

In this case, dataSource property will contain a path to the file with JSON data. This file will be loaded via AJAX request once on page load.

    <script>
    jsoDControls({
        dataSource: 'data.json',
        // ... other settings
    });
    </script>

Custom Function Data Source

The custom function allows to apply any logic and, for example, bring data from the server-side.

    <script>
    jsoDControls({
        /**
         * @param {number} currentLevel
         * @param {string} selectedValue
         * @param {Element} $control
         * @param {Function} callback
         */ 
        dataSource: function(currentLevel, selectedValue, $control, callback) {

            // any logic here to create data object ....
            // ....
            // ...

            callback(data);
        },
        // ... other settings
    });
    </script>

Custom function arguments:

Property Name Values Description
currentLevel numeric value; -1 on page load The current level property defines the nesting level of the selected control. By default, it equals -1.
selectedValue text value or undefined in page load The value selected by the user.
$control form control element DOM element that represents selected form control.
callback callback function The library uses a callback function to apply data on the form controls.


The following example demonstrates AJAX request to the server:

    <script>
    jsoDControls({
        /**
         * @param {number} currentLevel
         * @param {string} selectedValue
         * @param {Element} $control
         * @param {Function} callback
         */ 
        dataSource: function(currentLevel, selectedValue, $control, callback) {

            fetch('http://my-domain.com/?level=' + currentLevel + '&value=' + encodeURIComponent(selectedValue))
                .then(function(response){
                    return response.json();
                })
                .then(function(data){
                    callback(data);
                });
        },
        // ... other settings
    });
    </script>

Templates

With templates, the user has the freedom to define any HTML structure for the form controls. It's possible to use any classes or HTML elements to create the look-and-feel best suitable for the website.

Global Template

The global template is defined only once and then is passed as a library setting. Any form control will use it to create the HTML structure.

For example, the following template defines <option>...</option> HTML within SELECT tag.

<template id="my-template">
    <option value="{{value}}">{{text}}</option>
</template>

The next example is Bootstrap radio button template:

<template id="radio-button-template">
    <div class="form-check form-check-inline">
        <input 
            class="form-check-input" 
            type="radio" 
            name="radio-{{level}}" 
            id="inlineRadio{{id}}" 
            value="{{value}}" 
            required
        />
        <label class="form-check-label" for="inlineRadio{{id}}">{{text}}</label>
    </div>
</template>

The CSS selector of the template element should be passed as in the below example. Any other selectors can be used.

<script>
jsoDControls({
    template: '#radio-button-template',
    // other settings...
});
</script>

It's also possible to pass a DOM element instead:

<script>
var myTemplate = document.querySelector('#radio-button-template');

jsoDControls({
    template: myTemplate,
    // other settings...
});
</script>

Another possibility is to pass the template as a string:

<script>
jsoDControls({
    template: '<option value="{{value}}">{{text}}</option>',
    // other settings...
});
</script>

Nested Templates

Nested templates can be used in cases when each control has a different structure. For example, in the first level, it could be a SELECT tag, in the second level - radio buttons, etc.

Unlike the global templates, nested templates are placed inside form controls (that's why they are called nested). For example:

<select 
    data-jso-level="1" 
    data-jso-disabled
    class="form-select m-2" 
    name="select-1"
    required>
    <template>
        <option value="{{value}}">{{text}}</option>
    </template>
</select>

In the case of nested templates, template setting should not be passed, otherwise, they won't work:

<script>
jsoDControls({
    // template: '#radio-button-template' -> THIS SHOULD NOT BE PASSED
});
</script>

Templates Macros

Any fields from the data source can be printed inside templates using macros. There are predefined macros, like {{value}}, {{text}} or {{id}}. But any additional custom field in the data source also could be printed as {{field_name}}.


Templates Function

The template can also be a custom function. This approach gives the maximal flexibility to apply any logic.

<script>
    /**
     * this function is used to apply custom logic and render a template HTML
     * @param {[Data]} dataList
     * @param {number} level
     * @param {Control} control
     */
    function templateFunction (dataList, level, control) {

        var html = '... any custom HTML here ... ';
        return html;
    }

    jsoDControls({
        template: templateFunction,
        // other settings ...
    });
</script>

Storage

The library uses storage to save the current state and restore it after page refresh or when the back button is pressed. The storage setting is optional.

There are three possible storages that the library supports: local storage, session storage, and cookies.

Local Storage

Local storage is persistent storage that keeps data even if the user closes the browser. It will be cleared only when the user clears the browser cache.

<script>
jsoDControls({
    storage: 'local-storage',
    storageName: 'jso-d-controls',
    // other settings...
});
</script>

Session Storage

Session storage will be cleared when the browser session is over, typically when the user closes the browser.

<script>
jsoDControls({
    storage: 'session-storage',
    storageName: 'jso-d-controls',
    // other settings...
});
</script>

Cookies

In this case, the state will be saved in cookies. Unlike local and session storage, cookie values can be used on the server too. It's possible to define cookies' expiration date.

<script>
jsoDControls({
    storage: 'cookies',
    storageName: 'jso-d-controls',
    cookiesExpiration: -1, // cookies expiration in minutes (-1 = cookies expire when browser is closed)
    // other settings...
});
</script>

Deep links are special links that send users directly into the predefined drop-down selection state.

<script>
jsoDControls({
    deepLink: true,
    // other settings...
});
</script>

Please note that deep links work correctly only with one form on the page.



When both deep links and storage settings are enabled, deep links are executed first.


Form

Form Settings

By default, the library looks for every form element on the page that has data-jso-form attribute. This can be not enough in some cases, for example, when multiple forms have different settings on the page.

Below you can find other options to define form elements.

It's possible to use CSS selector, that should be passed to the forms property:

<script>
jsoDControls({
    forms: 'any-css-selector-here', // for example, '.my-form', or '#my-form'
    // other settings...
});
</script>

Another option is to provide HTMLFormElement directly.

<script>
var myForm = document.querySelector('#my-form');

jsoDControls({
    forms: myForm
    // other settings...
});
</script>

It also can be a list of forms:

<script>
var myForms = document.querySelectorAll('.my-forms-list');

jsoDControls({
    forms: myForms
    // other settings...
});
</script>

The following way is also supported:

<script>
var myForms = Array.from(document.querySelectorAll('.my-forms-list'));
myForms.push(document.querySelector('#another-form'));

jsoDControls({
    forms: myForms
    // other settings...
});
</script>

Form Submit Options

The library will process each form that has data-jso-form attribute. Besides this, it's a standard form with all common properties. Also, it's recommended to add autocomplete="off" as it prevents issues with the back button.

<form action="/" method="get" autocomplete="off" data-jso-form>

    <!-- any control here -->

    <button type="submit">Submit</button>
    <button type="reset">Reset</button>
</form>

Another option is to provide a custom onSubmit function and then apply some logic instead of standard form submission:

<script>
jsoDControls({
    /**
     * custom onSubmit function
     * @param {SubmitEvent} event - the submit event object
     * @param {[[string]]} formData - the selected form items
     * @param {object} settings - the library settings object
     */ 
    onSubmit: function (evt, formData, settings) {
        console.log(evt, formData, settings);
    },
    // other settings ...
});
</script>    

Example of form data:

[
    ["select-1", "val-2", "Value 2", HTMLOptionElement],
    ["select-2", "val-2-2", "Value 2.2", HTMLOptionElement],
    ["select-3", "val-2-2-1", "Value 2.2.1", HTMLOptionElement],
    ["select-4", "val-2-2-1-2", "Value 2.2.1.2", HTMLOptionElement],
    ["select-5", "val-2-2-1-2-1", "Value 2.2.1.2.1", HTMLOptionElement]   
]

Form Validation

The library uses standard required attribute for validation. Any custom logic or library can be used instead.

<select data-jso-level="1" name="select-1" required></select>
<select data-jso-level="2" name="select-2" required></select>
<select data-jso-level="3" name="select-3" required></select>
...

Elements Visibility

The library fills controls with data on page load or once the user selects/deselects any control. There are three options for dealing with empty controls (that are not filled with data yet).

  • - leave them as-is (the default option)
  • - hide them using data-jso-hide attribute
  • - make them disabled using data-jso-disabled attribute


To hide empty elements, add data-jso-hide attribute to the placeholders:

<!-- placeholders -->
<div data-jso-level="1" data-jso-hide></div>
<select data-jso-level="2" name="select-2" data-jso-hide></select>
...

To disable empty elements, add data-jso-disabled attribute to the placeholders:

<!-- placeholders -->
<div data-jso-level="1" data-jso-disabled></div>
<select data-jso-level="2" name="select-2" data-jso-disabled></select>
...

In any case, the library adds jso-disabled CSS class to the empty control, so it's possible to apply custom styling via CSS.


Settings

Settings List

Property Name Values Description
dataSource JavaScript object, path to file, or custom function Data that will be used to fill form controls.
forms empty value, path (CSS selector, HTMLFormElement or array of HTMLFormElement This option defines FORM elements that the library will process. If the option is empty, the library automatically will use any form with data-jso-form attribute.
template path (CSS selector), HTMLTemplateElement, HTML string or Function The template option may contain a path to the <template> element, the template element object in the DOM tree, HTML string or a custom function.
storage '' (default), 'local-storage', 'session-storage', or 'cookies' Storage type. Empty value means that the storage is disabled.
storageName any text value, 'jso-d-controls' by default Storage name is used as a key in local / session storage or as a cookie name.
cookiesExpiration any numeric value, -1 by default cookies expiration in minutes (-1 = cookies expire when the browser is closed)
disabledClassName any text value, 'jso-disabled' by default CSS class name for disabled control.

HTML Data Attributes

Data Attribute HTML Element Values Description
data-jso-form Form no value This data attribute defines a form that the library should process.
data-jso-level Control number This data attribute defines a nesting level of the control.
data-jso-hide Control no value If this attribute is added to the control, it will disappear until it is filled with data.
data-jso-preloader Any HTML Element no value The elements will be used as preloaders. They may contain any HTML content (for example, CSS / image / SVG spinners).

Callback Functions

onSubmit

This callback occurs on form submit event:

<script>
jsoDControls({
    /**
     * custom onSubmit callback
     * @param {SubmitEvent} event - the submit event object
     * @param {[[string]]} formData - the selected form items
     * @param {object} settings - the library settings object
     */ 
    onSubmit: function (evt, formData, settings) {
        console.log(evt, formData, settings);
    },
    // other settings ...
});
</script>    

onReset

This callback occurs on form reset event:

<script>
jsoDControls({
    /**
     * custom onReset callback
     * @param {SubmitEvent} event - the reset event object
     * @param {object} settings - the library settings object
     */ 
    onReset: function (evt, settings) {
        console.log(evt, settings);
    },
    // other settings ...
});
</script>    

onChangeStart

This callback occurs at the beginning of control value changes:

<script>
jsoDControls({
    /**
     * custom onChangeStart callback
     * @param {object} settings - the library settings object
     * @param {number} level - nesting level number or -1
     * @param {string} value - selected value (if exists)
     * @param {HTMLFormElement|null} $element - form element or null
     */ 
    onChangeStart: function (settings, level, value, $element) {
        console.log(settings, level, value, $element);
    },
    // other settings ...
});
</script>    

onChangeEnd

This callback occurs at the end of control value changes:

<script>
jsoDControls({
    /**
     * custom onChangeEnd callback
     * @param {object} settings - the library settings object
     * @param {number} level - nesting level number or -1
     * @param {string} value - selected value (if exists)
     * @param {HTMLFormElement|null} $element - form element or null
     */ 
    onChangeEnd: function (settings, level, value, $element) {
        console.log(settings, level, value, $element);
    },
    // other settings ...
});
</script>    

onDisableControl

This callback occurs when a control is disabled:

<script>
jsoDControls({
    /**
     * custom onDisableControl callback
     * @param {object} settings - the library settings object
     * @param {HTMLFormElement|null} $control - form element or null
     * @param {control} control - form element object
     */ 
    onDisableControl: function (settings, $control, control) {
        console.log(settings, $control, control);
    },
    // other settings ...
});
</script>    

onEnableControl

This callback occurs when a control is enabled:

<script>
jsoDControls({
    /**
     * custom onEnableControl callback
     * @param {object} settings - the library settings object
     * @param {HTMLFormElement|null} $control - form element or null
     * @param {control} control - form element object
     */ 
    onEnableControl: function (settings, $control, control) {
        console.log(settings, $control, control);
    },
    // other settings ...
});
</script>