Here's a simplified version of my code :
<template>
/* ----------------------------------------------------------
* Displays a list of templates, #click, select the template
/* ----------------------------------------------------------
<ul>
<li
v-for="form in forms.forms"
#click="selectTemplate(form)"
:key="form.id"
:class="{selected: templateSelected == form}">
<h4>{{ form.name }}</h4>
<p>{{ form.description }}</p>
</li>
</ul>
/* --------------------------------------------------------
* Displays the "Editable fields" of the selected template
/* --------------------------------------------------------
<div class="form-group" v-for="(editableField, index) in editableFields" :key="editableField.id">
<input
type="text"
class="appfield appfield-block data-to-document"
:id="'item_'+index"
:name="editableField.tag"
v-model="editableField.value">
</div>
</template>
<script>
export default {
data: function () {
return {
editableFields: [],
}
},
methods: {
selectTemplate: function (form) {
/* ------------------
* My problem is here
*/ ------------------
for (let i = 0; i < form.editable_fields.length; i++) {
this.editableFields.push(form.editable_fields[i]);
}
}
}
}
</script>
Basically I want to update the array EditableFields each time the user clicks on a template. My problem is that Vuejs does not update the display because the detection is not triggered. I've read the documentation here which advise to either $set the array or use Array instance methods only such as splice and push.
The code above (with push) works but the array is never emptied and therefore, "editable fields" keep pilling up, which is not a behavior I desire.
In order to empty the array before filling it again with fresh data, I tried several things with no luck :
this.editableFields.splice(0, this.editableFields.length);
for (let i = 0; i < form.editable_fields.length; i++) {
this.editableFields.push(form.editable_fields[i]);
}
==> Does not update the display
for (let i = 0; i < form.editable_fields.length; i++) {
this.$set(this.editableFields, i, form.editable_fields[i]);
}
==> Does not update the display
this.editableFields = form.editable_fields;
==> Does not update the display
Something I haven't tried yet is setting a whole new array with the fresh data but I can't understand how I can put that in place since I want the user to be able to click (and change the template selection) more than once.
I banged my head on that problem for a few hours now, I'd appreciate any help.
Thank you in advance :) !
I've got no problem using splice + push. The reactivity should be triggered normally as described in the link you provided.
See my code sample:
new Vue({
el: '#app',
data: function() {
return {
forms: {
forms: [{
id: 'form1',
editable_fields: [{
id: 'form1_field1',
value: 'form1_field1_value'
},
{
id: 'form1_field2',
value: 'form1_field2_value'
}
]
},
{
id: 'form2',
editable_fields: [{
id: 'form2_field1',
value: 'form2_field1_value'
},
{
id: 'form2_field2',
value: 'form2_field2_value'
}
]
}
]
},
editableFields: []
}
},
methods: {
selectTemplate(form) {
this.editableFields.splice(0, this.editableFields.length);
for (let i = 0; i < form.editable_fields.length; i++) {
this.editableFields.push(form.editable_fields[i]);
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
<ul>
<li v-for="form in forms.forms"
#click="selectTemplate(form)"
:key="form.id">
<h4>{{ form.id }}</h4>
</li>
</ul>
<div class="form-group"
v-for="(editableField, index) in editableFields"
:key="editableField.id">
{{ editableField.id }}:
<input type="text" v-model="editableField.value">
</div>
</div>
Problem solved... Another remote part of the code was in fact, causing the problem.
For future reference, this solution is the correct one :
this.editableFields.splice(0, this.editableFields.length);
for (let i = 0; i < form.editable_fields.length; i++) {
this.editableFields.push(form.editable_fields[i]);
}
Using only Array instance methods is the way to go with Vuejs.
Related
I have the following data structure, and I'm trying to render each object individually on click whiteout overwriting the previous value with the current value.
boardCollection =
[
{
id: 1,
dashboardType: "Simple",
fields: [
"Board naspa",
"Cea mai mare mica descriere"
]
},
{
id: 2,
dashboardType: "Simple",
fields: ["Titlu fara idei", "Descriere in speranta ca se va afisa"]
},
{
id: 3,
dashboardType: "Complex",
fields: ["Primu board complex", "descriere dorel", "Hai ca merge cu chiu cu vai"]
},
{
id: 4,
dashboardType: "Complex",
fields: ["Kaufland", " merge si asta ", "aaaaaaaaaaaaaaaaaaaaaaaaaa"]
}
]
in which I am accessing the elements in the following manner ->value/index are defined globally.
display() {
let currentElement = this.boardCollection[this.index]
this.value = currentElement;
if (this.index < this.boardCollection.length - 1) {
this.index++;
} else {
this.index = 0;
}
}
Here is the HTML and the way that i`m trying to render each object.
<div *ngIf="show">
<h1>{{value.dashboardType}}</h1>
<ol *ngFor="let prop of value.fields |keyvalue">
<li>{{prop.value }}</li>
</ol>
</div>
<button (click)="display()">Show</button>
show is set to true in the display method.
What I have achieved so far is to display each object or the properties from them, but each time the button is pressed, the current value will overwrite the previous value, therefore I'm looking for some help into saving the previous value in order to display each object so in the end to have all the objects from the Array rendered to the UI
I would have another array in the TypeScript and keep adding to this array as display is clicked.
boards = [];
...
display(index: number) {
let currentElement = this.boardCollection[this.index]
this.value = currentElement; // might not be needed
this.boards = [...this.boards, ...currentElement]; // append to this.boards immutably so change detection takes effect (this.boards.push won't force change detection)
if (this.index < this.boardCollection.length - 1) {
this.index++;
} else {
this.index = 0;
}
}
...
<div *ngFor="let board of boards>"
<h1>{{board.dashboardType}}</h1>
<ol *ngFor="let prop of board.fields |keyvalue">
<li>{{prop.value }}</li>
</ol>
</div>
<button (click)="display()">Show</button>
Clicking on Show each time should keep on displaying each one one by one.
Tried all variations of this. Used the official Vue guides extensively, as well as other stack overflow posts and various tutorials. I've tried with and without quotes, I've tried array syntax, I've tried just about everything. For some reason even though I can toggle the data property successfully, my css class doesn't get applied to elements when isLearned is true.
Here's my HTML:
<li
v-for="(flashcard, index) in flashcards"
v-bind:class="{learned: isLearned, flashcard}"
#click="toggleSide(flashcard)">
<p>{{flashcard.isFlipped ? flashcard.phrase : flashcard.definition}}</p>
<button #click="learnedCard(flashcard, index)">Learned</button>
</li>
Here's my JS:
new Vue({
el: "#esl-flashcards",
data: {
flashcards: flashcards,
inputPhrase: '',
inputDef: '',
isLearned: false,
},
methods: {
learnedCard: function(flashcard, index) {
for (let i = 0; i < flashcards.length; i += 1){
if (i === index) {
flashcards[i].isLearned = !flashcards[i].isLearned;
}
};
},
},
});
Few issues here:
isLearned is actually a property of each flashcard object. So, when you do:
flashcards[i].isLearned = !flashcards[i].isLearned;
you are actually updating that property, but you are checking for static data isLearned property change in the class like
v-bind:class="{learned: isLearned, flashcard}"
Thus you don't see any class change at all. You simply need to call it like:
v-bind:class="{learned: flashcard.isLearned, flashcard}"
Also, here:
for (let i = 0; i < flashcards.length; i += 1){
if (i === index) {
flashcards[i].isLearned = !flashcards[i].isLearned;
}
};
You are calling flashcards directly which will be always undefined in vie. You need to call it like this.flashcards. But as you are already passing the index of the array to learnedCard() method you don't need to loop here. You can simply use .find() method to achieve the same result in few lines like:
learnedCard: function(flashcard, index) {
var card = this.flashcards.find((f,i) => i===index)
card.isLearned = !card.isLearned;
},
Working Demo:
new Vue({
el: "#esl-flashcards",
data: {
flashcards: Array.from({length:4}, (_, i) => ({text: `Item ${i+1}`, isLearned: false})),
inputPhrase: '',
inputDef: '',
isLearned: false,
},
methods: {
learnedCard: function(flashcard, index) {
var card = this.flashcards.find((f,i) => i===index)
card.isLearned = !card.isLearned;
},
},
});
li.learned {background-color:skyblue;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="esl-flashcards">
<ul>
<li v-for="(flashcard, index) in flashcards" v-bind:class="{learned: flashcard.isLearned, flashcard}">
<p>{{flashcard.text}}</p>
<button #click="learnedCard(flashcard, index)">Learned</button>
</li>
</ul>
</div>
I was able to create this dynamic elements.
My purpose in this is to create dynamic divs that will be based on "count", and inside that div, I can add multiple textboxes.
Here's what I came up with
You'll notice that the first click, it will not be the expected result. But when you click it the 2nd time, it will work.
I should be missing something. But I don't know what it is as I'm new to vue.
Here's the code as well:
<div id="app">
<button #click="populate">Populate</button>
<div v-for="(input, act) in inputs" >
Id
<div v-for="(i, ii) in input.items">
<input type="text" v-model="i.name">
</div>
<button v-show="act > 0" #click=input_add(act)>Add</button>
</div>
{{inputs}}
</div>
const app = new Vue({
el: '#app',
data: {
inputs: [],
counter: 0,
count: 3
},
methods: {
populate(){
var x = 1
while(x <= this.count){
this.inputs.push(
{
id: this.counter + 1,
items: []
}
)
this.input_add(x)
this.counter++
x++
}
},
input_add(x){
this.inputs[x].items.push(
{
name: null
}
)
}
}
})
Try setting var x = 1 to var x = 0 - that way it should have the functionality you get on the second click on initial.
Hello everyone I'm building a simple notes app and I can't figure out how to implement one feature.
I have a card element and delete button as a child of this element. I need to check if the card element child's(.card-title) html value(jQuery's .html()) is equal to the localStorage(I'm using for to loop through the localStorage object) key by clicking on Delete button(that is a child of the card element alongside with the card's title) .Then, if true, I need to delete the localStorage item by key that is equal to the .card-title's html value.
So basically I have
.card
.card-title (with html value I need to get)
.card-body (nothing to do with it)
.delete-button (by clicking on it I need to get .card-title's html value)
That's only my point of view, which, most likely, is wrong. So, maybe, there is a better approach for deleting notes in my app?
Any ideas?
Full code on CodePen
Thank you very much for spending your precious time with my issue! Thank you for any help!
So I have a code like this :
<div id="notes">
<div class="container">
<div class="form-group">
<label for="title">Enter title</label>
<input class="form-control" id="title"/>
</div>
<div class="form-group">
<label for="body">Enter body</label>
<textarea class="form-control" id="body"></textarea>
</div>
<div class="form-group">
<button class="btn btn-primary" #click="add">Add</button>
<button class="btn btn-danger" #click="clear">Delete all</button>
</div>
<div class="card" v-for="o,t,b,c in notes">
<div class="card-body">
<h5 class="card-title">{{t}}</h5>
<p class="card-text">{{o[b]}}</p>
<a class="card-link" #click="remove">Delete</a>
</div>
</div>
</div>
</div>
new Vue({
el: "#notes",
data: {
notes: {}
},
methods: {
add: function() {
localStorage.setItem($("#title").val(), $("#body").val());
location.reload(true);
},
clear: function() {
localStorage.clear();
location.reload(true);
},
remove: function(e) {
for (i = 0; i < localStorage.length; i++) {
if (
localStorage.key(i) ==
$(this)
.closest(".card")
.find(".card-title")
.html()
) {
alert(true);
}
}
}
},
created: function() {
for (i = 0; i < localStorage.length; i++) {
this.notes[localStorage.key(i)] = [
localStorage.getItem(localStorage.key(i)),
"red"
];
}
}
});
so i built this very simple app so you can check it out
https://jsfiddle.net/vrxonsq1/2/
new Vue({
el:"#app",
data:{
form:{
title:"",
body:""
},
notes:[]
},
methods:{
add: function(){
this.notes.push({
title: this.form.title,
body: this.form.body
});
this.form.title = "";
this.form.body = "";
this.save();
},
remove: function(title){
this.notes.forEach(function(note,index){
if (note.title == title){
this.notes.splice(index,1);
}
})
this.save();
},
save: function(){
localStorage.setItem("notes", JSON.stringify(this.notes) );
}
},
created: function(){
notes = JSON.parse(localStorage.getItem("notes") );
this.notes = notes ? notes : []
}
})
it doesn't use jquery, only vuejs, I think it is better this way
simply create an array contains 'note' objects where each one have title and body.
Tell me if you have any questions.
with jQuery you can get an elements parent element with .parent().
So in this case you should be able to do this to get the html you're looking for:
$(this).parent().find('.card-title').html()
Well, found the solution myself, thanks everyone for help!
Updated code :
new Vue({
el: "#notes",
data: {
notes: {}
},
methods: {
add: function() {
localStorage.setItem($("#title").val(), $("#body").val());
location.reload(true);
},
clear: function() {
localStorage.clear();
location.reload(true);
},
remove: function(e) {
var t = $(e.target)
.parent()
.find(".card-title")
.html();
for (i = 0; i < localStorage.length; i++) {
if (localStorage.key(i) == t) {
localStorage.removeItem(localStorage.key(t));
location.reload(true);
}
}
}
},
created: function() {
for (i = 0; i < localStorage.length; i++) {
this.notes[localStorage.key(i)] = [
localStorage.getItem(localStorage.key(i)),
"red"
];
}
}
});
It may help to use VueJS' $refs
Assigning a ref to your elements gives you access to the specifically named DOM element within your component via a $refs property on this e.g
<div ref="myDiv">I'm a div</div> // = { myDiv: [DOMELEMENT] }
created () {
console.log(this.$refs.myDiv.innerHTML) // I'm a div
}
By using refs you should be able to use querySelector to query child elements of parent elements and vice versa.
I've changed the meteor example leaderboard into a voting app. I have some documents with an array and in this array there are 6 values. The sum of this 6 values works fine, but not updating and showing the values in my app.
The values are only updating, if I click on them. The problem is, that I get the booknames (it's a voting app for books) from the "selected_books" variable (previously selected_players), but I don't know how I can get the book names.
By the way: _id are the book names.
I will give you some code snippets and hope, somebody have a solution.
This is a document from my database:
{
_id: "A Dance With Dragons: Part 1",
isbn: 9780007466061,
flag: 20130901,
score20130714: [1,2,3,4,5,0],
}
parts of my html file:
<template name="voting">
...
<div class="span5">
{{#each books}}
{{> book}}
{{/each}}
</div>
...
</template>
<template name="book">
<div class="book {{selected}}">
<span class="name">{{_id}}</span>
<span class="totalscore">{{totalscore}}</span>
</div>
</template>
and parts of my Javascript file:
Template.voting.books = function () {
var total = 0;
var book = Session.get("selected_book");
Books.find({_id:book}).map(function(doc) {
for (i=0; i<6; i++) {
total += parseInt(doc.score20130714[i], 10);
}
});
Books.update({_id:book}, {$set: {totalscore: total}});
return Books.find({flag: 20130901}, {sort: {totalscore: -1, _id: 1}});
};
Thanks in advance
Don't update data in the helper where you fetch it! Use a second helper for aggregating information or a transform for modifying data items. Example:
Template.voting.books = function() {
return Books.find({}, {sort: {totalscore: -1, _id: 1}});
};
Template.books.totalscore = function() {
var total = 0;
for(var i=0; i<6; i++) {
total += this.score[i];
}
return total;
};
As a side note, DO NOT USE the construct for (i=0; i<6; i++), it's deadly. Always declare your index variables: for (var i=0; i<6; i++).