问题描述
我使用 keystonejs 和 Postgresql 作为我的后端,Apollo 在我的应用程序的前端。 我有一个模式,它有一个链接到另一个列表的列表。 我希望能够允许用户更改第二个列表的顺序。
这是我的架构的简化版本
keystone.createList(
'forms',{
fields: {
name: {
type: Text,isrequired: true,},buttons: {
type: Relationship,ref: 'buttons.attached_forms',many: true,}
);
keystone.createList(
'buttons',attached_forms: {
type: Relationship,ref: 'forms.buttons',}
);
所以我想做的是允许用户更改按钮的顺序,以便我将来从表单中获取它们时:
const QUERY = gql`
query getForms($formId: ID!) {
allforms(where: {
id: $formId,}) {
id
name
buttons {
id
name
}
}
}
`;
按钮应该按照预定义的顺序从后端返回。
{
id: 1,name: 'Form 1',buttons: [
{
id: 1,name: 'Button 1',{
id: 3,name: 'Button 3',{
id: 2,name: 'Button 2',}
]
}
或者甚至只是通过查询返回一些数据,允许根据前端用户定义的排序顺序进行排序。
问题在于这种关系是多对多的。
因此,将一列添加到按钮架构是不够的,因为排序需要特定于关系。换句话说,如果用户将特定按钮放在特定表单的最后,则不应更改其他表单上相同按钮的顺序。
在我自己创建的后端中,我会向连接表中添加一些内容,例如 sortOrder
字段或类似字段,然后更改这些值以更改顺序,或者甚至使用它在前端对它们进行排序信息。
类似 this answer here 的东西。
多对多连接表将包含诸如 formId、buttonId、sortOrder 之类的列。
我一直在深入研究 keystonejs 的文档,但我无法找到一种方法来完成这项工作,而不会陷入覆盖我们正在使用的 KnexAdapter 的杂草中。
我正在使用:
{
"@keystonejs/adapter-knex": "^11.0.7","@keystonejs/app-admin-ui": "^7.3.11","@keystonejs/app-graphql": "^6.2.1","@keystonejs/fields": "^20.1.2","@keystonejs/keystone": "^17.1.2","@keystonejs/server-side-graphql-client": "^1.1.2",}
对我如何实现这一目标有任何想法吗?
解决方法
我曾经遇到过类似的挑战,因此经过一些研究并发现了这个 answer,我使用 PostgreSQL TRIGGER 为一个项目实施了一个解决方案。
因此您可以在更新时添加触发器,它应该移动 buttonOrder
。
这是我对我的 SQL,这是测试代码,我用正则表达式替换了术语以适合您的问题:)
// Assign order
await knex.raw(`
do $$
DECLARE form_id text;
begin
CREATE SEQUENCE buttons_order_seq;
CREATE VIEW buttons_view AS SELECT * FROM "buttons" ORDER BY "createdAt" ASC,"formId";
CREATE RULE buttons_rule AS ON UPDATE TO buttons_view DO INSTEAD UPDATE buttons SET order = NEW.order WHERE id = NEW.id;
FOR form_id IN SELECT id FROM form LOOP
ALTER SEQUENCE buttons_order_seq RESTART;
UPDATE buttons_view SET order = nextval('buttons_order_seq') WHERE "formId" = form_id;
END LOOP;
DROP SEQUENCE buttons_order_seq;
DROP RULE buttons_rule ON buttons_view;
DROP VIEW buttons_view;
END; $$`);
// Create function that shifts orders
await knex.raw(`
CREATE FUNCTION shift_buttons_order()
RETURNS trigger AS
$$
BEGIN
IF NEW.order < OLD.order THEN
UPDATE buttons SET order = order + 1,"shiftOrderFlag" = NOT "shiftOrderFlag"
WHERE order >= NEW.order AND order < OLD.order AND "formId" = OLD."formId";
ELSE
UPDATE buttons SET order = order - 1,"shiftOrderFlag" = NOT "shiftOrderFlag"
WHERE order <= NEW.order AND order > OLD.order AND "formId" = OLD."formId";
END IF;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql'`);
// Create trigger to shift orders on update
await knex.raw(`
CREATE TRIGGER shift_buttons_order BEFORE UPDATE OF order ON buttons FOR EACH ROW
WHEN (OLD."shiftOrderFlag" = NEW."shiftOrderFlag" AND OLD.order <> NEW.order)
EXECUTE PROCEDURE shift_buttons_order()`);
,
一种方法是拥有两个“按钮”列表,一个带有按钮模板(下面的buttonTemplate
),其中包含名称等公共数据,另一个(下面的button
)引用一个 buttonTemplate
和一个 form
。这允许您为每个 formIndex
分配一个 button
属性,该属性指示其在相应表单上的位置。
(未经测试)示例代码:
keystone.createList(
'Form',{
fields: {
name: {
type: Text,isRequired: true,},buttons: {
type: Relationship,ref: 'Button.form',many: true,}
);
keystone.createList(
'Button',{
fields: {
buttonTemplate: {
type: Relationship,ref: 'ButtonTemplate.buttons',many: false,form: {
type: Relationship,ref: 'Form.buttons',formIndex: {
type: Integer,}
);
keystone.createList(
'ButtonTemplate',ref: 'Button.buttonTemplate',}
);
我认为这比您的 buttonOrder
解决方案(例如用户删除此字段引用的按钮。
如果您决定采用这种方法,您可以使用 Keystone 中的 hook functionality 来防止此类问题。例如。在 button
为 deleted 之前,检查所有表单并重写 buttonOrder
字段,删除对已删除按钮的任何引用。
我们想到的一个选项是将订单添加到表单表中。
keystone.createList(
'forms',{
fields: {
name: {
type: Text,buttonOrder: {
type: Text,buttons: {
type: Relationship,ref: 'buttons.attached_forms',}
);
这个新字段 buttonOrder
可以包含按钮 Id 顺序的字符串表示,就像在 JSON 字符串化数组中一样。
这样做的主要问题是很难使该字段与实际链接的按钮保持同步。