1
votes

I'm a total newbie in VueJS. I've been working on customizing a tree view example from the vuejs docs: Example.

On selecting an item in the treeview, I'm not able to understand how to unselect i.e. unset the class of the previously selected item. Some approaches I've tried include

  • Setting a global variable using Vue.prototype and accessing it in the computed function in which case the computed function doesn't even run.
  • I'm aware of the event object that is passed. Using that and jQuery, removing the class of the previously selected div would work but that seems like a hack.
  • Setting an array of selected items in data on the click event and accessing it in the computed function. This also does not work.

Is there a way that would work or am I not understanding something?

The codepen link that I'm working on: Codepen. For selecting a node, just click on the node and try selecting some other node. The previous node doesn't get cleared.

Thanks!

Update:

The below answer works but it would remove the selected class if clicked somewhere else. I wanted a solution where the selected class would only be removed if I clicked on some other node. All I had to do was create an Event Bus and store the previously selected component object in a parent variable. On clicking a new node, a global event would be emitted which would be listened to by the main instance method. There, it would set a boolean value which would unset the previous component selection and another boolean value to set the selected class to the new component object. I'm not sure if a better way exists.

Updated codepen with some changes: CodePen link

1

1 Answers

1
votes

It's nothing to do with VueJS, We have to play with CSS by setting the required css properties when the folder node is focused.

//https://github.com/vuejs/Discussion/issues/356
// demo data
Vue.prototype.$selectedNode = []
var data = {
  name: 'My Tree',
  children: [{
      name: 'hello'
    },
    {
      name: 'wat'
    },
    {
      name: 'child folder',
      children: [{
          name: 'child folder',
          children: [{
              name: 'hello'
            },
            {
              name: 'wat'
            }
          ]
        },
        {
          name: 'hello'
        },
        {
          name: 'wat'
        },
        {
          name: 'child folder',
          children: [{
              name: 'hello'
            },
            {
              name: 'wat'
            }
          ]
        }
      ]
    }
  ]
}

// define the item component
Vue.component('item', {
  template: '#item-template',
  props: {
    model: Object
  },
  data: function() {
    return {
      open: false,
      selectedNode: []
    }
  },
  computed: {
    isFolder: function() {
      return this.model.children &&
        this.model.children.length
    },
    setChevronClass: function() {
      return {
        opened: this.isFolder && this.open,
        closed: this.isFolder && !this.open,
        folderChevronSpan: this.isFolder
      }
    },
    setSelected: function() {
      if (this.selectedNode.length > 0 && this.selectedNode[0].title == this.model.name)
        return true;
      else
        return false;
    }
  },
  methods: {
    toggle: function() {
      if (this.isFolder) {
        this.open = !this.open
        this.$refs.toggler.focus();
      }
    },
    changeType: function() {
      if (!this.isFolder) {
        Vue.set(this.model, 'children', [])
        this.addChild()
        this.open = true
      }
    },
    addChild: function() {
      this.model.children.push({
        name: 'new stuff'
      })
    },
    selectNode: function() {
      this.selectedNode = [];
      this.selectedNode.push({
        'title': this.model.name,
        'isSelected': true
      });
    }
  }
})

// boot up the demo
var demo = new Vue({
  el: '#demo',
  data: {
    treeData: data
  }
})
body {
  font-family: Menlo, Consolas, monospace;
  color: #444;
}

.item {
  cursor: pointer;
}

.folderTitleSpan:hover {
  font-weight: bold;
  border: 1px solid darkblue;
}

.folderTitleSpan:focus,
li span:nth-child(1):focus+.folderTitleSpan {
  background-color: darkblue;
  color: white;
}

.node,
.add {
  list-style-type: none;
  padding-left: 10px !important;
}

.folderChevronSpan::before {
  color: #444;
  content: '\25b6';
  font-size: 10px;
  margin-left: -1em;
  position: absolute;
  transition: -webkit-transform .1s ease;
  transition: transform .1s ease;
  transition: transform .1s ease, -webkit-transform .1s ease;
  -webkit-transition: -webkit-transform .1s ease;
}

.folderChevronSpan.opened::before {
  transform: rotate(90deg);
  -webkit-transform: rotate(90deg);
}

ul {
  padding-left: 1em;
  line-height: 1.5em;
  list-style-type: dot;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17-beta.0/vue.js"></script>
<!-- item template -->
<script type="text/x-template" id="item-template">
  <li>
    <span :class="setChevronClass" tabindex="0" ref="toggler" @click="toggle">  
  </span>
    <span @click="selectNode" tabindex="1" :class="{folderTitleSpan: isFolder}">
      {{ model.name }}
      </span>
    <span v-if="isFolder">[{{ open ? '-' : '+' }}]</span>

    <ul v-show="open" v-if="isFolder">
      <item class="item node" v-for="(model, index) in model.children" :key="index" :model="model">
      </item>
      <li class="add" @click="addChild">+</li>
    </ul>
  </li>
</script>

<p>(You can double click on an item to turn it into a folder.)</p>

<!-- the demo root element -->
<ul id="demo">
  <item class="item node" :model="treeData">
  </item>
</ul>