Expanding choice by incorporating additional or default selections with ng-options

I am currently working on a tag-manager feature within an angular form that utilizes two dropdown menus (in this example, a food category and a specific item). The functionality I am aiming for is when a user selects a food category, the item dropdown should appear. When a value is chosen from the dropdown, I want to append a string to my tag list in the format of ': '. Below you can find the code:

app.js

var app = angular.module('myApp', []);

app.controller('myCtrl', function($scope){

  $scope.tags = [];
  $scope.userCategory;
  $scope.userFood;
  $scope.primaryFoods = [
    {
        'id': 1,
        'parent_id': null,
        'name': 'Pizza'
    },
    {
        'id': 4,
        'parent_id': null,
        'name': 'Burgers'
    },
    {
        'id': 7,
        'parent_id': null,
        'name': 'Pasta'
    },
  ];
  $scope.secondaryFoods = [
    {
        'id': 2,
        'parent_id': 1,
        'name': 'Cheese Pizza'
    },
    {
        'id': 3,
        'parent_id': 1,
        'name': 'Combo Pizza'
    },
    {
        'id': 5,
        'parent_id': 4,
        'name': 'Cheese Burgers'
    },
    {
        'id': 6,
        'parent_id': 4,
        'name': 'Hamburgers'
    },
  ];

});

app.directive('doubleTagManager', function() {
  return {
    restrict: 'E',
    scope: {tags: '=', primary: '=', secondary: '=', userPrimary: '=', userSecondary: '='},
    templateUrl: 'double-tag-manager.html',
    link: function ($scope, $element) {
      var input = angular.element($element.find('select')[1]);
      // This adds the new tag to the tags array in '<Primary>: <Secondary>' format
      $scope.add = function() {
        var new_value = input[0].value;
        if ($scope.tags.indexOf(new_value) < 0) {
          $scope.tags.push($scope.userPrimary.name + ': ' + $scope.userSecondary.name);
        }
      };
      // This is the ng-click handler to remove an item
      $scope.remove = function (idx) {
          $scope.tags.splice(idx, 1);
      };
      input.bind( 'change', function (event) {
        $scope.$apply($scope.add);
      });
    }
  };
});

double-tag-manager.html

<div class="row">
  <div class="col-md-6">
    <select name="uFoodsPrimary" id="foodPrimary" class="form-control"
            ng-model="userPrimary"
            ng-options="item.name for item in primary track by item.name" required>
      <option value="">Select a Food category!</option>
    </select>
  </div>
  <div class="col-md-6" ng-show="userPrimary">
    <select name="uFoodsSecondary" id="foodSecondary" class="form-control"
            ng-model="userSecondary"
            ng-options="item.name for item in (secondary | filter: {parent_id: userPrimary.id})
            track by item.name"
            required>
      <option value="">Select a Food sub-category!</option>
    </select>
  </div>
</div>
<div class="tags">
  <a ng-repeat="(idx, tag) in tags" class="tag" ng-click="remove(idx)">{{tag}}</a>
</div>

One additional feature I would like to include is the option for users to select 'All foods' so they don't have to individually select each item. I'm facing some challenges figuring out how to add this extra field using ng-options.

Fiddle

BONUS: If a category without any children is selected, I would like it to be automatically added to the tags list.

Answer №1

Hello Erik, I have made some modifications to the code to support all your select feature requirements. You can also enhance it further to meet your BONUS use case.

Instead of investing a lot of effort into achieving tagging in this manner, I recommend using the existing angularui-select2 component. It comes with a wide range of additional options that will simplify your development process.

var app = angular.module('myApp', []);

app.controller('myCtrl', function($scope) {

  $scope.tags = [];
  $scope.sub_cat_show = false;
  $scope.all_sub_cat_show = false;
  $scope.userCategory;
  $scope.userFood;
  $scope.primaryFoods = [{
    'id': 0,
    'parent_id': null,
    'name': 'All Foods'
  }, {
    'id': 1,
    'parent_id': null,
    'name': 'Pizza'
  }, {
    'id': 4,
    'parent_id': null,
    'name': 'Burgers'
  }, {
    'id': 7,
    'parent_id': null,
    'name': 'Pasta'
  }];
  $scope.secondaryFoods = [
  {
    'id': 2,
    'parent_id': 1,
    'name': 'Cheese Pizza'
  }, {
    'id': 3,
    'parent_id': 1,
    'name': 'Combo Pizza'
  }, {
    'id': 5,
    'parent_id': 4,
    'name': 'Cheese Burgers'
  }, {
    'id': 6,
    'parent_id': 4,
    'name': 'Hamburgers'
  }, ];

});

app.directive('doubleTagManager', function() {
  return {
    restrict: 'E',
    scope: {
      tags: '=',
      primary: '=',
      secondary: '=',
      userPrimary: '=',
      userSecondary: '=',
      sub_cat_show: '=',
      'all_sub_cat_show': '='
    },
    template: "<div class='row'><div class='col-md-6'><select ng-change='primaryChange()' name='uFoodsPrimary' id='foodPrimary' class='form-control' ng-model='userPrimary' ng-options='item.name for item in primary track by item.name' required> <option value=''>Select a Food category!</option></select></div><div ng-show='sub_cat_show' class='col-md-6'><select ng-show='all_sub_cat_show' ng-change='secondaryChange()' name='uFoodsSecondary' id='foodSecondary' class='form-control' ng-model='userSecondary'" +
      //options code
      "ng-options='item.name for item in (secondary | filter: {parent_id: userPrimary.id}) track by item.name' required>" +
      //end options code
      "<option value=''>Select a Food sub-category!</option></select> <select ng-show='!all_sub_cat_show'><option value=''>Food all sub-category</option></select> </div></div><div><a ng-repeat='(idx, tag) in tags' class='tag' ng-click='remove(idx)'>{{tag}}</a></div>",
    link: function($scope, $element) {
      var primarySel = angular.element($element.find('select')[0]);
      var secondarySel = angular.element($element.find('select')[1]);
      // This adds the new tag to the tags array in '<Primary>: <Secondary>' format

      $scope.primaryChange = function() {
        $scope.tags = []; // reset
        $scope.sub_cat_show = primarySel[0].value?true:false;
        if (primarySel[0].value == 'All Foods')
        {
          $scope.all_sub_cat_show = false;
          angular.forEach($scope.primary, function(primary, index) {
            angular.forEach($scope.secondary, function(secondary, index) {
              if(secondary.parent_id==primary.id)
                  $scope.tags.push(primary.name + ': ' + secondary.name);
            });
          });
        }
        
        else
        {
          $scope.all_sub_cat_show = true;
        }
      }
      
      $scope.secondaryChange = function() {
          var new_value = secondarySel[0].value;
          if ($scope.tags.indexOf(new_value) < 0) {
            $scope.tags.push(primarySel[0].value + ': ' + new_value);
          }
        };
      // This is the ng-click handler to remove an item
      $scope.remove = function(idx) {
        $scope.tags.splice(idx, 1);
      };
    }
  };
});
<script src="https://code.angularjs.org/1.4.3/angular-animate.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
<!DOCTYPE html>
<html >

<head>
  <meta charset="utf-8" />
  <title>AngularJS Demo</title>
  <script>
    document.write('<base href="' + document.location + '" />');
  </script>
</head>

<body>
  <div ng-app="myApp" ng-controller="myCtrl">
    <double-tag-manager tags="tags" primary="primaryFoods" secondary="secondaryFoods" user-primary="userCategory" user-secondary="userFood">
    </double-tag-manager>
  </div>
</body>

</html>

Answer №2

Upon further investigation on stack overflow, the most effective (and possibly only) solution I found was to implement a new filter that would duplicate the filtered array and insert an additional option at the beginning.

The newly created filter:

app.filter('addAll', function () {
  return function(input) {
    // To prevent adding a new "None" option to your "values" array with every digest cycle,
    // clone the array.
    var newArray = input.slice(0);
    newArray.unshift({name: "All Foods"});
    return newArray;
  };
});

Updated ng-options tag:

ng-options="item.name for item in (secondary | filter: {parent_id: userPrimary.id} | addAll)
            track by item.name"

Link to Stack Overflow post

Updated Fiddle

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

Issue with modal component triggering unexpected page reload

I'm encountering a strange issue with my modal in Vue.js. It only appears on a specific page named 'Item', but when I click on a different view, the page reloads unexpectedly. This problem seems to occur only with the route containing the mo ...

Utilizing Dynamic Image Sources in Vue.js with the Help of APIs

Can someone help me figure out how to solve this issue? I have an API that returns a base64 image, and I want to load this image on my site. Any suggestions on where or how I should implement my function? This is the API call located in the methods: metho ...

Encountering an issue with blogs.map function in a Next.js application

import Head from 'next/head' import { useState } from 'react' import Image from 'next/image' import styles from '../styles/Home.module.css' const Home = (props) => { const [blogs, setblogs] = useState(props.dat ...

Guide to writing a unit test for a parameter decorator in NestJs

I want to implement a unit test for a basic custom decorator that I created, but I'm facing some challenges. This decorator was developed based on the solution provided here. I have Keycloak authentication set up and using this solution in my controll ...

Opting for "npm ci" over "npm install" for ensuring a consistent project setup

When working on a project where the package-lock.json is managed in source control to ensure consistency among all developers, there may be confusion surrounding the use of npm ci versus npm install. According to npm documentation, developers should utili ...

"Displaying a popup message prompting users to refresh the page after clicking

I need to implement a feature where the page refreshes only after the user clicks the "OK" button on a dialog box that appears once a process is completed. The issue I'm facing is that in my current code, the page refreshes immediately after the proc ...

What are some ways to conceal the API connection within a button?

I have a code that allows me to perform a certain API call with a link. Here is an example: <a class="btn btn-default" href="https://testapi.internet.bs/Domain/Transfer/Initiate?ApiKey='.$user.'&Password='.$pass.'&Domain=&ap ...

What method does Express use to determine which Router path to follow if there are multiple matching paths?

If there are 2 router.route() methods, like the examples below: router.route('/app/:id').get(function(req, res, next){ console.log("id route"); }); and router.route('/app/:username').get(function(req, res, next){ console.log ...

Unable to delete element from the given array

I have been working on removing instances of 'store.state.location.locations' from my locationData array that should no longer be there, but for some reason, they are persisting in the array even though I am no longer passing those instances. ta ...

Unexpected Issues with Page Refresh in AngularJS

I am currently working on an Angular application using the MEAN stack. Here's a scenario: imagine you have an express route that queries the database and sends the results back in the response: app.get('/api', function(req, res){ Todo.f ...

React Native - Implementing a dynamic form that adapts based on the answer given by its parent

My JavaScript Object has a simple state structure as follows: pertanyaan: [{ label: "label1", type: 'dropdown', key: 'keyFoo1', option: [{ value: "foo1" }, { value: "foo2", additional ...

Array Filtering with Redux

I have come across similar queries, but I am still unable to find a solution. While typing in the search box, the items on the screen get filtered accordingly. However, when I delete a character from the search box, it does not show the previous items. For ...

Having trouble with jsPlumb accurately rendering lines

http://jsbin.com/ofapew/1/edit I am currently experimenting with jsPlumb to connect two points, specifically trying to link the green dots to the top of a black border box. One thing that is puzzling me is that even though the green dot end point appears ...

The global variable remains unchanged within an ajax request

Here is the code I am working with: In developing this code, I referenced information about the window variable from here <script> $(window).on("load", function() { function myForeverFunc(){ ...

Displaying an HTML file in a Node and Express application using JavaScript File handling

Starting my first Node, Express & Angular project has been a bit tricky due to issues with the asset pipeline. I'm not entirely sure if it's related to my folder structure, which I tried setting up based on online tutorials but am open to suggest ...

What issues are present with the JavaScript event management in this scenario? (Specifically using the click() and hover() jQuery functions)

Currently, I am in the process of developing a proof-of-concept for a project that mimics Firebug's inspector tool. For more detailed information, please refer to this linked question. You can view an example page of my work which has only been teste ...

React & Material UI: Unleashing the Power of Chained Arrow Functions

I stumbled upon this code snippet while browsing through the Material UI docs on Accordion. Despite spending hours trying to understand it, I'm still struggling to grasp its functionality: export default function CustomizedAccordions() { const [expa ...

Best practices for aligning the widths of input-group-addon elements

Hey there, I'm working with an HTML file that has 3 input options. Here's a snippet: <script type="text/ng-template" id="dashboard_assigngroup_popup.html"> <div class="modal-header modal-header2"> <h3 class="modal-tit ...

Tips for customizing the appearance of popup windows

I want to enhance the appearance of my popup window by applying a different format for opening it. How can I style it so that it looks visually appealing when the popup window opens? You can find below the source code I am working with: HTML: <div onM ...

"JavaScript issue: receiving 'undefined' when trying to retrieve input

This code snippet is for a web app that tracks the number of losses in a game. The problem arises when trying to retrieve the value, which returns undefined. Every time I reference the username variable, it returns undefined. document.addEventListener(&a ...