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.