Solved - Vue dynamically add style to :active pseudo class - javascript

HIGHLIGHT: This is a solved problem.
Edit: Solved the problem using #Amaarockz's advice!
I haven't had much experience in CSS variables & complicated :style structures, but turned out they're great!
Original question:
I have a menu built with Vue.js (and Vuetify), data passed in from the backend like:
[
{
id: 13241243,
color: "#123456",
activeColor: "#abcdef",
text: "Asadpyqewri"
},
{
id: 742378104,
color: "#234567",
activeColor: "#bcdefa",
text: "Iudaofqepr"
}
]
Menu looks like:
<v-btn-toggle>
<v-btn v-for="item in items" :key="item.id" :color="item.color">
{{item.text}}
</v-btn>
</v-btn-toggle>
Problem
I want to know, how can I dynamically make the items in a different color when they have :active pseudo-class?
What I've tried:
Write something in "style" attribute of list items:
Failed, as I can't add a selector in <element style="">, only styles.
Use an attribute like "active-color" defined by Vuetify:
Failed, such things don't exist.
Dynamically add colors to the "v--btn-active" class, which Vuetify adds automatically:
Failed, I can't find a way to do this seperately for each button.
Watch when the :active pseudo-class appears, add style in the listener:
Somehow MutationObserver didn't work for me.
getElementsByClassName[0] keeps getting "null". I tried writing that in windows.onload, nothing changed.
One time it returned the correct node, and I was able to watch the class mutation. But I can't reproduce that even though nothing changed in the code.
mounted(){
window.onload = function(){
const targetNodes = document.getElementsByClassName('asdff');
var a = targetNodes.item(0);
//"targetNodes" is a HTMLCollection with 3 elements, but "a" is null.
}
}
Add one different class to every button, and write seperate CSS for them using JavaScript:
Perhaps doable, but code's going to be extremely ugly & difficult to maintain.
Besides, it's hard to overwrite Vuetify's default style outsides <element style>; you have to add !important.
Last two attempts are what I think more hopeful. Is there any way to work through?

Try using pseudo class like
Vue.component('pseudo', {
data() {
return {
msg: 'Hover on me',
}
},
props: {
color: {
type: String,
},
text: {
type: String,
}
},
computed: {
cssAttrs() {
return {
'--color': this.color,
'--text': JSON.stringify(this.text),
}
}
},
template: `<button class="content" :style="cssAttrs">{{msg}}</button>`,
});
var vm = new Vue({
el: '#app',
});
.content {
color: red;
}
.content:focus {
content: var(--text);
color: var(--color);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<pseudo color="blue" text="Changing!!"></pseudo>
</div>

Related

How to display dynamic variable in HTML

I'm wondering how to display a number in HTML that was created dynamically later on in the code. For instance I initialize the value reportDataLength in data(), and I tested that it was returning the right value by doing a console.log(), but now I want to display this value in HTML?
name: 'notification-tray',
data() {
return {
headers: [
{
text: 'Notifications',
value: 'Body',
width: '380px',
},
],
reportData: [],
reportDataLength: 0,
};
},
async created() {
await this.getReportDataLength();
},
methods: {
async getReportDataLength() {
... this.reportDataLength = this.reportData.length;
console.log(this.reportDataLength);
},
},
};
But obviously when I do something like this,
<span class="d-none">Notification Button</span>
<span class="badge">this.reportDataLength</span>
it doesn't work correctly. The other solutions I saw for similar problems used JQuery, and I feel like there's a simple way to reference this, but I can't seem to find it out.
okay so if you are using pure Javascript , u can use this in your javascript function :
Document.getElementsByClassName('badge').innerHtml=this.reportDataLength ;
otherwise if you are using vue js you can try something like this in your html file :
<span class="badge">{{reportDataLength}}</span>
You can do document.getElementById("ID-HERE").innerHTML = "new stuff", but be warned that setting innerHTML is dangerous.

Using Vue.js directives within component template

I'm new to Vue.js and trying to create a component that connects to one object within some global-scope data and displays differently based on the specifics of each object. I think I'm misunderstanding how the directives v-if and v-on work within component templates. (Apologies if this should actually be two different questions, but my guess is that the root of my misunderstanding is the same for both issues).
Below is a minimal working example. My goal is to have each member entry only display the Disable button if the associated member is active, and enable changing their status via the button. (I also want to keep the members data at the global scope, since in the actual tool there will be additional logic happening outside of the app itself).
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<member-display
v-for="member in members"
v-bind:member="member"
></member-display>
</div>
<script>
var members = [
{name: "Alex", status: "On"},
{name: "Bo", status: "On"},
{name: "Charley", status: "Off"}
]
Vue.component('member-display', {
props: ['member'],
computed: {
active: function() {
// Placeholder for something more complicated
return this.member.status == "On";}
},
methods: {
changeStatus: function() {
this.member.status = 'Off';
}
},
// WHERE MY BEST-GUESS FOR THE ISSUE IS:
template: `
<div>
{{member.name}} ({{member.status}})
<button v-if:active v-on:changeStatus>Disable</button>
</div>
`
});
var app = new Vue({
el: "#app",
data: {
members: members
}
})
</script>
</body>
</html>
Thanks for your help!
The code v-if and the v-on for the button just have the wrong syntax. The line should look like this:
<button v-if="active" v-on:click=changeStatus>Disable</button>

Vote button with Vue and Axios

I have an vote button in my app to vote for articles or comments. I almost got that to work with ajax but the sync of clicks and counter is the big problem. I now try to do it with vue js because someone recommended to do so referring to Sync vote button with backend vote function.
I have to say I am new to vue js and hope that someone can help me to get this to work. A little specification how I want it to work. A user can toggle the vote button so it adds +1 or 0 and changes the color like here on stack but only with an up button.
What I have for now sends the request to the backend and stores the vote in the database but I dont know how to set up the counter and color properly. What I have so far.
<template>
<div>
<a class="vote" :class="{ active: hasVotes }" #click="vote"></a>
<div class="points">{{ votes }}</div>
</div>
</template>
<script>
export default {
data(){
return{
votes: this.voted
}
},
props: {
voted: {
required: true,
type: Number
},
article: {
required: true,
type: Object
}
},
computed: {
hasVotes() {
return this.votes > 0;
}
},
methods:{
vote(){
axios.post('/article/vote/' + this.article.id)
.then(function (response) {
console.log(response.data);
this.votes = response.count;
});
}
}
}
</script>
What I have else to say is that it is an laravel 5.7 app with vue.js integrated. Maybe it is better to do it with components...?
Best
It would be easier to encapsulate this in a component because Vue is data driven and now you actually have to dive into the DOM and manipulate the arrow color for a specific arrow when your count is larger than 0.
I have changed and simplified your code example. The first thing you want to do is not have separate votes and voteCount properties because they are simply the same thing. You want to receive the initial votes from the backend via an article prop and update it via your XHR call.
I have mocked up a quick example which I have not tested but this should get you going in the proper direction.
Example:
<upvote-arrow :article="{{ $article }}"></upvote-arrow>
Component:
<template>
<div>
<a class="arrow" :class="{ active: hasVotes }" #click="vote"></a>
<div class="points">{{ votes }}</div>
</div>
</template>
<script>
export default {
data(){
return{
votes: this.article.votes
}
},
props: {
article: {
required: true,
type: Object
}
},
computed: {
hasVotes() {
return this.votes > 0;
}
},
methods:{
vote(){
axios.post('/article/vote/' + this.article.id)
.then(function (response) {
this.votes = response.count;
});
}
}
}
</script>
<style lang="scss" scoped>
.active {
border-color: transparent transparent #1267dc transparent;
}
</style>
An active class will get applied to the anchor with a computed property when you have more than 1 vote. With this style binding you can change the color of the arrow.
It is also a better idea to only change the votes when the XHR call is actually successful because it might fail for some reason and the proper state is not reflected in that case. Just update the vote with the response of the backend.

How to loop through a list of components in VueJS

I may have gone about this completely the wrong way from the beginning so all advise is welcomed.
I am trying to create a basic page with inputs on the right and hints for the inputs on the left and when you focus on the inputs the appropriate hint is highlighted on the left.
There is a JSFiddle here: https://jsfiddle.net/eywraw8t/210693/
This will not work as I do not know how to find the appropriate hint to highlight (and set isHighlighted to false on all the other hints).
I managed to get a working example by adding a highlighted prop on the field object and not using a hint component. However in reality the fields data will come from the database so it won't have a highlighted parameter so a hint component seemed more sensible.
To put my question in simple terms: How can I find the relevant hint component when focused on an input?
JS Fiddle showing functionality without a component: https://jsfiddle.net/as2vxy79/
Broken JS Fiddle trying to use a component: https://jsfiddle.net/eywraw8t/210693/
Here is the JS outside JS Fiddle:
Vue.component('hint', {
template: `
<div class="hint" :class="{'highlight-hint': isHighlighted }">
<slot></slot>
</div>
`,
data() {
return {
isHighlighted: false
}
}
});
new Vue({
el: "#app",
data: {
fields: [
{
'id': 'name',
'hint': 'Put the name here'
},
{
'id': 'description',
'hint': 'Put the description here'
},
{
'id': 'more',
'hint': 'Put more here'
}
]
},
methods: {
onFocus(focusedField) {
// Somehow loop through the hints
// I am aware there is no "hints" property this is an example
this.hints.forEach(function(field) {
field.isHighlighted = (focusedField == field.id)
})
}
}
})
Short answer: you don't need a direct reference to the components displaying the hint because you can solve this problem using reactivity.
Only thing you need to do is add a data field which stores the field id you want to highlight and then conditionally add a class to the hint you want to highlight based on the selected id (or render the components conditionally).
new Vue({
el: "#app",
data: {
highlightedFieldId: '',
fields: [
{
'id': 'name',
'hint': 'Put the name here' },
{
'id': 'description',
'hint': 'Put the description here' },
{
'id': 'more',
'hint': 'Put more here' }
]
},
methods: {
onFocus(focusedFieldId) {
this.highlightedFieldId = focusedFieldId;
}
}
})
Here's an update to your Fiddle.
NOTES:
If you really need direct references you can use the ref directive (this also works in a list of components generated by v-for).
You should probably think about using tooltips for the hints. E.g. using Element UI's Tooltip.
UPDATE:
So, here's a solution using the ref directive to obtain a reference to the highlighted hint component. You can see that I used the field id in :ref but you still get an array from this.$refs[focusedFieldId] since there is a surrounding v-for. Other than that, it's pretty simple.
I also updated the hint component to accept the is-highlighted prop so it can change its class on its own (you previously used a data property for this which does not result in a component update).

Instantiate Vue.js components from existing HTML?

I'm trying to make Vue.js instantiate it's components from existing HTML templates and I cannot find any reasonable information on doing that. I'm using non-JavaScript backend framework and Vue.js added in as a normal .js file in the view stack.
Let's say I have the HTML structure looking like this:
<div id="vueApp">
<div class="vueComponent">...</div>
<div class="vueComponent">...</div>
<div class="vueComponent">...</div>
</div>
Then I'm trying to enable it with JavaScript like this (mixing jQuery and Vue for safety):
<script>
// declare Vue component
Vue.component('irrelevantName', {
template: '.vueComponent',
data: function() {
return { something: false }
}
});
// launch Vue app
jQuery(document).ready(function() {
var vueApp = new Vue({
el: '#vueApp',
created: function() {
// TODO: some method that will read/parse the component data, do binding, whatever
}
});
});
</script>
This kind of approach worked perfectly for whenever I had a precisely declared and pointed <template> tag within the HTML structure and the component was supposed to be just a single one. In this case though I'm trying to instantiate n ones identified by a CSS class.
I am aware that one possible approach would be passing all the component data as hidden <input> with JSON-ized array/Collection data, and then asking vueApp to create single-template-driven components on the fly from this data, though that seems a bit tedious approach.
Any suggestions?
Thanks in advance!
I believe you might be thinking about this the wrong way. Vue binds to a single element, so if you want multiple elements to be manipulated, what you can do is create a new Vue instance around div#vueApp and create the components inside of that with Vue templates, like so:
Vue.component('vue-comp', {
data: function () {
return {
something: false
}
},
template: '<div class="vue-comp"></div>'
})
let vueApp = new Vue({
el: '#vueApp',
created: function() {
}
});
div#vueApp {
padding: 1rem;
background: #CD545b;
display: inline-block;
}
div.vue-comp {
height: 50px;
width: 50px;
margin: 0 1rem;
background: #236192;
display: inline-block;
}
<script src="https://unpkg.com/vue"></script>
<div id="vueApp">
<vue-comp></vue-comp>
<vue-comp></vue-comp>
<vue-comp></vue-comp>
</div>

Categories

Resources