The solution for resolving vue updates using checkboxes

I am currently working on creating a tree view with checkboxes where the parent node's state is always based on its children, including indeterminate values, and v-model it into an array. Here's what I have managed to do so far.

The issue arises when I select B2 (a leaf node) and expect its parent and grandparent to become indeterminate. However, this doesn't happen. I suspect that using v-if instead of v-show in the treeview children, combined with having the parent of B2 collapsed, might be causing this behavior.

However, I am puzzled as to why this could be problematic, given that I am only making changes to the data model and emitting events in a virtual DOM that does not directly impact the actual DOM. Does anyone have insights into why this might be happening?

Thank you!

https://jsfiddle.net/s8tkLeqp/

HTML

Your modified HTML code would go here...

JS

Your modified JS code would go here...

CSS

Your modified CSS code would go here...

Answer №1

It seems that the updateTree method you triggered requires a mounted component to function properly. As of now, there is nothing actively monitoring the checked property of B2, which is why nothing is being triggered.

One potential solution to at least trigger on toggling is:

watch : {
 'data.checked' :{
      handler: function(new_val, old_val) {
      if (this.data.value) {
        this.updateTree(new_val);
      }
    },
    immediate: true // This is equivalent to calling the handler in mounted
  }
},

Another approach would be to simply load empty components:

Vue.component('dd-treeview-inner', {
  template: $("#ddct-treeview-inner-template")[0],
  props: ['data', 'root', 'expanded'], // pass data.expanded
  data: function() {
    return {};
  },
  directives: {
    indeterminate: function(el, binding) {
      el.indeterminate = Boolean(binding.value);
    }
  },
  watch: {
    'data.checked': {
      handler: function(new_val, old_val) {
        if (this.data.value) {
          this.updateTree(new_val);
        }
      },
      /* immediate: true */
    }
  },
  methods: {
    clickToggle: function() {
      this.data.expanded = !this.data.expanded;
    },
    updateTree: function(state) {
      this.data.indeterminate = false;
      this.propDown(state);
      this.$emit('change');
    },
    propDown: function(state) {
      this.data.children.map(function(child) {
        child.checked = state;
        child.indeterminate = false;
        child.propDown(state);
      });
    },
    propUp: function() {
      var children = this.data.children;
      var checked = 0
      var indeterminate = 0;
      children.map(function(child) {
        if (child.checked && !child.indeterminate) checked++;
        if (child.indeterminate) indeterminate++;
      });
      if (indeterminate > 0) {
        this.data.checked = true;
        this.data.indeterminate = true;
      } else if (checked == 0) {
        this.data.checked = false;
        this.data.indeterminate = false;
      } else if (checked == children.length) {
        this.data.checked = true;
        this.data.indeterminate = false;
      } else {
        this.data.checked = true;
        this.data.indeterminate = true;
      }
      this.$emit('change');
    }
  }
});

Vue.component('dd-treeview', {
  template: $("#ddct-treeview-template")[0],
  props: ['value', 'data'],
  watch: {
    value: function(new_val, old_val) {
      this.setValues(new_val);
    }
  },
  data: function() {
    return {};
  },
  mounted: function() {
    this.setValues(this.value);
  },
  methods: {
    setValues: function(values) {
      values = values.map(x => x.toLowerCase());

      function ff(node) {
        if (node.value) {
          node.checked = values.indexOf(node.value.toLowerCase()) != -1;
          node.indeterminate = false;
        }
        node.children.map(ff);
      }
      ff(this.data);
    },
    change: function() {
      var arr = [];

      function ff(node) {
        if (node.value && node.checked && !node.indeterminate) {
          arr.push(node.value);
        }
        node.children.map(ff);
      }
      ff(this.data);
      this.$emit('input', arr);
    }
  }
});

new Vue({
  el: $("#app")[0],
  data: {
    treeVals: ["B2"],
    data: {
      name: "ROOT",
      collapsible: true,
      expanded: true,
      checked: false,
      indeterminate: false,
      children: [{
          name: "A",
          collapsible: true,
          expanded: false,
          checked: false,
          indeterminate: false,
          children: [{
              name: "A1",
              children: [],
              checked: false,
              indeterminate: false,
              value: "A1"
            },
            {
              name: "A2",
              children: [],
              checked: false,
              indeterminate: false,
              value: "A2"
            }
          ]
        },
        {
          name: "B",
          collapsible: true,
          expanded: false,
          checked: false,
          indeterminate: false,
          children: [{
              name: "B1",
              children: [],
              checked: false,
              indeterminate: false,
              value: "B1"
            },
            {
              name: "B2",
              children: [],
              checked: false,
              indeterminate: false,
              value: "B2"
            }
          ]
        }
      ]
    }
  }
});
.ddct_toggle {
  position: relative;
}

.ddct_toggle::before {
  content: '+';
  display: block;
  position: absolute;
  top: 0;
  left: -30px;
  border: 1px solid red;
  border-radius: 50%;
  width: 24px;
  text-align: center;
  transition: all 0.4s;
  cursor: pointer;
}

.ddct_toggle.ddct_collapse_toggle::before {
  content: '-';
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<!DOCTYPE html>
<html>

<head>
  <title>Title of the document</title>
  <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

</head>

<body>
  <template id="ddct-treeview-template">
            <dd-treeview-inner :expanded="true" v-bind:data="data" v-bind:root="0" v-on:change="change"></dd-treeview-inner>
        </template>

  <template id="ddct-treeview-inner-template">
            <div v-if="expanded" :style="{'marginLeft':root>0 || data.collapsible ? '30px' : '0', 'marginBottom' : '4px'}">
                <span v-if="data.collapsible" @click="clickToggle" :class="['ddct_toggle', {'ddct_collapse_toggle' : !data.expanded}]"></span>
                <input type="checkbox" v-model="data.checked" v-indeterminate="data.indeterminate"> <span>{{data.name}}</span>
                <div :class="data.class">
                    <dd-treeview-inner :expanded="data.expanded" v-for="child in data.children" v-bind:data="child" v-bind:root="root+1" v-on:change="propUp"></dd-treeview-inner>
                </div>
            </div>
            <div v-else>
            <dd-treeview-inner :expanded="data.expanded" v-for="child in data.children" v-bind:data="child" v-bind:root="root+1" v-on:change="propUp"></dd-treeview-inner>
            </div>
        </template>

  <div id="app">
    <dd-treeview v-bind:data="data" v-model="treeVals"></dd-treeview>
    <div v-for="val in treeVals">{{val}}</div>
  </div>

</body>

</html>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What strategies can be employed to enhance the natural movement of dots on my HTML canvas?

Check out my code pen here I'm looking for feedback on how to make the movement of the canvas dots more fluid and liquid-like. I've experimented with restricting the direction for a certain number of renders (draw()), which has shown some impro ...

select items using a dropdown menu in an Angular application

Let me describe a scenario where I am facing an issue. I have created an HTML table with certain elements and a drop-down list Click here for image illustration When the user selects in, only records with type in should be displayed Another image refere ...

Updating the select input value in Vuetify using the prepend-item event

Is there a way to update the select value in a Vuetify select by clicking on a specific item that is prepended? I'm having an issue where the model doesn't update the item-text in the select input. <v-select :items="loadedTasks" v- ...

The power of MobX lies in its ability to provide a deep

I'm currently delving into the concept of deep observability in MobX. I'm looking for insights on why the autorun function is not being triggered every time I call setCommentCountForPost in the code snippet below. Can someone point me in the rig ...

I am looking for a highly specialized jQuery selector that fits my exact requirements

Hello everyone, I'm encountering an issue with a jQuery selector. Here is the HTML code snippet: <div id="Id_Province_chzn"> <a href="javascript:void(0)" class="chzn-single chzn-default" tabindex="-1"> <span> + this.default_tex ...

Step-by-step guide: Mocking an image using a fixture in Cypress

I'm currently utilizing cypress to run tests on my VueJS application. One issue I am facing involves simulating the display of an image on a page. Specifically, I am trying to load a user profile using the following code snippet: describe('Test ...

Tips for maintaining a loading screen for longer than 4 seconds?

I want to maintain the loading screen for a minimum of 4 seconds. However, the timer at the end does not seem to be functioning as expected. Here is the code snippet I am using: window.addEventListener("load", () => { const preload = document.querySe ...

Ensure that you call setState prior to invoking any other functions

Is there a way to ensure that the setSearchedMovie function completes before executing the fetchSearchedMovie(searchedMovie) function? const { updateMovies, searchedMovie, setSearchedMovie } = useContext(MoviesContext); const fetchMoviesList = (ev ...

Issues arise with routing when specific route parameters are implemented

After setting a route parameter in my browser URL, I encountered errors with the routing of the public folder (which contains my CSS, JS, etc.). The app's structure is as follows: app | |-- public | └-- css | └-- profile.css | |-- ...

Combining Vue.js single file components with traditional components

I am attempting to integrate Vue.js single file components with the traditional style of components that I already have existing code for. However, I'm not sure what these traditional components are called. main.js import Vue from 'vue' im ...

Storing data from PHP in Local Storage using JavaScript variable

When a specific build name is clicked, the inner HTML content is captured and assigned to a JavaScript variable called loadDump. This variable is then sent over to PHP via an AJAX request. $.ajax({ url:"http://custom-assembly.tcad.co.uk/wp-content/t ...

Encountered an error in production mode with Angular 7: Uncaught ReferenceError - "environment" variable

During development, my application runs smoothly, and ng build --prod --source-map successfully compiles the application. However, when attempting to access it through the browser, an error occurs: app.module.ts:47 Uncaught ReferenceError: env is not defi ...

After fetching, null is returned; however, refreshing the page results in the object being returned. What is the

I have a specific case where I am dealing with an array of 500 post IDs and I need to retrieve the first 100 posts. To achieve this, I implemented the following code: API https://github.com/HackerNews/API BASE_URL = 'https://hacker-news.firebaseio.com ...

Trap mistakes while utilizing async/await

Within my Express application, I have a register function for creating new users. This function involves creating the user in Auth0, sending an email, and responding to the client. I am looking to handle errors from Auth0 or Postmark individually and send ...

Using Sinonjs fakeserver to handle numerous ajax requests

I utilize QUnit in combination with sinon. Is there a way to make sinon's fakeserver respond to multiple chained ajax calls triggered from the same method? module('demo', { beforeEach: function(){ this.server = sinon.fakeServer. ...

How can I keep the cursor from automatically moving to the beginning of an input field when the type changes?

Currently in the process of creating a Password field that allows users to hide or display their password in React/Ionic. I am aiming to maintain focus on the password field when switching the input type, similar to how it is done on the PayPal Login page ...

Having trouble with the nav-item dropdown functionality on Laravel Vue?

Currently utilizing Laravel and Vue.js along with AdminLTE. Initially it was functioning properly, but now... head <!-- Font Awesome Icons --> <link rel="stylesheet" href="plugins/fontawesome-free/css/all.min.css"> <!-- Theme style --> ...

An issue occurred while evaluating the Pre-request Script: Unable to access the 'get' property of an undefined object

I need help accessing the response of my POST request in Postman using a Pre-request Script. Script below : var mobiles = postman.environment.get("mobiles"); if (!mobiles) { mobiles =["8824444866","8058506668"]; } var currentMobile = mobiles. ...

How can I use Three.JS TWEEN to animate object movement between two objects at a

I'm working on a game where an object moves towards other objects. new TWEEN.Tween( object.position ).to({ x: Math.position = pointX, z: Math.position.z = pointZ }).easing( TWEEN.Easing.Linear.None).start(); However, I've encountered a pr ...

What is the process for triggering events when the previous and next buttons are clicked?

Within the jQuery fullcalendar, there are previous and next buttons. Is there a way to trigger specific events when these buttons are clicked? ...