I have a div which listens for click events like this:
<div v-bind="$attrs"
class="flex flex-col"
#click="showEditModal = true">
</div>
However, it also triggers when I highlight text after releasing the left mouse button. How do I ignore this type of clicks, and only trigger on simple click events?
You could achieve that by stopPropagation if the selection is a Range like this one
new Vue({
el: '#app',
methods: {
clickme(e){
if (document.getSelection().type === 'Range') e.stopPropagation();
else
console.log('welcome')
}
}
})
<script src="https://unpkg.com/vue#2"></script>
<div id="app">
<p #click="clickme">Welcome there</p>
</div>
Related
I'm new to vue and strugling with some props and attributes.
I have a vue application where the main app is calling three different components:
Navbar,
Sidebar
MapContainer
In Navbar user will fill a selector with information about city and states. After user presses search, the list of results will then show up in the Sidebar menu.
Sidebar itself is a simple component carrying only a router-view for it's children components which are Results and Details
Results will receive the result of the search performed in the Navbar component. When user clicks any item in the Results component, Sidebar will then load Details taking the place of Results with detailed information about that place.
the problem is that the data used to make the request(city and states) comes from the first component navbar. I'm passing this data to sub-components using vue-router params option. When component Results gets unmounted, I lost all the data that was passed, even adding a Watch couldn'd fix the problem and thus can't return back to the previous page. Even adding a Watch couldn'd fix the problem. What's the proper way to handle data across components that area unmounted?
Navbar.vue
<template>
<div class="navbar">
<div class="logo">
Logo
</div>
<div class="middle">
<div class="flex-selectors">
<div class="navbar-options">
<select v-model="state_id" class="main-selectors" #click="load_cities">
<option v-for="state in states" :value="state.state_id" :key="state">
{{ state.state }}
</option>
</select>
<select v-model="city_id" class="main-selectors">
<option v-for="city in cities" :value="city.city_id" :key="city">
{{ city.city }}
</option>
</select>
</div>
<div class="search">
<button #click="seach">
<router-link :to="{name: 'results', params: {state: state_id, city: city_id} }" aria-current="page" title="Resultados">
<div>Search</div>
</router-link>
</button>
</div>
</div>
</div>
</template>
Sidebar.vue
<template>
<div class="sidebar">
<router-view></router-view>
</div>
</template>
Results.vue
<template>
<div v-if="areas.length">
<router-link :to="{name: 'details' }" tag="div" class="container" #click="load(area)" v-for="area in areas" :key="area" :value="area">
<!-- BUNCH OF DIVS AND V-FOR -->
</router-link>
</div>
<div v-else>
NA
</div>
</template>
<script>
export default {
data() {
return {
state_id: this.$route.params.state,
city_id: this.$route.params.city,
areas: [],
}
},
methods: {
search(state_id, city_id) {
load_areas.get(state_id, city_id).then(
result => {
this.areas = result.data
}
)
}
},
mounted() {
this.emitter = inject('emitter')
this.searchAreas(this.state_id, this.city_id)
},
created() {
this.$watch(
() => this.$route.params,
(toParams, previousParams) => {
this.searchAreas(toParams.state, toParams.city)
}
)
},
watch: {}
}
</script>
What's the proper way to handle data across components that are
unmounted?
You cannot access data from an unmounted component.
When multiple components need to access or modify the same data, a good option to look into is state management. Pinia is the new official library recommendation for state management.
Instead of passing data through vue-router params, create a store for it. Set results of your search query from Navbar component and access it in Results component. When Results component gets unmounted, you won't lose the data.
I have a GET request in my home.vue component.
This query allows me to get an array of objects.
To display all the objects, I do a v-for loop and everything works fine.
<div class="commentaires" v-for="(com, index) of coms" :key="index">
My concern is that I want to display an image by clicking on it (coms[index].imageUrl), in a modal (popup).
The modal is displayed fine but not with the correct image, i.e. the modal displays the last image obtained in the loop, which is not correct.
Here is the full code of my home.vue component
<template>
<div class="container">
<div class="commentaires" v-for="(com, index) of coms" :key="index">
<modale :imageUrl="com.imageUrl_$this.index" :revele="revele" :toggleModale="toggleModale"></modale>
<img class="photo" :src=""" alt="image du commentaire" #click="toggleModale">
</div>
</div>
</template>
<script>
//import axios from "axios";
import axios from "axios";
import Modale from "./Modale";
export default {
name: 'HoMe',
data() {
return {
coms: [],
revele: false
}
},
components: {
modale: Modale
},
methods: {
toggleModale: function () {
this.revele = !this.revele;
},
</script>
Here is my modale.vue component
<template>
<div class="bloc-modale" v-if="revele">
<div class="overlay" #click="toggleModale"></div>
<div class="modale card">
<div v-on:click="toggleModale" class="btn-modale btn btn-danger">X</div>
<img :src=""" alt="image du commentaire" id="modal">
</div>
</div>
</template>
<script>
export default {
name: "Modale",
props: ["revele", "toggleModale", "imageUrl"],
};
</script>
I've been working on it for 1 week but I can't, so thank you very much for your help...
in your v-for loop you're binding the same revele and toggleModale to every modal. When there is only one revele then any time it's true, all modals will be displayed. It's therefore likely you're actually opening all modals and simply seeing the last one in the stack. You should modify coms so that each item has it's own revele, e.g.:
coms = [
{
imageUrl: 'asdf',
revele: false
},
{
imageUrl: 'zxcv',
revele: false
},
{
imageUrl: 'ghjk',
revele: false
}
];
then inside your v-for:
<modale
:image-url="com.imageUrl"
:revele="com.revele"
#toggle-modale="com.revele = false"
></modale>
<img class="photo" :src=""" alt="image du commentaire" #click="com.revele = true">
passing the same function as a prop to each modal to control the value of revele is also a bad idea. Anytime a prop value needs to be modified in a child component, the child should emit an event telling the parent to modify the value. Notice in my code snippet above I replaced the prop with an event handler that turns the revele value specific to that modal to false. Inside each modal you should fire that event:
modale.vue
<div class="btn-modale btn btn-danger" #click="$emit('toggle-modale')">
X
</div>
This way you don't need any function at all to control the display of the modals.
I'm getting starting with Vue.js and I have a simple page set up to experiment with conditional css.
<div id="app">
<div class="demo" #click="handleClick(0)" :class="{ 'red': attachRed[0] }">
</div>
<div class="demo" #click="handleClick(1)" :class="{ 'red': attachRed[1] }">
</div>
<div class="demo" #click="handleClick(2)" :class="{ 'red': attachRed[2] }">
</div>
</div>
and then my js
new Vue({
el: "#app",
data: {
attachRed: [false, false, false]
},
methods: {
handleClick: function(index) {
this.attachRed[index] = !this.attachRed[index];
console.log(this.attachRed)
}
}
});
Each div is a grey block. When attaching the "red" class, the block turns red. attachRed array is updating every time a demo div is clicked. But the class is never added. If I start the attachRed property off as being true, then the red class is attached initially, but this isn't toggled when clicked. This works if these values aren't stored in an array though.
Is it possible to make the view bindings watch for these changes or to manually trigger one? Or is there some sort of gotcha when it comes to array properties?
It is a gotcha. This page goes into it a bit: https://vuejs.org/2016/02/06/common-gotchas/
In short, you want to do
var val = this.attachedRed[index]
this.attachedRed.$set(index, !val);
I have 3 Bootstrap panels that expand/collapse when their headers are clicked.
However when changing to another route id (for example for :id=CarModel1 to id=CarModel2 with /cars/details/:id as route), I want to keep their state as was in the previous id after the user messes around with the panels, so that the user can compare the different details immediately when changing views.
How can I keep the panel state? Please, keep in mind I'm using ngRoute and not ui-Router.
Sample panel:
<div id="details">
<div class="panel-heading detailsPanel">
<a data-toggle="collapse" data-parent="#details" data-target="#collapseDetailsPanel">
<h4>Additional Details</h4>
</a>
</div>
<div id="collapseDetailsPanel" class="panel-collapse collapse">
<div class="panel-body">
<div>Lorem Ipsum</div>
</div>
</div>
</div>
Managed to do it by adapting a solution found here and using sessionStorage instead of $.cookie.
$("#details").on('shown.bs.collapse', function() {
var activeDetails = $("#details .in").attr('id');
sessionStorage.setItem('activeDetailsGroup', activeDetails);
});
$("#details").on('hidden.bs.collapse', function() {
sessionStorage.setItem('activeDetailsGroup', "inactive");
});
var lastDetails = sessionStorage.getItem('activeDetailsGroup');
if (lastDetails !== "inactive") {
//remove default collapse settings
$("#details .panel-collapse").removeClass('in');
//show the account_last visible group
$("#" + lastDetails).addClass("in");
}
I have created a test project with Meteor which uses Masonry. I added the package mrt:jquery-masonry(or isotope:isotope), and it works well at the beginning. However, the problem comes now.
Basically, I want to implement the feature that when user clicks the button, the page will be added one more div. Below is my code:
main.html
<body>
<div class="container">
{{> masonryContent}}
</div>
<script>
(function($){
var $container = $('.masonry-container');
$container.masonry({
columnWidth: 300,
gutterWidth: 50,
itemSelector: '.masonry-item'
})
}(jQuery));
</script>
</body>
style.css
.masonry-item {
width: 300px;
}
masonry-content.html
<template name="masonryContent">
<div class="masonry-container">
<div class="masonry-item">
<p>blabla...</p>
<p>
Button
</p>
</div>
<div class="masonry-item">
<p>test...</p>
</div>
<div class="masonry-item">
<p>another test...</p>
</div>
{{#if showItem}}
<div class="masonry-item">
<p>new added item...</p>
</div>
{{/if}}
</div>
</template>
masonry-content.js
Template.masonryContent.events({
"click #click-me": function(e) {
e.preventDefault();
Session.set('show_me', true);
}
});
Template.masonryContent.helpers({
showItem: function() {
return !!Session.get('show_me');
}
});
The problem is when I click the button, the new div was created; however, it wasn't placed by following Masonry rules. The new created item just overlapped to the first item, but I expect it performs the way to append to the last item.
I would appreciate if anyone could help me on this.
Thanks in advance!
As meteor does partial rendering the element needs to be there in the DOM for masonry to work. So there are two ways of getting over the problem
1) Hide or unhide the element when the button click happens
Or
2) re-render the DOM
You can use the chrome dev tools to see what DOM elements are touched/refreshed (Green color).
There is a typo in masonry in the template name insertion.
Check the package state, many mrt packages are not well supported anymore.