Use method in template, out of instance vue - vuejs2

This is warning when i click on go to contact in tab about: "Property or method "switchTo" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
(found in component )."
How do I fix this?
new Vue({
el: '#app',
data: {
currentPage: 'home',
},
methods: {
switchTo: function(page) {
this.currentPage = page;
}
},
components: {
home: {
template: `#home`,
},
about: {
template: `#about`,
},
contact: {
template: '#contact'
}
}
})
.navigation {
margin: 10px 0;
}
.navigation ul {
margin: 0;
padding: 0;
}
.navigation ul li {
display: inline-block;
margin-right: 20px;
}
input, label, button {
display: block
}
input, textarea {
margin-bottom: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js"></script>
<div id="app">
<div class="navigation">
<ul>
<li>Home</li>
<li>About</li>
</ul>
</div>
<div class="pages">
<keep-alive>
<component v-bind:is="currentPage">
</component>
</keep-alive>
</div>
</div>
<template id="home">
<p>home</p>
</template>
<template id="about">
<p>about go to contact</p>
</template>
<template id="contact">
<p>contact</p>
</template>

Just change your about template to this
<template id="about">
<p>about go to contact</p>
</template>
new Vue({
el: '#app',
data: {
currentPage: 'home',
},
methods: {
switchTo: function(page) {
this.currentPage = page;
}
},
components: {
home: {
template: `#home`,
},
about: {
template: `#about`,
},
contact: {
template: '#contact'
}
}
})
.navigation {
margin: 10px 0;
}
.navigation ul {
margin: 0;
padding: 0;
}
.navigation ul li {
display: inline-block;
margin-right: 20px;
}
input, label, button {
display: block
}
input, textarea {
margin-bottom: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js"></script>
<div id="app">
<div class="navigation">
<ul>
<li>Home</li>
<li>About</li>
</ul>
</div>
<div class="pages">
<keep-alive>
<component v-bind:is="currentPage">
</component>
</keep-alive>
</div>
</div>
<template id="home">
<p>home</p>
</template>
<template id="about">
<p>about go to contact</p>
</template>
<template id="contact">
<p>contact</p>
</template>

I already solved a problem like this in this question: Calling methods in Vue build
It's not the same problem so it's not a repeated question, but the answer is the same:
In the created hook, add the component to window.componentInstance like this:
methods: {
foo () {
console.log('bar')
}
},
created () {
window.componentInstance = this
}
Then you can call the method anywhere like this:
window.componentInstance.foo()

Related

nuxtjs add and remove class on click on elements

I am new in vue and nuxt and here is my code I need to update
<template>
<div class="dashContent">
<div class="dashContent_item dashContent_item--active">
<p class="dashContent_text">123</p>
</div>
<div class="dashContent_item">
<p class="dashContent_text">456</p>
</div>
<div class="dashContent_item">
<p class="dashContent_text">789</p>
</div>
</div>
</template>
<style lang="scss">
.dashContent {
&_item {
display: flex;
align-items: center;
}
&_text {
color: #8e8f93;
font-size: 14px;
}
}
.dashContent_item--active {
.dashContent_text{
color:#fff;
font-size: 14px;
}
}
</style>
I tried something like this:
<div #click="onClick">
methods: {
onClick () {
document.body.classList.toggle('dashContent_item--active');
},
},
but it changed all elements and I need style change only on element I clicked and remove when click on another
also this code add active class to body not to element I clicked
This is how to get a togglable list of fruits, with a specific class tied to each one of them.
<template>
<section>
<div v-for="(fruit, index) in fruits" :key="fruit.id" #click="toggleEat(index)">
<span :class="{ 'was-eaten': fruit.eaten }">{{ fruit.name }}</span>
</div>
</section>
</template>
<script>
export default {
name: 'ToggleFruits',
data() {
return {
fruits: [
{ id: 1, name: 'banana', eaten: false },
{ id: 2, name: 'apple', eaten: true },
{ id: 3, name: 'watermelon', eaten: false },
],
}
},
methods: {
toggleEat(clickedFruitIndex) {
this.fruits = this.fruits.map((fruit) => ({
...fruit,
eaten: false,
}))
return this.$set(this.fruits, clickedFruitIndex, {
...this.fruits[clickedFruitIndex],
eaten: true,
})
},
},
}
</script>
<style scoped>
.was-eaten {
color: hsl(24, 81.7%, 49.2%);
}
</style>
In Vue2, we need to use this.$set otherwise, the changed element in a specific position of the array will not be detected. More info available in the official documentation.

Why does Bootstrap Autocomplete send vue.js router go to /#/exclamationmark?

Why does it happen that my Vue router navigates to /#/! without apparent reason?
This seems to happen when I fire an event from an autocomplete form built with Bootstrap Autocomplete and trigger a function.
Calling the same function by clicking a button does not lead to the problem.
This is the parent component where the event is emitted to
<style scoped>
</style>
<template>
<div id="appspace">
<div id="leftbar">
</div>
<div id="workarea">
<div id="mapblock">
</div>
<div id="infoblock">
<div class="form-group"><label for="gotoff">Go to</label>
<autosuggest #locselect="locSelect($event)" id="gotoff"></autosuggest>
</div>
<button v-on:click="searchAround()" type="button" class="btn btn-primary">Search</button>
</div>
</div>
<div id="rightbar">
</div>
</div>
</template>
<script>
module.exports = {
data: function () {
return {
};
},
components: {
autosuggest: httpVueLoader('components/base/autosuggest.vue'),
},
mounted: function(){
},
destroyed: function(){
},
methods: {
setMarkerInCenter: function(){
this.locSelect({ value: { lng: 12, lat: 14 }})
},
locSelect: function(e) {
console.log('locSelect');
console.log(e);
},
},
}
</script>
and this is the component emitting the event:
<style scoped>
.autocomplete {
position: relative;
width: 130px;
}
.autocomplete-results {
padding: 0;
margin: 0;
border: 1px solid #eeeeee;
height: 120px;
overflow: auto;
}
.autocomplete-result {
list-style: none;
text-align: left;
padding: 4px 2px;
cursor: pointer;
}
.autocomplete-result:hover {
background-color: #4AAE9B;
color: white;
}
</style>
<template>
<div class="input-group">
<input ref="ac" class="form-control">
<div class="input-group-append">
<div class="input-group-text"><i class="fa fa-compass" style="height:0.5em;padding:0;margin:0;margin-bottom:4px"></i></div>
</div>
</div>
</template>
<script>
module.exports = {
mounted: function(){
var i = this.$refs.ac;
var c = this
$(i).autoComplete({ resolverSettings: { url: '/api/gc/autocomplete' } });
$(i).on('autocomplete.select', function(e, sel) {
e.preventDefault();
c.$emit('locselect', sel);
e.preventDefault();
});
},
}
</script>
Any leads as to how to debug this?
I couldn't find the reason why this autocomplete changes the route, that behavoir
seems to be undocumented. But here's a method to temporarily prevent this behavior until you find the solution, add this global route guard to your router to prevent navigation to '/#/!' route:
router.js
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
console.log(to)
// TEMP PATCH:
// Autocomplete changes route to '/#/!'
if (to.path !== '/#/!') {
next()
}
})
Just make sure that console.log(to) actually has a property path === '/#/!'

Initializing a variable of a child from a parent

I have a variable in my parent component and would like it to be sent to the child component.
I tried several ways but nothing works on my side, here is my code:
Parent :
<template>
<Widget/>
</template>
<script>
import Widget from './Widget'
export default {
data () {
return {
model: {
data: 'data send !'
}
}
},
components: {
Widget
},
methods: {
}
}
</script>
Child :
<template>
<div class="row">
<div class="box">
<p> {{data}} </p>
</div>
</div>
</template>
<script>
export default {
data () {
return {
data: '111'
}
}
}
</script>
<style scoped>
.box {
box-shadow: 10px 10px 5px grey;
}
</style>
thank you in advance for your help!
To bind data to a child component you can use props.
Parent :
<template>
<Widget :data="model.data"/>
</template>
<script>
import Widget from "./Widget";
export default {
data() {
return {
model: {
data: "data send !"
}
};
},
components: {
Widget
},
methods: {}
};
</script>
Child:
<template>
<div class="row">
<div class="box">
<p>{{data}}</p>
</div>
</div>
</template>
<script>
export default {
props: ["data"]
};
</script>
<style scoped>
.box {
box-shadow: 10px 10px 5px grey;
}
</style>

How to create a numeric input component in Vue with limits that doesn't allow to type outside limits

I'm trying to create a numeric input component in Vue with min and max values that doesn't allow to type outside outside limits without success:
<template id="custom-input">
<div>
<input :value="value" type="number" #input="onInput">
</div>
</template>
<div id="app">
<div>
<span>Value: {{ value }}</span>
<custom-input v-model="value" :max-value="50"/>
</div>
</div>
Vue.component('custom-input', {
template: '#custom-input',
props: {
value: Number,
maxValue: Number
},
methods: {
onInput(event) {
const newValue = parseInt(event.target.value)
const clampedValue = Math.min(newValue, this.maxValue)
this.$emit('input', clampedValue)
}
}
})
new Vue({
el: "#app",
data: {
value: 5
}
})
Fiddle here: https://jsfiddle.net/8dzhy5bk/6/
In the previous example, the max value is set in 50. If I type 60 it's converted automatically to 50 inside the input, but if I type a third digit it allow to continue typing. The value passed to the parent is clamped, but I also need to limit the input so no more digits can be entered.
When the value of input is great than 10, it will always emit 10 to parent component, but the value keeps same (always=10) so it will not trigger reactvity.
One solution, always emit actual value (=parseInt(event.target.value)) first, then emit the max value (=Math.min(newValue, this.maxValue)) in vm.$nextTick()
Another solution is use this.$forceUpdate().
Below is the demo for $nextTick.
Vue.component('custom-input', {
template: '#custom-input',
props: {
value: Number,
maxValue: Number
},
methods: {
onInput(event) {
const newValue = parseInt(event.target.value)
const clampedValue = Math.min(newValue, this.maxValue)
this.$emit('input', newValue)
this.$nextTick(()=>{
this.$emit('input', clampedValue)
})
}
}
})
new Vue({
el: "#app",
data: {
value: 5
},
methods: {
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<template id="custom-input">
<div>
<input
:value="value"
type="number"
#input="onInput"
>
</div>
</template>
<div id="app">
<div>
<span>Value: {{ value }}</span>
<custom-input v-model="value" :max-value="10"/>
</div>
</div>
Below is the demo for vm.$forceUpdate.
Vue.component('custom-input', {
template: '#custom-input',
props: {
value: Number,
maxValue: Number
},
methods: {
onInput(event) {
const newValue = parseInt(event.target.value)
const clampedValue = Math.min(newValue, this.maxValue)
this.$emit('input', clampedValue)
this.$forceUpdate()
}
}
})
new Vue({
el: "#app",
data: {
value: 5
},
methods: {
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<template id="custom-input">
<div>
<input
:value="value"
type="number"
#input="onInput"
>
</div>
</template>
<div id="app">
<div>
<span>Value: {{ value }}</span>
<custom-input v-model="value" :max-value="10"/>
</div>
</div>

Accessing Vue Component scope from external javascript

Can I access Vue components data properties and methods from external javascript? I am trying to create a hybrid application where a portion of the screen is a Vue component, and I want to call a method inside that component on click of a button which is handled by pure js. Is there a way to achieve this?
Thanks!
Yes. You need to assign your Vue object to a variable (i.e. vue) and then you can access vue.methodName() and vue.propertyName:
// add you new Vue object to a variable
const vue = new Vue({
el: "#app",
data: {
todos: [
{ text: "Learn JavaScript", done: false },
{ text: "Learn Vue", done: false },
{ text: "Play around in JSFiddle", done: true },
{ text: "Build something awesome", done: true }
]
},
methods: {
toggle: function(todo){
todo.done = !todo.done
}
}
});
// Add event listener to outside button
const button = document.getElementById('outsideButton');
button.addEventListener('click', function() {
vue.toggle(vue.todos[1]);
});
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.min.js"></script>
<div id="app">
<h2>Todos:</h2>
<ol>
<li v-for="todo in todos">
<label>
<input type="checkbox"
v-on:change="toggle(todo)"
v-bind:checked="todo.done">
<del v-if="todo.done">
{{ todo.text }}
</del>
<span v-else>
{{ todo.text }}
</span>
</label>
</li>
</ol>
</div>
<div class="outside">
<button id="outsideButton">
Click outside button
</button>
</div>
Yes, you can add an event listener to the Vue component that listens for the button click's event. See example here on codepen.
JS
new Vue({
el: '#app',
methods: {
clickMethod(event){
if (event.target.id === 'outsideButton') {
alert('button clicked')
}
}
},
created(){
let localThis = this
document.addEventListener('click', this.clickMethod)
}
})
HTML
<div>
<button id="outsideButton">Button</button>
<div id="app">
</div>
</div>