Skip to content
Advertisement

Directive v-if doesn’t work when changing tab

I’m trying to build a simple listing system that shows a list of items for different platforms, each platform is on a seperate tab . I created the tab switching logic via VueJS from scratch .

What I’m doing:

Basically I have two platforms : twitter and facebook, when user click on one of the tabs, the frontend send an ajax request to my server to fetch posts for that platform and render them via v-for .

I added a button called edit for each post, when user press it , it calls a function edit(p), where p is the current post that user want to edit .

in edit(p) I change an atrribute p.editing which using v-if shows a text area and a timepicker (I’m using flatpicker) for that post .

What’s Wrong:

All this works fine when I’m on the first tab, but once I switch the tab, it stop working, after debugging I noticed that v-if is not working event p.editing is updated when edit(p) is called, here’s the code :

var posts_app = new Vue({
    el: "#posts_app",
    data: {
        platforms : ['facebook','twitter'],
        current_tab: {
            'facebook' : true,
            'twitter': false
        },
        platform_posts: {
            'facebook': [],
            'twitter': []
        },
        posts: undefined,
    },
    methods:{
        showTab: function(i){
            platform = this.platforms[i]
            // UI stuff : to make the clicked tab active
            for(p in this.current_tab){
                if(p == platform){
                    this.current_tab[p] = true
                }
                else{
                    this.current_tab[p] = false
                }
            }
            // Show content by platform
            this.posts = this.platform_posts[platform]
        },
        edit: function(p){
            p.editing = true
            console.log(p)
            Vue.nextTick(function(){
                document.getElementsByClassName("dt-input")[0].flatpickr({enableTime : true});
            })
        },
        save: function(p){
            p.editing = false
        }
    },
    created(){
        self = this
        posts_loaded = false

        for(var i = 0;i < this.platforms.length; i++){
            (function(index){           
                self.$http.get('/fan/posts',{params:{platform : self.platforms[index]}}).then(function(resp){
                self.platform_posts[self.platforms[index]] = resp.body
                posts_loaded = true

            })//Promise of Ajax call
            }// Closure body
            )(i)//Closure
        }

        this.showTab(0)
    },
    delimiters: ['[[',']]']

})

and my basic html template :

 <div class = "panel-body">
        <img class = "pull-right responsive" v-bind:src = "p.image"/>
        <textarea v-if = "p.editing" class = "post-text-input" v-model = "p.text"></textarea>
        <p class = "post-text" v-if = "!p.editing">[[p.text]]</p>
        <p class = "post-source" v-if = "p.type == 'article'"> Source : [[post_source(p)]]</p>
        <p class = "post-time"><b>Scheduled on <i v-if = "!p.editing">[[p.time]] </i></b>
            <input placeholder="Choose a date and a time" class = "flatpickr dt-input" v-model = "p.time" v-if = "p.editing" />
        </p>

       </div>
       <div class = "panel-footer clearfix">
         <button class = "btn btn-danger">Delete</button>
         <button class = "btn btn-info pull-right" @click = "edit(p)" v-if = "!p.editing">Edit</button>
         <button class = "btn btn-success pull-right" @click = "save(p)" v-if = "p.editing">Save</button>

       </div>

Code explanation:

So, when a tab is clicked, showTab(index) is called where index is the number of tab, if index is 0 then we switched to facebook tab, if it’s 1 then we’re in the twitter tab, we send an AJAX request to get the posts for that current platform (tab) and fill it in platform_posts[current_platform], we then render them via v-for . All of this is working like a charm .

Second part, when user click on the edit button for a given post, it replace the text paragraph element with a textarea using v-model to keep track of change and update the time paragraph with an input which acts as datetime picker via flatpickr library . Basically this lib can turn any input into a datetime pickr using this line of code :

elemnt.flatpickr({config_options})

Where element is an HTML element . You can notice that I’m using Vue.nextTick, this is to make sure that the input is not hidden anymore (it shouldn’t be since p.editing is updated) . All of this work like a charm when I’m on the first tab, the problem is that when I switch the tab it stop working .

Here’s a gif I made to show you the error : http://imgur.com/a/QME4P

As you can see, the behaviour is very weird, it works perfectly on the twitter tab and it’s weird on the facebook tab .

Advertisement

Answer

It looks like you’re breaking reactivity somewhere. Read about Reactivity. In short, you cannot mutate an array or add a new property to an already created object.

For example, instead of p.editing = true you can do:

this.$set(p, 'editing', true)

Another key concept is to replace an array instead of mutating an array.

I would recommend moving your get posts out of created() and into a method called by created(), as you may want to reuse it to pull posts again. Also you should set posts to an empty array instead of undefined.

User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement