I have a local array with data which i pass to a table.
I created variables: page,limit,totalPage and function changePage.
In hook onMounted I calculate the number of pages based on the given limit.
Through the v-for loop, displayed the pages and made them clickable on the function, but I don't understand how to set the limit of displayed table elements, and how to make pagination work.
<template>
<div class="containerApp">
<Dialog
:showForm="showForm"
#changeToggle="changeToggle"
#hideDialog="hideDialog"
>
<PostForm #create="createPost" />
</Dialog>
<Select v-model="selectedSort" :options="sortOptions" />
</div>
<input type="text" v-model="searchQuery" />
<Table :tableData="searchAndSort" />
<div class="page_wrapper">
<div
v-for="(pageNumber, i) in totalPage"
:key="i"
#click="changePage(pageNumber)"
class="page"
:class="{ 'current-page': page === pageNumber }"
>
{{ pageNumber }}
</div>
</div>
</template>
<script>
import Table from "./components/Table.vue";
import Dialog from "./components/UI/Dialog.vue";
import PostForm from "./components/PostForm.vue";
import Select from "./components/UI/Select.vue";
import { ref } from "#vue/reactivity";
import { computed, onMounted } from "#vue/runtime-core";
export default {
name: "App",
components: {
...
},
setup() {
const showForm = ref(false);
const searchQuery = ref("");
const tableData = ref([
...
]);
const selectedSort = ref("");
const sortOptions = ref([
...
]);
const page = ref(1);
const limit = ref(5);
const totalPage = ref(0);
onMounted(() => {
totalPage.value = Math.ceil(tableData.value.length / limit.value);
});
const changePage = (pageNumber) => {
page.value = pageNumber;
};
const createPost = (post) => {
tableData.value.push(post);
showForm.value = false;
};
const changeToggle = (toggle) => {
showForm.value = !toggle;
};
const hideDialog = (val) => {
showForm.value = val;
};
const sortedPosts = computed(() => {
...
});
const searchAndSort = computed(() => {
...
});
return {
showForm,
tableData,
selectedSort,
sortOptions,
searchQuery,
changeToggle,
hideDialog,
createPost,
sortedPosts,
searchAndSort,
page,
limit,
totalPage,
changePage,
};
},
};
</script>
In order to show a portion of an array you can use Array.slice. (docs)
in your searchAndSort you should do something like this:
const searchAndSort = computed(() => {
const start = (page.value - 1) * limit.value;
const end = (page.value * limit.value)+1;
return tableData.value.slice(start, end);
});
page - 1 * limit - initially this will result in 0 * 5 meaning start from 0 (-1 is used because the page start from 1. When you go to the second page this will result in 1 * 5, etc.
end is defined by the limit itself multiplying the page.
So on the first page, you will slice the array from 0 to 5, on the second page - from 5 to 10, etc.
const end = (page.value * limit.value)+1; - +1 will give you the 5th element because Array.slice exlude the item at the end index)
You should add some checks as well.
Related
I have a basket, there are products in the basket, each of which is a separate component, the quantity * price is calculated inside each item. The parent gets the value, now it is necessary to somehow add the values together and get the total purchase amount, how to do this? I want to explicitly go through the queryselectorall and get data, for example, from date attributes, I know that this will be an error.
I use vue3 composition api
parent
<BasketItem
:item="item"
:type="i"
#remove="Remove"
#sum="calc"
></BasketItem>
child
<div class="count-manipulate">
<div
class="count-manipulate-item minus"
#click.prevent="parseInt(count) <= 1 ? count : count--"
></div>
<div
class="count-manipulate-item plus"
#click.prevent="count++"
></div>
</div>
<script setup>
let props = defineProps({
item: {
type: Object,
required: true,
},
type: {
type: String,
},
});
let emit = defineEmits(["remove", "sum"]);
let sum = ref(props.item.id);
let calc = () => {
sum.value = parseInt(count.value) * parseInt(props.item.attributes.Price);
emit("sum", sum.value);
};
let count = ref(1);
calc();
watch(
() => count.value,
() => {
calc();
}
);
</script>
Some advice:
If the value of the variable is not changing, declare it as a constant.
As much as possible, declare event handler functions if you want to update reactive states based on DOM events. As your component grows in size, it can be tiring scrolling back-and-forth from the script tag to the template tag to check which DOM event updates the state/s. Furthermore, you can better handle complex computations with it.
If a state is dependent on another state, and that state updates on DOM events, move them inside the event handler.
Child Component
<template>
<!-- Sample template -->
<div #click.prevent="calculateTotal('sub')">-</div>
<div #click.prevent="calculateTotal('add')">+</div>
</template>
<script setup>
import { ref } 'vue';
const props = defineProps(/** your props */);
const emit = defineEmits(["remove", "sum"]);
const count = ref(0);
const sum = ref(0);
/** Handler for the #click event */
const calculateTotal = (operation) => {
if (operation === 'add') {
count.value += 1;
} else if (operation === 'sub' && count.value > 0) {
count.value -= 1;
}
// You don't need to parseInt unless the value is not a number.
sum.value = count.value * props.item.attributes.Price;
emit('sum', { id: props.item.id, sum: sum.value });
};
</script>
Now, in your parent component, render your child components using v-for. Then, declare a state that contains the overallTotalSum of your basket. Finally, like the child component, create a handler that will grab the emitted basket item sum then do the necessary computation.
Parent Component
<template>
<BasketItem
v-for="item in items"
:key="item.id"
:item="item"
#sum="calculateOverallTotalSum"
/>
</template>
<script setup>
import { ref } 'vue';
import BasketItem from 'path';
const items = ref([/** your basket items */]);
const overallTotalSum = ref(0);
const calculateOverallTotalSum ({ id, sum }) => {
items.value = items.value.map(item => {
if (item.id === id) {
item.sum = sum;
}
return item
});
overallTotalSum.value = items.value.reduce(
(total, item) => item.sum + total, 0
);
};
</script>
I solved my problem by using a reactive sessionstorage from vueUse
let sessionOrder = useSessionStorage("basket", [] as SessionOrderType[]);
const calculateOverallTotalSum = (id: number, type: string, sum: number) => {
if (sessionOrder.value.some((p) => p.id === id && p.type.includes(type))) {
sessionOrder.value.map((p) => {
if (p.id === id && p.type === type) {
return (p.sum = sum);
}
});
} else {
let obj = {
id: id,
type: type,
sum: sum,
};
sessionOrder.value.push(obj);
}
summary.value = sessionOrder.value.reduce(
(total, item) => item.sum + total,
0
);
};
I'm fairly new to the composition API but it's been somewhat straight forward so far.
I'm making an Axios call that is returning some data;
const transaction = ref({});
let pbl = reactive({});
const getPayByLinkTransaction = () => {
axios({
method: "get",
url: "pay-by-link",
params: {
merchantUuid: import.meta.env.VITE_MERCHANT_UUID,
uuid: route.params.uuid,
},
})
.then((res) => {
transaction.value = res.data;
pbl = res.data;
})
.catch((e) => {
console.log(e);
});
}
getPayByLinkTransaction();
Then I have the following textfield:
<v-text-field v-model="transaction.reference" variant="solo" class="mb-1"
:rules="config?.fields?.reference?.required ? required : []"></v-text-field>
PBL - {{ pbl.reference }} <br>
Transaction - {{ transaction.reference }}
The reference key contains John Doe to start off with.
What's strange is that when I start typing into the textfield, it's changing reference in both the transaction and pbl object.
As the v-model is attached to transaction.reference, should it not only change the variable in the transaction object?
Why is the pbl object changing too?
What I'm after are two objects where one contains the original data and the other contains modified data if the user were to amend any details.
I was not able to reproduce the problem using Composition API in both Vue 2 and Vue 2.
So, here is my assumption about what's going on.
You are assigning the same object from res.data to transaction and to pbl.
Since it is the same object, the change of reference over transaction.reference changes also pbl.reference
Here is the simple Vue 2 playground using Options API to understand the problem.
const App = {
el: '#app',
data() {
return {
myObj: { id: 1, counter: 0 },
myObjCopy: {}
}
},
methods: {
replaceObj() {
let obj = { id: this.myObj.id + 1, counter: 0 };
this.myObj = obj;
this.myObjCopy = obj;
},
plus() {
this.myObj.counter++;
}
}
}
const app = new Vue(App);
#app { line-height: 2; }
[v-cloak] { display: none; }
<div id="app">
<button type="button" #click="replaceObj()">Replace My Object</button><hr/>
My Object Id: {{myObj.id}}<br/>
Counter: {{myObj.counter}}
<button type="button" #click="plus()">+1</button><br/>
<hr/>
My Object Copy Id: {{myObjCopy.id}}<br/>
Counter: {{myObjCopy.counter}}
</div>
<script type="text/javascript" src="https://unpkg.com/vue#2.7.14/dist/vue.min.js"></script>
The interesting this is, the reactivity in Vue 3 behaves different.
// true in 2.7, false in 3.x
reactive(foo) === foo;
The Vue 3 reactive() function create a Proxy object. Check the Behavior Differences from Vue 3
const { createApp, ref, reactive } = Vue;
const App = {
setup() {
let obj = { id: 1, counter: 0 };
const myObj = ref(obj);
let myObjCopy = reactive(obj);
const plus = () => {
myObj.value.counter++;
}
const replaceObj = () => {
let obj = { id: myObj.value.id + 1, counter: 0 };
myObj.value = obj;
myObjCopy = obj;
}
return { myObj, myObjCopy, plus, replaceObj}
}
}
const app = createApp(App)
app.mount('#app')
<div id="app">
<button type="button" #click="replaceObj()">Replace My Object</button><br/><br/>
My Object Id: {{myObj.id}}<br/>
Counter: {{myObj.counter}}
<button type="button" #click="plus()">+1</button><br/>
<hr/>
My Object Copy Id: {{myObjCopy.id}}<br/>
Counter: {{myObjCopy.counter}}
</div>
<script type="text/javascript" src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
Try to copy object by value, not reference:
const {ref, reactive} = Vue
const app = Vue.createApp({
setup() {
const data1 = ref({})
let data2 = reactive({})
const byReference = {ref: 'reference'}
data1.value = byReference
data2 = byReference
const byValue = {ref: 'value'}
const data3 = ref({})
let data4 = reactive({})
data3.value = byValue
data4 = {...byValue}
return {
data1, data2, data3, data4
};
},
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<input v-model="data1.ref" />
{{data1.ref}}
{{data2.ref}}
<input v-model="data3.ref" />
{{data3.ref}}
{{data4.ref}}
</div>
The main problem here is the understanding of how the Objects are threated in JavaScript.
const obj1 = { data : { value: 1 } }
const obj2 = obj1.data;
console.log( obj1.data === obj2 ); // --> true
Here is another very simple playground to demonstrate it.
const obj1 = { data : { value: 1 } }
console.log(`obj1.data.value = ${obj1.data.value}`);
// --> obj1.data.value = 1
const obj2 = obj1.data;
obj2.value++;
console.log(`obj1.data.value = ${obj1.data.value}`);
// --> obj1.data.value = 2
console.log(`obj1.data == obj2 : ${obj1.data == obj2}`);
// --> true
// even the JavaScript object destructuring assignment
// doesn't break the connection to original data Object
const { data } = obj1;
data.value++;
console.log(`obj1.data.value = ${obj1.data.value}`);
// --> obj1.data.value = 3
// this works, since 'value' is passed by value, not reference
const obj3 = { value: obj1.data.value }
obj3.value++;
console.log(`obj3.value = ${obj3.value}`);
// --> obj3.value = 4
console.log(`obj1.data.value = ${obj1.data.value}`);
// --> obj1.data.value = 3
// the other way is using the Spread Syntax
const obj4 = {...obj1.data}
obj4.value++;
console.log(`obj4.value = ${obj4.value}`);
// --> obj4.value = 4
console.log(`obj1.data.value = ${obj1.data.value}`);
// --> obj1.data.value = 3
const items =[];
const dropdownItems = ref(items);
const dropdownItem = ref(null);
onMounted(() => {
ApiService.getStores().then((data) => {
data.forEach((value) => {
items.push({ name: value.shop,value:value.id });
});
});
});
<Dropdown id="state" v-model="dropdownItem" :options="dropdownItems" :filter="true"
optionLabel="name" optionValue="value"></Dropdown>
<p>{{ dropdownItem }}</p>
i am new in vue js
PrimeVue using here
{{ dropdownItem }} this working fine get value of selected drop down data.
but
1) how to set selected valued in localstroge
2)how to first data set default selected
I'm using Vue3 with the composition API. For this example, I will use Pinia but this applies to Vuex as well.
Imagine you have to take care of entity A holding entities of type entity B. For this example I will use a single todo holding multiple labels.
The sample store for the labels
export type Label = {
title: string; // "unique"
};
export const useLabelsStore = defineStore("labels", () => {
const labels = ref<Label[]>([]);
function addLabel(title: string): void {
if (!labels.value.some((label) => label.title === title)) {
labels.value.push({ title });
}
}
function deleteLabel(title: string): void {
labels.value = labels.value.filter((label) => label.title !== title);
}
return { labels, addLabel, deleteLabel };
});
and for the todos
export type Todo = {
title: string; // "unique"
labels: Label[];
};
export const useTodosStore = defineStore("todos", () => {
const todos = ref<Todo[]>([{ title: "foo", labels: [] }]);
function applyLabelToTodo(todoTitle: string, label: Label) {
const todo = todos.value.find((todo) => todo.title === todoTitle);
if (
todo &&
!todo.labels.some((todoLabel) => todoLabel.title === label.title)
) {
todo.labels.push(label);
}
}
return { todos, applyLabelToTodo };
});
You can create todos, labels and apply labels to todos. But after applying a label to a todo and deleting it afterwards the todo still holds it, the code has no sync mechanism yet, so the todos store is no "invalid" / "outdated".
This shows the App.vue file for testing purposes
<template>
<div>
<div>Labels: {{ labelsStore.labels }}</div>
<br />
<div v-for="todo in todosStore.todos" :key="todo.title">
<div>Todo: {{ todo.title }}</div>
<div>Applied labels: {{ todo.labels }}</div>
</div>
<br />
<button #click="addLabel">Add new label</button>
<br />
<br />
<select v-model="selectedLabelTitle">
<option
v-for="label in labelsStore.labels"
:value="label.title"
:key="label.title"
>
{{ label.title }}
</option>
</select>
<button #click="labelsStore.deleteLabel(selectedLabelTitle)">
Delete label "{{ selectedLabelTitle }}"
</button>
<button #click="applyLabelToTodo">
Apply label "{{ selectedLabelTitle }}" to todo "{{
todosStore.todos[0].title
}}"
</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import { useTodosStore } from "./todosStore";
import { useLabelsStore } from "./labelsStore";
export default defineComponent({
setup() {
const todosStore = useTodosStore();
const labelsStore = useLabelsStore();
const selectedLabelTitle = ref<string>();
function addLabel(): void {
labelsStore.addLabel(`Label ${labelsStore.labels.length + 1}`);
}
function applyLabelToTodo(): void {
const label = labelsStore.labels.find(
(label) => label.title === selectedLabelTitle.value
);
if (label) {
todosStore.applyLabelToTodo(todosStore.todos[0].title, label);
}
}
return {
todosStore,
labelsStore,
addLabel,
selectedLabelTitle,
applyLabelToTodo,
};
},
});
</script>
The todos store already has a todo
Click on "Add new label"
Select it in the select box
Apply that label to the todo
Delete that label
You will see that this todo still holds the deleted label.
What is the common way to synchronize the stores? My current solution based on subscribing to actions is extending the todos store
export const useTodosStore = defineStore("todos", () => {
// ...
const labelsStore = useLabelsStore();
labelsStore.$onAction((context) => {
if (context.name === "deleteLabel") {
context.after(() => {
todos.value = todos.value.map((todo) => {
todo.labels = todo.labels.filter(
(label) => label.title !== context.args[0]
);
return todo;
});
});
}
});
// ...
});
Do I have to listen for all the actions or are there any better ways?
Factory function that defines a store and allows to define it with Vue reactive API directly is advanced feature that is generally not needed. A store is commonly defined with an object that is similar to Vuex API.
Values that are supposed to derive from other values are supposed to go to getters, very similarly to Vue computed values (computed is a part of Vue reactive API and used by Pinia internally).
There are many ways to do this, for instance, todo labels can be filtered against up-to-date labels from another store
const todoStore = defineStore({
state: () => ({ _todos: [] })
getters: {
todos() {
const labelStore = useLabelStore();
return this._todos
.map((todo) => ({
...todo,
labels: todo.labels.filter(label => labelStore.labels.includes(label))
});
}
}
});
I have a computed function that I'd like to utilize but I keep getting "Computed property was assigned to but it has no setter". I am simply trying to remove all the forward slashes and the 'SYG' at the end of this: 99/KRFS/010572//SYG when it's pasted into a v-model input to achieve this: 99KRFS010572.
Here is my setup function
<input v-model="policyMapName" />
policy-map <span>{{ policyMapName }}</span>
setup() {
const circuitID = ref('99/KRFS/010572//SYG');
const policyMapName = computed(() => {
const cID = circuitID.value;
return cID.replace(/[/]/g, '').slice(0, -3);
});
}
You should add a setter to your computed property with a getter :
<input v-model="policyMapName" />
policy-map <span>{{ policyMapName }}</span>
setup() {
const circuitID = ref('99/KRFS/010572//SYG');
const policyMapName = computed({
get: () => {
const cID = circuitID.value;
return cID.replace(/[/]/g, '').slice(0, -3);
},
set:(newval)=>{
circuitID.value =newval
}
});
}