Toggle class on click in VueJS - javascript

I am learning Vuejs and I am constantly finding simple things like removing a class to be a pain. Please tell me how I can allow the .active class to be added or removed based on clicks between the 3 links.
In the example below the adding is working fine but it adds .active to all the links, and does not remove when clicked on again.
<div id="app">
<h2>App</h2>
<ul>
<li>Link text</li>
<li>Link text</li>
<li>Link text</li>
</ul>
</div>
JS
var app = new Vue({
el: '#app',
data: {
isActive: false
},
methods: {
activeLink() {
// the line below did not work
// document.getElementsByClassName("active").isActive = false,
this.isActive = true
}
}
})
JSfiddle is here, https://jsfiddle.net/s9r4q0gc/

You need to catch the event handler in the method and using that you can refer to the callee i.e. anchor object in this case.
See the fiddle : https://jsfiddle.net/s9r4q0gc/2/
activeLink(event) {
if(event.target.className == "noclass")
{
event.target.className = "link active";
}
else
{
event.target.className = "noclass";
}
}
UPDATED:
May be try this fiddle and see if it is hitting the bulls eye : https://jsfiddle.net/s9r4q0gc/4/
var app = new Vue({
el: '#app',
data: {
isActive: false
},
methods: {
activeLink(event) {
var checkboxes = document.getElementsByClassName ("noclass");
for (var i=0; i<checkboxes.length; i++) {
checkboxes[i].className = "link active";
//checkboxes[i].className = "link";
}
event.target.className = "noclass";
}
}
})

What you can do is use a data property to hold the currently active link. Then with that you'll be able to have any given link test if it is the active link and if the .active class should be applied.
Then it's just a matter of setting that property's new value when a link is clicked. If the same link that's currently active is clicked, then the property is cleared, thus removing the .active class from all links. Otherwise, the class is added to the link that was clicked.
This is a CodePen demonstrating what I mean in action, but your markup could look something like this:
<li><a href="#" class="link" :class="{ active: activeId == 'link-1' }"
#click.prevent="activeLink('link-1')">Link text</a></li>
<li><a href="#" class="link" :class="{ active: activeId == 'link-2' }"
#click.prevent="activeLink('link-2')">Link text</a></li>
<li><a href="#" class="link" :class="{ active: activeId == 'link-3' }"
#click.prevent="activeLink('link-3')">Link text</a></li>
and your JS could look something like this:
data: {
activeId: null
},
methods: {
activeLink(linkIdent) {
this.activeId = this.activeId === linkIdent ? null : linkIdent
}
}
It's obviously not as clean a solution as it could be, but I am sticking to your requirement that the solution fit the markup you provided.

Here is an alternative solution that may modify your code more than you want. I thought this might be useful since you are just learning and may be interested in an alternative. I started by declaring your links as an array of objects in vue so that we can assign an active attribute to each of them. Then we can just toggle the active value inline or use the toggleActive function (not currently in use.. just there for illustration if you prefer to use functions over inline logic).
Html:
<div id="app">
<h2>App</h2>
<ul>
<li v-for="l in links">
{{l.text}}
</li>
</ul>
</div>
Javascript:
var app = new Vue({
el: '#app',
data: {
links: [{
text: "Link text",
active: false
},{
text: "Second text",
active: false
},{
text: "Third text",
active: false
}]
},
methods: {
//To use this function make #click in html to:
//#click="toggleActive(l)"
toggleActive(x) {
x.active = !x.active;
}
}
})
https://jsfiddle.net/yrbt90v9/

Use V-for and an array of items so you have no need to statically type the links. This allows for the dynamic functionality you are looking for.
var app = new Vue({
el: '#app',
data: {
links: [
{text: "Link Text", active: false},
{text: "Link Text", active: false},
{text: "Link Text", active: false}
]
},
methods: {
activate(link) {
link.active = !link.active
}
}
})
.link{
text-decoration: none;
color: #555;
}
.active{
text-decoration: underline;
color: #42b983;
}
<div id="app">
<h2>App</h2>
<ul>
<li v-for="link in links">Link text</li>
</ul>
</div>

Related

How to display menu items based on sessionStorage

I'm kind of lost in which is the best way to proceed. After fetching the user data from an API, I store the user data in the sessionStorage. How can I display menu items based on roles (ie- Admin, Representative, Guest).
I have a basic Bootstrap Sidebar that looks like below.
<ul class="sidebar-nav">
<li><a>Home</a>
<ul class="sidebar-dropdown">
<li>Default</li>
<li>Reports</li>
</ul>
</li>
<li><a>Contact</a>
<ul class="sidebar-dropdown">
<li>Admin Contact</li>
<li>Contact 1</li>
</ul>
</li>
</ul>
Should I add classes to menu items based on roles to my menu items and with a JavaScript function that runs on load display or hide menu items??
Any guidance would be greatly appreciated.
JSFIDDLE
What you want is dynamically render a list based on data. Receiving data from session storage is not an issue because it can be done by
JSON.parse(sessionStorage.getItem('data'));
your issue is to render them. Try to use hardcoded data first to see if you are able to render what you want and then just plug in data from the storage. For an example
https://codesandbox.io/s/elated-fast-my3ud?file=/src/index.js
const routes = [
{
href: "/home",
title: "Home",
children: [
{
href: "/home/defaults",
title: "Defaults"
},
{
href: "/home/reports",
title: "Reports"
}
]
},
{
href: "/contact",
title: "Contact",
children: [
{
href: "/contact/admin-contact",
title: "Admin Contanct"
},
{
href: "/contact/contact-1",
title: "Contact 1"
}
]
}
];
function renderSingleRoute(route) {
const children = route.children ? renderRoutes(route.children) : "";
return `
<li>
${route.title}
${children}
</li>
`;
}
function renderRoutes(routes) {
return `
<ul>
${routes.map((route) => renderRoute(route)).join("")}
</ul>
`;
}
document.getElementById("app").innerHTML = renderRoutes(routes);
So the idea here is to build your routes however you want and then pass it to render function

QuerySelector Id Target when Matches Value in Object

I cannot figure out how to correctly target and display an HTML id tag when a corresponding matching JS object, contained within an array, has a "live" value of true.
I want a JS loop to display a link in schedule module only if that link id matches the "scheduleId" of an object and when that object's "live" value is true. How would I target and display this link?
See my HTML here:
<div class="test-schedule">
<h2>Schedule</h2>
<div><span>School of Ed</span><a class="link-class" id="ed">School of Ed link</a></div>
<div><span>School of Humanities</span><a class="link-class" id="hss">School of Humanities link</a></div>
<div><span>School of Science</span><a class="link-class" id="sci">School of Science link</a></div>
<div><span>School of Nursing</span><a class="link-class" id="nursing">School of Nursing link</a></div>
</div>
<style>
.link-class{display:none}
</style>
JS here:
const eventList = [
{
scheduleId: 'ed',
live: 'true',
},
{
scheduleId: 'hss',
live: 'false',
},
{
scheduleId: 'sci',
live: 'false',
},
{
scheduleId: 'nursing',
live: 'false',
},
];
Codepen link:
https://codepen.io/lvl12sealclubber/pen/PoWbJZQ?editors=1011
If I understand correctly you want all the links to be hidden and only display some based on a JS value? Could you just do:
.link-class {
visibility: hidden;
}
for (let event of eventList) {
if (event.live) {
document.getElementById(event.scheduleId).style.visibility="visible"
}
}
If I understand you want to show the link of that event based on if the event is live on the events object, you can try something simple like creating a class that will show the element like this
const eventList = [
{
scheduleId: "ed",
live: "true",
},
{
scheduleId: "hss",
live: "false",
},
{
scheduleId: "sci",
live: "true",
},
{
scheduleId: "nursing",
live: "false",
},
];
const checkLinks = () => {
for (let event of eventList) {
if (event.live === "true") {
document.querySelector("#" + event.scheduleId).classList.add("show-link");
console.log(event.scheduleId);
}
}
};
checkLinks();
.link-class {
display: none;
}
.show-link {
display: inline-block;
color: blue;
}
<div class="test-schedule">
<h2>Schedule</h2>
<div>
<span>School of Ed </span
><a class="link-class" id="ed">School of Ed link</a>
</div>
<div>
<span>School of Humanities </span
><a class="link-class" id="hss">School of Humanities link</a>
</div>
<div>
<span>School of Science </span
><a class="link-class" id="sci">School of Science link</a>
</div>
<div>
<span>School of Nursing </span
><a class="link-class" id="nursing">School of Nursing link</a>
</div>
</div>
I’d update data type for ‘live’ property to a boolean instead of a string.
In a real world, this will be data driven and you wouldn’t have to compare dates but just check the ‘live’ property if it’s true or not.
If I understand correctly, when a user lands on this page, you calculate the current date/time and show which event is live. If this is the case, you can just compare current date/time and show link for the one that is live. There is no need to update event.live. In fact, you shouldn't update your data through JS.
Also, instead of updating style for banner through JS, just maintain a placeholder on the top and control styles through CSS. I didn’t see a value to do it through JS.
You could filter out the unwanted array elements and then use a simple forEach to display. Something like this:
const eventList = [{
scheduleId: 'ed',
live: 'true',
}, {
scheduleId: 'hss',
live: 'false',
}, {
scheduleId: 'sci',
live: 'false',
}, {
scheduleId: 'nursing',
live: 'true',
}];
const scheduleEl = document.querySelector("#schedule")
eventList
.filter(event => event.live === 'true')
.forEach(event => {
scheduleEl.innerHTML += `<p>- ${event.scheduleId}</p>`;
});
<p>Live event schedule:</p>
<div id="schedule">
</div>

Vue.js v-for element from $refs broken in watch function

With Vue.js 2.5.22 and FireFox 65.0. What am I missing?
https://jsfiddle.net/r083hqgv/2/
A v-for element identified by a :ref="x" attribute doesn't work as expected in a watch function. I've also tried using :id="x" & getElementById(), and calling setTimeout(..., 200) within $nextTick().
Code from the above fiddle:
<div id="app" style="position:relative">
<h2>last element top: {{offset+''}}</h2>
<button #click="add()">Add & get top</button>
<ol>
<li v-for="a in list" :key="'r.'+a">
<a #click.stop.prevent="get($event.target)" href="#"
:ref="'r.'+a">get top {{'r.'+a}}</a>
</li>
</ol>
</div>
new Vue({
el: "#app",
data: {
offset: 0,
last: 'unset',
list: [],
},
methods: {
add: function() {
this.last = 'r.'+ this.list.push(this.list.length+1);
this.list = this.list.slice();
},
get: function(iEl) {
this.offset = iEl.offsetTop;
iEl.style = 'font-style:italic';
}
},
watch: {
list: function() {
this.$nextTick(function() {
var aEl = this.$refs[this.last];
if (aEl) this.get(aEl);
});
}
}
})
As referenced by the documentation ($refs), this.$refs["..."] returns an array when v-for is used. Therefore, change
if (aEl) this.get(aEl);
to
if (aEl) this.get(aEl[0]);
Everything will work (I already tested it on your jsfiddle).

Pass click action using array and v-for (Vue.js)

I am trying to pass through a v-for the text and the #click action of each li. For the text I know how to do it...but for the click action?enter code here
Each item of the array menuOptions (which is in the 'data' part of my Vue component) is structured like this :
{name: "firstOption",action: "console.log('first option called')"}
The first parameter is the name of the option, the
<ul>
<li v-for="option in menuOptions" #click="option.action">{{option.name}}</li>
</ul>
Do you have some ideas? (I guess that's maybe a pure JS question, but maybe there are possibilities to do it Vue too?)
Pass a function expression to the action property, instead of a "stringified" function.
{
name: 'firstOption',
action: function() {console.log('first option called'}
}
var app = new Vue({
el: '#app',
data: {
menuOptions: [{
action: function() {
console.log('foo')
},
name: 'foo'
}, {
action: function() {
console.log('bar')
},
name: 'bar'
}],
}
})
<script src="https://unpkg.com/vue"></script>
<div id='app'>
<ul>
<li v-for="option in menuOptions" #click="option.action">{{option.name}}</li>
</ul>
</div>

Vue.js: How to read $index inside computed function?

I have list of links:
<ul id="menu">
<li v-for="item in items">
<a href v-bind:href=link>{{item.message}}</a>
</li>
</ul>
var example1 = new Vue({
el: '#menu',
data: {
items: [
{ message: 'Link1' },
{ message: 'Link2' }
]
},
computed: {
link: function() {
return f($index) // how do I access current array index ??
}
}
})
I can achieve the desired result by using mustache markup in a href attribute but it must be possible to access this $index variable from within a computed function?
You can't.
This is what methods are for:
<a href v-bind:href="link($index)">{{item.message}}</a>
methods: {
link: function(index) {
return f(index)
}
}

Categories

Resources