Mike Barwick Mike Barwick - 1 year ago 225
Javascript Question

Vue2 - Using :checked on checkbox input breaks default behaviour

I'm doing a "status update" component of sorts, in my actual component I'm marking the first custom checkbox (the circles at top, etc.) as

checked
...but for the CodePen below, I just marked them all as
:checked='false'
to iterate my issue.

The "twitter" checkboxes won't check and uncheck. The Facebook ones will.

I'm certain this has to do with the fact that I'm enabling/disabling a character counter on the twitter checkboxes. If you click one of the twitter checkboxes, you'll notice the character counter turn on, but the checkbox is never checked (or rather, is checked then immediately unchecked)...

For example, in the method
toggleMaxCharLength()
, if I comment out
self.enableMaxCharLength = true;
, the checkbox work as they should.

If I remove the
:checked='false'
from the input
v-for
, works as it should...

https://codepen.io/mikebarwick/pen/qXdqBO

Answer Source

Checking the twitter accounts causes a re-render, which, of course, sets your checked value to false.

You need to remember the checked value. I made a couple small modifications that will do so, but there are other ways.

<input type="checkbox"
       :ref="key"
       :name="scheduleUsingBuffer ? 'buffer_profiles[]' : key + '[]'" 
       :value="scheduleUsingBuffer ? account.profile_id : account.page_id"                              
       :checked="account.checked"   
       @change="handleAccountInputChange(key, account)">

And

handleAccountInputChange(type, account) {
  this.$set(account, 'checked', !account.checked)
  if (type == 'twitter') {                  
    this.toggleMaxCharLength();
  }
},

Updated pen.

Another way to avoid this would be to abstract the checkboxes into their own components that remember their state so that when the parent re-renders, the state of the checkboxes is not overwritten.

Also, the pen converted into an SO snippet.

var accounts = {
  facebook: {
    testing1: {
      page_id: '23derf56hg',
      img_url: null
    }
  },
  twitter: {
    testing2: {
      page_id: 'fr2wlfrhoi',
      img_url: null
    },
    testing3: {
      page_id: '92frgl5639',
      img_url: null
    }
  }
}

var app = new Vue({
  el: '#app',
  mounted(){
    setTimeout(() => {
      Object.keys(accounts).forEach((site, siteIndex) => {
        Object.keys(accounts[site]).forEach((account, actIndex) =>{
          accounts[site][account]["checked"] = (siteIndex === 0 && actIndex === 0)
        })
      })
      console.log(accounts)
      this.connectedAccounts = accounts
    }, 100)
  },
  data: {
    message: 'Hello Vue!',
    connectedAccounts: [],
    scheduleUsingBuffer: false,
    formData: {},
    enableMaxCharLength: false,
    maxCharCount: 140,
    remainingCharCount: 140,
    isAboveMaxCharCount: false,
    statusMessage: ''
  },
  
  methods: {
    onSubmit(event) {
		  this.formData = serialize(event.target, { hash: true });
		},
    
    toggleMaxCharLength() {
			this.enableMaxCharLength = false;

			Vue.nextTick(() => {
				var self = this;

				this.$refs.twitter.forEach(twitterInput => {
				   	if (twitterInput.checked) {
				   		self.enableMaxCharLength = true;
				   	}
				});
			});		
    },
    
    handleAccountInputChange(type, account) {
      this.$set(account, 'checked', !account.checked)
			if (type == 'twitter') {					
				this.toggleMaxCharLength();
			}
		},

		countdown() {
		 	this.remainingCharCount = this.maxCharCount - this.statusMessage.length;
		  this.isAboveMaxCharCount = this.remainingCharCount < 0;
		}    
  }
})
section.create-story {
  margin: 30px auto;
  width: 425px;
}
section.create-story h4 {
  margin-bottom: 15px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: normal;
  font-size: 14px;
}
section.create-story .switch {
  display: block;
  margin-bottom: 20px;
}
section.create-story #connected-accounts {
  margin-bottom: 15px;
}
section.create-story #connected-accounts label.account-checkbox {
  position: relative;
  display: inline-block;
  margin: 0 5px 0px 0;
  cursor: pointer;
}
section.create-story #connected-accounts label.account-checkbox .avatar {
  position: relative;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background-size: cover !important;
  background-position: center !important;
  background-color: #CCC !important;
  border: 2px solid #CCC;
  transition: all 0.1s ease-in-out;
}
section.create-story #connected-accounts label.account-checkbox .avatar,
section.create-story #connected-accounts label.account-checkbox .account-icon {
  filter: grayscale(100%);
  opacity: 0.3;
}
section.create-story #connected-accounts label.account-checkbox .avatar:hover,
section.create-story #connected-accounts label.account-checkbox .account-icon:hover {
  opacity: 1;
}
section.create-story #connected-accounts label.account-checkbox input[type="checkbox"] {
  display: none;
}
section.create-story #connected-accounts label.account-checkbox input[type="checkbox"]:checked + .avatar {
  border: 2px solid green;
}
section.create-story #connected-accounts label.account-checkbox input[type="checkbox"]:checked + .avatar + .account-icon {
  color: green;
}
section.create-story #connected-accounts label.account-checkbox input[type="checkbox"]:checked + .avatar, section.create-story #connected-accounts label.account-checkbox input[type="checkbox"]:checked + .avatar + .account-icon {
  filter: grayscale(0);
  opacity: 1;
}
section.create-story #connected-accounts label.account-checkbox .account-icon {
  position: absolute;
  font-size: 13px;
  width: 24px;
  height: 24px;
  line-height: 24px;
  right: -4px;
  bottom: -4px;
  background: white;
  color: #4d4d4d;
  border-radius: 50%;
  pointer-events: none;
  box-shadow: 0 1px 0 0px rgba(49, 49, 93, 0.05), 0 2px 3px 0 rgba(49, 49, 93, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.1);
}
section.create-story #status {
  margin-bottom: 20px;
  padding: 15px;
  border-radius: 5px;
  border: 1px solid #4d4d4d;
}
section.create-story #status textarea {
  display: block;
  margin-bottom: 15px;
  padding: 0;
  font-style: italic;
  font-size: 14px;
  min-height: 60px;
  border: none;
  box-shadow: none;
}
section.create-story #status .remaining-chars.has-text-danger {
  font-weight: bold;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
  <section class="create-story box content">
	  <h4>New Story</h4>

		<form id="new-story" v-on:submit.prevent="onSubmit">
		  <div id="status">
			  <div id="connected-accounts">
							<span v-for="(accounts, key, index) in connectedAccounts">
								<label v-for="(account, i) in accounts" class="account-checkbox">
                  <input type="checkbox"
                         :key="key"
                    :ref="key"
                    :name="scheduleUsingBuffer ? 'buffer_profiles[]' : key + '[]'" 
                    :value="scheduleUsingBuffer ? account.profile_id : account.page_id"				                
                    :checked="account.checked" 	
                    @change="handleAccountInputChange(key, account)"> <!-- mark first account as "checked" :checked="index == 0 && i == 0" -->
                  
                    <div 
                      class="avatar"
                      :style="'background: url(' + account.img_url + ')'">
                    </div>

                      <i :class="'account-icon fa fa-' + key"></i>
                  </label>
              </span>					
						</div>

				<div class="control">
							<textarea @keyup="countdown" v-model="statusMessage" name="status-message" class="textarea" placeholder="What story do you have to tell?"></textarea>
						</div>	

				<div class="level">
							<div class="level-right">
								<div v-if="enableMaxCharLength" class="level-item">
									<span :class="{'has-text-danger': isAboveMaxCharCount}" class="remaining-chars" v-text="remainingCharCount"></span>
								</div>
								<div class="level-item">
									<input type="submit" value="Schedule" class="button  is-primary">	
								</div>
							</div>
						</div>
			</div>
		</form>
	</section>
</div>

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download