在lsFusion中使用层次结构

图片

在各种应用中,经常需要实现对象的分层表示。 通常,这用于通过指定组对它们进行分类。 这些组形成一棵动态深度树,然后将其用于导航,数据聚合和参数设置。

在本文中,我将展示如何在开放和免费的lsFusion平台中实现此逻辑。

作为示例,让我们采用一个简单的逻辑,在该逻辑中,您需要实现产品的逻辑,这些产品被分组为形成动态深度层次结构的特定组。 在这种情况下,货物可以绑在树的中间节点上。

首先,根据标准方案,将Product组实体声明为具有编辑表单和列表的简单平面类:
CLASS Group '' ;
name '' = DATA ISTRING [ 50 ] (Group);

FORM group ''
OBJECTS g = Group PANEL
PROPERTIES (g) name

EDIT Group OBJECT g
;

FORM groups ''
OBJECTS g = Group
PROPERTIES (g) READONLY name
PROPERTIES (g) NEWSESSION NEW , EDIT , DELETE

LIST Group OBJECT g
;

NAVIGATOR {
NEW groups;
}

现在,让我们按组划分层次。 为此,我们引入一个属性,该属性将包含指向父组的链接:
parent = DATA Group (Group);
nameParent ' ' (Group g) = name(parent(g));

接下来,我们创建一个属性,该属性将递归确定两个组之间的关系:
level '' (Group child, Group parent) =
RECURSION 1l IF child IS Group AND parent = child
STEP 2l IF parent = parent($parent) MATERIALIZED ;

我不会在本文中描述RECURSION运算符的工作原理,但是level属性将返回2,即“相应方向树中子级父级之间的路径长度”。 MATERIALIZED表示平台应将其存储在单独的表中,在该表中,对于每对连接的节点,将在相应的列中具有级别值的单独记录。 更改树的结构后,该表将自动重新计数。

例如,对于这样的树:

图片

该表将如下所示:

图片

其中, key0是后代代码,而key1是父代码。 该表中的条目数将近似等于组数乘以树的平均深度。 这样的存储方案很有用,因为如果您需要计算组中的所有后代,则不必诉诸CTE请求,但是可以对该表使用通常的JOIN。

此外,基于构造的属性,可以计算组的规范名称:
canonicalName ' ' (Group group) =
GROUP CONCAT name(Group parent), ' / ' ORDER DESC level(group, parent) CHARWIDTH 50 ;

例如,对于上图中的Milk组,规范名称为All / Grocerries / Dairy / Milk 。 指定CHARWIDTH是为了告诉平台在构建接口时对此属性使用什么宽度(以字符为单位)。

现在,我们将扩展表单以查看和编辑具有新创建的属性的组:
EXTEND FORM group
PROPERTIES (g) nameParent, canonicalName
;

EXTEND FORM groups
PROPERTIES (g) READONLY nameParent, canonicalName
;

具有平面形式的组列表的表单将如下所示:

图片

组的逻辑完成后,添加实体Product
CLASS Product '' ;
name '' = DATA ISTRING [ 50 ] (Product);

创建一个产品链接到它所属的产品组:
group '' = DATA Group (Product);
canonicalNameGroup ' ' (Product p) = canonicalName(group(p));

最后,我们将创建一个用于输入商品的表单,该表单中将包含两个元素:一个分组树和一个商品列表。 对于选定的树组,列表中仅显示属于选定节点的任何后代的产品。 首先,声明一个表单,并向其中添加带有组列表的树:
FORM products ''
TREE groups g = Group PARENT parent
PROPERTIES READONLY name(g)
;

使用TREE命令,从Group类的对象创建 ,该树的层次结构由先前创建的属性确定。

将表单添加到导航器中:
NAVIGATOR {
NEW products;
}

在此示例中,将不通过单独的对话框而是直接在表单本身中进行商品的输入和编辑。 为此,请创建一个操作以参考所选组创建产品:
newProduct '' (Group g) {
NEW p = Product {
group(p) <- g;
}
}

现在,在先前创建的表单上,添加具有可编辑列的产品列表:
EXTEND FORM products
OBJECTS p = Product
PROPERTIES (p) name, canonicalNameGroup
FILTERS level(group(p), g)
;

抛出用于添加和删除商品的表单按钮:
EXTEND FORM products
PROPERTIES newProduct(g) DRAW p TOOLBAR , DELETE (p)
;

由于操作newProduct是为一组产品定义的,因此必须明确指示应将其添加到带有产品列表(p)的工具栏中。

仍然需要配置设计,以使树显示在左侧,产品列表显示在右侧,在它们之间有一个分隔符,您可以使用该分隔符调整对象的大小:
DESIGN products {
OBJECTS {
NEW pane {
type = SPLITH ;
fill = 1 ;
MOVE BOX ( TREE groups);
MOVE BOX (p);
}
}
}

最终形式如下所示:

图片

创建产品和组的层次结构后,通常需要在任何级别上设置一些参数。 此外,指定的层次结构越低,其值就越高。 例如,如果“ 乳品”组设置为30,而“ 牛奶”组设置为20,则应选择最后一个。

假设您要以这种方式定义高级选项。 为此,首先为该组创建适当的属性:
markup ', %' = DATA NUMERIC [ 10 , 2 ] (Group);

为了找到所需的值,只需将分组与最后一个值一起使用即可:
parentMarkup ' ( ), %' (Group child) =
GROUP LAST markup(Group parent) ORDER DESC level(child, parent) WHERE markup(parent);

转换为普通语言后,此表达式将按其距离的降序ORDER DESC级别(子级,子级,父级) )在顶部组( 父级Group )上找到( GROUP )最后一个标记LAST )。给定( WHERE标记(父级) )。 在这里,我想指出lsFusion如何与自然语言相对应。

将上面创建的属性添加到组树中的产品表单中:
EXTEND FORM products
PROPERTIES (g) markup, parentMarkup READONLY
;

假设需要直接为产品设置溢价,并且该溢价应高于该组的溢价。 为此,首先为产品创建主要属性:
dataMarkup ' , %' = DATA NUMERIC [ 10 , 2 ] (Product);

然后,我们声明一个属性,该属性将返回产品的溢价(如果已指定)或组的溢价:
markup ', %' (Product p) = OVERRIDE dataMarkup(p), parentMarkup(group(p));

之后,将两个属性添加到表单中:
EXTEND FORM products
PROPERTIES (p) dataMarkup, markup READONLY
;

为组和产品设置溢价的机制如下所示:

图片

结论


在上面的文章中,我们能够创建商品的逻辑,将它们组合成具有动态深度层次结构的组,还为用户提供了在任何级别设置溢价的能力。 所有这些花费了大约70行重要代码。 您可以尝试在线操作,也可以网站的相应部分 (“平台”标签)对代码进行更改。 这是您需要粘贴到适当字段中的整个源代码:

源代码
CLASS Group '' ;
name '' = DATA ISTRING [ 50 ] (Group);

FORM group ''
OBJECTS g = Group PANEL
PROPERTIES (g) name

EDIT Group OBJECT g
;

FORM groups ''
OBJECTS g = Group
PROPERTIES (g) READONLY name
PROPERTIES (g) NEWSESSION NEW , EDIT , DELETE

LIST Group OBJECT g
;

NAVIGATOR {
NEW groups;
}

parent = DATA Group (Group);
nameParent ' ' (Group g) = name(parent(g));

level '' (Group child, Group parent) =
RECURSION 1l IF child IS Group AND parent = child
STEP 2l IF parent = parent($parent) MATERIALIZED ;

canonicalName ' ' (Group group) =
GROUP CONCAT name(Group parent), ' / ' ORDER DESC level(group, parent) CHARWIDTH 50 ;

EXTEND FORM group
PROPERTIES (g) nameParent, canonicalName
;

EXTEND FORM groups
PROPERTIES (g) READONLY nameParent, canonicalName
;

CLASS Product '' ;
name '' = DATA ISTRING [ 50 ] (Product);

group '' = DATA Group (Product);
canonicalNameGroup ' ' (Product p) = canonicalName(group(p));

FORM products ''
TREE groups g = Group PARENT parent
PROPERTIES READONLY name(g)
;

NAVIGATOR {
NEW products;
}

newProduct '' (Group g) {
NEW p = Product {
group(p) <- g;
}
}
EXTEND FORM products
OBJECTS p = Product
PROPERTIES (p) name, canonicalNameGroup
FILTERS level(group(p), g)
;

EXTEND FORM products
PROPERTIES newProduct(g) DRAW p TOOLBAR , DELETE (p)
;

DESIGN products {
OBJECTS {
NEW pane {
type = SPLITH ;
fill = 1 ;
MOVE BOX ( TREE groups);
MOVE BOX (p);
}
}
}

markup ', %' = DATA NUMERIC [ 10 , 2 ] (Group);

parentMarkup ' ( ), %' (Group child) =
GROUP LAST markup(Group parent) ORDER DESC level(child, parent) WHERE markup(parent);

EXTEND FORM products
PROPERTIES (g) markup, parentMarkup READONLY
;

dataMarkup ' , %' = DATA NUMERIC [ 10 , 2 ] (Product);
markup ', %' (Product p) = OVERRIDE dataMarkup(p), parentMarkup(group(p));

EXTEND FORM products
PROPERTIES (p) dataMarkup, markup READONLY
;

通过向属性添加其他参数,可以修改上述模板并以各种方式使用。 例如,在ERP系统的一种实现中组和商品的保费不是以这种方式设置的,而是针对每种价格分别设置的。 而且,复杂度的实现与上述示例没有不同。

Source: https://habr.com/ru/post/zh-CN468047/


All Articles