Displaying and hiding box onclick in vue js - javascript

I want to hide and display when I click on the icon cart. The problem is in hiding that box again,
icon before click : https://imgur.com/RxmcwsX
after click: https://imgur.com/cCt4mk0
Here is css image : https://imgur.com/d6ZPUbY
vue js : https://imgur.com/2kWZdly
mycss code :
<li class="nav-item" id="cart">
<i class="fa fa-shopping-cart fa-lg" #click="showCart"></i>
<div id="list-cart">
<div class="shadow-lg" style="position:absolute;background-color: #FFF;width:300px;height:300px;right:210px;top:60px;border-radius: 5px;" v-bind:style="{ visibility: computedVisibility }"></div>
</div>
vue code
var cart = new Vue({
el: '#cart',
data: {
visibility: 'hidden'
},
computed: {
computedVisibility: function() {
return this.visibility;
}
},
methods: {
showCart: function(event) {
this.visibility = 'visible';
}
}
});

Use v-if instead of directly manipulating the styles:
<li class="nav-item" id="cart">
<i class="fa fa-shopping-cart fa-lg" #click="visible = !visible"></i>
<div id="list-cart">
<div class="shadow-lg" v-if="visible"></div>
</div>
var cart = new Vue({
el: '#cart',
data: () => ({
visible: false
})
});

You could try binding it to a class instead. Then you can have a ternary expression that determines your class.
<li class="nav-item" id="cart">
<i class="fa fa-shopping-cart fa-lg" #click="showCart"></i>
<div id="list-cart">
<div
style="position:absolute;
background-color: #FFF;
width:300px;
height:300px;
right:210px;
top:60px;
border-radius: 5px;"
v-bind:class="[visible ? 'show' : 'hide', 'shadow-lg']">
</div>
</div>
Then you can have a data element, visible, that is set initially to false. You should also make data a function
data: () => ({
visible: false
})
then your show cart function can just be:
showCart() {
this.visible = !this.visible
}
which you can also call to close the cart.
And then set your styles:
<style scoped>
.show {
visibility: visible
}
.hide {
visibility: hidden
}
</style>
That said there are plenty of packages that offer 'modals' where this would largely be handled for you. I'd recommend vuetify but if you're the old fashioned type you could even use bootstrap.

If the given script in your question works, you may just change the showCart function as below.
var cart = new Vue({
el: '#cart',
data: {
visibility: 'hidden'
},
computed: {
computedVisibility: function() {
return this.visibility;
}
},
methods: {
showCart: function(event) {
if(this.visibility ==='visible'){
this.visibility = 'hidden';
}else if(this.visibility==='hidden'){
this.visibility = 'visible'
}
}
}
});

Related

How to open open a side panel upon a button press using vue.js?

I'm developing my first app with Vue and need some advice with trying to open a panel upon pressing a button in the header.
I'm starting with a basic HTML template, trying to add interactivity after pressing a button that's contained in the header.
When pressing the button, I'm getting the following message in the console:
vue.js:597 [Vue warn]: Invalid handler for event "click": got
undefined
(found in )
The code I'm using is as follows:
HTML:
<!-- Doctype HTML5 -->
<!DOCTYPE html>
<html lang="en" />
<head>
<link rel="stylesheet" href="css/normalize.css">
{{ $style := resources.Get "scss/main.scss" | toCSS | minify | fingerprint }}
<link rel="stylesheet" href="{{ $style.Permalink }}">
<script src="js/vue.js"></script>
<script type="text/javascript" src="js/vue-proj-info.js"></script>
</head>
<body>
<header>
<div class="h-branding">
<div id="h-nLogo">
<img src="img/med-logo-rev.png"height="70" />
</div>
<div id="h-title">
<h1>Executive Blueprint</h1>
<p class="subheader"><span class="tr-tier">Tier 1</span> - <span class="tr-service">Executive</span></p>
</div>
</div>
<div id="h-toggles">
<div class="buttonGroup">
<button type="" name="tier1">Tier 1</button>
<button type="" name="tier2">Tier 2</button>
<button type="" name="tier3">Tier 3</button>
</div>
<div class="buttonGroup">
<button type="" name="executive">Executive</button>
<button type="" name="concierge">Concierge</button>
</div>
</div>
<proj-slideout ref="proj-slideout" id="proj-slideout" :class="{ isOpen: isOpen }">></proj-slideout>
<div id="h-infoButton">
<div class="buttonGroup">
<button type="button" name="projInfo" class="proj-slideout-opener"
#click="open">Project Information</button>
</div>
</div>
</header>
JS:
Vue.component('proj-slideout', {
template: '#proj-slideout',
props: ['show'],
data: () => ({
isOpen: false,
projContent: 'overview' /* overview, jtbd, tiers, files */
}),
methods: {
close() {
this.isOpen = false;
},
open() {
this.isOpen = true;
console.log('Open Panel');
}
}
});
document.addEventListener("DOMContentLoaded", function(event) {
var app = new Vue({
el: 'header'
})
});
SCSS:
#proj-slideout {
position: fixed;
top: 0;
right: 0;
width: 90vw;
height: 100vh;
padding: 30px;
display: flex;
flex-direction: row;
background-color: white;
transform: translateX(-100%);
transition: transform 0.6s ease(out-cubic);
display: none;
&.isOpen {
transform: translateX(0);
}
Any advice would be helpful!
In your code, #click="open" is refer to Vue parent component scope, so you should define isOpen and open in parent Vue component
document.addEventListener("DOMContentLoaded", function(event) {
var app = new Vue({
el: 'header',
data: () => ({
isOpen: false,
}),
methods: {
close() {
this.isOpen = false;
},
open() {
this.isOpen = true;
console.log('Open Panel');
}
}
})
});
All you need is to use v-show
On the template In your component tag add v-if or v-show like so :
<projContent v-show="isOpen" ... />
On the trigger button use directly the click event to set isOpen to false or true like this :
<button #click="isOpen = !isOpen" ... >Project Information</button>//or even better #click="isOpen ^= 1"
Now when you click the button it will set isOpen to its opposite value, if it's false it will become true and your projContent component will show up, if it's true, it will be set to false, hiding automatically your component, you don't even need to set a method to get the job done, it's directly set in the event.
Don't forget to remove display:none from your scss
If you need to add a transition animation here is the link "Vue transitions"

Vue.js: Collapse/expand all elements from parent

I need to add "expand/collapse all" functionality for my Vue component(some collapsible panel).
If user clicks collapse button then clicks on some panel and expand it then clicking on collapse button will do nothing because watched parameter will not change.
So how to implement this functionality properly (buttons must collapse and expand components always)?
I prepared simple example(sorry for bad formatting, it looks nice in editor :( ):
var collapsible = {
template: "#collapsible",
props: ["collapseAll"],
data: function () {
return {
collapsed: true
}
},
watch: {
collapseAll: function(value) {
this.collapsed = value
}
}
}
var app = new Vue({
template: "#app",
el: "#foo",
data: {
collapseAll: true
},
components: {
collapsible: collapsible
}
});
.wrapper {
width: 100%;
}
.wrapper + .wrapper {
margin-top: 10px;
}
.header {
height: 20px;
width: 100%;
background: #ccc;
}
.collapsible {
height: 100px;
width: 100%;
background: #aaa;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script>
<div id="foo"></div>
<script type="text/x-template" id="collapsible">
<div class="wrapper">
<div class="header" v-on:click="collapsed = !collapsed"></div>
<div class="collapsible" v-show="!collapsed"></div>
</div>
</script>
<script type="text/x-template" id="app">
<div>
<button v-on:click="collapseAll = true">Collapse All</button>
<button v-on:click="collapseAll = false">Expand All</button>
<collapsible v-for="a in 10" v-bind:collapseAll="collapseAll" v-bind:key="a"></collapsible>
</div>
</script>
Thanks!
This is a case where I might use a ref.
<button v-on:click="collapseAll">Collapse All</button>
<button v-on:click="expandAll">Expand All</button>
<collapsible ref="collapsible" v-for="a in 10" v-bind:key="a"></collapsible>
And add methods to your Vue.
var app = new Vue({
template: "#app",
el: "#foo",
methods:{
collapseAll(){
this.$refs.collapsible.map(c => c.collapsed = true)
},
expandAll(){
this.$refs.collapsible.map(c => c.collapsed = false)
}
},
components: {
collapsible: collapsible
}
});
Example.

Parent onClick is not triggered when the child element is clicked inside button

I have a dropdown-button component which has a click lister. The button has some text and icon. The click event is triggered if we click the button carefully on the outline and not on the text or icon. Here is my component:
<template lang="html">
<div>
<button class="button dropbtn" #click="toggleDropdown">
<span>{{ name }}</span>
<span class="icon"><i class="fa fa-caret-down"></i></span>
</button>
<div v-show="visible" class="dropdown-content is-primary-important">
<slot>
</slot>
</div>
</div>
</template>
<script>
export default {
props: ['name'],
data: function() {
return {
visible: false
}
},
mounted: function() {
this.hideDropdownOnClick();
},
methods: {
toggleDropdown: function() {
// trigged on click of the button
// make the dropdown visible
console.log("toggling dropdown");
this.visible = !this.visible;
},
hideDropdownOnClick: function() {
window.onclick = (event) => {
console.log(event.target);
if (!event.target.matches('.dropbtn')) {
console.log("here", this.visible);
this.visible = false;
}
}
}
}
}
</script>
<style lang="css">
/* Dropdown Content (Hidden by Default) */
.dropdown-content {
position: absolute;
background-color: #fff;
min-width: 140px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
}
/* Links inside the dropdown */
.dropdown-content a {
padding: 12px 16px;
text-decoration: none;
display: block;
}
</style>
I feel that I am missing something very basic here, can someone help me with this? Thanks.
Edit
It seems like a bug with the browser as the answer to this question says:
Button element not firing click event when clicking on button text and releasing off it (but still inside the button)?
Adding this CSS rule fixes the issue for me:
span {
pointer-events: none;
}
When You click in span its toggled twice:) once by toggleDropdown method,
second by windows onclick handler.
here is working example: jsfiddle
<template id="tmp">
<div>
<button class="button dropbtn" #click="toggleDropdown">
<span>{{ name }}</span>
<span class="icon"><i class="fa fa-caret-down"></i></span>
</button>
<div v-show="visible" class="dropdown-content is-primary-important">
<slot>
</slot>
</div>
</div>
</template>
<div id="x">
<tmp name="my name">
<span>toggle me!</span>
</tmp>
</div>
Vue.component('tmp', {
template: '#tmp',
props: ['name'],
data: function() {
return {
visible: false
}
},
mounted: function() {
this.hideDropdownOnClick();
},
methods: {
toggleDropdown: function() {
// trigged on click of the button
// make the dropdown visible
console.log("toggling dropdown");
this.visible = !this.visible;
}
}
});
new Vue({
el: '#x',
data: function(){
return {
name: 'myName'
}
}
});
edit:
if you don't want to use clickaway here is small directive to detect clicks outside of element:
var VueClickOutSide = {
bind: function(el, binding, vNode) {
var bubble = binding.modifiers.bubble;
var handler = function(e) {
if (bubble || (!el.contains(e.target) && el !== e.target)) {
binding.value(e);
}
}
el.__vueClickOutside__ = handler;
document.addEventListener('click', handler);
},
unbind: function (el, binding, vNode) {
document.removeEventListener('click', el.__vueClickOutside__);
el.__vueClickOutside__ = null;
}
};
You just have to register that directive:
Vue.directive('click-outside', VueClickOutSide)
And use it in template:
v-click-outside:delay="hideDropdownOnClick"
You can use vue-clickaway to hide dropdown when click outside :
$ npm install vue-clickaway --save
import { mixin as clickaway } from 'vue-clickaway';
export default {
mixins: [ clickaway ],
... the rest of your code ...
You put v-clickaway="visible = false" in your root div of your dropdown-button component.
On click you show and immediately hide list.
You click text or icon (they are "span"),
this.visible = !this.visible;
then code goes to window.onclick where
if (!event.target.matches('.dropbtn'))
and your spans dont have this class. So you set
this.visible = false;
Change check to
if (!event.target.closest('.dropbtn'))

Vue js: How to use a computed property to modify string and apply dynamically as CSS class

I am following a tutorial and I want to add a new feature where the surname of each candidate is added as a class. I got this to work inline but then I wanted to clean it up and rather call it as a function.
Working inline
mayor.name.replace(/ /g,'').replace('Mr.','').toLowerCase()
The function textClass removes spaces and "Mr." from the string. I've tried adding this as a computed property but I don't know how to call it on mayor.name
CSS
.black{ color: black;}
.brown{ color: brown;}
.pink{ color: pink;}
.red{ color: red;}
HTML
<div class="container">
<div id="mayor-vote">
<h2>Mayor Vote</h2>
<ul class="list-group" style="width: 400px;">
<li v-for="candidate in candidates" class="list-group-item clearfix">
<div class="pull-left">
<strong style="display: inline-block; width: 100px;">{{ candidate.name }}:</strong> {{ candidate.votes }}
</div>
<button class="btn btn-sm btn-primary pull-right" #click="candidate.votes++">Vote</button>
</li>
</ul>
<h2>Our Mayor is <span class="the-winner" :class="mayor.name.textClass">{{ mayor.name }}</span></h2>
<button #click="clear" class="btn btn-default">Reset Votes</button>
</div>
</div>
</body>
JS
new Vue({
el: '#mayor-vote',
data: {
candidates: [
{ name: "Mr. Black", votes: 140 },
{ name: "Mr. Red", votes: 135 },
{ name: "Mr. Pink", votes: 145 },
{ name: "Mr. Brown", votes: 140 }
]
},
computed: {
mayor: function(){
var candidateSorted = this.candidates.sort(function(a,b){
return b.votes - a.votes;
});
return candidateSorted[0];
},
textClass: function() {
return this.replace(/ /g,'').replace('Mr.','').toLowerCase();
}
},
methods: {
clear: function() {
this.candidates = this.candidates.map( function(candidate){
candidate.votes = 0;
return candidate;
})
}
}
});
There are few mistakes in your code, one is dynamic class binding in vue takes a hash object, not an string. So you have to return an hash like this : { active: true } from the computed property.
Second thing is computed property in vue always modify another vue propery or values returned from an mehtod, to correct these you need to make following changes:
You have to use this.mayor.name in computed property to calculate dynamic class like this:
computed: {
mayor: function(){
var candidateSorted = this.candidates.sort(function(a,b){
return b.votes - a.votes;
});
return candidateSorted[0];
},
textClass: function() {
var tmp = {}
tmp[this.mayor.name.replace(/ /g,'').replace('Mr.','').toLowerCase()] = true
return tmp
}
},
and apply like this in HTML:
<h2>Our Mayor is <span class="the-winner" :class="textClass">{{ mayor.name }}</span></h2>

Remove a class which is not recognized by DOM

I am working on a interactive map, which triggers an overlay image (which highlights the selected area)
But now I add classes to an div and I want to delete them if I highlight an other area. First I tried the starts with indicator to remove classes which starts with hl- this is my js file:
$('.btn-pointer').click(function() {
$('.highlight-layer').removeClass('[class^="hl-"]');
});
$('.btn-sp').click(function() {
$('.highlight-layer').addClass('hl-sp');
$('.popover').not(this).popover('hide');
});
$('.btn-vp').click(function() {
$('.highlight-layer').addClass('hl-vp');
$('.popover').not(this).popover('hide');
});
$('.btn-sl').click(function() {
$('.highlight-layer').addClass('hl-sl');
$('.popover').not(this).popover('hide');
});
$('.btn-ec').click(function() {
$('.highlight-layer').addClass('hl-ec');
$('.popover').not(this).popover('hide');
});
And here is the html:
<div>
<img src="../img/map/map-full.jpg" alt="">
<button class="btn btn-sp btn-pointer" data-container="body" data-toggle="popover" data-placement="top" data-content="<h2>Safaripark</h2>">Safaripark</button>
<button class="btn btn-vp btn-pointer" data-container="body" data-toggle="popover" data-placement="top" data-content="<h2>Vakantiepark</h2>">Vakantiepark</button>
<button class="btn btn-sl btn-pointer" data-container="body" data-toggle="popover" data-placement="top" data-content="<h2>Speelland</h2>">Speelland</button>
<button class="btn btn-ec btn-pointer" data-container="body" data-toggle="popover" data-placement="top" data-content="<h2>Event Center</h2>">Event Center</button>
<div class="highlight-layer hl-ec"></div>
</div>
I tried to create a Fiddle but never added external Resources so the error it gives me is the following: Uncaught Error: Bootstrap's JavaScript requires jQuery
You could use the callback for removeClass to filter out the classes you want to remove based on a starting string etc
$('.highlight-layer').removeClass(function(i, klass) {
var remove = 'hl-';
return klass.split(/\s/).filter(function(k) {
return k.indexOf(remove) === 0;
}).join(' ');
});
removeClass doesn't accept a selector, it accepts a space-delimited series of classes to remove. So just list the ones you want to remove:
$('.highlight-layer').removeClass('hl-sp hl-vp hl-sl hl-ec');
It's fine if one (or indeed all) of them aren't on any given element; the one(s) that is/are will be removed.
Example:
$("input[type=button]").on("click", function() {
$(".highlight-layer").removeClass("hl-sp hl-vp hl-sl hl-ec");
});
.hl-sp {
color: red;
}
.hl-vp {
color: green;
}
.hl-sl {
color: blue;
}
.hl-ec {
color: grey;
}
<div>
<input type="button" value="Remove">
<div class="highlight-layer hl-sp">Starts out with hl-sp</div>
<div class="highlight-layer hl-vp">Starts out with hl-vp</div>
<div class="highlight-layer hl-sl">Starts out with hl-sl</div>
<div class="highlight-layer hl-ec">Starts out with hl-ec</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Working Fiddle.
You have just to change one line in your code and it will works check the code below that remove all the classes and keep just the essential one highlight-layer :
$('.btn-pointer').click(function() {
$('.highlight-layer').removeClass().addClass('highlight-layer');
});
Like this the element classes are always two highlight-layer and clicker button class.
Hope this helps.
You can add extension to jQuery which would loop through elements returned from selector and remove list.
Following is a basic example:
JSFiddle.
$.fn.removeClasses = function(regex) {
Array.prototype.forEach.call(this, function(el) {
var class_list = el.classList;
var filtered_list = Array.prototype.filter.call(class_list, function(c) {
return regex.test(c);
});
console.log(filtered_list);
$(el).removeClass(filtered_list.join(" "))
});
}
$(".foo").on("click", function() {
var reg = /^test/;
$(this).removeClasses(reg);
});
.test {
background: #333;
}
.test1 {
color: #fff;
}
.test2 {
border: 1px solid gray;
}
.test3 {
padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<div class="test test1 test2 test3 foo">
Test
</div>

Categories