Yomguithereal / baobab

JavaScript & TypeScript persistent and optionally immutable data tree with cursors.
MIT License
3.15k stars 117 forks source link

Discussion / Feature request references to branches? #368

Closed SaphuA closed 9 years ago

SaphuA commented 9 years ago

So I'm migrating my React app to baobab, but I'm having issues with finding/storing a reference to the currently selected item. I don't think this is supported in baobab?

This is my tree:

{
    "selectedItem": null,
    "items": [
        { "name": "a"},
        { "name": "b"},
        { "name": "c"}
    ]
};

Now, currently, in my app whenever an item is selected selectedItem is set to a reference to it. That way I can do tree.selectedItem.name = "x";

I know this would validate the rules of a tree, but I think this is a feature many applications would benefit from.

The only alternative I can think of would be to somehow find/generate a path to the item and store that in selectedItem so I can do tree.select(tree.select("selectedItem").get()), but that would be very hard to do with the complexity of my tree.

saulshanabrook commented 9 years ago

I see a couple of options here. I think what you store under selectedItem depends on how you store references to your items. If you can guarantee the name to be unique, you can just store that value there, like this:

{
    "selectedItem": "b",
    "items": [
        { "name": "a"},
        { "name": "b"},
        { "name": "c"}
    ]
};

If you know the order won't change, you could just save the index:

{
    "selectedItem": 1,
    "items": [
        { "name": "a"},
        { "name": "b"},
        { "name": "c"}
    ]
};

Or if you need some unique ID, I would create a UUID for each item and reference it by that.

{
    "selectedItem": "4470dfd3-959e-4145-9351-11f39767ce69",
    "items": [
        { "name": "a", "uuid": "0ca9f890-1a3d-46e9-9d26-7fbfca474b9c"},
        { "name": "b", "uuid": "4470dfd3-959e-4145-9351-11f39767ce69"},
        { "name": "c", "uuid": "2aca55a3-92ac-4ae1-8fa0-e078e74a7732"}
    ]
};

Is that what you are asking about?

MichaelOstermann commented 9 years ago

It's also possible to use a simple monkey for this:

{
    "selectedItemName": "b",
    "selectedItem": Baobab.monkey(
        ["selectedItemName"],
        function(name) {
            // find and return the actual object from "items"
        }
    ),
    "items": [
        { "name": "a"},
        { "name": "b"},
        { "name": "c"}
    ]
};

Now you've got an always up to date cursor for all views, without having to implement that logic in each view, you just grab the selectedItem cursor.

Personally, I'm using some sort of external helpers, because some things are a bit more complex as you said and I'm not a big fan of trying to dump everything into monkeys - eg. in my example you can only use the "name" property, what if you want to be able to select an item either by "name" or by "xxx", for whatever reason?

As a little food for thought, here is an example:

var Example = new Tree({
    state: {
        selectedItem: null,
        items: []
    },
    selectItem: function(options) {
         // do what you want here and update the "selectedItem" cursor
    }
});

Hope that helps! :)

SaphuA commented 9 years ago

Thanks for the responses @saulshanabrook and @MichaelOstermann

I obviously should have explained the issue a bit clearer. Your solutions work fine with a simple list, but are troublesome when working with a dynamic tree of data. Say each item can have children which can also have children etc. This will change things quite a bit, no? The only solution I can think of is to store a path to the item by traversing all the parents. Not ideal.

{
  "selectedItem": null,
  "items": [
    {
      "name": "a",
      "children": [
        {
          "name": "aa",
          "children": [
            {
              "name": "aaa",
              "children": [ ]
            }
          ]
        }
      ]
    },
    {
      "name": "b",
      "children": [
        {
          "name": "ba",
          "children": [ ]
        }
      ]
    },
    {
      "name": "c",
      "children": [
        {
          "name": "ca",
          "children": [ ]
        }
      ]
    }
  ]
};
MichaelOstermann commented 9 years ago

There is nothing stopping you from doing something like

{
    "selectedItemPath": null,
    "selectedItem": Baobab.monkey(["selectedItemPath"], function(path) {
        // as before, find the nested child
    })
}

Now you could set the selectedItemPath property to a.aa.aaa for example. You could also return an array of items here instead of just the last child, so in this case the a, aa and aaa objects, if you want the parent objects to be considered to be selected as well!

Personally I would still go for the second example I showed you, meaning external helper methods that accept options as parameters, because for example you probably want the selectedItem to be updated in case the original child object changes, which you can not do atm, since you currently can not return cursors in a monkey.

SaphuA commented 9 years ago

But that is exactly what I was hoping to avoid ;) Thanks anyway!

saulshanabrook commented 9 years ago

I have used this strategy before. Why do you want to avoid this?

SaphuA commented 9 years ago

Generating a path requires that every item is uniquely identifiable. My app currently does not require this, which is also why I wanted to use Baobab: so that leaves can be updated by passing cursors as reference.

I know it's trivial to generate id's and paths. I just wished I'd be able to avoid coding/maintaining that bit. That's all.

saulshanabrook commented 9 years ago

In your current app, how do you store the selected item?

SaphuA commented 9 years ago

ID's :wink:

Btw, I found out that Baobab's cursors have hash and/or path property that could, in some cases, be used for the recommended solutions.

Yomguithereal commented 9 years ago

Hello everyone. Dropping a bit late. Reading the full thread to see if I can be of some help.

Yomguithereal commented 9 years ago

@SaphuA: you might be interested by #353 and its proposition of tree/cursor.find that would operate a selection on the tree based on a finder function like the locate unix command would. The method would define a dynamic cursor recursively searching the tree for an item returning true to the given predicate function.

However, this is not the more performant way to go and storing a path/hash etc. will always produce more performant results.

SaphuA commented 9 years ago

I'm closing this because I'm not expecting this to ever get implemented. Using paths is an ok alternative.