Can't get why the app is not re-rendering when data is updated using method, can anyone see what's the issue? thanks!
On clicking the button, selectedPage is updating using the changePage method, but nothing is changing on the page (in the Vue dev tool I can see that selectedPage is changing correctly)
var app = new Vue({
el: '#app',
data: {
showMain: false,
isReader: true,
selectedPage: [0,0],
books: [
{
id: 0,
name: 'איה פלוטו',
image: 'https://upload.wikimedia.org/wikipedia/he/f/f3/%D7%90%D7%99%D7%94_%D7%A4%D7%9C%D7%95%D7%98%D7%95.jpg',
pageList: [
{id: 0, text: [',פלוטו כלבלב מקיבוץ מגידו','יש לו הכל, חלב ועצם','זה טוב ויפה, אבל בעצם','נמאס לו לשבת כך לבדו'], image: './images/wheres_pluto/page_1.jpeg'},
{id: 1, text: ['lived a princes in a big castle'], image: './images/wheres_pluto/page_2.jpeg'},
{id: 2, text: ['the END'], image: './images/wheres_pluto/page_3.jpeg'}
]
},
{
id: 1,
name: 'ספר שני',
pageList:[]
}
],
},
methods: {
changePage: function(event){
if(event == 'next'){
this.selectedPage[1] = this.selectedPage[1] + 1
} else {
this.selectedPage[1] = this.selectedPage[1] - 1
}
console.log(this.selectedPage)
}
}
})
The html:
<html>
<head>
<link rel="stylesheet" type="text/css" href="css/style.css">
<link href="https://fonts.googleapis.com/css2?family=Heebo:wght#100;300;400;500&display=swap" rel="stylesheet">
</head>
<body>
<div id='app'>
<!-- book selection page -->
<div class='page app-page' v-show="showMain">
<div class='book-list' v-for="book in books" :book='book.name'>{{book.name}}
<img :src='book.image' />
</div>
</div>
<!-- Book -->
<div class="book" v-for="book in books" v-if='book.id == selectedPage[0]' :key=book.id>
<div class="book-page" v-for="(page,index) in book.pageList" v-if='book.id == selectedPage[0] && book.pageList[index].id == selectedPage[1]'>
<!-- Video component -->
<div class="video">VIDEO HERE</div>
<!-- Reader's page -->
<div class="page reader-page" v-if="isReader" :key = page.id>
<div class="main-text" v-for='(lines,lineIndex) in book.pageList[index].text'>
<span>{{page.text[lineIndex]}}</span>
</div>
</div>
<!-- Kid's page -->
<div class="page kid-page" v-else>
<div class="video">VIDEO HERE</div>
<div class="img-container" :style="{ backgroundImage: 'url(' + page.image + ')' }">
</div>
</div>
<!-- controllers -->
<div class="controllers" v-if=isReader>
<button class="next" v-on:click='changePage("next")'>הבא</button>
<button class="prev" v-on:click='changePage("prev")'>קודם</button>
</div>
</div>
</div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="js/main.js"></script>
</html>
DTal, A couple of things to consider here. I noticed that you're using selectedPage for both the page number and the page ID. It may be simpler to map both of these to two separate variables.
The other thing to consider is that you may also want to bind what book is selected as well. Vuejs won't update the internals because it's already done the v-for loop. If you bind one of the divs (using v-bind:) to a book, and keeping track of which book is selected in the data() area then it may have the behavior you want.
There were a lot of things to be fixed in the template to make your code run but the main thing missing which was not making it run was that (copying directly from Vue documentation)
Vue cannot detect the following changes to an array:
1. When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
2. When you modify the length of the array, e.g. vm.items.length = newLength
Change detection runs only for property mutations
Which can be achieved using
vm.$set(vm.items, indexOfItem, newValue)
which in your case would be
if (event === "next") {
this.$set(this.selectedPage, 1, this.selectedPage[1] + 1);
} else {
this.$set(this.selectedPage, 1, this.selectedPage[1] - 1);
}
I definitely feel that you can maintain the page numbers in a more efficient way(but that is a different concern)
Related
I want to add a bootstrap tooltip in each IMG tag that I create from Vue v-for but once I use v-for the tooltip does not work anymore.
This work without v-for-
<img src="warning.png" width="40px" heigh="40px" data-toggle="tooltip" title="Some tooltip text!">
Once using v-for, it stopped working-
<img v-for="item,idx in EQP_USER_NOW" src="warning.png" width="40px" heigh="40px"
data-toggle="tooltip" title="Some tooltip text!" v-bind:id="idx"></img>
I just found the problem was happened after i modify the Vue data (EQP_USER_NOW in my case)
For example,after click the button below (modify data)
the data changed but the new IMG tag not worked with tootip.
var vm = new Vue({
el: '#vm',
data: {
EQP_USER_NOW: [{
EQP: "A",
USER: "Andy"
},
{
EQP: "B",
USER: "Tony"
}
],
},
methods: {
modify_data() {
var vm = this
vm.EQP_USER_NOW = [{
EQP: "A",
USER: "Andy"
},
{
EQP: "B",
USER: "Tony"
},
{
EQP: "C",
USER: "Max"
}
]
},
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.11.6/umd/popper.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/4.5.3/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script>
$(function() {
$('[data-toggle="tooltip"]').tooltip({
animated: 'fade',
placement: 'right',
html: true
})
})
</script>
<body>
<div id="vm">
<button #Click="modify_data()">modify data</button>
<div class="container-fluid">
<div class="row">
<div class="col-12">
<img v-for="(item, idx) in EQP_USER_NOW" :key="idx" src="https://picsum.photos/200/200" width="100px" heigh="100px" data-toggle="tooltip" title="Some tooltip text!" v-bind:id="idx">
</div>
</div>
</div>
</div>
</body>
Try doing the following small fixes-
IMG is a self-closing tag, do not close it manually.
When using for loop, a key attribute is recommended to be used.
It's best practice to use parenthesis to wrap multiple params like (item, idx).
Finally, replace your code with this-
<img
v-for="(item, idx) in EQP_USER_NOW"
:key="idx"
src="https://picsum.photos/200/300"
width="40px"
heigh="40px"
data-toggle="tooltip"
title="Some tooltip text!"
v-bind:id="idx"
>
Consider the following code.
<template>
<div class="card-container">
<div class="row">
<div class="col s12">
<a #click="addCard()">Add Card</a>
</div>
</div>
<div class="row">
<div v-for="(card, index) in cards" :key="index">
<div class="card-panel">
<span class="card-title">Card Title</span>
<div class="card-action">
<a #click="deleteCard(index)">Delete</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data: function() {
return {
cards: []
}
},
methods: {
addCard: function() {
this.cards.push({
description: "",
});
},
deleteCard: function(index) {
this.cards.splice(index,1);
}
},
}
</script>
How to make the cards be grouped so that there are 4 rows and each row contains 4 cards? Upon reaching the fourth row the new cards go to the next page.
I thought I could use something like this codepen.io/parths267/pen/bXbWVv
But I have no idea how to get these cards organized in a pagination system.
The view would look something like this
My solution is calculate all cards of current page ahead.
Uses computed property to calculate the relate values which the pagination needs.
In below simple example (it is only one example, you need to add necessary validations as your actual needs, like boundary conditions) :
pages is the page count
cardsOfCurPage is the cards in current page
Then add one data property=pageIndex save the index of current page.
Anyway, keep data-driven in your mind.
List all arguments your pagination needs,
then declare them in data property or computed property.
execute the necessary calculations in computed property or methods.
PS: I don't know which css framework you uses, so I uses bootstrap instead.
Vue.component('v-cards', {
template: `<div class="card-container">
<div class="row">
<div class="col-12">
<a class="btn btn-danger" #click="addCard()">Add Card</a><span>Total Pages: {{pages}} Total Cards: {{cards.length}} Page Size:<input v-model="pageSize" placeholder="Page Size"></span>
</div>
</div>
<div class="row">
<div v-for="(card, index) in cardsOfCurrPage" :key="index" class="col-3">
<div class="card-panel">
<span class="card-title">Card Title: {{card.description}}</span>
<div class="card-action">
<a #click="deleteCard(index)">Delete</a>
</div>
</div>
</div>
</div>
<p><a class="badge" #click="gotoPrev()">Prev</a>- {{pageIndex + 1}} -<a class="badge" #click="gotoNext()">Next</a></p>
</div>`,
data: function() {
return {
cards: [],
pageSize: 6,
pageIndex: 0
}
},
computed: {
pages: function () {
return Math.floor(this.cards.length / this.pageSize) + 1
},
cardsOfCurrPage: function () {
return this.cards.slice(this.pageSize * this.pageIndex, this.pageSize * (this.pageIndex+1))
}
},
methods: {
addCard: function() {
this.cards.push({
description: this.cards.length,
});
},
deleteCard: function(index) {
this.cards.splice(index,1);
},
gotoPrev: function() {this.pageIndex -=1},
gotoNext: function() {this.pageIndex +=1}
},
})
new Vue({
el: '#app',
data() {
return {
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet" />
<div id="app">
<v-cards></v-cards>
</div>
I am using owl.carousel in my vue app. But carousel is not showing up on the page. I have include (owl.carousel.min.css) and other relevant files(jquery,bootstrap) inside my index.html. These files can also be seen in network tab of the browser which mean files are loading but no carousel(or images) are seen.When I remove owl.carousel.min.css from index, it displays images used inside carousel on top of one another but no carousel. Any idea what the issue might be..??Thank you.
<div class="banner-slider">
<div class="container">
<div class="row">
<div class="carousel-wrap">
<div id="sync2" class="navigation-thumbs owl-
carousel">
<div class="item">
<img src="./images/bslide4.png">
<div class="details">
<p>Alex</p>
<small>Guitarist</small>
</div>
</div>
<div class="item">
<img src="./images/bslide1.png">
<div class="details">
<p>MIRA</p>
<small>Filmmaking</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
And
var sync1 = $(".slider");
var sync2 = $(".navigation-thumbs");
var thumbnailItemClass = '.owl-item';
var slides = sync1.owlCarousel({
video:true,
startPosition: 3,
items:1,
loop:true,
margin:10,
autoplay:true,
autoplayHoverPause:false,
nav: false,
dots: true,
responsive: {
0: {
items: 1,
loop:true
},
600: {
items: 1,
loop:true
},
1000: {
items: 1,
loop:true
}
}
}).on('changed.owl.carousel', syncPosition);
I am trying to close a Modal of Materialize CSS if the validation is correct but I can not find the form.
The simplest thing was to do a type validation:
v-if = "showModal" and it works but leaves the background of the Modal and although click does not disappear. The background is a class named 'modal-overlay'
This is my code:
<i class="material-icons modal-trigger tooltipped" style="margin-left: 2px;
color:#ffc107; cursor:pointer;" #click="getById(article), fillSelectCategories(),
titleModal='Edit', type='edit'" data-target="modal1">edit</i>
I imported M from the JS file of MaterilizeCSS
import M from "materialize-css/dist/js/materialize.min";
Method:
update(){
var elem = document.querySelectorAll('.modal');
var instance = M.Modal.getInstance(elem);
console.log(instance)
That returns 'undefined'
I tried this too on the update() method:
var elem = document.querySelectorAll('.modal');
elem.close();
M.Modal.close()
I initialize the modal from mounted and it works fine but I can not close it at the moment I require it.
mounted(){
var elems = document.querySelectorAll('.modal');
var instances = M.Modal.init(elems, options);
}
But I know what else to try :(
It really is difficult to know why things aren't working for you without looking further into your code, but I've created this simple example to demonstrate how it could be done ..
new Vue({
el: "#app",
data: {
modalInstance: null,
closeAfterTimeElapsed: true,
seconds: 1
},
mounted() {
const modal = document.querySelector('.modal')
this.modalInstance = M.Modal.init(modal)
const select = document.querySelector('select');
M.FormSelect.init(select);
M.updateTextFields();
},
methods: {
handleClick() {
if (this.closeAfterTimeElapsed) {
setTimeout(() => { this.modalInstance.close() }, this.seconds * 1000)
}
}
}
})
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<!-- Compiled and minified JavaScript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app">
<!-- Modal Trigger -->
<button #click="handleClick" data-target="modal1" class="btn modal-trigger">Modal</button>
<!-- Modal Structure -->
<div id="modal1" class="modal">
<div class="modal-content">
<h4>Modal Header</h4>
<p>A bunch of text</p>
</div>
<div class="modal-footer">
Agree
</div>
</div>
<br>
<br>
<div class="row">
<div class="input-field col s6">
<select v-model="closeAfterTimeElapsed">
<option :value="false">False</option>
<option :value="true">True</option>
</select>
<label>Close Modal After</label>
</div>
<div class="input-field col s6">
<input id="seconds" type="number" v-model="seconds">
<label for="seconds">Seconds</label>
</div>
</div>
</div>
See this JSFiddle
This is my first time experimenting with Vue.js so it's altogether possible I'm missing something very obvious.
Basically, I have a component that calculates the number of boxes needed for a certain quantity of a printed piece (I work in the print industry).
If needed, I have a button to create an additional component if the printed piece has multiple parts.
I'd like to have a reactive way to update the total number of boxes needed for all parts, but I just can't seem to get there.
Here's a link to my Gitlab repo with the code: https://gitlab.com/dsross/printutils
Any help would be appreciated.
I'm also using Browserify to write the build.js and build.css files referenced in index.html.
Here are my files, in case no one wants to look at the repo:
App.vue
<template>
<div id="app">
<div>
</div>
<div>
<calculator v-for="(part, index) in parts" :key="index"></calculator>
<br />
<br />
<div class="card shadow-sm">
<div class="card-body">
<button type="button" class="btn" #click="addPart()">Add Part</button>
<button type="button" class="btn" #click="totalBoxes">Total Boxes</button>
<span>Box Total (all parts): </span><span id="grandtotal"></span>
</div>
</div>
</div>
</div>
</template>
<script>
// import Hello from './components/Hello.vue'
import Calculator from './components/Calculator.vue'
export default {
name: 'app',
components: {
Calculator
},
methods: {
addPart: function () {
console.log("Adding part");
this.parts.push(Calculator);
},
totalBoxes: function () {
console.log("totalBoxes called");
let totalBoxes = 0;
let partTotals = document.querySelectorAll("#partBoxTotal");
for (var i = 0; i < partTotals.length; i++) {
totalBoxes += parseInt(partTotals[i].innerHTML);
}
this.totalBoxCount = totalBoxes;
document.getElementById("grandtotal").innerHTML = totalBoxes;
}
},
data: function () {
return {
parts: [Calculator],
totalBoxCount: 0
}
},
}
</script>
Calculator.vue
<template>
<div class="card shadow-sm" id="boxCalculator">
<div class="card-body">
<form>
<div class="form-group">
<p>Paper:
<select class="custom-select" v-model="paperWeight">
<option v-for="(mweight, paper) in mweights" :key="mweight" v-bind:value="paper">{{paper}}</option>
</select>
</p>
<br />
<br />
<p>Final Width:
<input class="form-control" type="text" v-model="finalWidth" id="finalWidth" value="">
</p>
<p>Final Height:
<input type="text" class="form-control" v-model="finalHeight" id="finalHeight" value="">
</p>
<p>Sheets Per Unit:
<input type="text" class="form-control" v-model="numberOfSheets" id="numberOfSheets" name="numberOfSheets"
value="">
</p>
<p>Quantity:
<input type="text" class="form-control" v-model="quantity" id="quantity" name='quantity'>
</p>
<p>Stitched:
<input type="checkbox" v-model="stitched" name="stitched" id="stitched" value="">
</p>
</div>
</form>
<div class="card">
<div class="card-body">
<div id='results'>
<p id="partWeightTotal">Part Total Weight: {{ totalWeight }}</p>
<p><span>Part Box Total: </span><span id="partBoxTotal">{{ boxQuantity }}</span></p>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import {
mWeights,
stitchedMultiplier,
maxArea
} from "../constants.js"
module.exports = {
data: function () {
return {
paperWeight: this.selected,
paperType: "",
finalWidth: "",
finalHeight: "",
numberOfSheets: "",
quantity: "",
stitched: "",
boxes: "",
mweights: mWeights
}
},
computed: {
squareInches: function () {
return this.finalHeight * this.finalWidth;
},
squareInchWeight: function () {
let mWeight = mWeights[`${this.paperWeight}`];
return (mWeight / 1000) / maxArea;
},
totalWeight: function () {
return ((this.squareInches * this.squareInchWeight) * this.numberOfSheets) * this.quantity;
},
boxQuantity: function () {
let boxes = this.totalWeight / 35;
if (this.stitched) {
this.boxes = Math.ceil(boxes * stitchedMultiplier);
// this.$root.$emit('box-change', this.boxes);
return this.boxes
} else {
this.boxes = Math.ceil(boxes);
// this.$root.$emit('box-change', this.boxes);
return Math.ceil(this.boxes);
};
},
},
}
</script>
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>boxcalculator2</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="../favicon.png">
<title>Box Calculator</title>
<!-- Bootstrap core CSS -->
<link href="dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="dist/sticky-footer.css" rel="stylesheet">
<link rel="stylesheet" href="dist/build.css">
</head>
<body>
<div class="container">
<div class='row'>
<div class='col'>
<div id="app"></div>
</div>
</div>
</div>
<script src="dist/build.js"></script>
</body>
</html>
If I understand correctly, you'd like the App's total box count to be updated automatically whenever the individual Calculators determine their box counts. One way to do this is to emit an event from Calculator when its box count changes, which could be monitored with a watcher.
There are a couple issues we'll address below:
It doesn't make sense (and is inefficient) to store Calculator -- a single-file-component definition -- in this.parts[]. Instead, it could store meaningful data points, such as Calculator's output.
Instead of DOM manipulation (i.e., querying the DOM for an element to get/set its value), opt for modeling the data in Vue, and using interpolation in the template. This lets Vue automatically display the updated value in the target element. It also obviates the element ID assignments (assuming they were used exclusively for DOM manipulation), simplifying the template for improved readability.
Storing Calculator output
In App, we must use this.parts[] to track the result of each part's calculation (which we'll capture below). We'll define each array element (i.e., a "part") to be:
{
boxes: 0 // box count for this part
}
This definition allows a computed property (which we'll define later), based on .boxes, to be reactive.
So, in addPart() and the data option:
// App.vue
export default {
// ...
methods: {
addPart() {
this.parts.push({ boxes: 0 });
}
},
data() {
return {
parts: [{ boxes: 0 }]
}
}
}
Notifying App of Calculator output
Typically, parents pass data to children via props, and children communicate data to parents with events. Alternatives include using a state management library, such as Vuex, but for the sake of simplicity, we'll use events here.
In Calculator, we want to notify the parent (App) of changes to the boxes value, so we'll add a watcher that emits an event (e.g., called boxes-changed) whenever boxes changes:
// Calculator.vue
export default {
//...
watch: {
boxes(value) {
this.$emit('boxes-changed', value);
}
}
}
In App, we'll listen to the boxes-changed event, and copy the event detail's value to the current part's boxes variable, where part is the current array element of parts[] being iterated.
// App.vue
<calculator v-for="(part, index) in parts" #boxes-changed="part.boxes = $event" :key="index"></calculator>
Breakdown of #boxes-changed="part.boxes = $event":
#boxes-changed="..." - listen to boxes-changed event emitted from <calculator>
part.boxes = $event - set part.boxes to value of event detail
Making totalBoxCount reactive
With the changes above, we have the tools needed to make App's totalBoxCount reactive:
Change totalBoxCount into a computed property that sums up the .boxes fields of this.parts[]. This property will be computed automatically whenever array elements of this.parts[] changes.
// App.vue
export default {
computed: {
totalBoxCount() {
// Note: Alternatively, use a simple for-loop to sum .boxes
return this.parts
.filter(p => p.boxes && !Number.isNaN(p.boxes) // get only parts that have a positive `.boxes` value
.map(p => p.boxes) // map object array into an integer array of `.boxes` values
.reduce((p,c) => p + c, 0); // sum all array elements
},
},
data() {
return {
parts: [],
// totalBoxCount: 0 // CHANGED INTO COMPTUED PROP ABOVE
}
}
}
In App's template, use string interpolation to display totalBoxCount:
<!--
<span>Box Total (all parts): </span><span id="grandtotal"></span>
--> <!-- DON'T DO THIS -->
<span>Box Total (all parts): {{totalBoxCount}}</span>
We might as well remove the Total Boxes button (previously used to manually trigger a calculation) from the template:
<!--
<button type="button" class="btn" #click="totalBoxes">Total Boxes</button>
--> <!-- DELETE -->
and its associated click-handler:
// App.vue
export default {
methods: {
// totalBoxes: function() { /* .. */ } // DELETE THIS
}
}
demo