-
在Blazor中实现拖放(drag and drop)
前言
我在实现一个含有待办列表功能的页面时,发现了一个好看的设计,它将待办分为——“待办”,“正在进行”,和“已完成”三种状态,并且将待办通过拖拽的方式在这三种状态之间进行切换。
这种方式看起来真不错,但是使用Blazor来实现这种拖拽效果看起来似乎并不太容易。
经过一番百度难以找到,最后搜索英文通过Google找到了合适的解决方案。
感谢Chris Sainty的博客,通过学习他的代码,最终实现了自己想要的效果。
解决方案
需要实现的目标:
-
跟踪用户正在拖拽的项目
-
控制该项目在哪里可以掉落
-
反馈给用户哪些列表可以掉落,哪些不能
-
鼠标松开时更新项目
数据结构:
public class TodoItemDto
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public TodoStatusEnum Status { get; set; }
/// <summary>
/// 截止日期
/// </summary>
public DateTime ClosingDate { get; set; }
/// <summary>
/// 标签
/// </summary>
public string Tag { get; set; }
public bool IsImportant { get; set; }
public int RepeatTimes { get; set; }
}
public enum TodoStatusEnum
{
Todo, // 待办
InProgress, // 正在进行
Completed // 已完成
}
Components
使用三个组件来实现该效果:
-
TodoContainer.razor
- 使用TodoContainer包含Todo,InProgress,Completed三个待办列表
- 跟踪容器内被用户拖拽的项目
- 包含一个事件供状态更新时调用
TodoContainer.razor
<div class="todo-view-content-layout"> @* 将自己作为级联参数传递给子组件(TodoList) *@ <CascadingValue Value="this"> @ChildContent </CascadingValue> </div>
TodoContainer.razor.cs
[Parameter] public List<TodoItemDto> Todos { get; set; } [Parameter] public RenderFragment ChildContent { get; set; } public TodoItemDto Payload { get; set; } // 这个属性用来跟踪被用户拖拽的待办项 /// <summary> /// 当子组件更新状态时,调用父组件的该方法,改变待办集合中被拖拽项的状态 /// </summary> /// <param name="newStatus"></param> public async Task UpdateJobAsync(TodoStatusEnum newStatus) { var task = Todos.SingleOrDefault(x => x.Id == Payload.Id); if (task != null) { task.Status = newStatus; task.ClosingDate = DateTime.Now; await InvokeAsync(StateHasChanged); } }
-
TodoList.razor
- 使用TodoList包含 每个状态 的待办列表(一共三个TodoList)
- 当用户拖拽待办经过列表时呈现不同的UI效果
TodoList.razor
<div class="todo-view-content-item-layout @_dropClass"> <div class="todo-view-content-item"> @* 展示第一行,标题和任务数等(代码省略) *@ ... @* 展示第二行,添加栏(代码省略)*@ ... @* 第三行展示任务列表 *@ <div class="todo-view-content-list" // 调用html5关于拖拽的原生API,默认(default)阻止其它容器拖拽项目,(preventDefault)则允许容器进行拖拽放置 ondragover="event.preventDefault();" // 启用FireFox浏览器对拖放的支持 ondragstart="event.dataTransfer.setData('', event.target.id);" // 当用户进行放置操作时调用HandleDrop方法 @ondrop="HandleDrop" // 当用户进入该list范围时调用HandleDragEnter来判断能不能放置 @ondragenter="HandleDragEnter" // 当用户离开list范围后将dropClass置为默认状态 @ondragleave="HandleDragLeave"> @foreach (var todo in Todos) { <TodoDisplay TodoItem="todo"/> } </div> </div> </div>
TodoList.razor.cs
[CascadingParameter] TodoContainer Container { get; set; } [Parameter] public TodoStatusEnum ListStatus { get; set; } [Parameter] public TodoStatusEnum AllowedStatuses { get; set; } List<TodoItemDto> Todos = new List<TodoItemDto>(); private string _dropClass = ""; // 用来判断当前list能否放置 protected override void OnParametersSet() { Todos.Clear(); Todos.AddRange(Container.Todos.Where(x => x.Status == ListStatus)); } private void HandleDragEnter() { if (ListStatus == Container.Payload.Status) return; if (AllowedStatuses != Container.Payload.Status) { _dropClass = "can-drop"; } } private void HandleDragLeave() { _dropClass = ""; } private async Task HandleDrop() { _dropClass = ""; if (AllowedStatuses == Container.Payload.Status) return; await Container.UpdateJobAsync(ListStatus); }
-
TodoDisplay.razor
- 这个组件用来展示单个待办
- 当它被拖拽的时候就通知父组件(TodoContainer)跟踪它
该组件代码逻辑很简单,但是html的代码量过多就不展示了
TodoDisplay.razor
<div class="draggable" draggable="true" title="@TodoItem.Title" @ondragstart="@(() => HandleDragStart(TodoItem))">
// 内容(待办展示)...
</div>
TodoDisplay.razor.cs
[CascadingParameter] TodoContainer Container { get; set; }
[Parameter] public TodoItemDto TodoItem { get; set; }
/// <summary>
/// 通过每次drag开始将Container的Payload赋值以跟踪被拖拽的待办项
/// </summary>
/// <param name="selectedJob"></param>
private void HandleDragStart(TodoItemDto selectedJob)
{
Container.Payload = selectedJob;
}
使用
从TodoView.razor中进行调用
@page "/todo"
@attribute [Authorize]
<div class="todo-layout">
<TodoViewTitle></TodoViewTitle>
<TodoContainer Todos="Todos">
<TodoList ListStatus="TodoStatusEnum.Todo" AllowedStatuses="TodoStatusEnum.Todo"/>
<TodoList ListStatus="TodoStatusEnum.InProgress" AllowedStatuses="TodoStatusEnum.InProgress"/>
<TodoList ListStatus="TodoStatusEnum.Completed" AllowedStatuses="TodoStatusEnum.Completed"/>
</TodoContainer>
</div>
@code {
List<TodoItemDto> Todos = new List<TodoItemDto>
{
new TodoItemDto()
{
Id = 1,
Title = "开发大创项目右侧状态栏",
Description = "无",
IsImportant = true,
RepeatTimes = 1,
Tag = "工作",
ClosingDate = DateTime.Now
},
new TodoItemDto()
{
Id = 12,
Title = "每日背单词",
Description = "无",
IsImportant = true,
RepeatTimes = 1,
Tag = "学习",
ClosingDate = DateTime.Now
},
new TodoItemDto()
{
Id = 11,
Title = "AAAAAAAAAA",
Description = "无",
IsImportant = true,
RepeatTimes = 1,
Tag = "工作",
ClosingDate = DateTime.Now
},
new TodoItemDto()
{
Id = 111,
Title = "BBBBBBBBBBBB",
Description = "无",
IsImportant = true,
RepeatTimes = 1,
Tag = "工作",
ClosingDate = DateTime.Now
},
new TodoItemDto()
{
Id = 2,
Title = "CCCCCCCCCCCCCCCC",
Description = "无",
IsImportant = true,
RepeatTimes = 1,
Tag = "工作",
ClosingDate = DateTime.Now
}
};
}
实现效果如下:
进行拖拽...
拖拽成功!
原文:https://www.cnblogs.com/Aatrowen-Blog/p/15510566.html
栏目列表
最新更新
python爬虫及其可视化
使用python爬取豆瓣电影短评评论内容
nodejs爬虫
Python正则表达式完全指南
爬取豆瓣Top250图书数据
shp 地图文件批量添加字段
爬虫小试牛刀(爬取学校通知公告)
【python基础】函数-初识函数
【python基础】函数-返回值
HTTP请求:requests模块基础使用必知必会
SQL SERVER中递归
2个场景实例讲解GaussDB(DWS)基表统计信息估
常用的 SQL Server 关键字及其含义
动手分析SQL Server中的事务中使用的锁
openGauss内核分析:SQL by pass & 经典执行
一招教你如何高效批量导入与更新数据
天天写SQL,这些神奇的特性你知道吗?
openGauss内核分析:执行计划生成
[IM002]Navicat ODBC驱动器管理器 未发现数据
初入Sql Server 之 存储过程的简单使用
uniapp/H5 获取手机桌面壁纸 (静态壁纸)
[前端] DNS解析与优化
为什么在js中需要添加addEventListener()?
JS模块化系统
js通过Object.defineProperty() 定义和控制对象
这是目前我见过最好的跨域解决方案!
减少回流与重绘
减少回流与重绘
如何使用KrpanoToolJS在浏览器切图
performance.now() 与 Date.now() 对比