Reset response to null after search input is cleared Vue.js - javascript

How do I reset a my response to NULL, as it is in my data upon the clearing/deleting of the query in my search bar?
I've vaguely achieved this with v-show and a query length, but I know its not really correct because it's hiding the results, not actually clearing them from the DOM. I also tried tying an ELSE statement to the query method with no luck.
<div class="searchBarContainer">
<div class="search">
<div class="searchBar">
<form v-on:submit="queryGitHub(query)">
<input type="search" placeholder="Search Repositories Ex. Hello
World" v-model="query" />
<button type="submit" v-on:click="isHidden =
!isHidden">Search</button>
</form>
</div>
<div class="results" id="results" v-if="response" v-show="query.length =
0">
<div class="notFound" v-if="response.length == 0">
<p>Sorry buddy, try another search!</p>
</div>
<div class="resultsHeadings" v-if="response.length >= 1">
<p>Name</p>
<p>Language</p>
</div>
<div class="items" v-if="response.length >= 1">
<div class="item" v-for="(item, index) in response, filteredList"
v-bind:id="item.id" :key="index">
<p>{{item.name}}</p>
<p>{{item.language}}</p>
<div class="expand">
<a #click="pushItem(index)">
<div class="itemButton">
<button v-on:click="addFave(item.id, item.forks)">Add to
Favorites</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
export default{
data () {
return {
query:'',
response: null,
items: [],
faves: [],
activeItems: [],
}
},
methods: {
queryGitHub(q) {
if (q.length >= 1){
fetch('https://api.github.com/search/repositories?q=' + q)
.then((j) => {
return j.json();
})
.then ((r) => {
console.log(r);
//this.response = r.items;
this.response = r.items.slice(0, 15)
})
}
}
}
};
I need my search input to remove the response by resetting it to NULL once the input has been cleared by the visitor. Presently if you clear the input, the results disappear which is great but if you type again, the results just reappear. So they are hidden, not removed. I believe I need a function, possibly via computed, to set the response in data back to null upon the clearing of the input.

You could attach an input event handler to your input element and inside it you'll check the length of the query string. If it's zero, then set response to null.
<input type="search" placeholder="Search Repositories Ex. Hello
World" v-model="query" #input="onQueryChange" />
The onQueryChange function should be under methods instead of computed since it's not returning any derived data.
methods: {
onQueryChange(event) {
// can be this.query.length === 0 as well
if(event.target.value.trim().length === 0) {
this.response = null;
}
}
}

Related

Vuejs - Radio input remain checked when a new question is rendered in mobile app

I have implemented a tricky solution to display some questions without using v-for loop. I need it to avoid that all the questions are rendered at the same time in a mobile android app, this will avoid scroll. The logics works fine but I've noticed that when the user select an answer and the next new question is rendered, the radio input to select the answer will be not resetted and the selected radio input will be equal to the previous choiced from the user. The problem will not occur if I use v-for to render the questions but as I wrote, this isn't what I want.
<div class="container-fluid bg-light vh-100" v-if="isLoaded">
<div class="row m-0">
<div class="col-12 card shadow p-0 mt-5">
<div class="card-header">
<h6 class="fw-bold">{{ questions[n].question }}</h6>
</div>
<div class="card-body">
<div class="form-check mb-3" v-for="(choice, index) in questions[n].choices" :key="index">
<input class="form-check-input" type="radio" :name="questions[n].questionIndex" :value="index" #change="checkAnswer(questions[n].questionIndex, index)" :disabled="answeredQuestions[n]">
<small class="form-check-label" for="">{{ index }}) {{ choice }}</small>
</div>
</div>
</div>
</div>
</div>
<div class="navbar bg-light fixed-bottom">
<div class="container-fluid">
<small :class="score">{{ currentScore }}</small>
</div>
</div>
export default {
name: 'Quiz',
data() {
return {
n: 0,
answeredQuestions: [],
currentScore: 0
}
},
mounted() {
front.on('questions', (data) => {
console.log(data)
this.$store.commit('quizQuestions', data);
this.$store.commit('contentLoaded', true);
});
front.on('checkResponse', (response) => {
console.log(response);
if( response.answerCheck ){
this.currentScore++;
}
this.showNext();
});
},
computed: {
isLoaded() {
return this.$store.getters.showLoader;
},
questions() {
return this.$store.getters.quiz;
},
score() {
return this.currentScore > 0 ? 'text-success' : 'text-muted';
}
},
methods: {
showPrevious() {
if( this.n !== 0 ){
this.n--
}
},
showNext() {
if( this.n < this.$store.getters.quiz.length ){
this.n++
}
},
checkAnswer(questionIndex, choice) {
this.answeredQuestions.push(true);
front.send('checkAnswer', {questionIndex: questionIndex, choice: choice});
}
}
}
I think that the problem is with the name attribute of the radio inputs but not sure of this. Any idea of how I can solve this?
Maybe you can try this. bind your questions[n].answer in your input value. Once you get a new question, if the answer haven't fill in before. It will auto become null.
<input class="form-check-input" type="radio" :name="questions[n].questionIndex" :value="questions[n].answer" #change="checkAnswer(questions[n].questionIndex, index)" :disabled="answeredQuestions[n]">

Conditionally hide the nth element of a v-for loop without modifying the array. vue 3 composition api search function

I have a ref variable (foxArticles ), which holds a list that contains 100 items. In a v-for loop i loop over each value. As a result, i have 100 values rendered on the page.
<template>
<div class="news_container">
<div
v-for="article in foxArticles"
v-bind:key="article"
class="article_single_cell"
>
<div
class="news_box shadow hover:bg-red-100 "
v-if="containsKeyword(article, keywordInput)"
>
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
const foxArticles = ref([]);
</script>
I also have a search function, which returns the value, if it includes the passed in keyword. The function is used in the child of the v-for loop.
<div class="search_input_container">
<input
type="text"
class="search_input"
v-model="keywordInput"
/>
</div>
<script>
const keywordInput = ref("");
function containsKeyword(article, keywordInput) {
if (article.title.toLowerCase().includes(keywordInput.toLowerCase())) {
return article;
}
}
</script>
The problem is, i can't use .slice() on the foxArticles array in the v-for loop, because that screws up the search functionality, as it returns only the values from the sliced range.
How can i have the access the all of the values of the array, while not rendering all 100 of returned articles on the initial load?
Any suggestions?
I think your approach will make it incredibly complex to achieve. It would be simpler to always iterate over some set, this set is either filtered based on a search-term, or it will be the first 100 items.
I'm not very familiar yet with the Vue 3 composition api so I'll demonstrate with a regular (vue 2) component.
<template>
<div class="news_container">
<div
v-for="article in matchingArticles"
v-bind:key="article"
class="article_single_cell"
>
... news_box ...
</div>
</div>
</template>
<script>
export default {
...
computed: {
matchingArticles() {
var articles = this.foxArticles;
if (this.keywordInput) {
articles = articles.filter(article => {
return this.containsKeyword(article, this.keywordInput)
})
} else {
// we will limit the result to 100
articles = articles.slice(0, 100);
}
// you may want to always limit results to 100
// but i'll leave that up to you.
return articles;
}
},
....
}
</script>
Another benefit is that the template does not need to worry about filtering results.
ok, so i kind of came up with another solution, for which you don't have to change the script part...
instead of having one v-for loop , you can make two of them, where each one is wrapped in a v-if statement div
The first v-if statement says, If the client has not used the search (keywordInput == ''), display articles in the range of (index, index)
The second one says = If the user has written something (keywordInput != ''), return those articles.
<template>
<div class="news_container">
<!-- if no search has been done -->
<div v-if="keywordInput == ''">
<div
v-for="article in foxArticles.slice(0, 4)"
v-bind:key="article"
class="article_single_cell"
>
<div class="news_box shadow hover:bg-red-100 ">
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
<!-- if searched something -->
<div v-else-if="keywordInput != ''">
<div
v-for="article in foxArticles"
v-bind:key="article"
class="article_single_cell"
>
<div
class="news_box shadow hover:bg-red-100 "
v-if="containsKeyword(article, keywordInput) && keywordInput != ''"
>
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
im not sure how this impacts performance tho, but that's a problem for another day

Vue js: load more data button not working properly

In my Vue.js code below I'm trying to add a Show More button to my data coming from API so initially it should show 10 data and whenever clicked load more 10 and so on. I tried answer from:
Load more button in vuejs
but it's not working since I'm looping over an array it gives me the error below can't read property of question title. Is there a way to do it?
<div class="search-askbutton">
<b-row>
<div class="search-wrapper">
<input
type="text"
v-model="search"
placeholder="Search something..."
class="fas fa-search"
/>
</div>
<div class="container vue">
<div v-for="commentIndex in commentsToShow">
<div v-if="commentIndex <= commentsToShow">
<ul
class="container-question"
v-for="(question, index) in filteredList"
:key="index"
>
<div>{{question[commentIndex - 1].questionTitle}} says:</div>
<hr />
</ul>
</div>
</div>
<button #click="commentsToShow += 10">show more</button>
</div>
<script>
export default {
data() {
return { commentsToShow: 10,
search: '',
questions: [],}
},
computed: {
filteredList() {
return this.questions.filter((question) => {
return (
question.questionTitle
.toLowerCase()
.includes(this.search.toLowerCase()) ||
question.owner.username
.toLowerCase()
.includes(this.search.toLowerCase()) ||
question.questionTitle
.toUpperCase()
.includes(this.search.toUpperCase()) ||
question.owner.username
.toUpperCase()
.includes(this.search.toUpperCase())
);
});
},
},
mounted: function() {
questionService.getAllQuestions().then((response) => {
this.questions = response.data.response;}
}
</script>
The problem is your subtraction
<div>{{question[commentIndex - 1].questionTitle}} says:</div>
If commentIndex = 0 then you'll be saying 0-1 = -1 therefore it will not find the -1 index.
You could replace this line
<div v-if="commentIndex <= commentsToShow">
So that it can run only if the index is greater than 0
<div v-if="commentIndex > 0">
1)
v-for returns what's inside an array, not the array itself.
<div>{{question.questionTitle}} says:</div>
2)
also, you can remove the v-for loop.
note:- your reference question is also uses this way.
<div v-for="commentIndex in commentsToShow">
<div v-if="commentIndex <= commentsToShow">
<ul class="container-question">
<div>{{filteredList[commentIndex - 1].questionTitle}} says:</div>
<hr />
</ul>
</div>
</div>

Vue.js can't find element using querySelector

I am trying to create a chat style form. So a user inputs their data and then uses the button within my template with the class of continue-btn.
As you can see when the continue-btn is pressed it uses the nextStep method which adds 1 to the counter data property.
Within my template I then use v-if="counter >= 1" to display the next section of the chat dialog and input field.
I am then trying to use scrollTop to automatically scroll the page to the new section with the id of #conversation__tram-1. I originally tried running this block of code just after the counter had been given a value of 1:
const container = this.$el.querySelector("#conversation__tram-" + this.counter);
container.scrollTop = container.scrollHeight;
This didn't work though because I'm guessing the #conversation__tram-1 element hadn't been added to the DOM yet.
So for the sake of testing I tried wrapping it in a timeout function:
setTimeout(function(){
const container = this.$el.querySelector("#conversation__tram-" + this.counter);
container.scrollTop = container.scrollHeight;
}, 3000);
However I am left with this error when trying this:
Uncaught TypeError: Cannot read property 'querySelector' of undefined
Here is my whole single vue file:
<template>
<div id="conversation-app">
<!-- <div v-for="item in items">
{{ item.text }}
</div> -->
<div class="conversation__track">
<div id="conversation__tram-0">
<div class="conversation__item agent">
<img src="/assets/cdn.annuityadvicecentre.dev/images/theme-f/michael-chat-agent.jpg" class="conversation__item-prof-img" alt="Michael Chat Agent" />
<div class="conversation__item-content">
<p>
Hello my name is {{ agent }}, we'll compare the whole annuity market to bring you back the best annuity rates from the top providers for you. Let's get started, what's your name?
</p>
</div>
</div>
<div class="conversation__item customer" id="title-fullname">
<div class="conversation__item-content">
<p>
Hi {{ agent }}, my name is...
</p>
<div class="row">
<div class="col-4">
<select id="title" class="field-title" name="payload[title]"><option value="mr">Mr</option><option value="mrs">Mrs</option><option value="miss">Miss</option><option value="ms">Ms</option></select>
</div>
<div class="col-8">
<input v-model="customerName" id="full_name" class="field-full_name" name="payload[full_name]" type="text">
</div>
</div>
</div>
</div>
</div>
<transition name="fade">
<div id="conversation__tram-1" v-if="counter >= 1">
<div class="conversation__item agent">
<img src="/assets/cdn.annuityadvicecentre.dev/images/theme-f/michael-chat-agent.jpg" class="conversation__item-prof-img" alt="Michael Chat Agent" />
<div class="conversation__item-content">
<p>
Thanks {{ firstName }}, nice to meet you. To process your instant quote please can I have your Pension Value?
</p>
</div>
</div>
<div class="conversation__item customer">
<div class="conversation__item-content">
<p>
Sure, my pension value is...
</p>
<input id="pension_value" class="field-pension_value" placeholder="£" pattern="\d*" name="payload[pension_value]" type="number">
<div class="error-wrap error_pension_value is-hidden" data-format="<div class="error-text">:message</div>"></div>
</div>
</div>
</div>
</transition>
<div id="conversation__buttons">
<button type="button" class="continue-btn"
v-on:click="nextStep"
>Continue <i class="fa fa-chevron-right" aria-hidden="true"></i></button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'conversation-app',
data () {
return {
agent: 'Brick',
counter: 0,
customerName: '',
}
},
methods: {
nextStep: function() {
this.counter += 1;
setTimeout(function(){
const container = this.$el.querySelector("#conversation__tram-" + this.counter);
container.scrollTop = container.scrollHeight;
}, 3000);
},
},
computed: {
firstName() {
return this.customerName.split(' ')[0];
}
}
}
</script>
Any idea why this isn't working? Thanks.
This is a good time to use arrow functions, as they preserve the context of this.
nextStep: function() {
this.counter += 1;
setTimeout(() => {
const container = this.$el.querySelector("#conversation__tram-" + this.counter);
container.scrollTop = container.scrollHeight;
}, 3000);
Altenatively, instead of the timeout you can use Vue.nextTick which is a more technically-correct way of doing this.
nextStep: function () {
this.counter += 1
this.$nextTick(() => { ... })

Why isn't meteor injecting the text from my template helpers?

Im trying to dynamically generate a table of two different sets of data. My database isnt empty and the returns have been verified as well. but when i check the rendered page the corresponding html isnt there as if nothing as returned.
template/html:
<template name="room">
<div class="container-fluid">
<h1> Sprint Retrospective</h1>
<hr>
<div class="input-group">
<input type="text" class="form-control thoughts" placeholder="Thoughts..." aria-describedby="basic-addon1">
<span class="input-group-addon">
<input id="wentWell" type="checkbox" aria-label="..."> Went Well
</span>
<span class="input-group-addon">
<input id="wentWrong" type="checkbox" aria-label="..."> Went Wrong
</span>
<span class="input-group-btn">
<button class="btn btn-default" type="button">Submit!</button>
</span>
</div>
<hr>
{{#if haveCards}}
<div class="container-fluid">
<div class="row">
<div class="col-xs-6 col-sm-6">
<div class="row">Went Well</div>
{{#each wentWell}}
{{>card}}
{{/each}}
</div>
<div class="col-xs-6 col-sm-6">
<div class="row">Went Wrong</div>
{{#each wentWrong}}
{{>card}}
{{/each}}
</div>
</div>
</div>
{{/if}}
</div>
</template>
Javascript:
"use strict";
/**
*
**/
var Cards = new Mongo.Collection('cards');
var allCards;
var wentWellCards;
var wentWrongCards;
if(Meteor.isClient){
Tracker.autorun(function(){
allCards = Cards.find({},{sort:{createdAt:-1}});
wentWellCards = Cards.find({category:"Went Well"},{sort:{createdAt:-1}});
wentWrongCards = Cards.find({category:"Went Wrong"},{sort:{createdAt:-1}});
});
Template.room.helpers({
haveCards: function(){
if(allCards != null && allCards != undefined && allCards.length > 0)
return true;
return false;
},
wentWell: function(){
return this.wentWellCards;
},
wentWrong: function(){
return this.wentWrongCards;
}
});
}
Jeremy answer its actually more in point, but..
Lets try to fix that code a little bit.
Lets change the wentWell and wentWrong helpers to look more clean like this.
wentWell: function(){
return Cards.find({category:"Went Well"},{sort:{createdAt:-1}});
},
wentWrong: function(){
return Cards.find({category:"Went Wrong"},{sort:{createdAt:-1}});
}
Also for the haveCards helpers you can do something like
haveCards: function(){
return Cards.find().count() >= 1 //for example or return just findOne()
}
Your helpers should return wentWellCards instead of this.wentWellCards, etc.
Your helpers are not reactive, so, when the data is loaded (which happens after the page is rendered) the helpers are not re-run.
Simply, call the reactive methods (the minimongo queries) in the helper directly. This will get them re-run once the data is available
Also, when you check the count, you need to fetch the collection
Cards = new Mongo.Collection('cards');
if(Meteor.isServer){
Meteor.publish('cards', function() {
return Cards.find({},{sort:{createdAt:-1}});
});
}
if(Meteor.isClient){
Template.room.onCreated(function(){
this.subscribe('cards');
});
Template.room.helpers({
haveCards: function(){
var allCards = Cards.find({},{sort:{createdAt:-1}}).fetch();
return (allCards != null && allCards != undefined && allCards.length > 0);
},
wentWell: function(){
return wentWellCards = Cards.find({category:"Went Well"},{sort:{createdAt:-1}});
},
wentWrong: function(){
return wentWrongCards = Cards.find({category:"Went Wrong"},{sort:{createdAt:-1}});
}
});
}
And you will need to publish the collection from the server and subscribe from the template (unless you are using autopublish)

Categories

Resources