Computed function in Vue not defined - vue.js

I using a computed object to conditionally return a list item. But, I get "ReferenceError: matchingEmployees is not defined" even though I defined it in the computed object. What am I missing? I checked for spelling errors and the reference to matchingEmployees in the directive matches the computed function. Thank you. You can see the entire code on this codepen.
new Vue({
el: '#app',
template:
`<div>
<h1>Vue.js Application</h1>
Search: <input v-model="searchStr" type="text" placeholder="Search Employee...">
<h2>All employees are listed below</h2>
<p v-for="employee in matchingEmployees">
{{ employee }}
</p>
</div>
`,
data: {
searchStr:'',
employees: [
'Alex Han',
'Ali Usman',
'Peter Parker',
'John Lee',
'Eva Holmes'
],
computed: {
matchingEmployees: function () {
return this.employees.filter((user)=> {
if (this.searchStr == ''){
return true;
} else {
return user.includes(this.searchStr)
}
})
}
}
}
})

computed property must be outside the data option, the data option must be a function that returns an object :
new Vue({
el: "#app",
data() {
return {
searchStr: "",
employees: [
"Alex Han",
"Ali Usman",
"Peter Parker",
"John Lee",
"Eva Holmes"
]
};
},
computed: {
matchingEmployees: function () {
return this.employees.filter((user) => {
if (this.searchStr == "") {
return true;
} else {
return user.includes(this.searchStr);
}
});
}
}
});
However adding template as an option is working I recommend to move to the html section in order to separate the content from the logic :
<div id="app">
<div>
<h1>Vue.js Application</h1>
Search: <input v-model="searchStr" type="text" placeholder="Search Employee...">
<h2>All employees are listed below</h2>
<p v-for="employee in matchingEmployees">
{{ employee }}
</p>
</div>
</div>

Related

Search Turkish Character Problem on VueJs Bootstrap Vue Table

I have a problem. I used bootstrap vue table. And I have a search box. I have a yield as "Istanbul". It doesn't see it when I press i in lower case. It accepts a capital letter I. I tried toLocaleLowerCase() but didn't run.
I type "istanbul" in the search box, but it does not find it in the table. It finds it when you write it as "İstanbul".
This is my template and dataset:
<template>
<div>
<b-table striped hover :fields="fields" :items="cities"></b-table>
</div>
</template>
<script>
export default {
data() {
return {
cities : [
{key:1,city:'İstanbul'},
{key:2,city:'İzmir'},
{key:3,city:'Adana'},
],
cityCopyArray : [
{key:1,city:'İstanbul'},
{key:2,city:'İzmir'},
{key:3,city:'Adana'},
],
fields:["city"]
}
}
</script>
This is my input:
<input
:placeholder="'City Name"
:id="'cityNamr'"
v-model="citySearchSearch"></input>
This is my watch:
citySearchSearch: {
handler(val) {
this.cities = this.cityCopyArray.filter((city) => {
return this.converter(city.name).includes(this.converter(val))
})t
},
},
And I used this code as converter :
converter(text){
var trMap = {
'çÇ':'c',
'ğĞ':'g',
'şŞ':'s',
'üÜ':'u',
'ıİ':'i',
'öÖ':'o',
};
for(var key in trMap) {
text = text.replace(new RegExp('['+key+']','g'), trMap[key]);
}
return text.replace(/[^-a-zA-Z0-9\s]+/ig, '')
.replace(/\s/gi, "-")
.replace(/[-]+/gi, "-")
.toLowerCase();
},
You can compare Turkish characters using toLocaleUpperCase('tr-TR') like:
const firstWord = 'istanbul';
const secondWord = 'İstanbul';
// If firstWord contains secondWord, firstWordContainsSecondWord will be true otherwise false.
const firstWordContainsSecondWord = firstWord.toLocaleUpperCase('tr-TR').indexOf(secondWord.toLocaleUpperCase('tr-TR')) !== -1;
Simple example:
new Vue({
el: '#app',
data: {
firstWord: 'istanbul',
secondWord: 'İstanbul',
result: null,
},
watch: {
firstWord() {
this.contains();
},
secondWord() {
this.contains();
}
},
mounted() {
this.contains();
},
methods: {
contains() {
// If firstWord contains secondWord, result will be true otherwise false.
this.result = this.firstWord.toLocaleUpperCase('tr-TR').indexOf(this.secondWord.toLocaleUpperCase('tr-TR')) !== -1;
}
}
});
<script src="https://cdn.jsdelivr.net/vue/latest/vue.js"></script>
<div id="app">
<input placeholder="firstWord" v-model="firstWord">
<input placeholder="secondWord" v-model="secondWord">
<br/><br/>
<div>
Result =>
<br/> {{ firstWord }} contains {{ secondWord }} : {{ result }}
</div>
</div>

Vue3 updating data value is updating prop too

Don't know if this is the normal behaviour, I'm kind of new to Vue, but it's driving me nuts. Hope someone here have any clue about what's happpening...
This is my export:
props: [
'asset', //--- asset.price = 50
],
data() {
return {
local_asset: this.asset
}
}
Then, I update the value of a local_asset value with v-model:
<input type="number" v-model="local_asset.price" #change="test" />
And on filling the input with i.e. 100, it results in prop asset being changed too:
methods: {
test() {
console.log(this.local_asset.price) //--- console >> 100
console.log(this.asset.price) //--- console >> 100
}
}
Am I doing it wrong? Sorry if my code is a nonsense. Please help...
You need to copy value , not reference:
Vue.component('Child', {
template: `
<div class="">
<input type="number" v-model="local_asset.price" />
<div>data: {{ local_asset }}</div>
</div>
`,
props: [
'asset',
],
data() {
return {
local_asset: {...this.asset}
}
},
})
new Vue({
el: '#demo',
data() {
return {
asset: {price: 50}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<div>prop: {{ asset }}</div>
<Child :asset="asset" />
</div>
If your data in primitive (String, Number, BigInt, Boolean, undefined, and null) you can use
data() {
return {
local_asset: JSON.parse(JSON.stringify(this.asset))
}
}

VueJs Watch per key of a model

I have an object (model), as an input field,for a search method, and I want that the watcher to detect changes per key, and not for all.
Right now ,if an input is changed, the watcher is called 10 times (the number of inputs that I have).
<b-form-input
v-model="search[field.column]"
type="search"
id="filterInput"
placeholder="search.."
></b-form-input>
watch:{
search: {
handler(){
// do somenthing
},
deep: true
}
}
You can watch a computed value that return the specific field that you want to watch.
Vue.config.devtools = false;
Vue.config.productionTip = false;
var app = new Vue({
el: '#app',
data: {
form: {
name : '',
lastname: ''
}
},
computed: {
formName() {
return this.form.name;
}
},
watch: {
formName() {
console.log("Name has changed")
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
Name :
<input v-model="form.name" />
Lastname :
<input v-model="form.lastname" />
</div>
You can explicitly watch the required key only.
new Vue({
el: '#app',
data: {
search: {
name: { placeholder: 'search name', value: '' },
age: { placeholder: 'search age', value: '' },
country: { placeholder: 'search country', value: '' }
}
},
watch:{
'search.name.value': { // Explicitly watch the required key.
handler(){
console.log('name changed...')
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<form>
<input
v-for="(value, key, i) in search"
:key="i"
v-model="search[key].value"
type="search"
id="filterInput"
:placeholder="value.placeholder"
/>
</form>
</div>
So, here watch's handler is called only when the name is searched & does not get called when age or country is searched.

How to defer form input binding until user clicks the submit button?

I wanted to make a two-way data binding on my form input in Vue.js 2.3. However, I cannot use the v-model directive, because I want the data to be updated only on clicking the submit button. Meanwhile, the input value may be updated from another Vue method, so it should be bound to the data property text. I made up something like this jsFiddle:
<div id="demo">
<input :value="text" ref="input">
<button #click="update">OK</button>
<p id="result">{{text}}</p>
</div>
new Vue({
el: '#demo',
data: function() {
return {
text: ''
};
},
methods: {
update: function () {
this.text = this.$refs.input.value;
}
}
});
It works, but it does not scale well when there are more inputs. Is there a simpler way to accomplish this, without using $refs?
You can use an object and bind its properties to the inputs. Then, in your update method, you can copy the properties over to another object for display purposes. Then, you can set a deep watcher to update the values for the inputs whenever that object changes. You'll need to use this.$set when copying the properties so that the change will register with Vue.
new Vue({
el: '#demo',
data: function() {
return {
inputVals: {
text: '',
number: 0
},
displayVals: {}
};
},
methods: {
update() {
this.copyObject(this.displayVals, this.inputVals);
},
copyObject(toSet, toGet) {
Object.keys(toGet).forEach((key) => {
this.$set(toSet, key, toGet[key]);
});
}
},
watch: {
displayVals: {
deep: true,
handler() {
this.copyObject(this.inputVals, this.displayVals);
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="demo">
<input v-model="inputVals.text">
<input v-model="inputVals.number">
<button #click="update">OK</button>
<input v-for="val, key in displayVals" v-model="displayVals[key]">
</div>
If you're using ES2015, you can copy objects directly, so this isn't as verbose:
new Vue({
el: '#demo',
data() {
return {
inputVals: { text: '', number: 0 },
displayVals: {}
};
},
methods: {
update() {
this.displayVals = {...this.inputVals};
},
},
watch: {
displayVals: {
deep: true,
handler() {
this.inputVals = {...this.displayVals};
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="demo">
<input v-model="inputVals.text">
<input v-model="inputVals.number">
<button #click="update">OK</button>
<input v-for="val, key in displayVals" v-model="displayVals[key]">
</div>
You can use two separate data properties, one for the <input>'s value, the other for the committed value after the OK button is clicked.
<div id="demo">
<input v-model="editText">
<button #click="update">OK</button>
<p id="result">{{text}}</p>
</div>
new Vue({
el: '#demo',
data: function() {
return {
editText: '',
text: ''
};
},
methods: {
update: function () {
this.text = this.editText;
}
}
});
Updated fiddle
With a slightly different approach than the other answers I think you can achieve something that is easily scalable.
This is a first pass, but using components, you could build your own input elements that submitted precisely when you wanted. Here is an example of an input element that works like a regular input element when it is outside of a t-form component, but only updates v-model on submit when inside a t-form.
Vue.component("t-input", {
props:["value"],
template:`
<input type="text" v-model="internalValue" #input="onInput">
`,
data(){
return {
internalValue: this.value,
wrapped: false
}
},
watch:{
value(newVal){
this.internalValue = newVal
}
},
methods:{
update(){
this.$emit('input', this.internalValue)
},
onInput(){
if (!this.wrapped)
this.$emit('input', this.internalValue)
}
},
mounted(){
if(this.$parent.isTriggeredForm){
this.$parent.register(this)
this.wrapped = true
}
}
})
Here is an example of t-form.
Vue.component("t-form",{
template:`
<form #submit.prevent="submit">
<slot></slot>
</form>
`,
data(){
return {
isTriggeredForm: true,
inputs:[]
}
},
methods:{
submit(){
for(let input of this.inputs)
input.update()
},
register(input){
this.inputs.push(input)
}
}
})
Having those in place, your job becomes very simple.
<t-form>
<t-input v-model="text"></t-input><br>
<t-input v-model="text2"></t-input><br>
<t-input v-model="text3"></t-input><br>
<t-input v-model="text4"></t-input><br>
<button>Submit</button>
</t-form>
This template will only update the bound expressions when the button is clicked. You can have as many t-inputs as you want.
Here is a working example. I included t-input elements both inside and outside the form so you can see that inside the form, the model is only updated on submit, and outside the form the elements work like a typical input.
console.clear()
//
Vue.component("t-input", {
props: ["value"],
template: `
<input type="text" v-model="internalValue" #input="onInput">
`,
data() {
return {
internalValue: this.value,
wrapped: false
}
},
watch: {
value(newVal) {
this.internalValue = newVal
}
},
methods: {
update() {
this.$emit('input', this.internalValue)
},
onInput() {
if (!this.wrapped)
this.$emit('input', this.internalValue)
}
},
mounted() {
if (this.$parent.isTriggeredForm) {
this.$parent.register(this)
this.wrapped = true
}
}
})
Vue.component("t-form", {
template: `
<form #submit.prevent="submit">
<slot></slot>
</form>
`,
data() {
return {
isTriggeredForm: true,
inputs: []
}
},
methods: {
submit() {
for (let input of this.inputs)
input.update()
},
register(input) {
this.inputs.push(input)
}
}
})
new Vue({
el: "#app",
data: {
text: "bob",
text2: "mary",
text3: "jane",
text4: "billy"
},
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
<t-form>
<t-input v-model="text"></t-input><br>
<t-input v-model="text2"></t-input><br>
<t-input v-model="text3"></t-input><br>
<t-input v-model="text4"></t-input><br>
<button>Submit</button>
</t-form>
Non-wrapped:
<t-input v-model="text"></t-input>
<h4>Data</h4>
{{$data}}
<h4>Update Data</h4>
<button type="button" #click="text='jerome'">Change Text</button>
</div>

Focus input of freshly added item

So I have a list of items and list of inputs linked to each item via v-for and v-model.
I click a button and add new item to that list. I want to focus input which is linked to newly added item.
Can't figure out how to achieve this goal.
<div id="app">
<div v-for="item in sortedItems">
<input v-model="item">
</div>
<button #click="addItem">
add
</button>
</div>
new Vue({
el: '#app',
data: {
items: []
},
methods: {
addItem: function() {
this.items.push(Math.random());
}
},
computed: {
sortedItems: function() {
return this.items.sort(function(i1, i2) {
return i1 - i2;
})
}
}
})
Here's fiddle with sorted list
https://jsfiddle.net/sfL91r95/1/
Thanks
Update: inspired by pkawiak's comment, a directive-based solution. I found that calling focus in the bind section didn't work; I had to use nextTick to delay it.
Vue.directive('focus-on-create', {
// Note: using Vue 1. In Vue 2, el would be a parameter
bind: function() {
Vue.nextTick(() => {
this.el.focus();
})
}
})
new Vue({
el: '#app',
data: {
items: []
},
methods: {
addItem: function() {
this.items.push(Math.random());
}
},
computed: {
sortedItems: function() {
return this.items.sort(function(i1, i2) {
return i1 - i2;
})
}
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div id="app">
<div v-for="item in sortedItems">
<input v-focus-on-create v-model="item">
</div>
<button #click="addItem">
add
</button>
</div>
Original answer:
Make your input a component so that you can give it a ready hook.
const autofocus = Vue.extend({
template: '<input v-model="item" />',
props: ['item'],
ready: function() {
this.$el.focus();
}
})
new Vue({
el: '#app',
data: {
items: []
},
methods: {
addItem: function() {
this.items.push(Math.random());
}
},
components: {
autofocus
},
computed: {
sortedItems: function() {
return this.items.sort(function(i1, i2) {
return i1 - i2;
})
}
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div id="app">
<div v-for="item in sortedItems">
<autofocus :item="item"></autofocus>
</div>
<button #click="addItem">
add
</button>
</div>
I Like to extend #Roy's answer.
if you are using any UI framework then it will create DIV and within the DIV input tag will be created so this Snippet will handle that case.
Vue.directive('focus-on-create', {
bind: function(el) {
Vue.nextTick(() => {
el.querySelector('input').focus()
})
}
})