I wish to make pagination for my class project, currently using Vue.js and Bulma + Buefy.
I have an array of objects and I want to display 5 per page.
Already have the layout done and everything, I just want to know how to make the pagination work. Sorry can't post the code because it's not displaying correctly in here but I'm using v-for to display each iteration.
Much thanks!
You can use <b-pagination> component
Without any code sample from you, this would be minimal approach to use that component
const example = {
data() {
return {
items: [],
current: 1,
perPage: 5,
}
},
created() {
// populate array
for(var i = 1; i <= 100; i++){
this.items.push(i)
}
},
computed: {
total() {
return this.items.length
},
/*
Filtered items that are shown in the table
*/
paginatedItems() {
let page_number = this.current-1
return this.items.slice(page_number * this.perPage, (page_number + 1) * this.perPage);
}
},
}
Vue.config.devtools = false
Vue.config.productionTip = false
const app = new Vue(example)
app.$mount('#app')
<link href="https://cdn.materialdesignicons.com/2.0.46/css/materialdesignicons.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<!-- Buefy CSS -->
<link rel="stylesheet" href="https://unpkg.com/buefy/dist/buefy.min.css">
<!-- Buefy JavaScript -->
<script src="https://unpkg.com/buefy/dist/buefy.min.js"></script>
<div id="app" class="container">
<div v-for="(item, index) in paginatedItems">
{{ item }}
</div>
<b-pagination
:total="total"
:current.sync="current"
:per-page="perPage"
>
</b-pagination>
</div>
Do a filter with that displays your first 5. When you click in the number of the page or next page and have a var with that value.
The your filter will need to read that var and give you the right number or objects.
Example:
Page 1 -> 1 to 5
Page 2 -> 6 to 10 (2 = +5, 3= +10)
Your filter will update and show the new ones
Related
new to testing and was wondering if there was another way to inject data back to the component other than what's on the docs
The Docs
const Component = {
template: `
<div>
<span id="foo">{{ foo }}</span>
<span id="bar">{{ bar }}</span>
</div>
`,
data() {
return {
foo: 'foo',
bar: 'bar'
}
}
}
const wrapper = mount(Component, {
data() {
return {
bar: 'my-override'
}
}
})
wrapper.find('#foo').text() // 'foo'
wrapper.find('#bar').text() // 'my-override'
It seems like the relevant parts of the component are copied (mocked?) into the test itself and the test + data are self-containing. Is there a way to send the data back into the component and test what's there?
Ideally I want to be able to tweak the value of a variable from the test, and then have that pass to the component to display the correct card.
Something like:
<template>
<div id="wrapper">
<div v-if="cardOne" id="card1">Text 1</div>
<div v-if="cardTwo" id="card2">Text 2</div>
</div>
</template>
<script>
export default {
methods: {
//loop to go through numberArray
// if x = 1, this.cardOne = true and card1 is shown
// if x=2, this.cardTwo = true and card2 is shown etc
//Can I manually set x = 1 from the test, and then see if cardOne renders on the component?
},
data() {
return {
numberArray: [1, 2],
cardOne: false,
cardTwo: false
};
}
};
</script>
If your component gets data via props you can use props (Vue 3) or propsData (Vue 2) property of options object to pass data to it when you call shallowMount/mount.
If your component has some default values after instantiation and you want to change them your way is correct. You pass data via data method and if you want to change these values later in the test you should use setData method of your wrapper instance as it's shown in docs. API of this method is the same for both Vue 2 and Vue 3 apps.
I'm using Vue.js and have the following code.
When I type in div and this.content is updated, the caret is always reset to the beginning.
<template>
<div>
<div contenteditable="true"
v-html="content"
#input="onContentChange($event)">
</div>
</div>
</template>
<script>
export default {
props: ['content'],
methods: {
onContentChange: function(e) {
this.content = e.target.innerHTML;
},
},
}
</script>
<style>
</style>
How can I preserve the caret's position and update the content?
I've seen some other similar posts, but the solutions there either are not for Vue.js, or don't work in my case, or I might have failed to apply them correctly.
I've tested a few scenarios and I think what you actually need is plainly the Create a reusable editable component in this post.
However, if you want to have everything in one component, in chrome the following code works:
<template>
<div
ref="editable"
contenteditable
#input="onInput"
>
</div>
</template>
<script>
export default {
data () {
return {
content: 'hello world'
}
},
mounted () {
this.$refs.editable.innerText = this.content
},
methods: {
onInput (e) {
this.content = e.target.innerText
}
}
}
</script>
Note that the Vue plugin in Chrome doesn't seem to update correctly the value of content in this scenario, therefore you have to click on refresh on the top right of the vue plugin.
First we preserve current click on the contenteditable, then change HTML content, and set new selection.
const range = document.getSelection().getRangeAt(0)
const pos = range.endOffset
this.$el.innerHTML = this.content
const newRange = document.createRange()
const selection = window.getSelection()
const node = this.$el.childNodes[0]
newRange.setStart(node, node && pos > node.length ? 0 : pos)
newRange.collapse(true)
selection.removeAllRanges()
selection.addRange(newRange)
I can't get how to use v-html to save an existing content. For example:
<div ref="content" v-html="content">Hello, World! A lot of divs</div>
How to make to div content was replace only when I will assign a some not null value with content? Or how to make it in another way? Or is the single way to request div content asynchronously?
The next way works, of course, but I lose a data binding.
this.$refs['content'].innerHTML = "New content";
P.S. I am migrating from jQuery and still can't think in Vue.js philosophy clearly.
Actualy, you must read vue documentation.
In your component you must declare content in data, and simply change it in oher places, i.e. in button's click handler or inside component's methods:
new Vue({
el: "#root",
data: function () {
return {
content: 'Hello, World! A <b>lot</b> of divs'
};
},
methods: {
changeText: function() {
this.content = 'This text from component';
}
}
});
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="root">
<div v-html="content"></div>
<button v-on:click="content = 'This text from button'">Click me</button>
<button v-on:click="changeText">And me</button>
</div>
You could assign a default value to content.
data () {
return {
content: 'Hello, World! A lot of divs'
}
}
When you'll assign a new value to content it will get rendered.
Another way would be to check if content is not null and have 2 different divs using v-if/v-else for conditional rendering.
<div v-if="content" v-html="content"></div>
<div v-else>Hello, World! A lot of divs</div>
and the script
export default {
name: 'customComponent',
data () {
return {
content: null
}
}
}
I'm trying to build a quiz-game with VueJs and up until now everything worked out smoothly, but now that I'm started using dynamic components I'm running into issues with displaying the data.
I have a start component (Start View) that I want to be replaced by the actual Quiz component ("In Progress") when the user clicks on the start button. This works smoothly. But then, in the second components template, the data referenced with {{ self.foo }} does not show up anymore, without any error message.
The way I implemented is the following:
startComponent:
startComponent = {
template: '#start-component',
data: function () {
return {
QuizStore: QuizStore.data
}
},
methods: {
startQuiz: function () {
this.QuizStore.currentComponent = 'quiz-component';
}
}
}
};
And the template:
<script type="x-template" id="start-component">
<div>
<button v-on:click="startQuiz()">
<span>Start Quiz</span>
</button>
</div>
</script>
Note: I'm using x-templates since it somehow makes the most sense with the rest of the application being Python/Flask. But everything is wrapped in {% raw %} so the brackets are not the issue.
Quiz Component:
quizComponent = {
template: '#quiz-component',
data: function () {
return {
QuizStore: QuizStore.data,
question: 'foo',
}
};
And the template:
<script type="x-template" id="quiz-component">
<div>
<p>{{ self.question }}</p>
</div>
</script>
And as you might have seen I'm using a QuizStore that stores all the states.
The store:
const QuizStore = {
data: {
currentComponent: 'start-component',
}
};
In the main .html I'm implementing the dynamic component as follows:
<div id="app">
<component :is="QuizStore.currentComponent"></component>
</div>
So what works:
The Start screen with the button shows up.
When I click on the Start Button, the quizComponent shows up as expected.
What does not work:
The {{ self.question }} data in the QuizComponent template does not show up. And it does not throw an error message.
it also does not work with {{ question }}.
What I don't understand:
If I first render the quizComponent with setting QuizStore.currentComponent = 'startComponent', the data shows up neatly.
If I switch back to <quiz-component></quiz-component> (rather than the dynamic components), it works as well.
So it seems to be the issue that this. does not refer to currently active dynamic component - so I guess here is the mistake? But then again I don't understand why there is no error message...
I can't figure out what the issue is here - anyone?
You may have some issues with your parent component not knowing about its child components, and your construct for QuizStore has a data layer that you don't account for when you set currentComponent.
const startComponent = {
template: '#start-component',
data: function() {
return {
QuizStore: QuizStore.data
}
},
methods: {
startQuiz: function() {
this.QuizStore.currentComponent = 'quiz-component';
}
}
};
const QuizStore = {
data: {
currentComponent: 'start-component',
}
};
new Vue({
el: '#app',
data: {
QuizStore
},
components: {
quizComponent: {
template: '#quiz-component',
data: function() {
return {
QuizStore: QuizStore.data,
question: 'foo'
}
}
},
startComponent
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<script type="x-template" id="start-component">
<div>
<button v-on:click="startQuiz()">
<span>Start Quiz</span>
</button>
</div>
</script>
<script type="x-template" id="quiz-component">
<div>
<p>{{ question }}</p>
</div>
</script>
<div id="app">
<component :is="QuizStore.data.currentComponent"></component>
</div>
The following worked in the end:
I just wrapped <component :is="QuizStore.currentComponent"></component> in a parent component ("index-component") instead of putting it directly in the main html file:
<div id="app">
<index-component></index-component>
</div>
And within the index-component:
<script type="x-template" id="index-component">
<div>
<component :is="QuizStore.currentComponent"></component>
</div>
</script>
Maybe this would have been the right way all along, or maybe not, but it works now :) Thanks a lot Roy for your help!
So, I have an app with multiple child components. Basically a spreadsheet.
I want to be able to calculate the sum of the components when any cell changes. I've figured out a way to store all the values of the cells by caching them when a change event is propagated. But, is this the best way? Is there a better way to dynamically grab the children? I understand props are the way to send data down, but how do I pull data up?
This is the HTML:
<html>
<head>
</head>
<body>
<span id="calculator">
<template v-for="i in 5">
<cell v-bind:index="i" v-on:total="total"></cell>
</template>
{{ subtotal }}
{{ cells }}
</span>
<script src="vue.js"></script>
<script src="app.js"></script>
</body>
</html>
And the app.js:
Vue.component( 'cell', {
template: "<input v-model='value' v-on:change='total' size='10' type='text'/>",
props: {
index: Number
},
data: function() {
return {
value: 0
};
},
methods: {
total: function() {
console.log( "Value is now: " + this.value + " for index: " + this.index )
this.$emit( 'total', this.value, this.index )
}
}
});
var app = new Vue( {
data: {
subtotal: 0,
cells: []
},
el: "#calculator",
methods: {
total: function( value, indexPlusOne ) {
var index = indexPlusOne-1;
var v = parseInt( value );
Vue.set( this.cells, index, v);
console.log( "Inside the total function: " + v + " " + index );
this.subtotal = 0;
for( var i = 0; i < this.cells.length; i++ ) {
if( this.cells[i] ) {
this.subtotal += this.cells[i];
}
}
}
}
});
I understand props are the way to send data down, but how do I pull data up?
The best way is to use v-model for your custom cell component to pull data up.
Ref: https://v2.vuejs.org/v2/guide/components.html#Form-Input-Components-using-Custom-Events
As explained in the link above, <input v-model="something"> is a syntactic sugar for:
<input v-bind:value="something" v-on:input="something = $event.target.value">
So, your ideal solution would be like:
<cell v-model="item" v-for="item in all_cell_items"></cell>
From within the cell component, you may pass value back to parent (root) component by: this.$emit("input", newValue). The parent component (root) remains clean, and you may simply use a computed property for subTotal.
But this will not work if you have a plain list of integers, like this.cells = [1,2,3,4] and attempt to pass the values to cell component using v-model. You will get the following error:
[Vue warn]: : You are binding v-model directly to a v-for iteration alias. This will not be able to modify the v-for source array because writing to the alias is like modifying a function local variable. Consider using an array of objects and use v-model on an object property instead.
If you are ok to modify your this.cells to array of objects, then you have a clean way of doing it like:
<cell v-model="item.price" :label="item.name" v-for="item in all_items"></cell>
Here is a working jsFiddle for this example: https://jsfiddle.net/mani04/9b7n3qmt/