Good morning Folks,
I got an API from where I am getting the data from.
I am trying to filter that with Axios but I don`t get the result that I am expecting.
I created a search box. I created a computed filter and that I applied on the Axios.
I would like to see only the searched results in my flexboxes (apart from the last three articles as a start)
<template>
<div id="app">
<div class="search-wrapper">
<input
type="text"
class="search-bar"
v-model="search"
placeholder="Search in the titles"
/>
</div>
<paginate
ref="paginator"
class="flex-container"
name="items"
:list="filteredArticles"
>
<li
v-for="(item, index) in paginated('items')"
:key="index"
class="flex-item"
>
<div id="image"><img :src="item.image && item.image.file" /></div>
<div id="date">{{ formatDate(item.pub_date) }}</div>
<div id="title">{{ item.title }}</div>
<div id="article" v-html="item.details_en" target="blank">
Explore More
</div>
</li>
</paginate>
<paginate-links
for="items"
:limit="2"
:show-step-links="true"
></paginate-links>
</div>
</template>
<script>
import axios from "axios";
import moment from "moment";
export default {
data() {
return {
items: [],
paginate: ["items"],
search: "",
};
},
created() {
this.loadPressRelease();
},
methods: {
loadPressRelease() {
axios
.get(
`https://zbeta2.mykuwaitnet.net/backend/en/api/v2/media-center/press-release/?page_size=61&type=5`,
{ params }
)
.then((response) => {
this.items = response.data.results;
});
},
formatDate(date) {
return moment(date).format("ll");
},
openArticle() {
window.open(this.items.details_en, "blank");
},
},
computed: {
axiosParameters() {
const params = new SearchParams();
if (!this.search) {
return this.items;
}
return this.items.filter((item) => {
return item.title.includes(this.search);
});
},
},
};
</script>
Here is the basic code for implementing vue watcher along with the debounce for search functionality.
import _ from "lodash" // need to install lodash library
data() {
return {
search: "",
};
},
watch:{
search: _.debounce(function (newVal) {
if (newVal) {
// place your search logic here
} else{
// show the data you want to show when the search input is blank
}
}, 1000),
}
Explanation:
We have placed a watcher on search variable. Whenever it detects any change in search variable, it will execute the if block of code if it's value is not empty. If the value of search variable goes empty, it will execute else block.
The role of adding debounce here is, it will put a delay of 1 sec in executing the block of code, as we don't want to execute the same code on every single character input in the search box. Make sure you install and import lodash library. For more info on Lodash - Debounce, please refer here.
Note: This is not the exact answer for this question, but as it is asked by the question owner in the comment section, here is the basic example with code.
Related
I'm trying to create buttons and vue element inputs for each item on the page. I'm iterating through the items and rendering them with v-for and so I decided to expand on that and do it for both the rest as well. The problem i'm having is that I need to to bind textInput as well as displayTextbox to each one and i'm not sure how to achieve that.
currently all the input text in the el-inputs are bound to the same variable, and clicking to display the inputs will display them all at once.
<template>
<div class="container">
<div v-for="(item, index) in items" :key="index">
<icon #click="showTextbox"/>
<el-input v-if="displayTextbox" v-model="textInput" />
<el-button v-if="displayTextbox" type="primary" #click="confirm" />
<ItemDisplay :data-id="item.id" />
</div>
</div>
</template>
<script>
import ItemDisplay from '#/components/ItemDisplay';
export default {
name: 'ItemList',
components: {
ItemDisplay,
},
props: {
items: {
type: Array,
required: true,
},
}
data() {
displayTextbox = false,
textInput = '',
},
methods: {
confirm() {
// todo send request here
this.displayTextbox = false;
},
showTextbox() {
this.displayTextbox = true;
}
}
}
</script>
EDIT: with the help of #kissu here's the updated and working version
<template>
<div class="container">
<div v-for="(item, index) in itemDataList" :key="itemDataList.id">
<icon #click="showTextbox(item.id)"/>
<El-Input v-if="item.displayTextbox" v-model="item.textInput" />
<El-Button v-if="item.displayTextbox" type="primary" #click="confirm(item.id)" />
<ItemDisplay :data-id="item.item.uuid" />
</div>
</div>
</template>
<script>
import ItemDisplay from '#/components/ItemDisplay';
export default {
name: 'ItemList',
components: {
ItemDisplay,
},
props: {
items: {
type: Array,
required: true,
},
}
data() {
itemDataList = [],
},
methods: {
confirm(id) {
const selected = this.itemDataList.find(
(item) => item.id === id,
)
selected.displayTextbox = false;
console.log(selected.textInput);
// todo send request here
},
showTextbox(id) {
this.itemDataList.find(
(item) => item.id === id,
).displayTextbox = true;
},
populateItemData() {
this.items.forEach((item, index) => {
this.itemDataList.push({
id: item.uuid + index,
displayTextbox: false,
textInput: '',
item: item,
});
});
}
},
created() {
// items prop is obtained from parent component vuex
// generate itemDataList before DOM is rendered so we can render it correctly
this.populateItemData();
},
}
</script>
[assuming you're using Vue2]
If you want to interact with multiple displayTextbox + textInput state, you will need to have an array of objects with a specific key tied to each one of them like in this example.
As of right now, you do have only 1 state for them all, meaning that as you can see: you can toggle it for all or none only.
You'll need to refactor it with an object as in my above example to allow a case-per-case iteration on each state individually.
PS: :key="index" is not a valid solution, you should never use the index of a v-for as explained here.
PS2: please follow the conventions in terms of component naming in your template.
Also, I'm not sure how deep you were planning to go with your components since we don't know the internals of <ItemDisplay :data-id="item.id" />.
But if you also want to manage the labels for each of your inputs, you can do that with nanoid, that way you will be able to have unique UUIDs for each one of your inputs, quite useful.
Use an array to store the values, like this:
<template>
<div v-for="(item, index) in items" :key="index">
<el-input v-model="textInputs[index]" />
</div>
<template>
<script>
export default {
props: {
items: {
type: Array,
required: true,
},
},
data() {
textInputs: []
}
}
</script>
I am new to Vuejs and come across this bug which I have no idea what I have done wrong. I am not receiving any console errors. It doesn't work on initial page load but it seems to work after I comment something out (or make a minor change). It will still then continue to work if I reverse the changes I just made and put it back to the original code. But once again on a fresh page load it won't work.
The issue: I am making a to do list and on page load when I add new tasks through the input field, the list does not appear on the page like it should be. I also console log the data array for this and it shows it is getting added to the array but is not getting rendered to the page. No console errors. In my code I will comment out some other data property (there are 2 additional ones below todosList in the TodoList.vue file that are currently not being used yet) and save and then the tasks will automatically appear on the page. So I think oh ok that might be the issue so with this new minor change I decide to refresh the page to see if it works as expected. Nope it doesn't so I then uncomment out what I previously commented out and save and the list appears again. But once again if I refresh the page it doesn't work. It only seems to be if I make a change inside the data function in the TodoList.vue file.
Additional info: The data is stored in the parent todos[] (App.vue), updated/pushed to array in a child (TodoCreate.vue) and sent back to the parent using $emit. This data is then sent through to another child (TodoList.vue) using props so that it can be rendered on the page.
Wondering if there is something that is not quite right in my code which is causing this to bug out like that. I will include everything in case it is something that looks unrelated to me but could be causing it.
Here is also a link to a code sandbox where the issue can be replicated by following the instructions on the page https://codesandbox.io/s/adding-new-todo-not-working-properly-jwwex?file=/src/components/TodoList.vue
main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
App.vue
<template>
<div :class="currentMode">
<the-header #modeToggled="updateMode($event)"></the-header>
<main>
<todo-create #addedTodos="updateTodos"></todo-create>
<todo-list :todos="todos"></todo-list>
</main>
</div>
</template>
<script>
import TheHeader from './components/TheHeader.vue';
import TodoCreate from './components/TodoCreate.vue';
import TodoList from './components/TodoList.vue';
export default {
name: 'App',
components: {
TheHeader,
TodoCreate,
TodoList,
},
data() {
return {
currentMode: {
dark_mode: true,
light_mode: false
},
todos: [],
}
},
methods: {
updateMode(mode) {
this.currentMode = mode;
},
updateTodos(data) {
this.todos = data;
console.log(this.todos);
},
toggleCompleted() {
}
},
// provide() {
// return {
// todos: this.todos,
// };
// }
}
</script>
TheHeader.vue
<template>
<h1>To-do App</h1>
<div>
<label for="toggle-mode" aria-label="Toggle light and dark mode"></label>
<input type="checkbox" id="toggle-mode" #change="toggleMode">
</div>
</template>
<script>
export default {
emits: ['modeToggled'],
data() {
return {
toggleState: false,
}
},
methods: {
toggleMode() {
this.toggleState = !this.toggleState;
this.$emit('modeToggled', this.modeClasses);
}
},
computed: {
modeClasses() {
return {
dark_mode: !this.toggleState,
light_mode: this.toggleState
}
}
}
}
</script>
TodoCreate.vue
<template>
<div>
<label for="newtodo" class="sr-only">Create new to do</label>
<input type="text" id="newtodo" placeholder="Create a new todo..." v-model="todoval" v-on:keyup.enter="addTodo" >
</div>
</template>
<script>
export default {
emits: ['addedTodos'],
data() {
return {
todoval: '',
taskNumber: 0,
todos: [],
};
},
methods: {
addTodo() {
const val = this.todoval;
const taskNumber = this.taskNumber;
this.todos.push({ taskID: taskNumber, value: val, complete : 'not-completed'});
this.todoval = '';
this.taskNumber++;
console.log(this.todos);
this.$emit('addedTodos', this.todos);
},
}
}
</script>
TodoList.vue
<template>
<ul class="todo-items" :class="filterClass">
<li class="drop-zone" v-for="(listItem, index) in todosList" :class="listItem.complete" :key="listItem.taskID"
#drop='onDrop($event, index)'
#dragover.prevent
#dragenter.prevent>
<div class="drag-el" draggable="true"
#dragstart='startDrag($event, index)'>
<label :for="'checkbox-'+index" :aria-label="'Mark task ' + listItem.value + ' as completed'"></label>
<input type="checkbox" :id="'checkbox-'+index" #change="toggleCompleted(index, listItem.value, listItem.complete, listItem.taskID)">
<input type="text" disabled :value="listItem.value">
<img src="../assets/icon-cross.svg" #click="removeTask(index)">
</div>
</li>
</ul>
</template>
<script>
export default {
props: {
todos: Object,
filterClass: String
},
// inject: ['todos'],
data() {
return {
todosList: this.todos,
// completedTodos: [],
// activeTodos: [],
};
},
// watch: {
// todosList(data) {
// data.filter(function(todo) {
// if(todo.completed == 'completed') {
// completedTodos.push(todos);
// }
// });
// }
// },
methods: {
startDrag: (evt, item) => {
evt.dataTransfer.dropEffect = 'move'
evt.dataTransfer.effectAllowed = 'move'
evt.dataTransfer.setData('itemID', item)
},
onDrop (evt, list) {
const itemID = evt.dataTransfer.getData('itemID');
const movedData = this.todosList[itemID];
this.todosList.splice(itemID,1);
this.todosList.splice(list,0, movedData);
},
toggleCompleted() {
// still need to write this method
},
removeTask() {
// still need to write this method
}
}
}
</script>
I have a list of divs that include product information which i get from an API call. In another component/view i want to display a single product information when the divs are clicked on.
So what i'm trying to do is retrieve the product id by accessing the event object when clicking on the divs then store that id in a variable (not data property) and then $emit it with the event-bus and then listen for it in my other component and use that id to make the API call to get the information for that single product. I'm not sure if this is the best way of doing what i want to do, but its the only way that comes to mind right now.
However so far i have gotten a few different errors and my component that displays the single product does not render.
This is the component that displays the list of products/divs
<template>
<div>
<div class="pagination">
<button :disabled="disabled" #click.prevent="prev()">
<i class="material-icons">arrow_back</i>
</button>
<span class="page-number">{{ currentPage }}</span>
<button #click.prevent="next()">
<i class="material-icons">arrow_forward</i>
</button>
</div>
<div class="products">
<div
class="product"
#click="getSingleBeer($event)"
v-for="product in products"
:key="product.id"
>
<h2 class="name">{{ product.name }}</h2>
<div class="image">
<img :src="product.image_url" />
</div>
<h3 class="tagline">{{ product.tagline }}</h3>
<h3 class="first-brewed">{{ product.first_brewed }}</h3>
<h3 class="abv">{{ product.abv }}%</h3>
<p class="id">{{ product.id }}</p>
</div>
</div>
</div>
</template>
<script>
import axios from "axios";
import { eventBus } from "../main";
export default {
name: "Products",
data() {
return {
products: [],
currentPage: 1,
searchVal: ""
};
},
created() {
this.getBeers();
eventBus.$on("keyword", val => {
this.searchVal = val;
this.getBeersForSearch();
});
},
computed: {
apiUrl() {
return `https://api.punkapi.com/v2/beers?page=${this.currentPage}&per_page=16`;
},
apiUrlForSearch() {
return `https://api.punkapi.com/v2/beers?page=${this.currentPage}&per_page=12&beer_name=${this.searchVal}`;
},
disabled() {
return this.currentPage <= 1;
},
isFirstPage() {
return this.currentPage === 1;
}
},
methods: {
async getBeers() {
try {
const response = await axios.get(this.apiUrl);
this.products = response.data;
console.log(response);
} catch (error) {
console.log(error);
}
},
async getBeersForSearch() {
try {
this.currentPage = 1;
const response = await axios.get(this.apiUrlForSearch);
this.products = response.data;
console.log(response);
} catch (error) {
console.log(error);
}
},
getSingleBeer($event) {
const id = parseInt($event.target.lastChild.innerText);
eventBus.$emit("beer-id", id);
this.$router.push({ name: "Beer" });
}
}
};
</script>
And this is the component/view that is going to display info for the single selected product.
<template>
<div class="beer-container">
<div class="description">
<h2>{{ beer.description }}</h2>
</div>
<div class="img-name">
<h1>{{ beer.name }}</h1>
<img :src="beer.image_url" alt />
</div>
<div class="ingredients"></div>
<div class="brewer-tips">
<h2>{{ beer.brewers_tips }}</h2>
</div>
</div>
</template>
<script>
import { eventBus } from "../main";
import axios from "axios";
export default {
name: "Beer",
data() {
return {
beerId: null,
beer: []
};
},
created() {
eventBus.$on("beer-id", id => {
this.beerId = id;
this.getBeer();
console.log(this.beer);
});
},
methods: {
async getBeer() {
try {
const response = await axios.get(this.apiUrl);
this.beer = response.data[0];
console.log(response.data[0]);
} catch (error) {
console.log(error + "Eroorrrrrr");
}
}
},
computed: {
apiUrl() {
return `https://api.punkapi.com/v2/beers/${this.beerId}`;
}
}
};
</script>
Some of the errors i had so far:
1-the api call is made 2-3 simultaneously when i observe console logs instead of just once.
GET https://api.punkapi.com/v2/beers/null 400
Error: Request failed with status code 400Eroorrrrrr
GET https://api.punkapi.com/v2/beers/null 400
Error: Request failed with status code 400Eroorrrrrr
GET https://api.punkapi.com/v2/beers/null 400
Error: Request failed with status code 400Eroorrrrrr
2-The first time i click on the div it directs to the new route/component but i dont receive any errors and nothing seems to happen behind the scenes.
3- I have also been getting this error:
[Vue warn]: Error in v-on handler: "TypeError: Cannot read property 'innerText' of null"
And
TypeError: Cannot read property 'innerText' of null
My router.js
import Vue from "vue";
import Router from "vue-router";
import Home from "./views/Home.vue";
import Beer from "./views/Beer.vue";
Vue.use(Router);
export default new Router({
mode: "history",
base: process.env.BASE_URL,
routes: [
{
path: "/",
name: "home",
component: Home
},
{
path: "/beer",
name: "Beer",
component: Beer
}
]
});
UPDATE: I'm able to pass the data to the next component but when i click on the product divs the first time nothing happens, i only get directed to the next route/component but data does not get passed. And when i go back and click again,(without refreshing the page) the data gets passed but nothing renders on the component.
I believe you can simplify that a lot by changing your #click to be:
#click="getSingleBeer(product.id)"
Which should pass the id for you, so you can just do:
getSingleBeer(beerId) {
eventBus.$emit("beer-id", beerId);
this.$router.push({ name: "Beer" });
}
For some reason I can't perform actions after the sortable elements are moved. The event UIkit.util.on('#sortable', 'moved', function (item) {}); is not being called/triggered.
The app is made with vue.js, but I get no error and every other uikit functionality works just fine. There are ~600 line of code so, I'll just show the ones that matter (I think).
<template>
<div class="products">
<MainMenu />
<div id="ordering" uk-offcanvas="overlay: true">
<div class="uk-offcanvas-bar" style="width:500px">
<button class="uk-offcanvas-close" type="button" uk-close></button>
<div>Drag to re-arrange the fields order<br><br>
<ul id="sortable" class="uk-grid-small uk-child-width-1-1 uk-text-center" uk-sortable="handle: .uk-card" uk-grid>
<li v-for="(data, idx) in computedData" :key="idx" :id="data.product_field_name">
<div class="uk-card uk-card-default uk-card-body uk-padding-small">{{data.product_field_name}}</div>
</li>
</ul>
</div>
</div>
</div>
<button type="button" class="uk-button uk-button-default" uk-toggle="target: #ordering"><span uk-icon="move"></span> Field Ordering</button>
<!-- REST OF HTML GOES HERE: FORMS, MODALS, ETC. -->
</div>
</template>
<script>
UIkit.notification('test message','danger'); //THIS TRIGGERS OK
//UIKit ordering action - THIS DOES NOT
UIkit.util.on('#sortable', 'moved', function (item) {
console.log('moved triggered');
});
// # is an alias to /src
import MainMenu from "#/components/MainMenuEMVO.vue";
import axios from "axios";
export default {
name: "products",
components: {
MainMenu
},
data() {
return {
add_dependency: {
field: "",
field_selection: ""
},
remove_dependency: {
id: "",
field_name: "",
dependency_field: "",
dependency_rule: "",
dependency_value: "",
enforcing_value: "",
},
productFields: "",
lookupTables: "",
dependencies: "",
departments: "",
search: "",
computedFields: "",
};
},
mounted() {
let stateCheck = setInterval(() => {
if (document.readyState === 'complete') {
clearInterval(stateCheck);
this.getProductsConfig();
}
}, 100);
},
computed: {...},
methods: {
getProductsConfig(){...},
enableEdit(id){...},
cancelEdit(id){...},
submitEdit(id){...},
addRule(id, field_name){...},
removeDependency(fieldName, id){...}
}
};
</script>
As mentioned before there are no errors or even warnings in console, so I really don't event know where to start looking at this.
I managed to resolve the problem, so if anyone has the same issue:
Once I moved the code into the mounted() method of the vue.js it all works fine.
mounted() {
//UIKit ordering action - THIS DOES NOT
UIkit.util.on('#sortable', 'moved', function (item) {
console.log('moved triggered');
});
let stateCheck = setInterval(() => {
if (document.readyState === 'complete') {
clearInterval(stateCheck);
this.getProductsConfig();
}
}, 100);
},
the UIkit.notification works fine, but I guess since the util is attached to event it has to be made available to vue on startup.
If you added UIkit as <script src="./src/js/uikit.min.js"></script> in your index.html then you can't use it inside Vue as expected, instead do:
npm i --save uikit
then in your Component
import UIkit from "uikit";
after that you can use everywhere
methods: {
alertOnClick(msg) {
UIkit.notification(msg, "danger");
}
}
}
My view blade laravel like this :
<form slot="search" class="navbar-search" action="{{url('search')}}">
<search-header-view></search-header-view>
</form>
The view blade laravel call vue component (search-header-view component)
My vue component(search-header-view component) like this :
<template>
<div class="form-group">
<div class="input-group">
<input type="text" class="form-control" placeholder="Search" name="q" autofocus v-model="keyword" :value="keyword">
<span class="input-group-btn">
<button class="btn btn-default" type="submit" ref="submitButton"><span class="fa fa-search"></span></button>
</span>
<ul v-if="!selected && keyword">
<li v-for="state in filteredStates" #click="select(state.name)">{{ state.name }}</li>
</ul>
</div>
</div>
</template>
<script>
export default {
name: 'SearchHeaderView',
components: { DropdownCategory },
data() {
return {
baseUrl: window.Laravel.baseUrl,
keyword: null,
selected: null,
filteredStates: []
}
},
watch: {
keyword(value) {
this.$store.dispatch('getProducts', { q: value })
.then(res => {
this.filteredStates = res.data;
})
}
},
methods: {
select: function(state) {
this.keyword = state
this.selected = state
this.$refs.submitButton.click();
},
input: function() {
this.selected = null
}
}
}
</script>
If I input keyword "product" in input text, it will show autocomplete : "product chelsea", "product liverpool", "product arsenal"
If I click "product chelsea", the url like this : http://myshop.dev/search?q=product
Should the url like this : http://myshop.dev/search?q=product+chelsea
I had add :value="keyword" in input type text to udpate value of input type text, but it does not work
How can I solve this problem?
Update
I had find the solution like this :
methods: {
select: function(state) {
this.keyword = state
this.selected = state
const self = this
setTimeout(function () {
self.$refs.submitButton.click()
}, 1500)
},
...
}
It works. But is this solution the best solution? or there is another better solution?
Instead of timeout you can use vue's nextTick function.
I didn't checked your code by executing but seems its problem regarding timings as when submit is pressed your value isn't updated.
so setTimeout is helping js to buy some time to update value, but its 1500 so its 1.5 second and its little longer and yes we can not identify how much time it will take each time so we tempted to put max possible time, still its not perfect solution
you can do something like this. replace your setTimeout with this one
const self = this
Vue.nextTick(function () {
// DOM updated
self.$refs.submitButton.click()
})
nextTick will let DOM updated and set values then it will execute your code.
It should work, let me know if it works or not.