基于JS/HTML/AngularJS实现带可折叠分组的单选滚动列表/表格
Got it, let's build this collapsible grouped radio list with drag-and-drop between two lists using AngularJS, HTML, and vanilla JavaScript. I'll break this down into manageable parts so you can follow along easily.
1. Core Concept & Data Structure
First, we'll structure our data to support grouped items with collapse state. Each group will have a name, a list of items, and a flag to track if it's collapsed. We'll also track the selected radio item and manage two separate lists for drag-and-drop.
2. Collapsible Grouped List with Radio Selection
We'll use AngularJS directives like ng-repeat, ng-click, and ng-model to handle the collapse/expand and radio selection. The radio buttons will be bound to a single model to ensure only one item is selected at a time.
3. Drag-and-Drop Implementation
We'll leverage the HTML5 Drag and Drop API, wrapped in AngularJS directives to keep the logic clean. We'll handle three key events:
dragstart: Capture the selected item data when dragging startsdragover: Allow the drop action on the target listdrop: Transfer the item from the source list to the target list, updating our data models
Complete Working Example
HTML
<!DOCTYPE html> <html ng-app="collapsibleDragList"> <head> <style> .list-container { width: 300px; height: 400px; border: 1px solid #ccc; overflow-y: auto; float: left; margin: 0 20px; padding: 10px; } .group-header { cursor: pointer; padding: 8px; background-color: #f0f0f0; border: 1px solid #ddd; font-weight: bold; } .group-items { padding-left: 15px; } .list-item { padding: 6px; margin: 4px 0; border: 1px solid #eee; cursor: move; } .list-item.selected { background-color: #e3f2fd; border-color: #2196f3; } </style> </head> <body ng-controller="MainController"> <div class="list-container"> <h3>Source List</h3> <!-- Collapsible Groups --> <div ng-repeat="group in sourceGroups"> <div class="group-header" ng-click="group.isCollapsed = !group.isCollapsed"> {{group.name}} ({{group.items.length}} items) <span ng-show="group.isCollapsed">▼</span> <span ng-hide="group.isCollapsed">▲</span> </div> <div class="group-items" ng-hide="group.isCollapsed"> <!-- Radio Items with Drag Support --> <div class="list-item" ng-class="{'selected': selectedItem === item}" draggable="true" ng-drag-start="onDragStart(item, 'source')" ng-repeat="item in group.items"> <input type="radio" ng-model="$parent.$parent.selectedItem" ng-value="item"> {{item.name}} </div> </div> </div> </div> <div class="list-container"> <h3>Target List</h3> <div class="group-items" ng-drop="onDrop($event)" ng-dragover="allowDrop($event)"> <div class="list-item" ng-repeat="item in targetItems"> {{item.name}} </div> <div ng-if="targetItems.length === 0" style="color: #999; padding: 20px; text-align: center;"> Drag items here </div> </div> </div> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script> <script src="app.js"></script> </body> </html>
app.js
angular.module('collapsibleDragList', []) .controller('MainController', ['$scope', function($scope) { // Initialize source groups with collapse state $scope.sourceGroups = [ { name: 'Group A', isCollapsed: false, items: [ {id: 1, name: 'Item A1'}, {id: 2, name: 'Item A2'}, {id: 3, name: 'Item A3'} ] }, { name: 'Group B', isCollapsed: true, items: [ {id: 4, name: 'Item B1'}, {id: 5, name: 'Item B2'} ] } ]; // Target list for dropped items $scope.targetItems = []; $scope.selectedItem = null; // Drag-and-Drop Handlers $scope.onDragStart = function(item, source) { // Store item data and source type in the drag event event.dataTransfer.setData('text/plain', JSON.stringify({item: item, source: source})); }; $scope.allowDrop = function(event) { event.preventDefault(); // Allow drop action }; $scope.onDrop = function(event) { event.preventDefault(); const data = JSON.parse(event.dataTransfer.getData('text/plain')); const draggedItem = data.item; // Remove item from source groups $scope.sourceGroups.forEach(group => { const index = group.items.findIndex(item => item.id === draggedItem.id); if (index !== -1) { group.items.splice(index, 1); } }); // Add item to target list $scope.targetItems.push(draggedItem); // Deselect the item after drag $scope.selectedItem = null; $scope.$apply(); // Trigger AngularJS digest cycle }; }]) // Custom directives for drag events .directive('ngDragStart', function() { return { link: function(scope, element, attrs) { element.on('dragstart', function(event) { scope.$apply(function() { scope.$eval(attrs.ngDragStart, {$event: event}); }); }); } }; }) .directive('ngDrop', function() { return { link: function(scope, element, attrs) { element.on('drop', function(event) { scope.$apply(function() { scope.$eval(attrs.ngDrop, {$event: event}); }); }); } }; }) .directive('ngDragover', function() { return { link: function(scope, element, attrs) { element.on('dragover', function(event) { scope.$apply(function() { scope.$eval(attrs.ngDragover, {$event: event}); }); }); } }; });
Key Features Explained
- Collapsible Groups: Clicking the group header toggles the
isCollapsedflag, showing/hiding items viang-hide. - Single Radio Selection: All radio buttons bind to the same
selectedItemmodel, ensuring only one item is selected globally. - Drag-and-Drop: Custom AngularJS directives wrap the HTML5 drag events to transfer item data between lists, updating the source and target data models.
- Scrollable Lists: CSS sets a fixed height with
overflow-y: autofor both containers.
Notes
- If you need per-group radio selection instead of global, change the
ng-modeltogroup.selectedIteminstead of$parent.$parent.selectedItem. - The example uses AngularJS 1.8.2 (the latest stable version of AngularJS).
- Styling is kept minimal—you can customize the CSS to match your app's design.
内容的提问来源于stack exchange,提问作者Saverio Mancuso

