Giới thiệu về directive dndLists trong Angular
Bài đăng này đã không được cập nhật trong 3 năm
Tổng quan
Hôm nay mình sẽ giới thiệu đến các bạn directive dndLists - hỗ trợ kéo thả item giữa các list trong angular 1. Và mình sẽ làm 1 bảng KANBAN sử dụng directive trên. Trước khi bắt đầu, các bạn nên tìm hiểu sơ về dndLists trước nhé (tất nhiên các bạn nên biết sơ về directive là gì trong angular đã nhé, các bạn có thể tìm hiểu tại đây.
Demo
Đầu tiên chúng ta cần tạo app trước và import các thư viện cần thiết như (Angular JS, dndLists) và một vài thư viện hỗ trợ khác.
Ở đây mình dùng npm
để cài các package cần thiết, các bạn cũng có thể sử dụng bower
nhé.
Cài thư viện và init project
Bạn cần tạo 1 thư mục để chứa sourcecode. Ở đây mình tạo thư mục với tên là my-app
. Tiếp theo chúng ta mở terminal
(Ubuntu), Windows PowerShell
(Win 10) lên và dẫn đường dẫn đến thư mục chúng ta vừa tạo.
Chạy lệnh npm init
để tạo file package.json
, bạn sẽ được hỏi một vài câu hỏi, cứ điền thông tin hoặc không điền rồi cứ bấm Enter đến khi nào hết.
Tiếp đến, chúng ta sẽ cài các package như mình đã nói ở trên, ở đây mình sẽ cài: angular
, angular-animate
, angular-drag-and-drop-lists
, angular-sanitize
, bootstrap
, font-awesome
, lodash
bằng các lệnh sau:
npm install angular --save
npm install angular-animate --save
npm install angular-drag-and-drop-lists --save
npm install angular-sanitize --save
npm install bootstrap --save
npm install font-awesome --save
npm install lodash --save
--save
ở đây chỉ là option thêm, nếu các bạn thêm vào thì những người build app của các bạn chỉ cần chạy một lệnh duy nhất npm install
là có thể down các package mà bạn đã down.
Vậy là phần cài đặt thư viện đã xong, bây giờ các bạn hãy tạo một file index.html
, các bạn cũng có thể tạo một thư mục riêng ở trong thư mục my-app
để chứa sourcecode, ở đây mình đã tạo thư mục src
.
Ở đây các bạn chỉ cần viết cấu trúc như một file html thuần bình thường gồm các tag như <html>
, <head>
, <body>
, sau đó require các file js, css của các package mà mình đã lưu ở trên vào (tất cả đều nằm trong thư mục node_modules
cả nhé. Cơ bản ta sẽ có được file index.html
như sau:
<html>
<head>
<meta charset="utf-8"/>
<script src="../node_modules/angular/angular.js"></script>
<script src="../node_modules/angular-animate/angular-animate.js"></script>
<script src="../node_modules/angular-sanitize/angular-sanitize.js"></script>
<script src="../node_modules/angular-drag-and-drop-lists/angular-drag-and-drop-lists.js"></script>
<script src="../node_modules/lodash/lodash.js"></script>
<link href="../node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="../node_modules/font-awesome/css/font-awesome.css" rel="stylesheet">
</head>
<body>
</body>
</html>
Bắt đầu code
Như phần trên, chúng ta đã tạo được một file index.html
cơ bản, tiếp theo các bạn thêm directive ng-app
và ng-controller
vào nhé
...
<body ng-app="myApp" ng-controller="kanBanCtrl as ctrl">
</body>
...
Tiếp theo chúng ta sẽ phải tạo một file javascript để xử lý các sự kiện bên controller, tất nhiên đừng quên import nó vào file index nhé. Ở đây mình tạo file app.js
với nội dung sau:
var app = angular.module('myApp', ['ngAnimate', 'ngSanitize', 'dndLists'])
app.controller('kanBanCtrl', kanBanCtrl);
function kanBanCtrl() {
var ctrl = this;
console.log('Hello World!');
}
Ở đây chúng ta cần thêm các directive đã cài ở trên như ngAnimate
, ngSanitize
, dndLists
vào thì mới có thể sử dụng được nhé. Bây giờ mở file index.html
bằng trình duyệt và check ở console nào.
Tiếp theo chúng ta sẽ tạo view nhé. Trước hết chúng ta tạo khởi tạo các biến và gán dữ liệu vào ở bên javascript nhé, mình sẽ có một list các task với cấu trúc như sau:
ctrl.taskList = [
{tasks: [{name: 'Task 1', selected: false}, {name: 'Task 2', selected: false}, {name: 'Task 3', selected: false}], label: 'TO DO', class: 'info', dragging: false},
{tasks: [{name: 'Task 4', selected: false}, {name: 'Task 5', selected: false}, {name: 'Task 6', selected: false}], label: 'DOING', class: 'primary', dragging: false},
{tasks: [{name: 'Task 7', selected: false}, {name: 'Task 8', selected: false}, {name: 'Task 9', selected: false}], label: 'PENDING', class: 'warning', dragging: false},
{tasks: [{name: 'Task 10', selected: false}, {name: 'Task 13', selected: false}, {name: 'Task 12', selected: false}], label: 'DONE', class: 'success', dragging: false}
]
Trong đó:
- Mỗi list task ứng với một cột sẽ là một object gồm các thuộc tính như:
- tasks: Chứa các task trong cột, trong đó mỗi task sẽ có hai thuộc tính đơn giản như
name
(tên của task),selected
: để biết task đó có đang được pick hay không. - label: Ứng với nhãn của mỗi cột.
- class: Thuộc tính này chỉ là option class cho mỗi cột.
- dragging: Để biết cột đó đang có phần tử được
drag
hay không. Đây chỉ là cấu trúc cơ bản, các bạn có thể tùy chỉnh sao cho phù hợp. Tiếp theo, chúng ta sẽ tạo các cột tương ứng với các cột của bảngKANBAN
, ở đây mình sẽ có có code html như sau:
- tasks: Chứa các task trong cột, trong đó mỗi task sẽ có hai thuộc tính đơn giản như
<body ng-app="myApp" ng-controller="kanBanCtrl as ctrl">
<div class="container-fluid">
<div class="row">
<div class="col-md-3" ng-repeat="list in ctrl.taskList">
<div class="panel panel-{{list.class}}">
<div class="panel-heading list-header" ng-bind="list.label"></div>
<div class="panel-body task-list">
<ul>
<li ng-repeat="task in list.tasks">
<i class="fa" aria-hidden="true"
ng-class="{'fa-hand-o-right': list.label === 'TO DO', 'fa-cogs': list.label === 'DOING',
'fa-exclamation-circle': list.label === 'PENDING', 'fa-check-square-o': list.label === 'DONE'}">
</i>
<span ng-bind="task.name"></span>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</body>
Với đoạn code trên, khi chạy lại chúng ta sẽ được kết quả như sau: .
Tiếp theo chúng ta cùng sử dụng directive dndLists
để thực hiện việc kéo thả các task qua lại giữa các cột nào. Ta sẽ có đoạn code sau:
<ul dnd-list dnd-drop="ctrl.onDrop(list, item, index)">
<li ng-repeat="task in list.tasks" dnd-draggable="ctrl.getSelectedTasksIncluding(list, task)"
dnd-dragstart="ctrl.onDragstart(list, event)" dnd-moved="ctrl.onMoved(list)"
dnd-dragend="list.dragging = false" dnd-selected="task.selected = !task.selected"
ng-class="{'selected': task.selected}" ng-hide="list.dragging && task.selected">
<i class="fa" aria-hidden="true"
ng-class="{'fa-hand-o-right': list.label === 'TO DO', 'fa-cogs': list.label === 'DOING',
'fa-exclamation-circle': list.label === 'PENDING', 'fa-check-square-o': list.label === 'DONE'}">
</i>
<span ng-bind="task.name"></span>
</li>
</ul>
Mình sẽ giải thích cụ thể nhé:
- dnd-list: Đây là thuộc tính bắt buộc có, sẽ có giá trị là mảng của các phần tử được
drop
vào. Hoặc ta có thể để trống khi sử dụng kèm vớidnd-drop
. - dnd-drop: Như mình đã nói ở trên, đây là một option được gọi khi có một phần tử được
drop
vào mảng. Đây là các thuộc tính mình dùng cho thẻul
. Ở thẻli
sẽ có cácoption
như: - dnd-draggable: Nếu như
dnd-list
là thuộc tính bắt buộc dùng cholist các items
, thì đây sẽ là thuộc tính bắt buộc đối với mỗi item. - dnd-dragstart: Đây là một
callback
bắt sự kiện khi có một phần tử bịdragged
. - dnd-dragend: Đây là một
callback
trái ngược vớidnd-dragstart
, dùng để bắt sự kiện khi kết thúcdragged
một phần tử. - dnd-selected: Đây là một
callback
khác, dùng để bắt sự kiện khi có một phần tử đượcclick
vào nhưng khôngdragged
. - dnd-moved: Đây là
callback
được gọi khi có một phần tử bị di chuyển, và thường thì chúng ta phải tự xóa phần tử đó ra khỏi list ban đầu vì directive này không làm điều đó giúp chúng ta. Dựa vào những điều trên, chúng ta có thể viết code cho các hàm xử lý bên controller như sau:
// Hàm này sẽ xử lý việc gán trường *selected* của phần tử đang được dragged bằng true, và lấy các phần tử khác đang được select (selected = true) để thực hiện drop.
ctrl.getSelectedTasksIncluding = function(list, task) {
task.selected = true;
return _.filter(list.tasks, ['selected', true]);
};
// Hàm này sẽ gán trường *dragging* của list có phần tử đang được dragged bằng true.
ctrl.onDragstart = function(list, event) {
list.dragging = true;
};
// Hàm này sẽ gán trường *selected* của tất cả các phần tử trong mảng thành false
// Thường thì khi chúng ta drag, các phần tử được dragged sẽ có trường *selected* bằng true, vì thế sau khi drop, chúng ta phải gán lại trường *selected* của các phần tử đó bằng false.
// Sau đó chúng ta sẽ thực hiện việc thêm các phần tử được dragged đó vào list mới.
// Ở đây index chính là vị trí mà chúng ta drop vào trong mảng, vì vậy để chèn các phần tử đó vào chúng ta sẽ thực hiện như bên dưới.
ctrl.onDrop = function(list, tasks, index) {
_.forEach(tasks, function(task) { task.selected = false; });
list.tasks = list.tasks.slice(0, index).concat(tasks).concat(list.tasks.slice(index));
return true;
}
// Như mình đã giải thích ở trên, hàm này sẽ có tác dụng loại bỏ phần tử đang được dragged, bằng cách loại bỏ những phần tử có trường *selected* bằng true.
ctrl.onMoved = function(list) {
list.tasks = _.filter(list.tasks, ['selected', false]);
};
Với đoạn code như trên chúng ta đã có thể drop-drag các phần tử qua lại giữa các list rồi, cùng chạy lại và test xem nhé :kiss_mm:.
Tại vì mình chỉ làm ở client nên không có data, phải tạo data giả. Các bạn có thể kết nối đến server rồi lấy dữ liệu và trả về nhé.
Hi vọng qua bài này có thể giúp các bạn biết thêm một directive hỗ trợ việc drag and drop list
trong angular.
Cảm ơn các bạn đã tham khảo bài viết này. Nếu muốn góp ý hay thắc mắc, vui lòng để lại ở phần bình luận nhé :kiss_mm:.
Tham khảo
Các bạn cũng có thể tìm hiểu thêm về directive này tại đây.
All rights reserved