在这篇博客中,我们将学习如何通过小部件在应用程序中进行永久数据更改,如何组合小部件来创建流畅的最终用户体验、自定义挂钩等……
这是多部分系列博客中的第三篇,博客 1 和 2 可以在这里找到: 在以下位置构建小部件 Mendix 使用 React — 第一部分 — 颜色计数器 以及 在中构建小部件 Mendix 使用 React 第二部分 — 计时器.
在我们开始之前,先声明一下:对于此版本,我们利用了 VS Code 的一些功能,如果您更喜欢其他 IDE,则说明可能会有所不同。
我们正在建设什么
Mendix 最近发布了 史诗,一个功能齐全的全新故事管理工具!
作为其中的一部分,有一个非常棒的组件,可以让你拖放你的故事。我认为尝试构建类似的东西会很酷。

每个看板都应该能够做以下几件事:
- 允许用户设置看板阶段
- 可以通过拖动来更改项目的阶段
- 可以通过拖动来更改部分中项目的排序顺序
新会员入门指南
首先,我们通过运行小部件生成器来快速搭建小部件。运行以下命令:
yo @mendix/widget kanban
配置小部件属性时,请选择默认选项,同时确保 使用 Typescript.
接下来,我们将更新小部件与 Mendix 通过更新 Kanban.xml。为此,我们添加了一个数据源项列表,以允许小部件的用户指定什么 Mendix 他们想要使用的实体。
我们有两个选项来实际渲染小部件中的项目,我们可以使用传统的 JSX,或者我们可以允许用户在小部件中构建他们想要的设计 Mendix第二种方案主要有三个优点:
- 避免规定子数据必须显示的结构
- 不会隐藏难以编辑的已编译小部件文件中的代码
- 允许用户利用 WYSIWG 编辑器 Mendix
渲染列表
选项二获胜!为了实现这一点,我们可以使用'小部件' 标签来自 可插入小部件 API.更新后的 Kanban.xml 现在包含:
项目项目列表内容使用数据源的小部件
接下来,我们可以更新 Kanban.TSX 文件以包含:
导出函数 Kanban({ widgetList, items }: KanbanContainerProps): ReactElement { 返回( {items.items && items.items.map(item => { return widgetList.get(item);})} ); }
上面的代码中发生了一些事情,因此我将其分解如下:
- “小部件列表”我们的小部件接口是 Mendix 模型,如果我们右键单击“widgetList”并单击“转到类型定义”,我们就可以看到此类型的文档。
导出声明接口 ListWidgetValue {
* 以基于提供的对象项呈现的反应节点形式返回用户配置的小部件。*
* @param item 来自链接数据源的 {@link ObjectItem} 实例。*/
获取:(item:ObjectItem)=> ReactNode;
}
这基本上告诉我们,我们的小部件有一个 get 函数,它需要一个称为 ObjectItem 的东西来渲染 ReactNode
- “Items” 是我们的数据源,我们可以将其作为 ListValue( Mendix-defined type),如果我们右键单击“items”并单击“Go To Type Definition”,我们可以看到此类型的文档。从此文档中,我可以看到我可以使用 。项目 财产
然后我们可以使用内置函数 JS map 遍历项目列表中的每个元素,并使用该项目的“get”函数返回结果。
- 然后,我们 包装我们的函数 in 大括号 显示它是 JavaScript,并将其包装在 父级 div 为了呈现每个项目
- 最后,我们添加一个 三元表达式 以避免在列表中没有项目的情况下调用该函数。
测试与验证
为了测试我们的实现,我们在小部件的“测试”文件夹中设置了一个测试项目,就像我们在 博客1.
我们添加一个名为 名称 使用字符串属性 内容

然后我们添加一些微流到 在启动后微流中创建一些 $Items。

然后我们可以从终端构建我们的小部件 npm run build .
当我们回到我们的 Mendix 模型并按 F4,我们的小部件就可以放置在我们的主页上。

在这里我添加了一个简单的容器,其类为'卡' 以及显示内容的字符串。瞧!它渲染出来了。

拖放
拥有一个静态列表是一个好的开始,但我们需要能够移动这些项目!
让我们选择我们的 React 拖放库。我们可以自己构建它(和一些聪明的 Mendix 开发人员已经这样做了!)但有一个 提供大量受良好支持、使用率高的库.
我已经 react-beautiful-dnd 因为它有一个 易于使用 API 并且非常 功能丰富.
然后,我们通过导航到小部件目录(./kanban)并运行命令来导入拖放库
npm i react-beautiful-dnd .
通过阅读文档我们可以知道我们的小部件需要三个主要区域:

<DragDropContext />– 包装您想要启用拖放功能的应用程序部分<Droppable />– 可以掉落进入的区域。<Draggable />– 哪些东西可以拖拽
步骤 1 — 可拖拽
因此让我们从最低级别开始,将我们的项目转换为可拖动的。
首先,让我们将 ItemList 分解为一个单独的函数,但目前将其保留在我们的组件中以方便开发(我们可以稍后将其移到单独的组件中)。
导出函数 Kanban({ widgetList, items }: KanbanContainerProps): ReactElement {
const ItemList = (): ReactElement => {
返回 ( {items.items && items.items.map(item => { return widgetList.get(item);})} (英文):
}
返回 ( ) }
为了让每个列表项都可拖动,我们需要将小部件包装在 Draggable 组件中,该组件 需要一个唯一的 ID 对于可拖动项目—— 对项目进行排序的索引,以及一个用于渲染组件的子函数。
按照 react-beautiful-dnd 库中的文档完成其余的实现,我们得到以下代码。
const ItemList = (): ReactElement => { 返回 ( {items.items &&items.items.map((item, i) => { return ( {提供 => ( {widgetList.get(item)} )} ); })} ); };
我们在这里所做的是将我们的小部件组件包装在一个“可拖动”包装器中,这使我们能够传递引用和道具。
引用是我们在这些博客中尚未涉及的一个概念,它是 React 的关键部分,它们本质上是一种访问页面内元素的方式,在本例中用于跟踪正在拖动的内容: Refs 和 DOM – React.
步骤 2——可拖放
现在我们有了可拖动组件,我们需要创建拖放画布。
为了实现这一点,我们需要使用与上面概述的模式非常相似的模式将 Draggable 组件的 ItemList 包装在 Droppable 容器中:
{提供 => ( )}
步骤 3——背景
最后,我们需要提供该功能所在的拖放上下文:
{提供 => ( )}
上下文期望在项目被拖动后调用一个函数。现在,我们只需记录已调用该函数即可。
我们的代码目前看起来应该是这样的:
导出函数 Kanban({ widgetList, items }: KanbanContainerProps): ReactElement { const ItemList =(): ReactElement => { return( {items.items && items.items.map((item, i) => { return ( {提供 => ( {widgetList.get(item)} )} ); })} ); };
函数 onDragEnd(result: DropResult): void { console.log("Dragged"); }
返回 ( {提供 => ( )} ); }
如果我们跑 npm run build 并重新运行我们的 Mendix 应用程序,我们现在可以拖动卡片了!有点……

我们稍后会重新讨论这个问题...最重要的是我们可以拖动我们的物品。
多列
因此,我们可以在一个列表中上下拖动内容,但我们真正想要做的是能够在列之间拖动项目来确定状态。
我们希望小部件的用户能够指定他们想要的列数。
为此我们可以使用 “物体” 在我们的 Kanban.xml 中。我们只需要将现有属性包装在对象列表中,如下所示:
章节姓名姓名部分项目项目列表内容使用数据源的小部件
眼尖的朋友们可能还会注意到,我添加了一个名字,以便我们跟踪我们的专栏。
如果你打开 Mendix 我们可以看看我们可爱的新界面:

很酷,但是现在我们必须呈现我们的列,让我们回到我们的小部件……
这其实很简单。
首先,让我们更新 JSX 来为每个对象渲染一列:
{myObject.map((obj, i) => {返回( {提供 => ( )} ); })}
我们还应该 可放置对象 独特并将一些道具传递给我们的ItemList。
{myObject.map((obj, i) => {返回( {提供 => ( )} ); })}
这意味着我们还需要更新我们的项目列表以接受新的道具,同时我们也可以将其重构为一些更容易阅读的东西 - 我最终在我的组件文件夹中得到了两个新文件:
可拖拽.TSX
从“react”导入 { ReactElement,createElement };从“react-beautiful-dnd”导入 { Draggable };从“mendix”导入 { ObjectItem,ListWidgetValue };
导出接口 DraggableProps { item:ObjectItem;i:number;widgetList:ListWidgetValue;}
导出函数 DraggableItem({ item,i,widgetList}:DraggableProps):ReactElement { 返回( {提供 => ( {widgetList.get(item)} )} ); }
和 ItemList.TSX
从“react”导入 { ReactElement,createElement };从“mendix”导入 { ListValue,ListWidgetValue };从“./Draggable”导入 { DraggableItem };
导出接口 ItemListProps { items: ListValue; widgetList: ListWidgetValue; }
导出 const ItemList = ({ items, widgetList }: ItemListProps): ReactElement => { 返回( items && ( {items.items && items.items.map((item, i) => { 返回; })} ));};
造型
我们需要为看板添加一些样式,将其从丑小鸭变成美丽的天鹅。
我们将通过两种主要方式实现此目标:
- 利用 CSS 类和 'className' 属性 每个 dom 元素都有
- 利用内联样式来利用我们代码中计算的属性(即列宽)
首先,让我们将 Kanban.css 文件更新为
.kanban-col { 边距:15px; 填充:10px; 背景:#9bedff; 边框半径:15px; }
.kanban-col h6 { 文本对齐:居中; }
然后我们可以使用一些额外的 dom 元素、我们的类和样式,最终得到:
从 “./components/ItemList” 导入 { ItemList }; 从 “../typings/KanbanProps” 导入 { KanbanContainerProps };
导入“。/ui/Kanban.css”;
导出函数 Kanban({ myObject }: KanbanContainerProps): ReactElement {
函数 onDragEnd(result:DropResult):void { console.log(“拖动”); }
返回 ( {myObject.map((obj, i) => {返回( {提供 => ( {obj.sectionName} {提供的.占位符} )} ); })} ); }
等一下……那是什么? ${100/myObject.length}%
这是一个 模板字面量 它允许您在 JS 中快速构建字符串,通过在“中编写字符串并在${} 中包装表达式。
但回到小部件...我们现在有了用户定义的列,以及项目。

拖放操作
现在我们希望我们的拖放功能能够真正发挥作用……

为了实现这一点,我们将利用可插拔小部件操作 API 与数据源结合。
首先,让我们在 Kanban.xml 中为对象添加两个新属性
部分项目项目列表所有商品项目列表
通过将我们的操作链接到数据源,我们可以使用来自的参数执行操作 Mendix。这将允许我们改变物品的属性。
现在我们可以更新我们的“onDragEnd”函数来执行动作。
通过阅读 react-beautiful-dnd 的文档,我们可以看到 onDragEnd 接收一个结果对象,该对象允许我们访问 draggableId 和 droppableID。我们可以使用这些来确定正在拖动的项目是什么以及它被拖动到哪里(以及我们需要调用什么操作)。
首先要做的是解构我们的结果并找到正确的对象:
const { 源, 目标 } = 结果; const destObj = myObject[parseInt(destination.droppableId, 10)];
这里我们解析 droppableId(我们将其设置为对象的索引),并使用它来从 我的对象 数组。
一旦我们有了正确的对象,我们就可以通过从列表中找到它来获得被拖动项目的表示。 所有商品.
const destObjItem = destObj.allItems.items!.find(item => { return item.id === result.draggableId; });
注意,我们需要为每个对象指定 allItems,因为只能对属于同一对象的项目调用操作。
然后我们可以对对象执行一个操作:
const actionOnObj = destObj.action!.get(destObjItem!); if (actionOnObj.canExecute) { actionOnObj.execute(); }
我们进行一些检查以确保该项目被拖到列表中,我们的最终函数应如下所示:
函数 onDragEnd(result: DropResult): void { const { source, destination } = result; if (!destination) { return; } const destObj = myObject[parseInt(destination.droppableId, 10)]; const destObjItem = destObj.allItems.items!.find(item => { return item.id === result.draggableId; }); const actionOnObj = destObj.action!.get(destObjItem!); if (actionOnObj.canExecute) { actionOnObj.execute(); } }
因此,这让我们拥有了一个可以正常工作的拖放式看板,我们只需要在 Studio Pro 中做几件事。首先,让我们添加一个包含各种状态的状态属性。

然后让我们为想要设置的每个状态添加一个纳米流。

现在让我们通过将项目设置为具有相关状态的所有项目和正确的放置操作来配置每个部分,并为 AllItems 添加指向数据库中所有项目的链接。

我们有拖放功能!

排序
我们快完成了,只剩一件事要做,那就是引入排序。为此,我们将使用一种有用的模式从可插拔小部件中获取数据。
理想情况下,我们会完全使用“项目”上的值来管理这一点,但目前 Pluggable Widget API 存在限制,即无法直接更改列表中对象的属性。这一限制将在路线图中被删除。与此同时,我们可以采取以下方法:
让我们首先将排序顺序存储在 Mendix 实体:项目。

在此过程中,我将更新我的 ASU,以便每次使用具有排序值的项目重新填充数据库:

回到我们的小部件,让我们更新 Kanban.xml 以获得一个新的“排序”属性组,我们可以使用它来存储拖动项目时先前和更新的排序索引。
新分类排序值存储上一排序排序值存储
然后我们可以更新我们的 onDrag 函数以将这些属性传递给我们的 Mendix 模型:
函数 onDragEnd(result:DropResult): void { const { source,destination } = result; if (!destination) { return; } const destObj = myObject[parseInt(destination.droppableId, 10)]; const destObjItem = destObj.allItems.items!.find(item => { return item.id === result.draggableId; });
prevSort.setValue(new Big (源.索引)) newSort.setValue(new Big (目标.索引))
const actionOnObj = destObj.action!.get(destObjItem!); if (actionOnObj.canExecute) { actionOnObj.execute(); } }
如果我们回到我们的 Mendix 模型我们可以使用这些值来构建我们的排序逻辑。
让我们建立一个实体来保存我们之前和新的排序值。

然后让我们将小部件包装在提供该实体的数据视图中:

然后我们可以配置我们的小部件以将排序值传递到实体中。

鉴于我们已将小部件包装在数据视图中,我们现在可以将 KanbanHelper 作为参数添加到 onDrag 执行的操作中。

我们在每个 onDrag 纳米流中做 3 件主要的事情:
- 线上商城 这个 起始状态 项目(这决定了项目是否已更改列,用于排序计算)
- 更新商品 达到新的地位和 新排序 折扣值
- 实施 排序逻辑 其余项目
对于排序逻辑,我们可以在 Sub nanoflow 中构建它:

我不会在这里详细介绍这是如何组合在一起的,但是 纳米流可以在 Github回购.
然后当我们运行应用程序并最后一次测试它时......

结语
现在我们有了一个可以唱歌跳舞的看板!我们使用了 react-beautiful-dnd 库, Mendix 小部件和一些简单的纳米流来构建功能齐全、用户可定义的看板。
该小部件的最终状态可以在这里找到: GitHub – joe-robertson-mx/kanban-widget
如果您能够实现这个看板并想展示您所构建的内容,请在下面的评论中分享。
接下来 我们将研究如何实施 地图 在我们的可插拔小部件中,并在此过程中探索如何使用复杂的外部库,以及在我们的构建中包含自定义文件。