how to fix grid issue in vuejs - vue.js

in vuejs i cant make grid css with v-for , i used template-grid-columns so i can have 3 divs in same row but the result was just one div in one row and this is not the result i want, so is there any optimal solution i can use, here is the code
this is the html part :
<template>
<div>
<div>
<select class="select" v-model="status">
<option value="onSale">onSale</option>
<option value="featured">featured</option>
</select>
<caption>Total {{computedProducts.length}} Products</caption>
<div class ="productListing" v-for="(product, index) in computedProducts" :key="index">
<div class="singleProduct box effect1">
<h1>{{product.name}}</h1>
<h1></h1>{{product.color}}
{{product.featured}}
</div>
</div>
</div>
</div>
</template>
vuejs part :
<script>
// # is an alias to /src
export default {
name: 'home',
data() {
return {
status: [],
products: [
{name:'test1', color:'red', size:'XL',status:"featured"},
{name:'test2', color:'black', size:'L',status:"onSale"},
{name:'test3', color:'red', size:'L',status:"featured"},
],
}
},
computed: {
computedProducts: function () {
return this.products.filter((item) => {
return (this.status.length === 0 || this.status.includes(item.status))
})
}
}
}
</script>
css part :
<style lang="scss" scoped>
.productListing {
display: grid;
grid-template-columns: 1fr 1fr
}
.box {
background:#FFF;
margin:40px auto;
}
/*==================================================
* Effect 1
* ===============================================*/
.effect1{
-webkit-box-shadow: 0 10px 6px -6px #777;
-moz-box-shadow: 0 10px 6px -6px #777;
box-shadow: 0 10px 6px -6px #777;
}
$green: #2ecc71;
$red: #e74c3c;
$blue: #3498db;
$yellow: #f1c40f;
$purple: #8e44ad;
$turquoise: #1abc9c;
.select {
border: 0.1em solid #FFFFFF;
margin: 0 0.3em 0.3em 0;
border-radius: 0.12em;
box-sizing: border-box;
text-decoration:none;
font-family:'Roboto',sans-serif;
}
</style>
thank you in advance for your help

You grid effect would appear under it children instead of itself.
You need to add one parent div for your products, like below
<div class="productListing">
<div v-for="(product, index) in computedProducts" :key="index">
......
</div>
</div>
CSS would be
.productListing {
display: grid;
grid-template-columns: repeat(3, 1fr);
}

Related

Vue Accordion with dropdown select filter. How to bind data

I am trying to get a local Vue project working where there is an E-Library Accordion with 3 types of media and a dropdown button to filter between the types of media. I am also trying to get a color coding system going and I attempted this through created a class and trying to bind it (not working). I am trying to filter the media with a method and then applying that method as an #click event to each dropdown button, but I know this is probably poor practice or just not the correct way to do this. Can anyone point me in the right direction as to how to get these two features working correctly? Much appreciated.
This is my page that I am routing to and creating the code on for the Vue project:
<template>
<label for="accordion">Choose type of media:</label>
<select name="accordion-types" id="types">
<option #click="filteredAccordion(index)" value="book">Book</option>
<option #click="filteredAccordion(index)" value="dvd">DVD</option>
<option #click="filteredAccordion(index)" value="stream">Streaming Video</option>
</select>
<div v-for="(accordion, index) in accordions" :key="index">
<button :class="{red: accordions.type === 'Book' }" #click="toggleOpen(index)">
{{ accordion.title }}
</button>
<div class="panel" :class="{open: accordion.isOpen}">
<p>{{ accordion.content }} </p>
<p>{{ accordion.type }}</p>
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
accordions: []
};
},
created: function () {
const appData = this;
axios.get("data.json").then(function (response) {
appData.accordions = appData.addIsOpen(response.data.accordions);
});
},
methods: {
addIsOpen: function(items) {
return items.map(function(item) {
item.isOpen = false;
return item;
});
},
toggleOpen: function(index) {
this.accordions[index].isOpen = !this.accordions[index].isOpen;
}
},
computed: {
filteredAccordion: function(items) {
return this.accordions.filter(accordions => !accordions.type.indexOf(this.type));
}
}
};
</script>
<style>
.accordion {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
width: 100%;
text-align: left;
border: none;
outline: none;
transition: 0.4s;
}
.active, .accordion:hover {
background-color: #ccc;
}
.panel {
padding: 0 18px;
background-color: white;
display: none;
overflow: hidden;
}
.panel.open {
display: block;
}
.red {
color: red;
}
</style>

vue js pagination with simple vuejs plugin

I am trying to create a paginated flexbox using data from an API but struggle with it although I read the setup step-by step.
Here is my code without styles:
<template>
<div id="app">
<paginate name="articles" :list="articles" class="paginate-list">
<li v-for="item in paginated('articles')">
{{ item }}
</li>
</paginate>
<paginate-links for="items" :show-step-links="true"></paginate-links>
<paginate-links for="items" :limit="2" :show-step-links="true">
</paginate-links>
<paginate-links for="items" :simple="{ next: 'Next »', prev: '« Back' }">
</paginate-links>
</div>
</template>
<script>
import axios from 'axios';
import VuePaginate from 'vue-paginate'
export default {
data() {
return {
items:[],
paginate: [articles]
}
},
created() {
axios.get(https://zbeta2.mykuwaitnet.net/backend/en/api/v2/media-center/press-release/?page_size=61&type=5)
.then(response => {
this.items = response.data
})
}
}
</script>
Step 1: Install npm install --save vue-paginate
Step 2: Import vue-paginate component in main.js
import VuePaginate from "vue-paginate";
Vue.use(VuePaginate);
Step 3: HTML template will be like,
<template>
<div id="app">
<paginate ref="paginator" class="flex-container" name="items" :list="items">
<li
v-for="(item, index) in paginated('items')"
:key="index"
class="flex-item">
<h4>{{ item.pub_date }}, {{ item.title }}</h4>
<img :src="item.image && item.image.file" />
<div class="downloads">
<span
v-for="downloadable in item.downloadable.filter(
(d) => !!d.document_en
)"
:key="downloadable.id">
<a :href="downloadable.document_en.file">Download</a>
</span>
</div>
</li>
</paginate>
<paginate-links
for="items"
:limit="2"
:show-step-links="true"></paginate-links>
</div>
</template>
Step 4: Your component script like
<script>
import axios from "axios";
export default {
data() {
return {
items: [],
paginate: ["items"],
};
},
created() {
this.loadPressRelease();
},
methods: {
loadPressRelease() {
axios.get(`https://zbeta2.mykuwaitnet.net/backend/en/api/v2/media-center/press-release/?page_size=61&type=5`)
.then((response) => {
this.items = response.data.results;
});
}
}
};
</script>
Step 5: CSS style
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
ul.flex-container {
padding: 0;
margin: 0;
list-style-type: none;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-flex-flow: row wrap;
flex-direction: row wrap;
flex-wrap: wrap;
justify-content: space-around;
}
li img {
display: initial;
height: 100px;
}
.flex-item {
background: tomato;
width: calc(100% / 3.5);
padding: 5px;
height: auto;
margin-top: 10px;
color: white;
font-weight: bold;
text-align: center;
}
.downloads {
margin-top: 10px;
}
ul.paginate-links.items li {
display: inline-block;
margin: 5px;
}
ul.paginate-links.items a {
cursor: pointer;
}
ul.paginate-links.items li.active a {
font-weight: bold;
}
ul.paginate-links.items li.next:before {
content: " | ";
margin-right: 13px;
color: #ddd;
}
ul.paginate-links.items li.disabled a {
color: #ccc;
cursor: no-drop;
}
</style>
DEMO Link

How do I open and close a modal programmatically using Vue.js?

I've read through innumerable posts about how to do this with jquery, but Vue.js jealously owns and manages the dom, so I will need a solution that uses standard Vue.js components or libraries.
Thus far, I've located several examples that are what I'd call 'button-event-driven' solutions, but I will need to programmatically handle open and close of the modal.
Problem / Design Requirement: When a public user attempts to interact with a tempting button or other function on my application, and they are not yet logged in, I wish to programmatically launch a modal dialogue to then ask them to log in.
Once successfully, I'll need to programmatically close the same dialogue modal. Or, of course, they can choose to cancel and continue browsing as a public user without the ability to do those functions.
Other Helpful Information: I'm using bootstrap 4.4.1
You can use a watch property. If a user is not logged in as login=false then the modal shows.
// register modal component
Vue.component("modal", {
template: "#modal-template"
});
// start app
new Vue({
el: "#app",
data: {
showModal: false,
login: null
},
created() {
this.login = false;
},
watch: {
"login": function(val) {
this.showModal = !val;
}
}
});
.modal-mask {
position: fixed;
z-index: 9998;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: table;
transition: opacity 0.3s ease;
}
.modal-wrapper {
display: table-cell;
vertical-align: middle;
}
.modal-container {
width: 300px;
margin: 0px auto;
padding: 20px 30px;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
transition: all 0.3s ease;
font-family: Helvetica, Arial, sans-serif;
}
.modal-header h3 {
margin-top: 0;
color: #42b983;
}
.modal-body {
margin: 20px 0;
}
.modal-default-button {
float: right;
}
/*
* The following styles are auto-applied to elements with
* transition="modal" when their visibility is toggled
* by Vue.js.
*
* You can easily play with the modal transition by editing
* these styles.
*/
.modal-enter {
opacity: 0;
}
.modal-leave-active {
opacity: 0;
}
.modal-enter .modal-container,
.modal-leave-active .modal-container {
-webkit-transform: scale(1.1);
transform: scale(1.1);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<!DOCTYPE html>
<html>
<head>
<title>Modal Component</title>
<link rel="stylesheet" type="text/css" href="/style.css" />
<!-- template for the modal component -->
<script type="text/x-template" id="modal-template">
<transition name="modal">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
default header
</slot>
</div>
<div class="modal-body">
<slot name="body">
default body
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
default footer
<button class="modal-default-button" #click="$emit('close')">
OK
</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
</script>
</head>
<body>
<!-- app -->
<div id="app">
<!-- use the modal component, pass in the prop -->
<modal v-if="showModal" #close="showModal = false">
<!--
you can use custom content here to overwrite
default content
-->
<h3 slot="header">custom header</h3>
</modal>
</div>
</body>
</html>
I was able to construct this using a project with a (relatively) recent example. Here is the component, as well as a 'Tester.vue' view that uses that component:
LoginModal.vue:
<template>
<transition name="modal-fade">
<div class="modal-backdrop">
<div
class="modal"
role="dialog"
aria-labelledby="modalTitle"
aria-describedby="modalDescription"
>
<header class="modal-header" id="modalTitle">
<slot name="header">
</slot>
</header>
<section class="modal-body" id="modalDescription">
<slot name="body">
Your Login Form Goes Here
<button type="button" v-on:click="validateLoginForm">
Log In
</button>
<button type="button" #click="close" aria-label="Close modal">
Cancel
</button>
</slot>
</section>
<footer class="modal-footer">
<slot name="footer"> </slot>
</footer>
</div>
</div>
</transition>
</template>
<script>
export default {
name: "loginModal",
data() {
return {
loginValidationAlerts: [],
};
},
methods: {
close() {
this.$emit("close");
},
validateLoginForm() {
//Login Form Validations go here
},
clearAllLoginValidationErrors() {
this.loginValidationAlerts = [];
},
attemptLogin() {
// Your login code
},
},
};
</script>
<style>
.modal-fade-enter,
.modal-fade-leave-active {
opacity: 0;
}
.modal-fade-enter-active,
.modal-fade-leave-active {
transition: opacity 0.5s ease;
}
.modal-backdrop {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
}
.modal {
background: #ffffff;
box-shadow: 2px 2px 20px 1px;
overflow-x: auto;
display: flex;
flex-direction: column;
}
.modal-header,
.modal-footer {
padding: 15px;
display: flex;
}
.modal-header {
border-bottom: 1px solid #eeeeee;
color: #4aae9b;
justify-content: space-between;
}
.modal-footer {
border-top: 1px solid #eeeeee;
justify-content: flex-end;
}
.modal-body {
position: relative;
padding: 20px 10px;
}
.btn-close {
border: none;
font-size: 20px;
padding: 20px;
cursor: pointer;
font-weight: bold;
color: #4aae9b;
background: transparent;
}
.btn-green {
color: white;
background: #4aae9b;
border: 1px solid #4aae9b;
border-radius: 2px;
}
</style>
Tester.vue:
<template>
<div>
Test Page
<div>
<button type="button" class="btn" #click="showModal">
Open Modal!
</button>
</div>
<div>
<LoginModal v-show="isModalVisible" #close="closeModal"></LoginModal>
</div>
</div>
</template>
<script>
import LoginModal from "#/components/LoginModal.vue";
export default {
components: {
// eslint-disable-next-line vue/no-unused-components
LoginModal,
},
data() {
return {
isModalVisible: false,
};
},
methods: {
showModal() {
// Do something here to determine
// if you should show modal
this.isModalVisible = true;
},
closeModal() {
// this will catch the close event
// after you're done processing the login in the component
this.isModalVisible = false;
},
},
};
</script>
<style scoped></style>
The appearance of the view and its component is pretty rough and without much formatting, but you get the picture.
Hopefully this helps somebody else if they're looking for a straight-forward modal in Vue; I believe it has to be a very common design requirement, and this approach worked for me.
The simple solution is:
add a reference to modal component
<share-modal ref="share-modal-ref"/>
import Modal like this:
import { Modal } from 'bootstrap'
then in your method do this:
let element = this.$refs.listModal.$el
let shareModal = new Modal(element, {})
shareModal.show()
It works on Vue 3 & Boostrap 5

How to close a modal by clicking on the backdrop in Vue.js

I have created a simple reusable modal component using Vue.js and it works fine, but I want to make so that when I click on the backdrop the modal closes, how can I achieve this? I searched and found a similar question on stackoverflow:
vuejs hide modal when click off of it
And did the same that the accepted answer does, putting #click="$emit('close')" on the wrapper but the modal does not get closed by clicking the backdrop as it is in the provided example. Here is my code:
<template>
<div :class="backdrop" v-show="!showModal">
<div class="modal-wrapper">
<div class="modal-container" :class="size" #click="$emit('close')">
<span class="close-x" #click="closeModal">X</span>
<h1 class="label">{{label}}</h1>
<div class="modal-body">
<slot></slot>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'custom-modal',
data() {
return {
showModal: false
};
},
props: {
label: String | Number,
size: String,
backdrop: String
},
components: {
'custom-btn': customBtn
},
methods: {
closeModal() {
this.showModal = true;
}
}
};
</script>
<style>
.modal-wrapper {
display: table-cell;
vertical-align: middle;
}
.modal-container {
margin: 0px auto;
padding: 20px 30px;
border-radius: 2px;
background-color: #fff;
font-family: Helvetica, Arial, sans-serif;
box-shadow: 0 2px 8px rgba(0, 0, 0, .33);
transition: all .3s ease;
}
.close-x {
color: #00A6CE;
float: right;
}
.close-x:hover {
cursor: pointer;
}
</style>
Without a library you need to set it up like this:
<div class="modal-wrapper" #click="$emit('close')>
<div class="modal-container" :class="size" #click.stop=""></div>
</div>
It looks like you're missing the #click.stop="" which is required. Additionally you want to move the $emit('close') up to the modal-wrapper level.
With a library it may be overkill, but this is something that I have used v-click-outside for.
Vue directive to react on clicks outside an element without stopping the event propagation. Great for closing dialogues, menus among other things.
Simply npm install --save v-click-outside
Then (from the docs):
<div v-click-outside="onClickOutside"></div>
and:
onClickOutside (event, el) {
this.closeModal();
},
Try creating a transparent div that covers all the screen but with a z-index < your modals z-index. Then #click on it, you emit your event to close the modal :) Hope it will hellp
<template>
<div #click="handleBackdropClick" class="backdrop" ref="backdrop">
<div class="modal">
<h1> Modal Title </h1>
<input type="text" />
<p> Modal Content </p>
</div>
</div>
</template>
<style>
.modal {
width: 400px;
padding: 20px;
margin: 100px auto;
background: white;
border-radius: 10px;
}
.backdrop{
top: 0;
position: fixed;
background: rgba(0,0,0,0.5);
width: 100%;
height: 100%;
}
.close{
display: none;
}
</style>
export default {
methods: {
handleBackdropClick(e){
console.log(e)
if (e.path[0].className == "backdrop") {
this.$refs.backdrop.classList.add('close');
}
}
}
}
</script>

Error when trying to use ReactiveProp in vue-chartjs

I'm trying to use a component called "Vue-Chartjs" to create a LineChart.[
I'm passing some data from a MySql database to the 'chartData' prop, defined in the Chart.js file.
But I'm getting this error. What I understood (I think), is that chartData doesn't get updated.
Does anyone know why it doesn't work? Thank you very much!
This are my Chart.js file and my Forecast.vue file
import {
Line,
mixins
} from 'vue-chartjs'
const {
reactiveProp
} = mixins
export default {
name: 'line-chart',
extends: Line,
mixins: [reactiveProp],
props: {
chartData: {
type: Array,
required: true
},
chartLabels: {
type: Array,
required: true
}
},
data() {
return {
options: {
scales: {
yAxes: [{
display: true
}],
xAxes: [{
display: false
}]
},
legend: {
display: true
},
responsive: true,
maintainAspectRatio: false
}
}
},
mounted() {
this.renderChart({
labels: this.chartLabels,
datasets: [{
label: 'Temperature',
colors: '#000000',
backgroundColor: '#000000',
data: this.chartData
}]
}, this.options)
}
}
<template>
<div class="FiveDaysForecast">
<div class="tabs is-fullwidth">
<ul>
<li><router-link to="OneDayForecast"><span class="icon is-small"><i class="fas fa-cloud"></i></span>One day forecast</router-link></li>
<li class="is-active"><router-link to="FiveDaysForecast"><span class="icon is-small"><i class="fas fa-cloud"></i></span>Five days forecast</router-link></li>
<li><router-link to="FilterByDate"><span class="icon is-small"><i class="fas fa-calendar-alt"></i></span>Filter forecast by date</router-link></li>
</ul>
</div>
<h1>FIVE DAYS FORECAST</h1>
<form>
<div class="box">
<div class="field">
<div class="control">
<input class="input is-rounded" type="text" placeholder="Place" v-model="place">
</div>
<br>
<div class="control">
<input class="input is-rounded" type="text" placeholder="Country" v-model="country">
</div>
<br>
<div class="control">
<input class="input is-rounded" type="text" placeholder="Unit of measure (Celsius = 'metric' | Fahrenheit = 'imperial')" v-model="unitOfMeasure" required>
</div>
<br>
<div class="control">
<input class="input is-rounded" type="text" placeholder="Latitude" v-model="latitude">
</div>
<br>
<div class="control">
<input class="input is-rounded" type="text" placeholder="Longitude" v-model="longitude">
</div>
</div>
<button class="button is-medium is-rounded" #click="getFiveDaysForecast">Search</button>
</div>
</form>
<br>
<table class="table is-narrow">
<thead>
<tr>
<th>Temperature</th>
<th>TemperatureMin</th>
<th>TemperatureMax</th>
<th>Humidity</th>
<th>Pressure</th>
<th>Time</th>
</tr>
</thead>
<tbody>
<tr v-for="forecast in forecastData" :key="forecast.Pressure">
<td>{{forecast.temperature}}</td>
<td>{{forecast.temperatureMin}}</td>
<td>{{forecast.temperatureMax}}</td>
<td>{{forecast.humidity}}</td>
<td>{{forecast.pressure}}</td>
<td>{{forecast.timeStamp}}</td>
</tr>
</tbody>
</table>
<line-chart
:chart-data="tempRows"
:chartLabels="weatherDate"
></line-chart>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "FiveDaysForecast",
data() {
return {
place: "",
country: "",
unitOfMeasure: "",
latitude: "",
longitude: "",
forecastData: [],
temperature: [],
weatherDate: [],
tempRows: []
};
},
methods: {
getFiveDaysForecast() {
axios({
method: "get",
url: "http://localhost:55556/api/ForecastActions/fiveDaysForecast",
params: {
Place: this.place,
Country: this.country,
UnitOfMeasure: this.unitOfMeasure,
Lat: this.latitude,
Lon: this.longitude
}
})
.then(response => {
console.log(response);
this.forecastData = response.data;
this.forecastData.forEach(item => {
var tempArray = [];
this.tempRows.push(item.temperature);
this.weatherDate.push(item.timeStamp);
});
})
.catch(error => {
console.log(error);
});
}
}
};
</script>
<style scoped>
.FiveDaysForecast {
width: 1200px;
margin: 30px auto;
}
.tabs {
width: 600px;
margin-left: auto;
margin-right: auto;
}
h1 {
font-size: 50px;
}
#forecast-params {
margin-top: 30px;
margin-bottom: 50px;
text-align: left;
font-size: 20px;
}
.param-names {
font-weight: bold;
color: black;
font-size: 20px;
}
.box {
background-color: transparent;
width: 600px;
margin: 0 auto;
border: 3px solid royalblue;
box-shadow: 0px 0px 30px royalblue;
}
::placeholder {
color: rgb(170, 170, 170);
}
input,
button {
background-color: #fcfcfc;
border: 2px solid rgb(170, 170, 170);
}
input:focus {
border: 2px solid royalblue;
box-shadow: 0px 0px 30px royalblue;
}
input:hover {
border: 2px solid royalblue;
}
button:focus {
border: 2px solid royalblue;
box-shadow: 0px 0px 30px royalblue;
}
button:hover {
border: 2px solid royalblue;
}
.table {
margin: 0 auto;
width: 1000px;
}
td,
th {
text-align: center;
}
thead {
font-size: 20px;
display: table-header-group;
vertical-align: middle;
border-bottom: 3px solid royalblue !important;
}
tr {
font-weight: 600;
transition: background-color 0.5s ease;
}
tr:hover {
background-color: rgb(153, 179, 255);
}
#chart {
display: inline-block;
padding: 0px;
margin: 0px;
}
#tempChart {
padding: 0 auto;
}
#charts {
padding: 0px auto;
}
</style>
Well your "problem" is that your data is async. So the chart will be rendered without any data or without proper data.
You have to put a
v-if="loaded"
on your chart component.
And in your axios call you need to set it to true.