React Drag and Drop
Phải nói là trước đây dù cũng có tìm hiểu về Angular JS nhưng chẳng hiểu sao mình không có cảm hứng code lắm với thằng Ăng ngu lờ. Với thằng React thì ngược lại =)), đây là lần đầu tiên mà mình hứng thú code front-end sau chuỗi dài tháng ngày only back-end. Trong bài viết trước thì mình có giới ...
Phải nói là trước đây dù cũng có tìm hiểu về Angular JS nhưng chẳng hiểu sao mình không có cảm hứng code lắm với thằng Ăng ngu lờ. Với thằng React thì ngược lại =)), đây là lần đầu tiên mà mình hứng thú code front-end sau chuỗi dài tháng ngày only back-end. Trong bài viết trước thì mình có giới thiệu qua về Material UI với React, còn trong bài viết này thì mình sẽ giới thiệu về package react-dnd sử dụng để drag and drop component trong React.
(Mình tìm hiểu thằng này vì đơn giản repo của nó hơn 1k3 Star (yeah3))
1. Giới thiệu React-dnd
React-dnd được sử dụng để drag and drop component trong React. Không những thế react-dnd còn hỗ trợ cả drag and drop file ảnh, sortable list, multi drop target, drop effect... Trang chủ của react-dnd ở địa chỉ:
http://gaearon.github.io/react-dnd/
trong đó sẽ có mọi thứ từ docs, các ví dụ cho đến tutorial làm một demo...
Còn để cài đặt thì đơn giản là
npm install react-dnd
Trong các phần tiếp theo mình sẽ hướng dẫn những thành phần cơ bản để có thể drag and drop component.
##2. Drag Source Sử dụng Drag Source để component của bạn có thể drag. Code đơn giản như sau
// ... // Các thể loại requires var PropTypes = React.PropTypes; var DragSource = require('react-dnd').DragSource; var dragResource = { beginDrag: function (props) { return { image_url: props.data.image_url, }; } }; function collect(connect, monitor) { return { connectDragSource: connect.dragSource() } } var DragableImage = React.createClass({ propTypes: { connectDragSource: PropTypes.func.isRequired }, render: function(){ var connectDragSource = this.props.connectDragSource; var style = {awidth: "80px", height: "80px"}; return connectDragSource( <div style={style}> <img src={this.props.data.image_url} /> </div> ); } }); module.exports = DragSource("Element", dragResource, collect)(DragableImage);
Các hàm, giá trị cần phải có ở DragSource là:
- dragResource : trong dragResource react-dnd hỗ trợ phục vụ cho drag ví dụ như:
- beginDrag: chạy trước khi component bắt đầu drag, giá trị trả về là giá trị mà bạn muốn bên drop nhận được khi component drop.
- canDrag: return true hay false tương ứng với component này có thể drag được hay không
- enDrag: những hàm muốn chạy giá trị muốn gán sau khi việc drag kết thúc (hiểu ở đây là sau khi component được drop).
- collect: react-dnd sinh ra props của component ví dụ như:
- connectDragSource: bắt buộc phải có để connect drag và drop.
- isDragging: giá trị sẽ là true hay false tùy theo component có đang được drag hay là không
Khi export module thì bạn cần theo cấu trúc cú pháp như ví dụ trên.
Cần lưu ý ở export module trong ví dụ trên, giá trị Elements được hiểu là type của component. Đặt tên gì là tùy bạn còn mục đích của nó mình sẽ giải thích phần sau.
3. Drop Target
Sau khi đã xong phần drag, chúng ta sẽ tiếp tục với phần tạo khu vực drop cho drag component trên. Ví dụ cụ thể như sau:
// ... // Các thể loại requires var PropTypes = React.PropTypes; var DropTarget = require('react-dnd').DropTarget; var dropField = { drop: function (props, monitor) { } } }; function collect(connect, monitor) { return { connectDropTarget: connect.dropTarget() }; } var DropArea = React.createClass({ propTypes: { connectDropTarget: PropTypes.func.isRequired }, render: function(){ var connectDropTarget = this.props.connectDropTarget; return connectDropTarget( <div className="right-div"> </div> ); } }); module.exports = DropTarget("Element", dropField, collect)(DropArea);
Các hàm, giá trị cần phải có ở DropTarget là:
- dropField: trong dropField sẽ là các hàm hỗ trợ cho drop ví dụ như:
- drop: chạy sau khi mà component được drop. Ở trong hàm này gọi monitor.getItem() để lấy giá trị mà bạn đã return trong hàm beginDrag ở trên.
- canDrop: return true false nếu tùy theo bạn muốn component có được drop hay không.
- collect: react-dnd sinh ra props của component ví dụ như:
- connectDropTarget: bắt buộc phải có để connect drag và drop.
- isOver: giá trị sẽ là true hay false tùy theo drag component có đang hover qua drop component hay không
Khi export module thì bạn cần theo cấu trúc cú pháp như ví dụ trên.
Ở đây bạn sẽ lại thấy component type mình khai báo là Elements Giá trị Elements ở đây có nghĩa là chỉ những Drag Source nào có type là ELements thì mới có thể Drop được vào Target này. Do đó nếu bên DragSource bạn để type là Image còn bên Drop Target bạn đẻ type là File thì mặc định là không drop được.
4. DragDropContext.
Rất đơn giản như sau:
var React = require('react'); var DragImage = require('./DragImage.jsx'); var DropArea = require('./DropArea.jsx'); var DragDropContext = require('react-dnd').DragDropContext; var HTML5Backend = require('react-dnd/modules/backends/HTML5'); var Main = React.createClass({ render: function(){ return ( <div> <DragImage /> <DropArea /> </div> ); } }); module.exports = DragDropContext(HTML5Backend)(Main);
Có thể thấy là cả Drag Source và Drop Target sẽ nằm trong DragDropContext. Như vậy là component của bạn đã có thể drag và drop thành công Cần lưu ý là bạn có thể không có phần Drop (kiểu chỉ có drag cho đẹp) hoặc không có phần Drag (ví dụ drop ảnh từ local) nhưng DragDropContext là bắt buộc phải có để Drag Source hay Drop Target hoạt động.
5. Một vài chức năng khác và chú ý.
Một vài chức năng khác mà react-dnd hỗ trợ như:
- Drop Image từ directory: Rất đơn giản ở phần type của Drop Target, bạn để type là __NATIVE_FILE__, react-dnd sẽ hỗ trợ drop image từ directory cho bạn.
- Drag Layer (lườm rau gắp thịt): Sử dụng trong trường hợp mà Drag Source là ảnh A, thế nhưng khi mà đang drag bạn lại muốn nó hiện ảnh B hoặc là thành bất cứ một component nào đó, thì bạn sẽ cần tìm hiểu và sử dụng Drag Layer
Ngoài ra còn rất nhiều chức năng khác mà bạn có thể tìm hiểu ở docs
Vài chú ý nho nhỏ trong quá trình mình sử dụng:
- Ở Drag Source bạn có thể thấy là mình có khai báo style cho return div:
render: function(){ var connectDragSource = this.props.connectDragSource; var style = {awidth: "80px", height: "80px"}; return connectDragSource( <div style={style}> <img src={this.props.data.image_url} ref="element_image" /> </div> ); }
Bạn có thể không có phần này nhưng cứ thử bỏ đi và cảm nhận khi bạn drag là sẽ hiểu tại sao lại cần có =))
- DropTarget bạn có thể khai báo nhiều type có thể drop, đơn giản là khai báo nó là một mảng ["Elements, "__NATIVE_FILE__]
Dưới đây là link github 1 demo nhỏ về react-dnd của mình. Demo giống như là game Littel Alchemy ấy. Tất nhiên vì là** demo nhỏ **nên bạn chỉ có thể drag drop để combine 4 thành phần cơ bản Air, Earth, Fire, Water thôi.
https://github.com/linuxhjkaru/react-drag-drop