Vuetify: [Feature Request]: Treeview - Is that possible when select parent node only get parent value, not all children value

Created on 25 Sep 2019  Â·  14Comments  Â·  Source: vuetifyjs/vuetify

Problem to solve

I have a countries & divisions select requirement which uses v-treeview implement, it's an awesome component, but I met a little bit confusion abt it:

assume the items data looks like:

const items = {
  id: 'AU',
  name: 'Australia',
  children: [{
    id: 'AU-VIC',
    name: 'Victoria',
  }...]
}

For example: The goal is when I select Australia, I only want to get ['AU'] as a result, not ['AU-VIC', 'AU-TSM'...], I know there's an independent type selection, but it lost parent-child relationship at the same time

Proposed solution

Add a select-type like leaf-grouped?

duplicate

Most helpful comment

@waynecz

As a workaround i used this:

<template>
                <v-treeview
                        v-if="!loading"
                        dense
                        v-model="tree"
                        :items="items"
                        :search="search"
                        :item-children="children"
                        selection-type="all" <---- this does the trick
                        open-on-click
                        selectable
                        transition
                        return-object
                        hoverable
                        item-key="name"
                />
</template>

selection-type="all" trick the library in avoiding the node filtering, therefore it returns all the selected node/subnode/subsubnodes...

and in the script:

  export default {
    name: 'FilteredTree',
    props: {
      items: {
        type: Array,
        default: () => []
      },
      children: {
        type: String,
        default: 'children'
      },
      icon: {
        type: String,
        default: 'mdi-help-rhombus'
      }
    },
    data() {
      return {
        tree: [],
      }
    },
    computed: {
      treeParents: function() { // this will return the nested array parent->children->children
        let tree = [...this.tree];
        // Filter tree with only parents of selections
        tree = tree.filter(elem => {
          for (let i = 0; i < tree.length; i++) {
            // Skip current element
            if (tree[i].id === elem.id) continue;

            // Check only elements with childrens
            if (tree[i][this.children]) {
              let item = this.findTreeItem([tree[i]], elem.id);
              // If current element is a children of another element, exclude from result
              if (item) {
                return false;
              }
            }
          }
          return true;
        });
        return tree;
      }
    },
    methods: {
      findTreeItem(items, id) {
        if (!items) {
          return;
        }

        for (const item of items) {
          // Test current object
          if (item.id === id) {
            return item;
          }

          // Test children recursively
          const child = this.findTreeItem(item[this.children], id);
          if (child) {
            return child;
          }
        }
      }
    }
  }

you can use treeParent.map(parent => parent.propertyyouwant) to retrieve only the parents.
Work like a charm, selecting the parent and selecting alla of its children give the same result.

All 14 comments

From what you described I thought you'd want to use independent mode but you say it does not fit your use case?

Could you perhaps illustrate what you need with a codepen?

I can explain what he wants cause now I'm trying to use v-treeview too. So, you can go to https://wwwendt.de/tech/fancytree/demo/sample-select.html and click "Select all" in the last example:
image
There are "Selected root keys", that are exactly the thing.

By the way, that JQuery library is a greate treeview plugin, you can look at it as a standard when you working on v-treeview. And also, it would be good to always check the existent solutions functionality _before_ implementing your own component and releasing it into production.

@nekosaur Thank you for the reply!

If I use independent, When I select Australia, I could still select Victoria and will get ['AU', 'AU-VIC'] as a result which actually these two values are contradictory (AU means the whole country, it already contains AU-VIC)

Hmm.

  • So then what _would_ you expect to happen if you first clicked on AU and then on AU-VIC? What would the selection be?
  • And conversely, what would you expect to happen if you only clicked on AU-VIC?

You will need to explain in more detail exactly how you expect this new mode to work.

@nekosaur

So then what would you expect to happen if you first clicked on AU and then on AU-VIC? What would the selection be?

When I clicked AU, the value should be ['AU'], then I clicked AU-VIC, the value should be ['AU-VIC'], as u can see, 'AU' been removed due to I choose its leaf

The requirement is that when I clicked AU, the value I get should not be ['AU-VIC' ...and other leafs], it should just ['AU'] only in this mode

Ah, I see. I guess that's a valid mode.

@nekosaur

So then what would you expect to happen if you first clicked on AU and then on AU-VIC? What would the selection be?

When I clicked AU, the value should be ['AU'], then I clicked AU-VIC, the value should be ['AU-VIC'], as u can see, 'AU' been removed due to I choose its leaf

Actually it should be different:

  1. You click AU → [AU] gets returned and both AU and all his children (and children's children...) are visually selected
  2. You click one of its children (which are all already "selected") → AU gets partial selection, return the list of the still selected AU children
  3. Same apply for subchildren

Basically it's the difference between what i select against what it visually means.

@waynecz

As a workaround i used this:

<template>
                <v-treeview
                        v-if="!loading"
                        dense
                        v-model="tree"
                        :items="items"
                        :search="search"
                        :item-children="children"
                        selection-type="all" <---- this does the trick
                        open-on-click
                        selectable
                        transition
                        return-object
                        hoverable
                        item-key="name"
                />
</template>

selection-type="all" trick the library in avoiding the node filtering, therefore it returns all the selected node/subnode/subsubnodes...

and in the script:

  export default {
    name: 'FilteredTree',
    props: {
      items: {
        type: Array,
        default: () => []
      },
      children: {
        type: String,
        default: 'children'
      },
      icon: {
        type: String,
        default: 'mdi-help-rhombus'
      }
    },
    data() {
      return {
        tree: [],
      }
    },
    computed: {
      treeParents: function() { // this will return the nested array parent->children->children
        let tree = [...this.tree];
        // Filter tree with only parents of selections
        tree = tree.filter(elem => {
          for (let i = 0; i < tree.length; i++) {
            // Skip current element
            if (tree[i].id === elem.id) continue;

            // Check only elements with childrens
            if (tree[i][this.children]) {
              let item = this.findTreeItem([tree[i]], elem.id);
              // If current element is a children of another element, exclude from result
              if (item) {
                return false;
              }
            }
          }
          return true;
        });
        return tree;
      }
    },
    methods: {
      findTreeItem(items, id) {
        if (!items) {
          return;
        }

        for (const item of items) {
          // Test current object
          if (item.id === id) {
            return item;
          }

          // Test children recursively
          const child = this.findTreeItem(item[this.children], id);
          if (child) {
            return child;
          }
        }
      }
    }
  }

you can use treeParent.map(parent => parent.propertyyouwant) to retrieve only the parents.
Work like a charm, selecting the parent and selecting alla of its children give the same result.

I am faced with the same issue described here. The parent node needs to be included in the selection and not just the children. Selection-type 'all' is working, however I get a console error "Invalid prop".

Thanks,

selection-type="all" gave me the output I was looking for as well.

@wbuc The "Invalid prop" console errors will not appear if NODE_ENV=production.
If you want to use NODE_ENV=development, e.g. for local development, this is a workaround/hack to prevent those error messages if they are a nuisance.

Vue.config.warnHandler = function (msg, vm, trace) {
  if (msg !== 'Invalid prop: custom validator check failed for prop "selectionType".') {
    const hasConsole = typeof console !== 'undefined';
    if (hasConsole && !Vue.config.silent) {
      console.error(`[Vue warn]: ${msg}${trace}`);
    }
  }
}

It works by overriding the Vue warnHandler, https://vuejs.org/v2/api/#warnHandler.
https://github.com/vuejs/vue/blob/dev/src/core/util/debug.js#L18-L26

Note: This is a workaround/hack that could break in the future say if vuetifyjs changes the name of the prop or Vue changes the prop validation error message.

Thanks for the great info @derekbekoe ! I'll give this a go for now.

@derekbekoe , just for info selection-type="all" works only because 'all' is outside of the expected values. It would work with everything outside the expected values, I used 'all' because it would be easier to understand, when reading the code.

It can surely break, since using an unexpected value triggers the "old" 1.5 return value... It would be nice, since the values are already there, if the "all" prop value would became "canon".

This does somehow break, when using async loading with selected children.

1) Select parent, that has children = [] which is supposed to be filled async.
2) Open parent after selecting, async load triggers, => treeParent returns too many elements and the parent that was selected now, is missing from the list.

It starts working after manually toggling one of the affected items.
Not sure whats going on there.

Any ideas?

Edit:
See https://github.com/vuetifyjs/vuetify/issues/8720, its probably enough to find the parent the data was async loaded on and then mark it as selected again if it was selected.

Duplicate of #6759

Was this page helpful?
0 / 5 - 0 ratings