Creating interconnected select boxes based on JSON data to display parent-child relationships in a cascading manner

A dynamic creation of chained select-boxes has been implemented based on JSON data fetched from the server. In this cascading process, each select-box is an object with specific properties:

  1. Parent Attribute: The name of the parent object for the current select-box.
  2. Options: An array of option objects comprising: (a) Option Value (b) Parent Option Value - mapping the value of the parent select-box to the current one. (c) Option ID.

  3. Selected Option: An object containing two properties: (a) Currently selected value (b) ID of the currently selected value.

The select-boxes are generated using ng-repeat in the "option" tag or ng-option in the "select" tag, followed by the application of a custom filter. This custom filter matches the parent option value (2 > b) of the option values (2 > a) with the "currently selected value" (3 > a) of its parent object, essentially establishing a many-to-one mapping from child option values to the selected parent value.

https://i.sstatic.net/ym2R2.jpg

While successful in mapping parent-child select-box relationships, an issue arises when changing the parent select-box value results in the child object's "selected option value" not updating (failure to grab the first item in the filtered list, causing the grandchild dropdown to remain unchanged)[1].

Is there a way to initialize the child select-box and subsequent children/grandchildren with the first option value when the parent value changes instead of maintaining the blank value?

Here is the working example on Plunker (ng-repeat implementation). Any assistance would be highly appreciated.

Another version can be found on Plunker, demonstrating the ng-options implementation: Plunker Example.

HTML (ng-repeat):

<div ng-repeat="selection in vm.selectionData">
    <div ng-repeat="(key, attribute) in selection.attributes">
      <span>{{key}}</span>
      <select class="form-control" ng-model="attribute.selectedOption.name">
        <option ng-repeat="option in attribute.options | optionFilter : selection.attributes[attribute.parentAttr]">{{option.name}}</option>
      </select>
    </div>
</div>

HTML (ng-options):

<div ng-repeat="selection in vm.selectionData">
    <div ng-repeat="(key, attribute) in selection.attributes">
      <span>{{key}}</span>
      <select ng-model="attribute.selectedOption" ng-options="attribute.name for attribute in (attribute.options | optionFilter : selection.attributes[attribute.parentAttr]) track by attribute.id">
      </select>
    </div>        
</div>

JS:

myApp.filter('optionFilter', function() {
  return function(items, parent) {
    var result = [];
    if (parent) {
      for (var i = 0; i < items.length; i++) {
        console.log(items[0].parent, parent.selectedOption.name);
        if (items[i].parent === parent.selectedOption.name) {
          result.push(items[i]);
        }
      }
      return result;
    } else {
      return items;
    }
  }
});

myApp.controller("MyCtrl", function($scope) {

  this.selectionData = [{
    selectionType: "Geography",
    attributes: {
      country: {
        parentAttr: "none",
        options: [{
          name: "India",
          parent: "None",
          id: 1
        }, {
          name: "Bangladesh",
          parent: "None",
          id: 2
        }, {
          name: "Afghanistan",
          parent: "None",
          id: 3
        }],
        selectedOption: {
          name: "India",
          id: 1
        }
      },
      state: {
        parentAttr: "country",
        options: [{
          name: "Rajasthan",
          parent: "India",
          id: 1
        }, {
          name: "Haryana",
          parent: "India",
          id: 2
        }, {
          name: "Dhaka",
          parent: "Bangladesh",
          id: 3
        }, {
          name: "Kabul",
          parent: "Afghanistan",
          id: 4
        }],
        selectedOption: {
          name: "Rajasthan",
          id: 1
        }
      },
      city: {
        parentAttr: "state",
        options: [{
          name: "Kota",
          parent: "Rajasthan",
          id: 1
        }, {
          name: "Sirsa",
          parent: "Haryana",
          id: 2
        }, {
          name: "Alwar",
          parent: "Rajasthan",
          id: 3
        }, {
          name: "Gurgaon",
          parent: "Haryana",
          id: 4
        }, {
          name: "Kabul",
          parent: "Kabul",
          id: 5
        },{
          name: "Dhaka",
          parent: "Dhaka",
          id: 6
        }
        ],
        selectedOption: {
          name: "Kota",
          id: 1
        }
      }
    },
  }];

});

References:

  • Cascading select/dropdowns

Answer №1

After facing some challenges, I managed to come up with a solution (although not certain if it follows best practices). Within my custom filter that returns filtered options based on the selected option value of the parent object, I also pass the current select box object. Here's how it works:

  1. The filter first verifies if the parent object exists; if not, it returns all options.
  2. If the parent exists, it iterates through all available options of the current select-box object.
  3. If the parent value of the current select-box options matches the selected option value of the parent object, it adds the filtered option values to the result array, only if the parent object's selected option is not null. This scenario can arise when the grandparent value changes and the parent does not get the resulting filtered options, causing an empty first option in the parent select-box momentarily.
  4. Next, it checks if the current select-box object's selected option is null. If so, it assigns the first element (object) of the result array to the selected option object and returns the result array. As this filter runs for all select-boxes (grandparent, parent, and child), it sets the first element in the parent's filtered array as the parent object's selected option. Subsequently, when the parent object's selected option is no longer null, the current select-box displays filtered options (from the result array), with the first element of the result array assigned to the selected option of the current select-box.

View the working demo here. Feel free to suggest a better solution if you have one. Below is the implementation of the custom filter:

myApp.filter('optionFilter', function() {
  return function(items, parent, self) {
    var result = [];
    if (parent) {
      for (var i = 0; i < items.length; i++) {
        if (parent.selectedOption !== null && items[i].parentValue === parent.selectedOption.value) {
          result.push(items[i]);
        }
      }
      if (self.selectedOption === null) {
        self.selectedOption = result[0];
      }
      return result;
    } else {
      return items;
    }
  }
});

HTML:

<div ng-repeat="(key, item) in data">
    <span>{{key}}</span>
    <select ng-model="item.selectedOption" ng-options="option.value for option in (item.availableOptions | optionFilter : data[item.parent] : item) track by option.id">
    </select>
</div>

Data:

this.data = {
  Country: {
    parent: "None",
    availableOptions: [{
      value: "United States",
      parentValue: "None",
      id: 1
    }, {
      value: "China",
      parentValue: "None",
      id: 2
    }, {
      value: "India",
      parentValue: "None",
      id: 3
    }],
    selectedOption: {
      value: "United States",
      parentValue: "None",
      id: 1
    }
  },
  State: {
    parent: "Country",
    availableOptions: [{
      value: "California",
      parentValue: "United States",
      id: 1
    }, {
      value: "Shanghai",
      parentValue: "China",
      id: 2
    }, {
      value: "Delhi",
      parentValue: "India",
      id: 3
    }],
    selectedOption: {
      value: "California",
      parentValue: "United States",
      id: 1
    }
  },
  City: {
    parent: "State",
    availableOptions: [{
      value: "Greenfield",
      parentValue: "California",
      id: 1
    }, {
      value: "Shanghai",
      parentValue: "Shanghai",
      id: 2
    }, {
      value: "New Delhi",
      parentValue: "Delhi",
      id: 3
    }],
    selectedOption: {
      value: "Greenfield",
      parentValue: "California",
      id: 1
    }
  }
};

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Submitting a base64 encoded image as a file stream to a Web API

What I'm struggling with: I have a webpage that utilizes the webcam to take a photo. Upon clicking a button, it saves the current webcam image to an HTML canvas object. The issue arises when trying to send this image from the canvas object to a Web AP ...

Is there a way to retrieve $httpProvider.defaults.xsrfCookieName within .factory?

Here's a question that might come from someone new to programming. I'm trying to avoid hard-coding the values for xsrfHeaderName and xsrfCookieName. Can anyone guide me on how to retrieve them from the $httpProvider? .factory('XSRFIntercept ...

Ways to access the ngModel data in a personalized directive

Hey everyone, I'm facing an issue where I'm trying to get the value of an ngmodel when a button in a custom directive triggers a click event. However, it seems like the value isn't ready in the link function. Is my assumption correct? How ca ...

The passport authentication process is currently stalled and failing to provide any results

The current authentication process is functioning properly app.post('/login', passport.authenticate('local-login', { successRedirect: '/home', failureRedirect: '/login', failureFlash: true }) ); Howev ...

When an AJAX call is made during a PHP session that has timed out

I am working on an AJAX form that handles data authentication. In the event of a session timeout, I need to implement a redirect to the login page. How can I go about achieving this? Here is an excerpt from my simplified server-side code: function doExecu ...

Replace the hyphen with a comma using JavaScript

Looking for a way to modify a string like this: "PALS español K- add-on" by replacing the - with ,. The desired output should be: "PALS español K, add-on" Is there a JavaScript solution to achieve this task? ...

Exploring the Google Plus API: Discover individuals who are following a specific individual

Has anyone managed to successfully extract a list of individuals who have included a specific user in their circles or are following them on social media platforms? I am currently using the official JS Library for this task, but any solution in any progr ...

What is the best way to structure files within the css and js folders after running the build command in Vue-cli?

Vue-cli typically generates files in the following structure: - dist -- demo.html -- style.css -- file.commom.js -- file.commom.js.map -- file.umd.js -- file.umd.js.map -- file.umd.min.js -- file.umd.min.js.map However, I prefer to organize them this way: ...

Issues with AJAX functionality in select fields - LATEST UPDATE

I have created a form with two select fields. The first field is populated with the parents of a custom taxonomy, and the second field is populated with the children of each parent. I used AJAX to accomplish this, but it doesn't seem to be working pro ...

Adjust the map automatically as the cursor approaches the map's edge in Google Maps API V3

My latest project involved creating a selection tool using the Rectangle shape tool. With this tool, users can easily select markers by drawing a rectangle over them and releasing their mouse to erase the selection (similar to selecting items on a desktop ...

Redirect in React Route

I've been researching how to programmatically redirect in React, but I'm struggling to get it to work. Here's a simplified version of my code: import React from 'react'; import {render} from 'react-dom'; import {Browser ...

Error encountered in Bootstrap 5: Popper__namespace.createPopper function is not defined

Currently using Django to host web pages. Focus is on enabling offline access by downloading all necessary resources to ensure webpage functionality, like Bootstrap 5. Attempting to utilize the dropdown menu feature in Bootstrap: Dropdowns depend o ...

Simplify rootscope in the ionic framework

Hey, I'm currently working on implementing a login feature for my Ionic app. I am using $rootScope as a global variable across all controllers (LoginCtrl, SalirCtrl) to store user information when they log in and display it in the SalirCtrl. However, ...

Issue: Encounter of "Uncaught (in promise) TypeError" while implementing RiveScript chatbot in Angular

I've been working on integrating a chatbot based on RiveScript into Angular. The chatbot is functioning well - I always see the correct response in the console. Displaying the user's input is also working seamlessly. However, I'm encounterin ...

The function angular.isNumeric does not exist in this context

Recently, I added the following code to an AngularJS controller to test number values: $scope.isNumeric = angular.isNumeric; However, I encountered the following error: TypeError: angular.isNumeric is not a function I wanted to check if a value is a ...

Why is the ajax request in JQuery initiated before the code in beforeSend is fully executed?

I have encountered an issue with my code. I am trying to get a Reload.gif to start playing on my webpage before sending an ajax request to reload the data. However, the GIF only starts playing after the success function is called. Here is the timeline of e ...

What is the process for initiating a state transition?

I have created two separate components. One component uses v-navigation-drawer, while the other component contains a button that toggles the state of the v-navigation-drawer using vuex. On smaller screens, when clicking on the gray area to close the navig ...

Is there a way to execute the Javascript function that is declared in a subpage?

When working with AngularJS, I often use the directive to include subpages within the main page. However, I have encountered an issue where the browser consistently returns an error message when I try to call a JavaScript function in the subpage. I am uns ...

Failure to properly declare custom service

I recently started learning Angular.js and I hit a roadblock while trying to create a custom service. I followed the tutorial available at https://docs.angularjs.org/tutorial. Here is my app.js: var myApp = angular.module('myApp', [ 'ngR ...

Looking to conceal the edges of a ThreeJS box primitive

I'm trying to figure out how to hide the edges displayed in a box primitive using ThreeJS. The edges only appear when I apply a texture to the faces. I've attempted various options such as setting wireframe=false, but the edges persist. Here&ap ...