Calling functions inside Vue.js template - javascript

My template:
<template id="players-template" inline-template>
<div v-for="player in players">
<div v-bind:class="{ 'row': ($index + 1) % 3 == 0 }">
<div class="player col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{{ player.username }}
<span class="small pull-right">{{ player.createdAt }}</span>
</h3>
</div>
<div class="panel-body">
<img v-bind:src="player.avatar" alt="{{ player.username }}" class="img-circle center-block">
</div>
<div class="panel-footer">
<div class="btn-group btn-group-justified" role="group" aria-label="...">
<span class="glyphicon glyphicon-envelope"></span>
<span class="glyphicon glyphicon-user"></span>
<span class="glyphicon glyphicon-option-horizontal"></span>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
My script:
new Vue({
el: 'body',
methods: {
createConversation: function(id) {
console.log("createConversation()");
console.log(id);
}
}
});
When the template is rendering i gets an error [Vue warn]: v-on:click="createConversation" expects a function value, got undefined. I don't know how to use methods inside a component template. If someone could help me I would appreciate is.

If you need the createConversation method to be on the global Vue instance, you should look at dispatching events. Your component should like this:
Vue.component('playersTemplate', {
template: '#players-template',
methods: {
createConversation: function (id) {
this.$dispatch('createConversation', id)
}
}
}
});
The global Vue instance should implement the createConversation event, instead of a method:
new Vue({
el: 'body',
events: {
createConversation: function(id) {
console.log("createConversation()");
console.log(id);
}
}
});

Your method should be in the component, not in your global Vue instance. All functions are called as this.createConversation behind the scenes, so it needs to be within the component that is the template for.

Related

Vue CLI - TypeError: Cannot read properties of undefined (reading '1')

I'm beginner in VueJS and hoping for your help.
I'm trying to create Weather forecast app based on OpenWeatherMap API.
The concept is such that:
Enter some location to input on homepage and click to search button. (in my code it's a component Search.vue)
Redirecting to another page and show results - current weather and forecast for next 6 days. (component Weather.vue)
I created function with two consistent fetch calls. First fetch taking entered input query and return needed data from Current Weather Data API. After that, function run second fetch to One Call API based on latitude longitude from first fetch.
Everything is working and showing fine, but i don't undestand why i have an error Uncaught (in promise) TypeError: Cannot read properties of undefined (reading '1') in console:
Сan someone know how to fix this error?
My Search.vue (homepage) component:
<template>
<div class="row">
<div class="search-col col">
<div class="search-box">
<input
type="text"
class="search-bar"
placeholder="Location"
v-model="query">
<router-link :to="{name: 'DetailedWeather', params: { query: query }}" class="btn-search">
<i class="fas fa-search"></i>
</router-link>
</div>
</div>
</div>
</template>
My Weather.vue (weather results showing page) component:
<template>
<div class="row" v-if="typeof weather.main != 'undefined'">
<div class="weather-col col">
<div class="weather-app">
<div class="weather-box">
<div class="weather-top-info">
<div class="clouds-level"><span class="icon"><i class="fas fa-cloud"></i></span> {{ weather.clouds.all }}%</div>
<div class="humidity"><span class="icon"><i class="fas fa-tint"></i></span> {{ weather.main.humidity }}%</div>
</div>
<div class="weather-main-info">
<div class="temp-box">
<div class="temp-main">{{ Math.round(weather.main.temp) }}</div>
<div class="temp-inner-box">
<div class="temp-sign">°C</div>
<div class="temp-max"><span class="icon"><i class="fas fa-long-arrow-alt-up"></i></span> {{ Math.round(weather.main.temp_max) }}°</div>
<div class="temp-min"><span class="icon"><i class="fas fa-long-arrow-alt-down"></i></span> {{ Math.round(weather.main.temp_min) }}°</div>
</div>
</div>
<div class="weather-desc">{{ weather.weather[0].description }}</div>
<div class="weather-icon">
<img :src="'http://openweathermap.org/img/wn/'+ weather.weather[0].icon +'#4x.png'">
</div>
</div>
<div class="weather-extra-info">
<div>Feels like: <span>{{ Math.round(weather.main.feels_like) }}°C</span></div>
<div>Sunrise: <span>{{ weather.sys.sunrise }}</span></div>
<div>Sunset: <span>{{ weather.sys.sunset }}</span></div>
</div>
</div>
</div>
</div>
<div class="forecast-col col">
<div class="row">
<div class="forecast-item-col col" v-for="day in forecastDays" :key="day">
<div class="forecast-box">
<div class="forecast-date">{{ forecast.daily[day].dt }}</div>
<div class="forecast-temp">{{ Math.round(forecast.daily[day].temp.day) }}°C</div>
<div class="forecast-icon"><img :src="'http://openweathermap.org/img/wn/'+ forecast.daily[day].weather[0].icon +'#2x.png'"></div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="actions-col col">
<router-link :to="{name: 'Search'}" class="btn btn-default">
Back to search
</router-link>
</div>
</div>
</template>
<script>
export default {
name: 'Weather',
props: ['query'], //getting from homepage
data() {
return {
api_key:'b7fe640e9a244244a6f806f3a6cbf5fc',
url_base:'https://api.openweathermap.org/data/2.5/',
forecastDays: 6,
weather: {},
forecast: {}
}
},
methods: {
fetchWeather(){
// first call
fetch(`${this.url_base}weather?q=${this.query}&units=metric&appid=${this.api_key}`)
.then(response =>{ return response.json() }).then(this.setResults);
},
setResults(results){
this.weather = results;
// consistent second call
fetch(`${this.url_base}onecall?lat=${results.coord.lat}&lon=${results.coord.lon}&exclude=current,minutely,hourly,alerts&units=metric&appid=${this.api_key}`)
.then(data =>{ return data.json() }).then(this.setForecast);
},
setForecast(results){
this.forecast = results
},
},
created() {
this.fetchWeather();
}
</script>
My router/index.js file:
import { createRouter, createWebHashHistory } from 'vue-router'
import Search from '../components/Search.vue'
import Weather from '../components/Weather.vue'
const routes = [
{
path: '/',
name: 'Search',
component: Search
},
{
path: '/detailed-weather',
name: 'DetailedWeather',
component: Weather,
props: true
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
From what I guess (given the code and the error), there might be an issue with the objects you are receiving from the API.
The error message suggests you are trying to read something from an array at a specific index that is undefined.
The only occurrence in your code that could cause this error is from the template, where you are reading, for example:
{{ forecast.daily[day].dt }}
{{ Math.round(forecast.daily[day].temp.day) }}
I can't tell exactly which one is it, but try to double check the shape of the objects you are working with.

How to read data from a v-for (vue.js)?

Given the next v-for:
<div class="container-fluid" id="networdapp" style="display:none;">
<div class="row" >
<div v-for="result in results" class="col-sm-6" >
<div class="card m-3 h-240 bg-light" >
<div class="card-header text-center" > {{ result.title }} </div>
<div class="card-body" style="height:200px" >
<p class="card-text" v-html="result.prevDesc"></p>
</div>
<div class="card-footer bg-transparent border-info">
<a href="/details" class="btn btn-info" #click="getData(result)" >Details</a>
</div>
</div>
</div>
</div>
</div>
And the next Vue.js script:
<script type="text/javascript">
const vm = new Vue({
el: '#networdapp',
data: {
results:[]
},
methods: {
getData: function(result){
window.alert($(this).parents("#networdapp").find(".card-header.text-center").outerHTML);
window.alert(document.getElementsByClassName("card-header").outerHTML);
window.alert(result.outerHTML);
}
},
mounted() {
axios.get('/getJson')
.then(response => {
this.results = response.data;
})
.catch( e => {
console.log(e);
});
}
});
</script>
I want to get data from a specific iteration,let's say if I click the "Details" button of the 3rd div from the v-for I want to get the {{result.title }} data from the 3rd for.Is it possible?I've been reading the Vue.js documentation but I didn't find anything about reading the data from DOM.If it is not possible,than how can I do that without Vue.js?Is there any other option?
The main goal is to get this data and to put it into a js object passing it to another webpage.
you have to pass index key and use is to get from results's position.
change the for loop div into
<div v-for="(result,i) in results" :key="i" class="col-sm-6" >
also chnange the methods parameter
<a href="/details" class="btn btn-info" #click="getData(i)" >Details</a>
and the method will get the index key and here i have used console to see the result.title that you have wanted. you can use it any how you want.
getData: function(key){
console.log(this.results[key].title)
}
so
Given the next v-for:
<div class="container-fluid" id="networdapp" style="display:none;">
<div class="row" >
<div v-for="(result,i) in results" :key="i" class="col-sm-6" >
<div class="card m-3 h-240 bg-light" >
<div class="card-header text-center" > {{ result.title }} </div>
<div class="card-body" style="height:200px" >
<p class="card-text" v-html="result.prevDesc"></p>
</div>
<div class="card-footer bg-transparent border-info">
<a href="/details" class="btn btn-info" #click="getData(i)" >Details</a>
</div>
</div>
</div>
</div>
And the next Vue.js script:
<script type="text/javascript">
const vm = new Vue({
el: '#networdapp',
data: {
results:[]
},
methods: {
getData: function(key){
console.log(this.results[key].title)
}
},
mounted() {
axios.get('/getJson')
.then(response => {
this.results = response.data;
})
.catch( e => {
console.log(e);
});
}
});
To get the data you want to access in the results array, you can use an index in your v-for loop
v-for="(result, index) in results"
you can check the docs here https://v2.vuejs.org/v2/guide/list.html
I also strongly recommend you to add a key attribute after the v-for to help vue.js
keep track of each result, see https://v2.vuejs.org/v2/guide/list.html#key

Vue JS dynamic modal with components

in news.twig
{% extends 'layouts.twig' %}
{% block content %}
<section class="ls section_padding_bottom_110">
<div id="cart" class="container">
<cart
v-bind:materials="news"
type="news"
test="materials"
></cart>
<modal></modal>
</div>
<script type="text/x-template" id="modal-template">
<transition name="modal">
<div class="modal-mask" v-if="active" #click="close()">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<h3>${ item.name }</h3>
</div>
<div class="modal-body">
${ item.body }
<br>
modal #${ item.id }
</div>
<div class="modal-footer">
<button class="modal-default-button" #click="close()">
close
</button>
</div>
</div>
</div>
</div>
</transition>
</script>
</section>
{% endblock %}
I have 2 components and 1 Vue in my js.
var Hub = new Vue();
Vue.component(
'modal', {
template: '#modal-template',
delimiters: ['${', '}'],
data: function() {
return {
active: false,
item: {
id: '',
name: '',
body: ''
}
}
},
methods: {
open: function (item) {
this.active = true;
this.item = item;
},
close: function () {
this.active = false;
}
},
mounted: function() {
this.$nextTick(function () {
Hub.$on('open-modal', this.open);
Hub.$on('close-modal', this.close);
}.bind(this));
}
});
Vue.component('cart', {
props: {
materials: { type: Array, required: true},
type: { type: String, required: true}
},
computed: {
isPoints() {
return this.type == 'paymentPoints';
},
isNews() {
return this.type == 'news';
}
},
template : `
<div class="row masonry-layout isotope_container">
<div class="col-md-4 col-sm-6 isotope-item" v-for="item in materials">
<div class="vertical-item content-padding topmargin_80">
<div class="item-media">
<img v-bind:src="item.image" alt="">
<div class="media-links p-link">
<div class="links-wrap">
<i class="flaticon-arrows-2"></i>
</div>
<a v-if="!isNews" v-bind:href="item.image" class="abs-link"></a>
</div>
</div>
<button #click="openModal(item)" #keyup.esc="closeModal()">more</button>
<div class="item-content with_shadow with_bottom_border">
<span v-if="isNews" class="categories-links" style="font-size:20px;">
<a rel="category" href="#modal1" data-toggle="modal">
{{item.name}}
</a>
</span>
<p>{{item.body}}</p>
<div v-if="isPoints">
<hr class="light-divider full-content-divider bottommargin_10">
<div class="media small-icon-media topmargin_5">
<div class="media-left">
<i class="fa fa-map-marker grey fontsize_18"></i>
</div>
<div class="media-body">
{{item.adress}}
</div>
</div>
<div class="media small-icon-media topmargin_5">
<div class="media-left">
<i class="fa fa-phone grey fontsize_18"></i>
</div>
<div class="media-body">
{{item.telephone}}
</div>
</div>
</div>
<div v-if="isNews" class="text-center">
<hr class="light-divider full-content-divider bottommargin_10">
<span class="date">
<i class="flaticon-clock-1 grey"></i>
<time class="entry-date">
{{item.date}}
</time>
</span>
</div>
</div>
</div>
</div>
</div>
`
});
var vm = new Vue({
el: '#cart',
name: 'cart',
delimiters: ['${', '}'],
data: {
complated: [],
continuing: [],
planned: [],
points: [],
infoSlider: [],
news: []
},
methods: {
openModal: function (item) {
Hub.$emit('open-modal', item);
},
closeModal: function () {
Hub.$emit('close-modal');
}
},
created() {
axios.get(url).then(res => {
var proje = res.data.projects[0];
this.complated = proje.complated;
this.continuing = proje.continuing;
this.planned = proje.planned;
this.points = res.data.paymentPoints;
this.infoSlider = res.data.sliderİnfos;
this.news = res.data.news;
})
.catch(e => {
console.log(e);
})
}
});
When I click openModal(item) button give me error ;
Property or method "openModal" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
I can not use the openModal function in component.
I can use the function in news.twig without any problems, but then I can not use the component. Can you help me?
You are using openModal in cart component, but that method is defined in root component.
According to Vue's documentation:
Everything in the parent template is compiled in parent scope;
everything in the child template is compiled in the child scope.
in my case need to define variable in vuejs
like this
<script>
export default {
name: "MegaMenu",
props: {
categories: Array,
},
}

Vue js template rendering issue

First time with vue. I am learning it playing around with some examples from Laracasts. I cannot get external template to render and the console shows cannot find element: #toolbar-chat.
My template is:
<template>
<div id="toolbar-chat">
<div class="toolbar-chat">
<ul v-for="chat in messages">
<li><b>#{{ chat.nickname }} says:</b> #{{ chat.message }}</li>
</ul>
</div>
<div class="input-group input-group-sm">
<input class="form-control" value="" placeholder="Type message..." required="required" maxlength="140" v-model="newMsg">
<div class="input-group-btn">
<button class="btn btn-primary" type="button" #click="press">
<i class="fa fa-paper-plane"></i>
</button>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
console.log('Component mounted.')
},
data() {
return {
nickname: [],
message: ''
}
},
ready() {
Echo.channel(chat_channel)
.listen('ChatMessageWasReceived', (data) => {
// Push data to messages list.
this.messages.push({
message: data.chat.message,
nickname: data.player.nickname
});
});
},
methods: {
press() {
// Send message to backend.
this.$http.post(chat_send_route, {message: this.newMsg})
.then((response) => {
// Clear input field.
this.newMsg = '';
});
}
}
};
</script>
My HTML contains the following tag:
<div class="col-xs-12 col-md-4" id="toolbarChat">
<my-chat></my-chat>
</div>
My vue component call is inside a document ready function like this:
require('./../app/bootstrap');
$(document).ready(function()
{
....
// Set up chat
Vue.component('my-chat', require('./../generic/chat.vue'));
const app = new Vue({
el: '#toolbar-chat'
});
});
And I include vue in my bootstrap file like this, then compile with webpack and no errors.
window.Vue = require('vue');
Why is my HTML template not rendering?
In your HTML you have the following div:
<div class="col-xs-12 col-md-4" id="toolbarChat">
<my-chat></my-chat>
</div>
Change it to
<div class="col-xs-12 col-md-4" id="toolbar-chat">
<my-chat></my-chat>
</div>
Because that is the id that new Vue({el: "#toolbar-chat",...}) is looking for.

Meteor template not updating on changed data

I'm currently building a nifty little 'talent point distributor' view, similar to what popular RPG games offer. I didn't want a huge wall of HTML code for all the buttons and textboxes, so I created a template to which I pass two parameters:
the name of the stat I want to alter
the initial value of the stat
The template renders correctly, and I notice that when I log the results to the console, the variable seems to be changed correctly. However, the displayed value does not change and will always stay at 0.
Here is the template itself:
<template name="attributeStepper">
<div class="row" style="margin: 1em;">
<div class="col-sm-3 col-md-2">
<h4>{{toUpper attribute}}</h4>
</div>
<div class="col-sm-6 col-md-4">
<div class="btn-group" role="group">
<button type="button" class="btn btn-default btn-value-dec">
<span class="glyphicon glyphicon-chevron-down"></span>
</button>
<button type="button" class="btn btn-default disabled">{{attributeValue}}</button>
<button type="button" class="btn btn-default btn-value-inc">
<span class="glyphicon glyphicon-chevron-up"></span>
</button>
</div>
</div>
</div>
</template>
Here is the helper I defined for the template:
Template.attributeStepper.helpers({
toUpper : function(str) {
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
})
Template.attributeStepper.events({
'click .btn-value-inc' : function(event, tmpl) {
tmpl.data.attributeValue ++;
},
'click .btn-value-dec' : function(event, tmpl) {
tmpl.data.attributeValue --;
}
});
And this is how I call the templates from the actual view:
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Attributes</h3>
</div>
{{ >attributeStepper attribute="strength" attributeValue="0"}}
{{ >attributeStepper attribute="courage" attributeValue="0"}}
{{ >attributeStepper attribute="intelligence" attributeValue="0"}}
{{ >attributeStepper attribute="agility" attributeValue="0"}}
{{ >attributeStepper attribute="dexterity" attributeValue="0"}}
{{ >attributeStepper attribute="intuition" attributeValue="0"}}
{{ >attributeStepper attribute="charisma" attributeValue="0"}}
</div>
I hope you can make any sense out of this and tell me what I'm doing wrong, because I feel like I'm not following the mindset behind Meteor correctly yet.
Cheers!
There is nothing wrong but also nothing reactive in your code. For the attributeValue you should use a template based ReactiveVar which is created at the onCreate Event
Template.attributeStepper.onCreated(function() {
if (! _.isUndefined(this.data.startingValue))
this.attributeValue = new ReactiveVar(Number(this.data.startingValue));
else
this.attributeValue = new ReactiveVar(0);
})
You can use some initialValue from Template as you like
See complete example at the MeteorPad I created for you.
http://meteorpad.com/pad/Zw7YnnW57uuGKcu3Q/MultipleTemplateUsage
This should solve your question
Cheers
Tom
Do you have idea about reactive-var in meteor (Meteor Doc) or you can also use Session instead of reactive-var (ReactiveVar is similar to a Session variable)
Have a look at changes as per your code.
Here is the template(.html)
<template name="attributeStepper">
<div class="row" style="margin: 1em;">
<div class="col-sm-3 col-md-2">
<h4>{{toUpper attribute}}</h4>
</div>
<div class="col-sm-6 col-md-4">
<div class="btn-group" role="group">
<button type="button" class="btn btn-default btn-value-dec">
<span class="glyphicon glyphicon-chevron-down"></span>
</button>
<button type="button" class="btn btn-default disabled">{{getAttributeValue}}</button>
<button type="button" class="btn btn-default btn-value-inc">
<span class="glyphicon glyphicon-chevron-up"></span>
</button>
</div>
</div>
</div>
</template>
Here is helpers for your template(.js)
Template.attributeStepper.created = function(){
this.attributeValue = new ReactiveVar(parseInt(this.data.attributeValue));
}
Template.attributeStepper.helpers({
toUpper : function(str) {
return str.substring(0, 1).toUpperCase() + str.substring(1);
},
getAttributeValue : function(){
return Template.instance().attributeValue.get();
}
});
Template.attributeStepper.events({
'click .btn-value-inc' : function(event, tmpl) {
tmpl.attributeValue.set(tmpl.attributeValue.get()+1)
},
'click .btn-value-dec' : function(event, tmpl) {
tmpl.attributeValue.set(tmpl.attributeValue.get()-1)
}
});
Template.attributeStepper.created = function(){...} method called before your template's logic is evaluated for the first time.

Categories

Resources