VueJs not rerendering after new element is added to an array - javascript

I have set a watcher to check if the dropdown value is equals to "select", if the value is equal then a new array "option" is created with one element in it. I have a button that will add a value to the array that the watcher created when the dropdown value is equal to select. The button function is working but the input field that should loop the array doesn't re-render after the button click.
I am sorry I am terrible at explaining things.
here is my code
<template lang="pug">
.formHolder
.quest
input(type="text" placeholder="Question" v-model="tempForm.tempQuestions[0].question")
input(type="text" placeholder="Description" v-model="tempForm.tempQuestions[0].description")
select(name="category" v-model="tempForm.tempQuestions[0].type")
option(value="text") text
option(value="select") Dropdown
option(value="number") Number
this is the input field that will loop the array
.option(v-for="(option, index) in tempForm.tempQuestions[0].options")
input(type="text" placeholder="Option" v-model="tempForm.tempQuestions[0].options[index]")
this button adds a value to the array
button(v-if="tempForm.tempQuestions[0].type === 'select'" #click="addItems") Add Options
</template>
<script>
// # is an alias to /src
import HelloWorld from "#/components/HelloWorld.vue";
export default {
name: "HomeView",
components: {
HelloWorld,
},
data() {
return {
questions: 0,
tempForm: {
tempQuestions: [
{
question: null,
description: null,
type: null,
},
],
},
form: {
questions: [],
},
};
},
methods: {
addQuestion() {
this.tempForm.tempQuestions.push({
question: null,
description: null,
type: null,
});
this.questions++;
this.readerNos++;
},
addOptionArray() {
this.tempForm.tempQuestions[0].options.push("");
},
//////// this is the function
addItems() {
console.log(this.tempForm.tempQuestions[0].options);
let length = this.tempForm.tempQuestions[0].options.length;
console.log(length);
this.$set(this.tempForm.tempQuestions[0].options, length, "");
},
},
watch: {
tempForm: {
handler: function () {
this.tempForm.tempQuestions.forEach((question) => {
if (question.type === "select") {
question.options = [];
question.options.push("");
} else if (question.type === "text") {
if (question.options) {
delete question.options;
}
} else if (question.type === "number") {
if (question.options) {
delete question.options;
}
}
});
},
deep: true,
},
},
};
</script>

You should use Vue.$set() when adding an empty options array - otherwise it won't be reactive and there will be no rerendering of the options in the template:
watch:
{
tempForm:
{
handler()
{
this.tempForm.tempQuestions.forEach((question) =>
{
if (question.type === "select")
{
this.$set(question, 'options', []);
question.options.push("");
}
else if (question.type === "text")
{
if (question.options)
{
question.options.splice();
}
}
else if (question.type === "number")
{
if (question.options)
{
question.options.splice();
}
}
});
},
deep: true,
},
},

Related

Have variable changes on button click but initially set using localStorage in vue

I am trying to setup a button that changes a data value in Vue but also have it set using localStorage initally. This way I can have it keep the previous state it was in before a page refresh. Below is the code I'm using and I'm able to get it to work but know that it would be preferable to use the computed section but haven't been able to get that to work properly.
Would anyone know what is going wrong?
My button is triggered using the testing method and the variable in question is isGrid.
export default {
data() {
return {
option: 'default',
}
},
components: {
FileUploader,
},
mixins: [
visibilitiesMixin,
settingsMixin
],
props: {
vehicleId: {
type: Number,
required: true,
default: null,
}
},
computed: {
...mapState([
'isLoading',
'images',
'fallbackImageChecks',
'selectedImages'
]),
isGrid: {
get() {
return localStorage.getItem('isGrid');
},
},
imagesVModel: {
get() {
return this.images;
},
set(images) {
this.setImages(images);
}
},
selectedImagesVModel: {
get() {
return this.selectedImages;
},
set(images) {
this.setSelectedImages(images);
}
},
removeBgEnabled() {
return this.setting('nexus_integration_removebg_enabled') === 'enabled';
},
},
mounted() {
this.loadImages(this.vehicleId);
},
methods: {
testing() {
if (this.isGrid === 'false' || this.isGrid === false) {
localStorage.setItem('isGrid', true);
this.isGrid = true;
console.log(this.isGrid);
console.log(localStorage.getItem('isGrid'));
} else {
localStorage.setItem('isGrid', false);
this.isGrid = false;
console.log('b');
console.log(this.isGrid);
console.log(localStorage.getItem('isGrid'));
}
},
}
I suggest you use vuex with vuex-persistedstate.
https://www.npmjs.com/package/vuex-persistedstate

Restrict Input tags maximum 5 tags

Hi friends I'm using this source Tags input for my project , but I require only 5 tags to be entered after that it should not allow the tags to be entered into the input field .
The input field can now receive unlimited tags,But I do not know how to limit it
please guide me
Thanks in advance.
function tagSelect() {
return {
open: false,
textInput: '',
tags: [],
init() {
this.tags = JSON.parse(this.$el.parentNode.getAttribute('data-tags'));
},
addTag(tag) {
tag = tag.trim()
if (tag != "" && !this.hasTag(tag)) {
this.tags.push( tag )
}
this.clearSearch()
this.$refs.textInput.focus()
this.fireTagsUpdateEvent()
},
fireTagsUpdateEvent() {
this.$el.dispatchEvent(new CustomEvent('tags-update', {
detail: { tags: this.tags },
bubbles: true,
}));
},
hasTag(tag) {
var tag = this.tags.find(e => {
return e.toLowerCase() === tag.toLowerCase()
})
return tag != undefined
},
removeTag(index) {
this.tags.splice(index, 1)
this.fireTagsUpdateEvent()
},
search(q) {
if ( q.includes(",") ) {
q.split(",").forEach(function(val) {
this.addTag(val)
}, this)
}
this.toggleSearch()
},
clearSearch() {
this.textInput = ''
this.toggleSearch()
},
toggleSearch() {
this.open = this.textInput != ''
}
}
}
You could update the fireTagsUpdateEvent() function to disable the input when there are 5 (or more) tags
fireTagsUpdateEvent() {
this.$refs.textInput.setAttribute('disabled', this.tags.length >= 5)
this.$el.dispatchEvent(new CustomEvent('tags-update', {
detail: {
tags: this.tags
},
bubbles: true,
}));
},

VueJs Not Updating Array

I'm trying to make a dynamic menu at vue, but, while I trying to apply a "active" css class to the menu item, not work, I see the menu variable "menuFomartado" is not updating by VUE, what I thinking wrong?
This code only works while loading page again..
<script>
export default {
data() {
return {
menuFomartado: [],
menu: [
{
icon: "fa fa-chart-area",
pageName: "dashboard",
title: "Dashboard",
role: "admin"
},
{
icon: "fa fa-user-circle",
pageName: "clientes",
title: "Clientes",
role: "admin"
},
]
};
},
computed: {
sideMenu() {
return this.nestedMenu(this.menu);
}
},
watch: {
$route() {
this.menuFomartado = this.sideMenu;
}
},
mounted() {
this.menuFomartado = this.nestedMenu(this.sideMenu);
},
methods: {
nestedMenu(menu) {
menu.forEach((item, key) => {
if (typeof item !== "string" && item.pageName != "") {
menu[key].active = item.pageName == this.$route.name;
}
});
return menu;
}
}
};
</script>
Based in #pierre-said from post Vuejs and Vue.set(), update array
I just changed the line
menu[key].active = item.pageName == this.$route.name;
To
this.$set(menu[key], "active", item.pageName == this.$route.name);
And works fine

Vue v-for on first item containing property X call a method

Let's say I have a component which repeats with a v-for loop like so:
<hotel v-for="(hotel, index) in hotels"></hotel>
And my hotels array would look like so:
[
{
name: false
},
{
name: false
},
{
name: true
},
{
name: true
}
]
How could I perform an action when my v-for loop encounters the property name set to true only on the very first time it encounters this truthy property?
I know I could probably cache a property somewhere and only run something once and not run again if it has been set but this does not feel efficient.
Use a computed to make a copy of hotels where the first one has an isFirst property.
computed: {
hotelsWithFirstMarked() {
var result = this.hotels.slice();
var first = result.find(x => x.name);
if (first) {
first.isFirst = true;
}
return result;
}
}
Just use computed source
HTML
<div v-for="(hotel, index) in renderHotels"></div>
JS
export default {
name: 'message',
data () {
return{
hotels:[
{
name: false
},
{
name: false
},
{
name: true
},
{
name: true
}
] ,
wasFirst : false
}
},
methods:{
},
computed:{
renderHotels(){
return this.hotels.map((hotel)=>{
if(hotel.name && !this.wasFirst){
this.wasFirst = true;
alert('First True');
console.log(hotel);
}
})
}
}
}
Best way is to use filter function.
data() {
return {
firstTime: false,
};
},
filters: {
myFunc: function (hotel) {
if (hotel.name && !this.firstTime) {
//perform some action
this.firstTime = true;
}
}
}
<hotel v-for="(hotel, index) in hotels | myFunc"></hotel>

How to get a dynamically generated VueJS component to render

I am trying to dynamically add components to my vue template. Things have not gone as expected.
I have this component:
Vue.component('form-x-text', {
delimiters: ["[[", "]]"],
template: `
<div v-if="_.indexOf(['text','email','tel','number','url','color'], fType) > 0" class="form-group" :class="{'has-error': errors.has(fName)}">
<label :for="fId">[[fLabel]]</label>
<input v-validate="{ rules: parsedRule }" class="form-control" :type="fType" :name="fName" :id="fId" data-vv-delay="700">
<span class="field-error" v-show="errors.has(fName)"> [[ errors.first(fName) ]]</span>
</div>
`,
props: {
fType: {
type: String,
validation: function(value){
return _.indexOf(['text','email','tel','number','url','color'], value) > 0
},
required: true
},
fName: {
type: String,
required: true
},
fId: null,
fLabel: null,
fRule: null
},
computed: {
parsedRule: function(){
console.log(this.fRule);
return JSON.parse(this.fRule);
}
}
})
class FormX{
constructor(){
}
static newForm(){
return JSON.parse(`
{
"node": "root",
"child": []
}
`)
}
static textInput(ftype, name, label, id, required = false){
let emailR = false;
if (ftype == 'email'){
emailR = true;
}
return JSON.parse(
`
{
"node": "element",
"tag": "form-x-text",
"attr": {
"f-type": "${ftype}",
"f-name": "${name}",
"f-label": "${label}",
"f-id": "${id}"
}
}
`
)
}
}
var builder = new Vue({
el: '#builder',
delimiters: ["[[", "]]"],
data: {
blankTemplate: {
node: 'root',
child: [
{
node: 'element',
tag: 'div',
attr: { id: '1', class: 'foo'},
child: []
},
workingTemplate: {
content: {}
},
primaryButton: {},
secondaryButton: {},
bForm: null,
},
methods: {
openFormDesigner: function(){
$('#formDesignerModal').modal('show');
this.primaryButton = {'show':false, 'text':'Save', 'spanstyle':'fa fa-floppy-o'};
this.secondaryButton = {'show':true, 'text': 'Close'};
},
addToFormCanvas: function(ftype){
if (!ftype){
console.log("You need to provide the type of the Form element");
}else if (ftype == 'email'){
this.bForm.child.push(FormX.textInput('email', 'mail1','mail1','mail1'));
}else if (ftype == 'text'){
this.bForm.child.push(FormX.textInput('text', 'mail1','mail1','mail1'));
}else if (ftype == 'number'){
this.bForm.child.push(FormX.textInput('number', 'mail1','mail1','mail1'));
}
},
jsonToHtml: function(jsono){
return json2html(jsono);
},
getAttr: function(aobj){
return aobj
}
},
mounted: function(){
$('#vwr-panel > iframe').contents().find('body').css({"margin":"0 auto", "background-color":"#fff"}).html(
json2html(this.blankTemplate));
},
created: function(){
this.bForm = FormX.newForm();
},
computed: {
bFormHtml: function(){
return json2html(this.bForm)
}
},
filters: {
capitalize: function(v){
return _.toUpper(v);
}
}
})
When a click happens, I basically append to a data object in my vue instance.
I then use json2html to convert this data object to "html" which is really a component.
I expect that the component will be rendered, but although I can see it in inspector, it doesn't render.
I am inserting the component with <div v-html="bFormHtml"></div>
Have you tried using a mutable <component> tag?
html:
...
<component :is="componentId"></component>
...
javascript:
...
data: {
componentId: 'blank'
},
components: {
'blank': {
'template': '<div></div>'
}
},
methods: {
'updateComponent': function() {
this.componentId = 'form-x-text'
}
}
...
JsFiddle an Example of multiple components.
I'm not sure if it will work exactly with what you have but it might do better than trying to inject the HTML.

Categories