Vote button with Vue and Axios - javascript

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.

Related

Solved - Vue dynamically add style to :active pseudo class

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>

How can I let the list page remember some params?

I have a Vue project, but there is a issue to me,
In my router.js:
{
path: '/home/aftersale_physicalserver_workpanel/:use_flag/:areapartition_homeshowtext',
meta: { keepAlive: true },
icon: 'compose',
name: 'aftersale_physicalserver_workpanel',
title: 'work panel',
component: resolve => {
require(['./views/main_home/home/components/general_admin_staff_panel/physicalserver/physicalserver_workpanel.vue'], resolve);
}
},
there is the code:
...
<template>
<lml-page
ref="lml_page_ref"
v-if=" origin_data && origin_data.count"
:data_count="origin_data.count"
:current.sync="cur_page"
#change_page_for_parent="server_change_page">
</lml-page>
</template>
...
<script>
export default {
props: {
...
cur_page: 1,
},
</script>
you see the cur_page is the page number. I want the URL append the page number.
because when I enter a detail page, when I go back, there will go to page 1 by default.
My purpose
my purpose is let the list_page remember some params. such as the upper page_number, and some search params. but in Vue I don't know how.
When I from a searched params page enter a detail page, when I go back:
<span class="go-left" #click="$router.go(-1)">
<Icon type="chevron-left"></Icon>
<span>Go back</span>
</span>
there will get a list_page without the searched data.
You would need to use either local storage or a central state, what I use is vuex like this:
onPageChange: function (pageNo) {
this.$store.dispatch(this.$mts.some.SOMETHING, pageNo);
},
Then you can call your store wherever you need and get the page number.
Look for the vuex docs on how to setup state management.
Like this:
this.page = this.$store.getters.some.page

Polymer DOM update after viewport refresh

I've been trying to work this out but sofar have been unable to find an answer. My Polymer element loads a base template JSON file, which is then run through a dom-repeat to create a basic HTML page.
Then another JSON text-file is loaded, which completes the various areas of the HTML with a JS function.
Upon button-click form child, a function is run that triggers the loading of another JSON file that adds additional info. This all works fine.
But when I go out of the page and back in it, it has remembered all my settings but does not display things correctly. It displays the translatedText well and the html code is there, but it does not complete the html code for the originalText.
It seems to want to load the last JSON file before the DOM is properly rendered. So I want it to refresh the whole DOM, but how do I do this?
My MWE:
<template>
<iron-ajax
auto
url="basetext.json"
handle-as="json"
last-response="{{baseText}}"></iron-ajax>
<div class="textcontent">
<template is="dom-repeat" items="{{baseText.lines}}" as="line">
<div class="lineblock">
<div class="line" id="line{{line.lineid}}" inner-h-t-m-l="{{line.linetext}}"></div>
<template is="dom-if" if="[[extraShowEnabled]]">
<div class="linepi" id='linepi{{line.lineid}}' inner-h-t-m-l="{{line.linetext}}"></div>
</template>
</div>
</template>
</div>
<template is="dom-if" if="[[extraLoadEnabled]]">
<iron-ajax
auto
url="originaltext.json"
handle-as="json"
last-response="{{originalText}}"></iron-ajax>
</template>
<iron-ajax
auto
url="translatedtext.json"
handle-as="json"
last-response="{{translatedText}}"></iron-ajax>
</template>
<script>
Polymer({
is: 'text-page',
properties: {
translatedText: Object,
originalText: Object,
extraShowEnabled: {
type: Boolean,
value: false
},
extraLoadEnabled: {
type: Boolean,
value: false
},
showViewer: {
type: String,
value: "none"
}
},
observers: [
'setView(showViewer)',
' _computeSegments(translatedText,".line")',
' _computeSegments(originalText,".linepi")'
],
ready: function() {
this.addEventListener('eventFromChild', this.changeView);
},
changeView: function(event) {
this.showViewer = event.detail.selectedView;
},
setView: function(showViewer) {
\\ first some code here to reset all css.
if (showViewer === "none") {
this.extraShowEnabled = false;
this.extraLoadEnabled = false;
}
if (showViewer === "sidebyside") {
this.extraShowEnabled = true;
this.extraLoadEnabled = true;
this._computeSegments(this.originalText,".linepi");
this._addSideBySideCode();
}
},
_computeSegments: function(inputText,linetype) {
if (inputText) {
Array.from(this.querySelectorAll(linetype+" sc-segment")).forEach(item => item.innerHTML = inputText.segments[item.id]);
}
},
_addSideBySideCode: function() {
\\ this function just adds some css.
},
});
</script>
I think You should try to use a compute function result as a dom-repeat item source, something like this:
<template is="dom-repeat" items="{{itmesByParamsCompute(baseText, originalText, translatedText, extraloadEnabled, ...)}}" as="line">
Add as many params as You need to recompute on. Then that compute function should return a valid source anytime at least one of the paras changes.
Also keep in mind, that if any of these params will become undefined that compute function might be ignored completely. Work around for this is making this opposite way - one property which is modified from manny observers, something like this:
properties: {
items_to_use: {
type: Array,
value: []
},
translatedText: {
type: Object,
observer: 'updateItemsToUse'
},
originalText: {
type: Object,
observer: 'updateItemsToUse'
}
},
updateItemsToUse: function (data) {
let updatedArray = this.someMixFixFunction(this.item_to_use, data);
this.set('items_to_use', updatedArray);
},
someMixFixFunction: function (old_array, data_to_apply) {
// do some merging or what ever You need here, for example
let updatedArray = old_array.concat(data_to_apply);
return updatedArray;
}

function call within a vue js v-for directive makes the loop run into an infinite loop and I don't know why

I have this template:
<template>
<div>
<div v-for="report in reports">
<div class="map" v-bind:id="mapID = report.started.toUpperCase()" v-text="report.started">
{{hello(mapID)}}
</div>
</div>
</div>
and this model:
<script>
import addmap from '../components/addmap';
export default {
data: function(){
return {
reports: [],
mapid: "",
map_id: ''
}
},
mounted: function(){
this.mapID = this.map_id;
this.allReport()
},
components: {
'addmap': addmap
},
computed: {
mapID: {
get: function(){
return this.map_id;
},
set: function(newValue){
this.map_id = newValue.toUpperCase();
}
}
},
methods: {
hello: function(val){
console.log(val)
},
allReport: function(){
axios.get('/reports').then(response => {
this.reports = response.data
});
}
}
}
Whenever I load this view, it report an infinite loop in the console, and I don't know why.
When I print just text it works fine, but whenever I try to call a function on each loop, it reports an infinite loop.
I don't know why this happens and any possible recommendation or suggestion will be greatly appreciated. Thank you.
Your problem is this:
v-bind:id="mapID = report.started.toUpperCase()"
You are changing a reactive data property during rendering, which triggers a re-render, which makes you change the property again, which triggers a re-render, and so on ...
So in short: Don't do this. I'm not sure what you are trying to achieve, so I'm not sure what to advise instead. Maybe just this?
v-bind:id="report.started.toUpperCase()"

Passing data to components in vue.js

I'm struggling to understand how to pass data between components in vue.js. I have read through the docs several times and looked at many vue related questions and tutorials, but I'm still not getting it.
To wrap my head around this, I am hoping for help completing a pretty simple example
display a list of users in one component (done)
send the user data to a new component when a link is clicked (done) - see update at bottom.
edit user data and send it back to original component (haven't gotten this far)
Here is a fiddle, which fails on step two: https://jsfiddle.net/retrogradeMT/d1a8hps0/
I understand that I need to use props to pass data to the new component, but I'm not sure how to functionally do it. How do I bind the data to the new component?
HTML:
<div id="page-content">
<router-view></router-view>
</div>
<template id="userBlock" >
<ul>
<li v-for="user in users">{{user.name}} - <a v-link="{ path: '/new' }"> Show new component</a>
</li>
</ul>
</template>
<template id="newtemp" :name ="{{user.name}}">
<form>
<label>Name: </label><input v-model="name">
<input type="submit" value="Submit">
</form>
</template>
js for main component:
Vue.component('app-page', {
template: '#userBlock',
data: function() {
return{
users: []
}
},
ready: function () {
this.fetchUsers();
},
methods: {
fetchUsers: function(){
var users = [
{
id: 1,
name: 'tom'
},
{
id: 2,
name: 'brian'
},
{
id: 3,
name: 'sam'
},
];
this.$set('users', users);
}
}
})
JS for second component:
Vue.component('newtemp', {
template: '#newtemp',
props: 'name',
data: function() {
return {
name: name,
}
},
})
UPDATE
Ok, I've got the second step figured out. Here is a new fiddle showing the progress: https://jsfiddle.net/retrogradeMT/9pffnmjp/
Because I'm using Vue-router, I don't use props to send the data to a new component. Instead, I need set params on the v-link and then use a transition hook to accept it.
V-link changes see named routes in vue-router docs:
<a v-link="{ name: 'new', params: { name: user.name }}"> Show new component</a>
Then on the component, add data to the route options see transition hooks:
Vue.component('newtemp', {
template: '#newtemp',
route: {
data: function(transition) {
transition.next({
// saving the id which is passed in url
name: transition.to.params.name
});
}
},
data: function() {
return {
name:name,
}
},
})
-------------Following is applicable only to Vue 1 --------------
Passing data can be done in multiple ways. The method depends on the type of use.
If you want to pass data from your html while you add a new component. That is done using props.
<my-component prop-name="value"></my-component>
This prop value will be available to your component only if you add the prop name prop-name to your props attribute.
When data is passed from a component to another component because of some dynamic or static event. That is done by using event dispatchers and broadcasters. So for example if you have a component structure like this:
<my-parent>
<my-child-A></my-child-A>
<my-child-B></my-child-B>
</my-parent>
And you want to send data from <my-child-A> to <my-child-B> then in <my-child-A> you will have to dispatch an event:
this.$dispatch('event_name', data);
This event will travel all the way up the parent chain. And from whichever parent you have a branch toward <my-child-B> you broadcast the event along with the data. So in the parent:
events:{
'event_name' : function(data){
this.$broadcast('event_name', data);
},
Now this broadcast will travel down the child chain. And at whichever child you want to grab the event, in our case <my-child-B> we will add another event:
events: {
'event_name' : function(data){
// Your code.
},
},
The third way to pass data is through parameters in v-links. This method is used when components chains are completely destroyed or in cases when the URI changes. And i can see you already understand them.
Decide what type of data communication you want, and choose appropriately.
The best way to send data from a parent component to a child is using props.
Passing data from parent to child via props
Declare props (array or object) in the child
Pass it to the child via <child :name="variableOnParent">
See demo below:
Vue.component('child-comp', {
props: ['message'], // declare the props
template: '<p>At child-comp, using props in the template: {{ message }}</p>',
mounted: function () {
console.log('The props are also available in JS:', this.message);
}
})
new Vue({
el: '#app',
data: {
variableAtParent: 'DATA FROM PARENT!'
}
})
<script src="https://unpkg.com/vue#2.5.13/dist/vue.min.js"></script>
<div id="app">
<p>At Parent: {{ variableAtParent }}<br>And is reactive (edit it) <input v-model="variableAtParent"></p>
<child-comp :message="variableAtParent"></child-comp>
</div>
I think the issue is here:
<template id="newtemp" :name ="{{user.name}}">
When you prefix the prop with : you are indicating to Vue that it is a variable, not a string. So you don't need the {{}} around user.name. Try:
<template id="newtemp" :name ="user.name">
EDIT-----
The above is true, but the bigger issue here is that when you change the URL and go to a new route, the original component disappears. In order to have the second component edit the parent data, the second component would need to be a child component of the first one, or just a part of the same component.
The above-mentioned responses work well but if you want to pass data between 2 sibling components, then the event bus can also be used.
Check out this blog which would help you understand better.
supppose for 2 components : CompA & CompB having same parent and main.js for setting up main vue app. For passing data from CompA to CompB without involving parent component you can do the following.
in main.js file, declare a separate global Vue instance, that will be event bus.
export const bus = new Vue();
In CompA, where the event is generated : you have to emit the event to bus.
methods: {
somethingHappened (){
bus.$emit('changedSomething', 'new data');
}
}
Now the task is to listen the emitted event, so, in CompB, you can listen like.
created (){
bus.$on('changedSomething', (newData) => {
console.log(newData);
})
}
Advantages:
Less & Clean code.
Parent should not involve in passing down data from 1 child comp to another ( as the number of children grows, it will become hard to maintain )
Follows pub-sub approach.
I've found a way to pass parent data to component scope in Vue, i think it's a little a bit of a hack but maybe this will help you.
1) Reference data in Vue Instance as an external object (data : dataObj)
2) Then in the data return function in the child component just return parentScope = dataObj and voila. Now you cann do things like {{ parentScope.prop }} and will work like a charm.
Good Luck!
I access main properties using $root.
Vue.component("example", {
template: `<div>$root.message</div>`
});
...
<example></example>
A global JS variable (object) can be used to pass data between components. Example: Passing data from Ammlogin.vue to Options.vue. In Ammlogin.vue rspData is set to the response from the server. In Options.vue the response from the server is made available via rspData.
index.html:
<script>
var rspData; // global - transfer data between components
</script>
Ammlogin.vue:
....
export default {
data: function() {return vueData},
methods: {
login: function(event){
event.preventDefault(); // otherwise the page is submitted...
vueData.errortxt = "";
axios.post('http://vueamm...../actions.php', { action: this.$data.action, user: this.$data.user, password: this.$data.password})
.then(function (response) {
vueData.user = '';
vueData.password = '';
// activate v-link via JS click...
// JSON.parse is not needed because it is already an object
if (response.data.result === "ok") {
rspData = response.data; // set global rspData
document.getElementById("loginid").click();
} else {
vueData.errortxt = "Felaktig avändare eller lösenord!"
}
})
.catch(function (error) {
// Wu oh! Something went wrong
vueData.errortxt = error.message;
});
},
....
Options.vue:
<template>
<main-layout>
<p>Alternativ</p>
<p>Resultat: {{rspData.result}}</p>
<p>Meddelande: {{rspData.data}}</p>
<v-link href='/'>Logga ut</v-link>
</main-layout>
</template>
<script>
import MainLayout from '../layouts/Main.vue'
import VLink from '../components/VLink.vue'
var optData = { rspData: rspData}; // rspData is global
export default {
data: function() {return optData},
components: {
MainLayout,
VLink
}
}
</script>

Categories

Resources