cortex-cms / cortex

:pencil: A headless, multitenant dynamic content platform powered by Rails, GraphQL and Elasticsearch
https://docs.cortexcms.org/
Apache License 2.0
32 stars 6 forks source link

Refactor Tree FieldType #555

Open MKwenhua opened 6 years ago

MKwenhua commented 6 years ago

We need to create a data structure that could support saving and rendering data for UI elements like: http://experiments.wemakesites.net/css3-treeview-with-multiple-node-selection.html

Current Tree Field Type Data Structure:

Categories Field:

 name: "Categories",
 name_id: "categories",
 field_type: "tree_field_type",
 metadata:
  {"allowed_values"=>
    {"data"=>
      {"tree_array"=>
        [{"id"=>1, "node"=>{"id"=>1, "name"=>"Product News", "children"=>[]}, "children"=>[], "parent_id"=>nil},
         {"id"=>2, "node"=>{"id"=>2, "name"=>"Company News, Research and Trends", "children"=>[]}, "children"=>[], "parent_id"=>nil},
         {"id"=>3, "node"=>{"id"=>3, "name"=>"Client Success Stories", "children"=>[]}, "children"=>[], "parent_id"=>nil},
         {"id"=>4, "node"=>{"id"=>4, "name"=>"Recruiting Solutions", "children"=>[]}, "children"=>[], "parent_id"=>nil},
         {"id"=>5, "node"=>{"id"=>5, "name"=>"Employment Screening", "children"=>[]}, "children"=>[], "parent_id"=>nil},
         {"id"=>6, "node"=>{"id"=>6, "name"=>"Human Capital Management", "children"=>[]}, "children"=>[], "parent_id"=>nil}]}}},

Research Field:

  name: "Research",
  name_id: "research",
  field_type: "tree_field_type",
  metadata:
   {"allowed_values"=>
     {"data"=>
       {"tree_array"=>
         [{"id"=>1, "node"=>{"id"=>1, "name"=>"CB Research", "children"=>[]}, "children"=>[], "parent_id"=>nil},
          {"id"=>2, "node"=>{"id"=>2, "name"=>"Third Party Research", "children"=>[]}, "children"=>[], "parent_id"=>nil}]}}},
MKwenhua commented 6 years ago

@toastercup I thought of two general approaches that both support multidimensional storage but vary in shape and traversal complexity:

Rough Drafts

Multi Dimensional Array

{
  head: [
    { id: "0",  value: "accountant" },
    {id: "1",  value: "human resources" },
    { id: "2",  value: "software engineer",
      children: [
         {id: "2_0", value: "DBA"},
         {id: "2_1", value: "Gaming" },
         {id: "2_2", value: "Web", 
             children: [
                {id: "2_2_0", value: "Front End"},
                {id: "2_2_1", value: "Back End"},
                {id: "2_2_2", value: "Full Stack"}
             ]
          }
      ]
    }
  ]
}

Pros With this structure you have the advantage of easily traversing and having access to all ancestor nodes using the id which is made of their array index. Cons

Honestly in a half finished example of how to traverse I realized that the above is clearly the inferior data structure.

Flat Normalized Lookup Tree

{
   head: [
    "gvghvs",
    "nuewe",
    "uwuwu"
   ],
   tree_fields: {
     gvghvs: {value: "accountant"},
     nuewe: {value: "human resources" },
     uwuwu: {
         value: "software engineer",
         children:  ['zuzuz', 'dbdbd', 'gsgsg']
     },
    zuzuz: {value: "DBA"},
    dbdbd: { value: "Gaming" },
    gsgsg: {
      value: "Web",
      children: ["qqqq", "dddd", "oooo"]
    },
   qqqq: {
      value: "Front End"
    },
   dddd: { 
     value: "Back End"
    },
   oooo: {
     value: "Full Stack"
   }   
}

Pros

Cons

here values on FieldItem could be either just the ids ["uwuwu", "qqqq"] or an array of objects with the key value pair.

MKwenhua commented 6 years ago

@toastercup I was thinking that I should build either a Gem To Handle this new logic that would be owned by cbdr. Or just build a singleton class within Cortex.

Which do you think I should do?

toastercup commented 6 years ago

@MKwenhua I definitely like the flat tree approach - it definitely has some big advantages. However, both approaches suffer from the same problem - they require custom logic to create and parse the tree. Getting away from this is my top priority in this story - complexity of the data structure is secondary, but also important.

To this end, I'd like you to dig into the fully-tested RubyTree library and see if it's appropriate for our needs. It requires a single root node, but I think there are easy workarounds for this - either using the second level of the tree as the 'root level', or by initializing multiple root TreeNodes for each root node and putting together some simple logic to juggle them.

I'd also like to know what weakness in the frontend realm initially prompted your concern. Let me know what you think!

MKwenhua commented 6 years ago

@toastercup Ok I will look into RubyTree