Vue JS nested loop search not returning results - javascript

I'm building a key-command resource and giving VueJS a whirl while doing so. I'm a newbie but am gaining the grasp of things (slowly...).
I want to be able to search in a global search form for key commands I'm defining as actions within sections of commands (see data example below). I would like to search through all the actions to show only those that match the search criteria.
My HTML is below:
<div id="commands">
<input v-model="searchQuery" />
<div class="commands-section" v-for="item in sectionsSearched"
:key="item.id">
<h3>{{ item.section }}</h3>
<div class="commands-row" v-for="command in item.command" :key="command.action">
{{ command.action }}
</div>
</div>
</div>
My main Vue instance looks like this:
import Vue from 'vue/dist/vue.esm'
import { commands } from './data.js'
document.addEventListener('DOMContentLoaded', () => {
const element = document.getElementById("commands")
if (element != null) {
const app = new Vue({
el: element,
data: {
searchQuery: '',
commands: commands
},
computed: {
sectionsSearched() {
var self = this;
return this.commands.filter((c) => {
return c.command.filter((item) => {
console.log(item.action)
return item.action.indexOf(self.searchQuery) > -1;
});
});
},
}
});
}
});
And finally the data structure in data.js
const commands = [
{
section: "first section",
command: [
{ action: '1' },
{ action: '2' },
{ action: '3' },
],
},
{
section: "second section",
command: [
{ action: 'A' },
{ action: 'B' },
{ action: 'C' },
]
},
]
export { commands };
I'm able to output the commands using the console.log(item.action) snippet you see in the computed method called sectionsSearched.
I see no errors in the browser and the data renders correctly.
I cannot however filter by searching in real-time. I'm nearly positive it's a combination of my data structure + the computed method. Can anyone shed some insight as to what I'm doing wrong here?
I'd ideally like to keep the data as is because it's important to be sectioned off.
I'm a Rails guy who is new to this stuff so any and all feedback is welcome.
Thanks!
EDIT
I've tried the proposed solutions below but keep getting undefined in any query I pass. The functionality seems to work in most cases for something like this:
sectionsSearched() {
return this.commands.filter((c) => {
return c.command.filter((item) => {
return item.action.indexOf(this.searchQuery) > -1;
}).length > 0;
});
},
But alas nothing actually comes back. I'm scratching my head hard.

There is a issue in your sectionsSearched as it is returning the array of just commands.
See this one
sectionsSearched() {
return this.commands.reduce((r, e) => {
const command = e.command.filter(item => item.action.indexOf(this.searchQuery) > -1);
const section = e.section;
r.push({
section,
command
});
}, []);
}

const commands = [
{
section: "first section",
command: [
{ action: '1' },
{ action: '2' },
{ action: '3' },
],
},
{
section: "second section",
command: [
{ action: 'A' },
{ action: 'B' },
{ action: 'C' },
]
},
]
const element = document.getElementById("commands")
if (element != null) {
const app = new Vue({
el: element,
data: {
searchQuery: '',
commands: commands
},
computed: {
sectionsSearched() {
var self = this;
return this.commands.filter((c) => {
// the code below return an array, not a boolean
// make this.commands.filter() not work
// return c.command.filter((item) => {
// return item.action.indexOf(self.searchQuery) > -1;
// });
// to find whether there has command action equal to searchQuery
return c.command.find(item => item.action === self.searchQuery);
});
},
}
});
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="commands">
<input v-model="searchQuery" />
<div class="commands-section" v-for="item in sectionsSearched"
:key="item.id">
<h3>{{ item.section }}</h3>
<div class="commands-row" v-for="command in item.command" :key="command.action">
{{ command.action }}
</div>
</div>
</div>
Is that work as you wish ?

sectionsSearched() {
return this.commands.filter((c) => {
return c.command.filter((item) => {
return item.action.indexOf(this.searchQuery) > -1;
}).length > 0;
});
},
}
since filter will always return an array(empty or not) which value always is true.

Related

VueJS - How to check an array on matching values?

I'm working on a couponcode VueJS app, in which I want to check an array with different discountcodes on matching values. Below I have an array with two discountcodes. If the button is clicked, I want to check the array for any matches. I am not sure what would be the best solution for this..
<template>
<div class="container">
<input placeholder='type discount' v-model="discountInput">
<button #click="checkDiscount">check for discount</button>
<span class="alert" v-if="discountValid">
Code juist
</span>
<span class="alert" v-if="discountInvalid">
Code onjuist
</span>
</div>
</template>
<script>
export default {
props: {
},
data: () => {
return {
discountInput: '',
discountValid: false,
discountInvalid: false,
discountCodes: [
{ code: 'discount-code-1', message: '10% discount' },
{ code: 'discount-code-2', message: '5 dollar discount' }
]
}
},
components: {
},
methods: {
checkDiscount() {
if (this.discountInput === this.discountCode) {
return true;
} else {
return false;
}
}
},
watch: {
}
}
</script>
A find should work.
checkDiscount() {
if (this.discountCodes.find(x => x.code === this.discountInput)) {
return true;
} else {
return false;
}
}
or as comments pointed out could be reduced to:
checkDiscount() {
return !!this.discountCodes.find(x => x.code === this.discountInput);
}
Try to use array some method as follows :
checkDiscount() {
return this.discountCodes.some(dis => dis.code === this.discountInput)
}

Errors: "[Vue warn]: Property or method "posts" is not defined on the instance but referenced during render." and also for "books"

enter image description hereI am making an app in Nuxt and vue using storyblok as my CMS. However, I have been receiving errors when trying to link the storyblok array to my arrays called in my template using v-for.
Here is the template:
<template>
<div>
<!-- instance header -->
<InstanceHeader title="Books" />
<div class="pageContainer">
<div class="booksInfoPost">
<div class="booksInfoPost__subHeader"><h3>Top Books</h3></div>
<div class="booksInfoPost__topBooks">
<BooksInfoPostTop
v-for="book in books"
:key ="book.id"
:bookCover="book.bookCover"
:title="book.title"
:author="book.author"
:content="book.content"
:id="book.id"
/>
</div>
<div class="booksInfoPost__subHeader"><h3>Book Titles</h3></div>
<BooksInfoPost
v-for="book in posts"
:key ="book.id"
:bookCover="book.bookCover"
:title="book.title"
:author="book.author"
:content="book.content"
:id="book.id"
/>
</div>
</div>
Here is my script:
export default {
components: {
InstanceHeader,
BooksInfoPostTop,
BookTitles,
BooksInfoPost
},
data() {
/* return {
books: [],
posts: []
} */
},
async asyncData(context) {
return {
bookTitles: context.app.$storyapi
.get("cdn/stories", { version: "draft", starts_with: 'books/book-titles'})
.then(response => {
console.log(response);
return {
posts: response.data.stories.map(bp => {
return {
id: bp.slug,
bookCover: bp.content.bookCover,
title: bp.content.title,
author: bp.content.author
};
}),
}
}),
topBooks: context.app.$storyapi
.get("cdn/stories", { version: "draft", starts_with: 'books/top-books'})
.then(response => {
console.log(response);
return {
books: response.data.stories.map(b => {
return {
id: b.slug,
bookCover: b.content.bookCover,
title: b.content.title,
author: b.content.author
};
}),
}
})
}
}
}
I noticed this error more when I tried calling two APIs from storyblok. When I called one API call I did not see this error. I have also tried using Axios but I am getting errors using that method as well. I am not the most experienced developer and If anyone can help I'll appreciate it. Thanks
export default {
components: {
InstanceHeader,
BooksInfoPostTop,
BookTitles,
BooksInfoPost
},
async asyncData(context) {
const result = {};
const mapBooks = b => {
return {
id: b.slug,
bookCover: b.content.bookCover,
title: b.content.title,
author: b.content.author
};
};
const { data } = await context.app.$storyapi
.get("cdn/stories", {
version: "draft",
starts_with: 'books/book-titles'
});
result.posts = data.stories.map(mapBooks);
const result = await context.app.$storyapi
.get("cdn/stories", {
version: "draft",
starts_with: 'books/top-books'
});
result.books = result.data.stories.map(mapBooks);
return result; // it has right property names {books:[], posts:[]}
}
}
Well as you mentioned in the comment it was a little mess before. So i tidied it up. The idea is that you need direct property names instead of nested objects. This way it should work, if it is not working check the network tab for the errors.

Filter/Function causing infdig

I am making a pie chart for my data. I am using Angular Chart (and subsequently, charts.js).
My data looks like this (vm being the controller):
vm.persons = [
{
name:'smith',
cart: [
{
id: 1,
category: 'food'
},
{
id: 2,
category: 'clothes'
}
]
},
{
name: 'adams',
cart: [
{
id: 3,
category: 'automobile'
},
{
id:1, category: 'food'
}
]
}
]
As such, my template looks like:
<div ng-repeat="person in vm.persons">
<div class="person-header">{{person.name}}</div>
<!-- chart goes here -->
<canvas class="chart chart-pie" chart-data="person.cart | chart : 'category' : 'data'" chart-labels="person.cart | chart : 'category' : 'labels'"></canvas>
<div class="person-data" ng-repeat="item in person.cart">
<div>{{item.category}}</div>
</div>
</div>
I decided to go with a filter for the chart as I thought that would be appropriate, DRY and reusable:
angular.module('myModule').filter('chartFilter', function() {
return function(input, datum, key) {
const copy = JSON.parse(JSON.stringify([...input.slice()])); // maybe a bit overboard on preventing mutation...
const entries = Object.entries(copy.reduce((o,n) => {o[n[datum]] = (o[n[datum]] || 0) + 1}, {}));
const out = {
labels: entries.map(entry => entry[0]);
data: entries.map(entry => entry[1]);
};
return out[key];
}
});
THIS WORKS, and the chart does show up, with the proper data. However per the console, it throws an infdig error every time. Per the docs, it's because I am returning a new array, which I am because it is almost a different set of data. Even if I get rid of copy (which is meant to be a separate object entirely) and use input directly (input.reduce(o,n), etc.) it still throws the error.
I tried also making it into a function (in the controller):
vm.chartBy = (input, datum, key) => {
const copy = JSON.parse(JSON.stringify([...input.slice()])); // maybe a bit overboard on preventing mutation...
const entries = Object.entries(copy.reduce((o,n) => {o[n[datum]] = (o[n[datum]] || 0) + 1}, {}));
const out = {
labels: entries.map(entry => entry[0]);
data: entries.map(entry => entry[1]);
};
return out[key];
};
and in the template:
<canvas class="chart chart-pie" chart-data="vm.chartBy(person.cart, 'category', 'data')" chart-labels="vm.chartBy(person.cart, 'category', 'labels')"></canvas>
However this is throwing an infdig error as well.
Does anyone know how to not get it to through an infdig error each time? That is what I am trying to solve.
As you pointed out, you can't bind to a function which produces a new array or the digest cycle will never be satisfied that the new value matches the old, because the array reference changes each time.
Instead, bind only to the data and then implement the filter in the directive, so that the filtered data is never bound, just shown in the directive's template.
HTML
<canvas class="chart chart-pie" chart-data="person.cart" chart-labels="person.cart"></canvas>
JavaScript
app.directive('chartData', function(){
return {
template: '{{chartData | chart : "category" : "data"}}',
scope: {
'chartData': '='
}
}
});
app.directive('chartLabels', function(){
return {
template: '{{chartLabels | chart : "category" : "labels"}}',
scope: {
'chartLabels': '='
}
}
});
app.filter('chart', function() {
return function(input, datum, key) {
...
return out[key];
}
});
I've hardcoded the datum/key strings in the directives but you could pass those in as additional bindings if needed.
Simple Mock-up Fiddle

Vue component watch

all how i can watch changes in my component in data?
I need watch when user choose car brand to take from server models for that brand
this is my code
Templete
<template>
<div class="category-info">
<div v-for="input in inputs.text">
<label >{{ input.placeholder}}</label>
<input type="text" id="location" :name="input.name" v-model="input.value" #click="console">
</div>
<div class="select" v-for="select in inputs.select">
<label >{{ select.placeholder }}</label>
<my-select :data="select" v-model="select.value"></my-select>
</div>
<button #click="console">click</button>
</div>
Script
<script>
export default {
name: "profile-add-inputs",
props: ['category'],
data() {
return {
inputs: {
text : {},
select: {}
},
}
},
methods: {
getCategories(){
axios.get('/profile/inputs', {
params: {
category: JSON.stringify(this.category.href)
}
})
.then((response) => {
this.inputs.text = response.data.text;
this.inputs.select = response.data.select;
for(let key in this.inputs.text){
this.inputs.text[key].value = '';
}
for(let key in this.inputs.select){
this.inputs.select[key].value = '';
if(this.category.href.sub == 'car' && this.inputs.select[key].name == 'brand'){
console.log('CAR BREND');
this.$watch.inputs.select[key].value = function () {
console.log(this.inputs.select[key].value);
}
}
}
},this)
.catch(function (error) {
console.log(error);
});
},
console(){
console.log(this.inputs.select);
}
},
watch: {
category : function () {
this.getCategories();
console.log('categoty');
},
inputs : {
handler() {
console.log('watch inputs');
}
}
}
}
So, i tried to use watch and $watch but its not working, plz give me a reason why that not work, or maybe some another way to resolve this problem
this.$watch can i create dynamiclly watchers with this stement?
The correct syntax is
watch : {
inputs : function(val, oldVal){
//val : New value
//oldVal : Previous value.
}
}

File Upload Error in VuesJS Form and Rails 5.1

I have a form that is built with Vuejs in my Rails 5.1 app. All my fields work well and persist data to the database, except for file uploads. I get the error
[Vue warn]: Error compiling template: printed at the top of the console, then essentially my entire template code, then
- <input v-model="variation.photo_one" type="file">:
File inputs are read only. Use a v-on:change listener instead.
I am new to Vuejs and cannot figure out how to get this to work even after reading many other online posts regarding this.
_form.html.erb
<%= content_tag :div,
id: "product-form",
data: {
id: product.id,
product: product.to_json(except: [:id, :created_at, :updated_at]),
variations_attributes: product.variations.to_json(except: [:product_id, :created_at, :updated_at]),
} do %>
...
<div class="col-md-4 upload-block">
<label>Photo One</label>
<input type="file" v-model="variation.photo_one" style="margin-bottom: .5em">
</div>
...
<% end %>
app_vue.js
import Vue from 'vue/dist/vue.esm'
import TurbolinksAdapter from 'vue-turbolinks'
import VueResource from 'vue-resource'
Vue.use(VueResource)
Vue.use(TurbolinksAdapter)
document.addEventListener('turbolinks:load', () => {
Vue.http.headers.common['X-CSRF-Token'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content')
var element = document.getElementById("product-form")
if (element != null) {
var id = element.dataset.id
var product = JSON.parse(element.dataset.product)
var variations_attributes = JSON.parse(element.dataset.variationsAttributes)
variations_attributes.forEach(function(variation) { variation._destroy = null })
product.variations_attributes = variations_attributes
var app = new Vue({
el: element,
data: function() {
return { id: id, product: product }
},
methods: {
addVariation: function() {
this.product.variations_attributes.push({
id: null,
name: "",
photo_one: "",
//position: "",
_destroy: null
})
},
removeVariation: function(index) {
var variation = this.product.variations_attributes[index]
if (variation.id == null) {
this.product.variations_attributes.splice(index, 1)
} else {
this.product.variations_attributes[index]._destroy = "1"
}
},
undoRemove: function(index) {
this.product.variations_attributes[index]._destroy = null
},
saveProduct: function() {
// Create a new product
if (this.id == null) {
this.$http.post('/products', { product: this.product }).then(response => {
Turbolinks.visit(`/products/${response.body.id}`)
}, response => {
console.log(response)
})
// Edit an existing product
} else {
this.$http.put(`/products/${this.id}`, { product: this.product }).then(response => {
Turbolinks.visit(`/products/${response.body.id}`)
}, response => {
console.log(response)
})
}
},
existingProduct: function() {
return this.product.id != null
}
}
})
}
})
Files are a bit awkward in Vue. As the message says, you cannot use v-model for an input with type="file". Instead you must use the change event and call a method in your component to manually handle the file.
<input type="file" #change="handleFileChange" />
methods: {
handleFileChange(event) {
//you can access the file in using event.target.files[0]
this.fileField = event.target.files[0];
}
}
When you submit the AJAX request, you will likely need to submit a FormData object instead of submitting a javascript object. The MDN docs have an explanation on how to use that. I find the FormData is the more awkward part of dealing with file uploads. https://developer.mozilla.org/en-US/docs/Web/API/FormData

Categories

Resources