Custom multiselect list tailored for Angular

I am working on creating a list that can act as either a select or multiselect based on a parameter. For example, if the parameter is false, selecting A and then B will unselect A and select B.

 <ul>
      <li>a,</li>
      <li>b, and</li>
      <li>c</li>
</ul>

My initial approach involves using a custom directive, which looks like this:

<ul myDirective>
      <li ng-model="a">a,</li>
      <li ng-model="b">b, and</li>
      <li ng-model="c">c</li>
</ul>

In this approach, the directive watches for clicks on the list items. Upon click, it determines whether to unselect any previously selected item and then selects the new value while applying a 'selected' class.

I believe I may not be following Angular's best practices, so I am seeking guidance on how to proceed in the right direction.

** Edit ** I aim to achieve a user experience similar to: http://jsfiddle.net/qbt2myj8/

Answer №1

Personally, I believe that implementing a custom directive can greatly enhance the cleanliness of your code, especially when working with AngularJS.

Here is a comprehensive solution utilizing an AngularJS Custom Directive that promotes code reusability and organization.

HTML

Custom Directive Default Behavior

<ul data-my-directive class="list">
    <li>a</li>
    <li>b</li>
    <li>c</li>
</ul>

(The following is a preview screenshot. Refer to the PLAYGROUND link below for interactive exploration)


HTML

With Custom Attribute

<ul data-my-directive data-multi-select="true" class="list">
    <li>a</li>
    <li>b</li>
    <li>c</li>
</ul>

(The following is a preview screenshot. Refer to the PLAYGROUND link below for interactive exploration)


Javascript

angular.module('app',[])
  .directive('myDirective', function(){
    return {
      transclude: true,
      template: '<div ng-transclude></div>',
      link: function(scope, element, attrs){
        function addClickListener(i, callback){
          selections[i].addEventListener('click', function(){
            callback(i);
          }, false);
        }

        // Set up attribute value. Goes to default if attribute is not set up
        attrs.multiSelect = attrs.multiSelect || 'false';

        scope.isSelected = [];

        var selections = element[0].getElementsByTagName('li');

        if(attrs.multiSelect === "true"){
          for(var i = 0; i < selections.length; i++){
            scope.isSelected[i] = false;
            addClickListener(i, function(i){
              if(scope.isSelected[i] === true){
                // if previously it is selected (red color), now make it unselected (black color / default color)
                angular.element(selections[i]).removeClass('selected');
                scope.isSelected[i] = false; 
              } else {
                // previously black color, so change it to red color
                angular.element(selections[i]).addClass('selected');
                scope.isSelected[i] = true;
              }
            });
          }
        } else {
          var currentSelection = -1;
          for(var i = 0; i < selections.length; i++){
            scope.isSelected[i] = false;
            addClickListener(i, function(i){
              if(scope.isSelected[i] === true){
                // do nothing
              } else {
                // previously black color, so change it to red color
                angular.element(selections[i]).addClass('selected');
                scope.isSelected[i] = true;

                angular.element(selections[currentSelection]).removeClass('selected');
                scope.isSelected[currentSelection] = false;
                currentSelection = i;
              }
            });
          }
        }
      }
    }
  });

And lastly, the ultimate

PLAYGROUND

for hands-on experimentation! Enjoy exploring :) Have a wonderful day!

Answer №2

Instead of creating a directive right away, my suggestion is to start by developing a custom JavaScript data structure. Here's a quick example:

/**
 * Generates a selector data structure
 * @param items elements to be listed
 * @param multi whether multiple selection is supported
 * @returns {{cells: *, selectedItems: selectedItems, unselectAll: unselectAll}}
 */
function createSelector (items, multi) {

  var cells = items.map(function (item) {
    // each cell wraps an item and a selected flag
    return  {
      item: item,
      selected: false
    };
  });

  // retrieves an array of the currently selected items
  function selectedItems() {
    return cells
      .filter(function (cell) {
        return cell.selected;
      })
      .map(function (cell) {
        return cell.item;
      });
  }

  // deselects all items
  function unselectAll() {
    cells.forEach(function (cell) {
      cell.selected = false;
    })
  }

  // adding methods to cells
  cells.forEach(function (cell) {
    cell.selectMe = (multi
      ? function () {
      cell.selected = true;
    }
      : function () {
      unselectAll();
      cell.selected = true;
    });
    cell.unselectMe = function () {
      cell.selected = false;
    };

    cell.toggle = function () {
      if (cell.selected) {
        cell.unselectMe();
      } else {
        cell.selectMe();
      }
    }
  });

  return {
    cells: cells,
    selectedItems: selectedItems,
    unselectAll: unselectAll
  };
}

You can easily access your list of selected items in your controller like this:

// ...
$scope.xyzSelector = createSelector(['X','Y','Z'],false);
var getSelectedItems = $scope.xyzSelector.selectedItems; // perform actions with these items in your controller
// ...

Next, utilize Angular data-binding to display this data-structure in your HTML:

    <h3>List of items :</h3>
    <ul>
        <li ng-repeat="cell in xyzSelector.cells" ng-click="cell.toggle()"
            ng-class="{'active': cell.selected}">
            {{cell.item}}
        </li>
    </ul>

    <h3>List of selected items :</h3>
    <ul>
        <li ng-repeat="item in xyzSelector.selectedItems()">
            {{item}}
        </li>
    </ul>

If you wish, you can later encapsulate this functionality into a directive that binds the list of selected items to an ng-model.

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

Using getElementsByTagName in E4X code involves querying for specific

Is it possible to retrieve an array of elements in E4X for an unknown tagname, similar to how the DOMs getElementsByTagName function works, within a function? My initial idea was: (function (doc, tag) { return doc..[tag]; }) Can this be achieved? ...

Why does my counter keep incrementing by more than one every time?

As I work on creating a test in jQuery, I've encountered an issue with my counter variable count. It seems to increase by more than one when the correct answer is used. function nextQuestion(){ $('#submit').show(); $('#next&apo ...

When you try to import a component, it may result in a blank page displaying an error message stating: "TypeError: Cannot set property 'components'

One interesting feature is the Vue component named DisplayContent, <template> <div></div> </template> <script lang="ts"> import { Vue, Component } from "vue-property-decorator"; import Home from "@/ ...

Retrieving outcomes from a sequence of callback functions in Node.Js

I've been struggling to get my exports function in Node.Js / Express app to return the desired value after going through a series of callback functions. I've spent hours trying to fix it with no success. Can someone provide some guidance? Here is ...

What is the best way to send a lengthy JSON string using a GET/POST request on Internet Explorer 8?

Having an issue sending a lengthy JSON string from the front-end to PHP on the back-end. Unfortunately, IE8's 2,048 character limit is causing the request to get cut off, regardless of whether it's a GET or POST request. Any suggestions on how t ...

Make object calculations easy with the power of JavaScript

I've stored a JSON object in a variable and I'm performing calculations based on its properties. However, the current method of calculation seems lengthy, especially as the data grows larger. I'm searching for a more concise approach to per ...

JavaScript event listener for SVG path element click is not functioning correctly as anticipated

How to determine if an element or its children have been clicked? I am trying to identify when a parent element or any of its child SVG icons with the attribute name set to "setGameState" have been clicked. The issue I am facing is that sometimes the even ...

Tips for incorporating a Survey Monkey website embed into an Angular application

I have a Survey Monkey account with multiple surveys. I am trying to embed a survey from this website into my Angular website, which already has Bootstrap and jQuery added. I attempted to directly add the script in an HTML component, but it did not work. ...

Preventing Copy and Paste and Right-Click Actions With JavaScript

Similar Question: How can I prevent right-click on my webpage? Looking to prevent right-clicking and key combinations like Ctrl+V & Ctrl+C on a webpage using JavaScript. I have a form where customers need to input their details, and I want to avoid ...

Utilizing Selenium Webdriver to efficiently scroll through a webpage with AJAX-loaded content

I am currently utilizing Selenium Webdriver to extract content from a webpage. The challenge I'm facing is that the page dynamically loads more content using AJAX as the user scrolls down. While I can programmatically scroll down using JavaScript, I a ...

Building Vertical Tabs with external JS for loading custom visuals

I am trying to figure out how to display the visuals I created in an alternate page within my Vertical tabs. The tabs are already styled, and I have omitted the CSS due to its length. The HTML file I am working with is test.html, and the visuals are in the ...

The file-loader is able to organize images into separate folders when saving them. For example, an image file named "example.jpg" located in Project/Module

Hey there! I'm currently in the process of setting up a Webpack configuration that will generate files for each folder within my project. Unfortunately, creating a separate webpack.config file for each folder is not an option. So far, I have successf ...

Creating stunning 3D animations using Canvas

I am knowledgeable about Canvas 2D context, but I would like to create a 3D animation similar to this example. Would using the Three.js library be the most suitable option for this type of animation? Any recommendations for tutorials or documentation tha ...

What are some ways to adjust red and green blocks using CSS?

One question that arises is how to create a version of a webpage where only the yellow block can slide up, while the red and green blocks remain fixed. Currently, the green block is treated with the following CSS: position:sticky; right:0px; top:100px; ...

Switching colors after uncovering text using JavaScript

I'm striving to achieve specific results with my code, but I'm having trouble pinpointing what exactly I'm doing wrong. Any guidance on how to correct my approach would be greatly appreciated. Currently, I've created rows that can be c ...

Utilizing Angular.js to nest directives seamlessly without cluttering the markup

Expressing my query might pose some difficulty, but I appreciate your patience. I comprehend that in the realm of Angular.js, directives play a crucial role in driving dynamic markup. What was once achieved through jQuery can now be accomplished using dir ...

What is the best way to store a personalized configuration for a user within a Node module?

For my CLI project in Node.js utilizing commander.js, I am interested in implementing a way to store user-specific configuration settings. This will allow users to input their preferences only once during the initial usage. What would be the best approac ...

When implementing the Dropdown Picker, it is important to avoid nesting VirtualizedLists inside plain ScrollViews for optimal

Currently, I am utilizing the RN library react-native-dropdown-picker. However, when I enclose this component within a ScrollView, it triggers a warning: "VirtualizedLists should never be nested inside plain ScrollViews with the same orientation because ...

Can one retrieve an express session using the sessionID given?

I have a NodeJS Express application with express-session that works well, however, it needs to be compatible with a cookie-less PhoneGap app. My question is: Can I access the data in an express session using the sessionID? I was thinking of adding the se ...

Is it possible to incorporate an existing svg from the page into another svg element?

Currently, I am facing a challenge where I am programmatically creating logos as svgs with d3. Now, I have an svg map and I want to incorporate these logos into it. I am wondering if there is a way, whether through d3 or another method, to transfer these ...