"Unleashing the Power of ngSelect: Organizing Options with Infinite Depth

Within my application, there is an array of objects structured as follows:

[{"ID":1, "parentID":0, "name":"Parent #1"}, 
{"ID":2, "parentID":0, "name":"Parent #2"}, 
{"ID":3, "parentID":1, "name":"Child #1 1"}, 
{"ID":4, "parentID":3, "name":"child #1 2"},
{"ID":5, "parentID":2, "name":"child #2 1"},
{"ID":6, "parentID":5, "name":"child #2 2"}]

I am looking to create a select menu that allows users to choose a leaf node while displaying the non-selectable parent nodes to show the hierarchy.

I have experimented with various approaches and found some success using the following template in Angular:

<div ng-repeat="(idx, category) in $scope.allCats">
    <select ng-model="$scope.cats[idx]" 
            ng-options="cat as cat.name group by $scope.parentName(cat.parentID, idx) for cat in $scope.allCategories track by cat.ID">
        <option value="">Select A Category</option>
    </select>
</div>

The $scope.allCats array contains the data mentioned above, and the $scope.parentName() method returns a string.

However, I encountered issues where parent items displayed twice - once as an option and once as an optgroup. Additionally, the structure did not maintain the correct family tree relationships.

https://i.sstatic.net/PlYSj.png

How can I adjust my data or Angular template to achieve the desired behavior? Specifically, I want to display the entire hierarchy based on the parentID attributes, ensuring each family shares a common ancestor and displaying parent items only once.

The complexity arises from having multiple levels of descendants and wanting to keep the solution general.

Answer №1

Have you considered organizing the array in the scope before sending it to the angular template? For instance, if you start with:

var arr = [
  {"ID":1, "parentID":0, "name":"Parent #1"}, 
    {"ID":3, "parentID":1, "name":"Child #1 1"}, 
      {"ID":4, "parentID":3, "name":"child #1 2"},
        {"ID":7, "parentID":4, "name":"asdfadsf"},
  {"ID":2, "parentID":0, "name":"Parent #2"}, 
    {"ID":5, "parentID":2, "name":"child #2 1"},
      {"ID":6, "parentID":5, "name":"child #2 2"}
];

This approach could simplify things. All you need to do is show the array as it is.

Sorting Code:

var sortKeys = {};

// create a unique sorting key
function getSortKey(item) {
  if(sortKeys[item.ID] === undefined) {
    var parentItem = arr.filter(function(it) {
      return it.ID === item.parentID;
    })[0];

    if(parentItem != null) {
      return getSortKey(parentItem)  + '' + item.ID;
    }

    return item.parentID + '' + item.ID;
  }

  return sortKeys[item.ID];
}

// generate sort keys for sorting items based on hierarchy
arr.forEach(function(item) {
  sortKeys[item.ID] = getSortKey(item);
});

// sort the array
arr.sort(function(item1, item2) {    
  var item1SortKey = sortKeys[item1.ID],
      item2SortKey = sortKeys[item2.ID];

  return item1SortKey < item2SortKey ?
    -1 :
    (item1SortKey > item2SortKey ?
      1:
      0);
});

var arr = [{"ID":1, "parentID":0, "name":"Parent #1"}, 
{"ID":2, "parentID":0, "name":"Parent #2"}, 
{"ID":3, "parentID":1, "name":"Child #1 1"}, 
{"ID":4, "parentID":3, "name":"child #1 2"},
{"ID":5, "parentID":2, "name":"child #2 1"},
{"ID":6, "parentID":5, "name":"child #2 2"},
{"ID":7, "parentID":4, "name":"asdfadsf"}];

var sortKeys = {};

// create a unique sorting key
function getSortKey(item) {
  if(sortKeys[item.ID] === undefined) {
    var parentItem = arr.filter(function(it) {
      return it.ID === item.parentID;
    })[0];

    if(parentItem != null) {
      return getSortKey(parentItem)  + '' + item.ID;
    }

    return item.parentID + '' + item.ID;
  }
  
  return sortKeys[item.ID];
}

// generate sort keys for sorting items based on hierarchy
arr.forEach(function(item) {
  sortKeys[item.ID] = getSortKey(item);
});

// sort the array
arr.sort(function(item1, item2) {    
  var item1SortKey = sortKeys[item1.ID],
      item2SortKey = sortKeys[item2.ID];
  
  return item1SortKey < item2SortKey ?
    -1 :
    (item1SortKey > item2SortKey ?
      1:
      0);
});

document.getElementById('result').innerHTML = JSON.stringify(arr, null, 2);
<pre id="result"></pre>

Answer №2

To achieve this, you can utilize a filter function in your code implementation.

In the following example, I have made the data more straightforward by replacing the ParentID with the actual name of the parent. (You may use a custom function to handle this conversion while maintaining the same concept)

Sample Data:

$scope.data = [{ "ID": 1, "parentID":"Parent #1", name: "Parent #1" }, 
            { "ID": 2, "parentID": "Parent #1", name: "Parent #2" }, 
            { "ID": 3, "parentID": "Parent #2", name: "Child #1 1" }, 
            { "ID": 4, "parentID": "Parent #3", name: "child #1 2" },
            { "ID": 5, "parentID": "Parent #3", name: "child #2 1" },
            { "ID": 6, "parentID": "Parent #6", name: "child #2 2" }]

View:

<select ng-model="data"
        ng-options="d as d.name group by d.parentID for d in data | filter:checkParent(d)"></select>

Note that we introduced a custom function call within the filtering process. Below is an example of how this function can be defined in the controller:

$scope.checkParent = function () {
    return function (d) {
        return d.name != d.parentID;
    }
}

Upon applying this logic, the output will resemble the following image:

https://i.sstatic.net/r19tD.png

For your specific scenario, consider integrating your custom function that fetches the parent name into the filter method, such as:

(While it may vary based on the functionality of your custom function, the core concept should remain coherent)

$scope.checkParent = function () {
    return function (d) {
        return d.name != $scope.getParentName(d.parentID, idx);
    }
}

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

Exploring JQuery and JavaScript for loop guide

Currently working on a small application where users are required to input information, and I am implementing text hints in the input fields. However, I have encountered an issue when using a for loop in my code. Oddly enough, the text hints display corre ...

Updating the status of a checkbox array within the reducer to reflect changes made in the user interface

I've implemented something similar in React: const CheckboxItems = (t) => [ { checked: true, value: 'itemsCancelled', id: 'checkBoxItemsCancelled', labelText: t('cancellations.checkBoxItemsCancelled&apos ...

Trying out an ajax request in React by clicking a button

I have been working on testing a simple Login component that involves filling out an email and password, then clicking a button to log in. When the login button is clicked, it triggers an ajax post request using axios. I am interested in testing both happy ...

Obtain a specific text value as a variable using Google Tag Manager

In my quest to extract the dynamic "sku" value from GTM, I am faced with a challenge. This particular value is unique for each product page. Below is an example of how the code appears on the page: <script type="application/ld+json"> { "@context" ...

The pop-up orientation is not being adjusted according to my CSS when it changes

I am currently working with Cordova 3.1.0 and Android 17. Here is the HTML code for my pop-up: <div id="rightNavPrpPopup1" data-role="popup" class="r-menu-dropdown"> <div class="r-menu-triangle"></div> <a class=" ...

Tips for Adding or Deleting 2 Rows in a Table

When the basket icon on the right is clicked, I want to remove both the current <tr> and the yellow one as well. Check out this screenshot: This is the HTML code for the two rows that need to be deleted with a click: <tr> <t ...

Switch between divs using an update panel

Consider this scenario: Numerous div controls are present on an aspx page, and update panels are being used to prevent page refresh. These divs contain various controls like buttons and textboxes: <asp:UpdatePanel ID="UpdatePanel1" runat="server"> ...

What is the reason behind my page automatically scrolling to the bottom when it loads?

I am currently working on a project for my company, and unfortunately, I cannot share the code as it is proprietary. However, I am encountering a problem where the page loads and automatically scrolls to the bottom. I have checked for any unclosed tags in ...

Tips for utilizing multiple filters in AngularJS's $filter function

Two filters need to be applied on the content of a controller. The first filter should make the text lowercase, and the second one is a custom filter. I attempted to use them as follows: $filter('lowercase','cardShortNameRegex')(curre ...

Attempting to implement a b-table that can manage multiple arrays within an array

This is the b-table, designed to display a column of food items <b-table :fields="Fields" :items="ITEMS"> <template #cell(food)="data"> {{data.item.food}} </template> </b-table> //Column ...

Display a JavaScript variable as an attribute within an HTML tag

let disableFlag = ''; if(value.action.length === 0) { disableFlag = 'disabled="disabled"'; } row += '<td><input type="checkbox" name="create" value="1" class="checkbox" data-action="'+ value.action + '" data-co ...

The 'authorization' property is not available on the 'Request' object

Here is a code snippet to consider: setContext(async (req, { headers }) => { const token = await getToken(config.resources.gatewayApi.scopes) const completeHeader = { headers: { ...headers, authorization: token ...

The Strophe.muc plugin and backbone improper binding of callbacks

Following the initial group message, I'm experiencing an issue with the strophe.muc plugin not responding to subsequent messages. Although I receive the first presence, message, and roster from the room, any additional messages and presence stanzas do ...

Vue.js encountered an unexpected strict mode reserved word error that was not caught

I have initialized a Vue instance var app = new Vue( {...} ) Additionally, I have defined a class named CustomTooltip class CustomTooltip { constructor(array_data,vue_instance) { this.tooltip_data = array_data; this.vue_instance = vue ...

Error: Encountered an unexpected token within the node_modules/aws-iot-device-sdk/thing/index.js file

I've integrated the aws-iot-device-sdk into our reactjs application. However, we encountered an error while trying to execute the command NODE_ENV=production npm run compile. The error message I received pertains to a syntax issue in the file paths me ...

What is the best approach for testing async XMLHttpRequest callbacks using Jest?

In my Jest test, I am trying to call a mock server using ajax with XMLHttpRequest: import mock from "xhr-mock"; describe("ajax callbacks", function() { beforeEach(function() { mock.setup(); }); afterAll(function() { mock ...

Can global scope be injected into a class instantiated in ES6, also known as a singleton?

Before I get started, I want to apologize in advance for the lengthy code that is about to follow. It may make this question seem a bit bloated, but I believe it's necessary for understanding my issue. Imagine we have a predefined MainModule: ' ...

transferring photos using tinymce to the server

Upload Handler I am in the process of implementing an upload handler for Tinymce on the server-side. After extensive research, I came across an example using PHP which can be found here. However, I require a similar handler to be implemented in Node.js. ...

Steps for retrieving user information post authentication query:1. Begin by initiating the query to authenticate the

This is my script.js file var express = require('express'); var router = express.Router(); var expressValidator = require('express-validator'); var passport = require('passport'); const bcrypt = require('bcrypt'); c ...

The removeClass method does not affect the class attribute when using $(this).attr('class'), but only when using $(this).attr('id')

I am currently facing an issue with reducing the size of my jQuery code. The main function is to validate a form - if empty, I want to add a class of 'highlight' or remove it using addClass('highlight') and removeClass('highlight&a ...