How to use b-pagination api? - javascript

layoutchange() {
this.layout = !this.layout;
if (this.layout === true) {
this.perPage = this.layout ? 8 : 12;
this.listProducts();
} else {
this.perPage = !this.layout ? 12 : 8;
this.gridProducts();
}
},
<a class="list-icon" v-bind:class="{ active: layout == true }" v-on:click="layoutchange"></a>
<a class="grid-icon" v-bind:class="{ active: layout == false }" v-on:click="layoutchange"></a>
<ul v-if="layout == true">
//code for product display
<b-pagination v-model="currentPage" :total-rows="rows" :per-page="perPage"></b-pagination>
</ul>
<ul v-if="layout == false">
//code for product display
<b-pagination v-model="currentPage" :total-rows="rows" :per-page="perPage"></b-pagination>
</ul
Basically i am trying to add the api call for the each page,(i have a api which need to call) for suppose if i click on pagination page no 1, i need to fire api, and same page 2 need to call api. Now i have a doubt, Now i am using the b-pagination (bootstrap-vue) are there any event to call for each page? like next previous or any event based. so with same name, i can call api using that.
I am using fr grid and list view, For both i have pagination
Reference document https://bootstrap-vue.org/docs/components/pagination

If there is no event provided by b-pagination that you can use, in that specific usecase, you can just watch the currentPage property.
https://v2.vuejs.org/v2/guide/computed.html#Watchers
export default {
data() {
return {
currentPage: null,
}
},
watch: {
currentPage(newVal) {
if(newVal) {
// Call the api
// Random api endpoint as example
const endpoint = 'https://jsonplaceholder.typicode.com/todos/'
fetch(endpoint + newVal).then((res) => {
console.log(res)
// update corresponding data
})
}
}
},
mounted() {
// Initialise currentPage to your route or 1 by default as example
this.currentPage = 1
}
}

Related

Apply CSS conditionally in a loop - VueJS

I have a JSON list of items that I import in my vue component,
I loop though that file in order to show them. Each item belong to a specific 'group' :
See IMG
E.g. :
{
"type": "Simple list",
"title": "Simple list",
"id": 1,
"group": "list-component",
"properties": "lorem lipsum"
},
I would like to apply a CSS 'border-top-color' to each item according to its group.
I was trying to apply the conditions when mouted(){} but I'm not sure if I'm doing it right. Here's my atempt :
The template (I'm using VueDraggable, don't mind it) :
<div class="item drag" :key="element" :style="[{ 'border-top-color': 'brdrTpClr' }]">
{{ element.title }}
<div class="addico" :key="index">
<i class="fas fa-add"#click="$emit('pushNewElt', element.id)"></i>
</div>
</div>
The script :
data() {
return {
dragItems: dragItemsList,
brdrTpClr: "",
};
},
mounted() {
for (let i = 0; i <= 15; i++) {
if (this.dragItems[i].group == "list-component") {
// I'm not sure how to do it
// the color I want to apply : #00A3A1b
} else if (this.dragItems[i].group == "location-media-component") {
// #005EB8
} else if (this.dragItems[i].group == "container-component") {
// #0091DA
} else if (this.dragItems[i].group == "UI-component") {
// #6D2077
} else if (this.dragItems[i].group == "reader-scanner-component") {
// #470A68
}
}
},
I'm using i<=15 instead of i<=this.dragItems.length because of a bug, don't mind it too.
Probably the most efficient (performance wise) and the most readable solution would be to declare a constant colorMap, outside the component, and then return the correct value or a fallback, using a method:
<script>
const colorMap = {
"list-component": '#00A3A1',
"location-media-component": '#005EB8',
"container-component": '#0091DA',
"UI-component": '#6D2077',
"reader-scanner-component": '#470A68'
}
export default {
//...
methods: {
borderColor(group) {
return colorMap[group] || '#000'
}
}
}
</script>
<template>
...
<div :style="{borderColor: borderColor(element.group)}">
content...
</div>
</template>
As a general rule, you want to take anything more complicated than a simple ternary outside of the template and provide it via either computed or methods.
Side note: the above method can also be written as computed:
computed: {
borderColor: group => colorMap[group] || '#000'
}
If you find yourself needing the colorMap in more than one component, export it from a constants.(js|ts) file and import everywhere needed. I typically name that file helpers, as it typically also contains static functions or maps (anything I reuse across multiple components/modules).
Important: you're currently passing an array to :style. You should be passing an object.
I would make a method called getBorderColor(item) which returns the colour based on the group, and then dynamically bind it using Vue.
<div
class="item drag"
:style="[{ 'border-top-color': getBorderColor(element) }]"
>
{{ element.title }}
// Add icon etc.
</div>
getBorderColor(element) {
// Can use a switch statement, but here's a simple ternary if/else as an example
return element.group === "list-component" ? `#00A3A1b`
: element.group === "location-media-component" ? `#005EB8`
: element.group === "container-component" ? `#0091DA`
: element.group === "UI-component" ? `#6D2077`
: element.group === "reader-scanner-component" ? `#470A68`
: `#000000`; // Default colour
}
or for a cleaner option you can have an object with your groups as keys and colours as values in data e.g.
return {
dragItems: dragItemsList,
brdrTpClr: "",
colors: {
"list-component": `#00A3A1b`,
"location-media-component": `#005EB8`,
// etc.
},
};
getBorderColor(element) {
return this.colors[element.group] || `#000`;
}

How to properly delete popup

I have a simple popUp component that I use in my entire app, it get's called by emitting an event on click. There are two types of popUps, success and danger. The success popUp should disappear on it's own after 5 seconds, the danger should get closed when clicked on the x sign. Currently it works the way I have created it, but if the user creates more than one danger popUp, then a success one and again a danger one, the danger one disappears after 5 seconds and not the success one. How can I make so that my success popUp disappears properly after 5 seconds? I am calling it here like this but it seems that it does not delete them correctly:
if(obj.type === 'success') {
setTimeout(this.closePopUp, 5000);
}
Here is my code:
<template>
<div class="popUp-wrapper">
<div
v-for="item in allItems"
:key="item.id"
:class="['popUp', `popUp--type--${item.newPopUpType}`]"
>
<div class="popUp-side">
<p class="exclamation-mark">!</p>
</div>
<h5 class="popUp-message">{{item.message}}</h5>
<div class="popUp-side">
<p class="closing-x" #click="closePopUp(item)" v-if="item.newPopUpType
=== 'danger'">X</p>
</div>
</div>
</div>
</template>
<script>
export default {
data: () => ({
allItems: []
}),
methods: {
closePopUp(item) {
const index = this.allItems.indexOf(item);
this.allItems.splice(index, 1);
},
onPopUpCall(obj) {
var newPopUp = {
newPopUpType: obj.type,
message: obj.message,
id: obj.id
};
if(obj.type === 'success') {
setTimeout(this.closePopUp, 5000);
}
this.allItems.push(newPopUp);
}
},
created() {
this.$root.$on('call-popUp', this.onPopUpCall);
},
destroyed() {
this.$root.$off('call-popUp', this.onPopUpCall);
}
};
</script>
closePopUp requires an item argument based on your code which you are not providing.
Try this:
if(obj.type === 'success') {
setTimeout(() => this.closePopUp(newPopUp), 5000);
}

update view_count col in database, laravel, vue js

I am trying to update view_count column on every #click. but couldn't figure out the right way of it.
First made the controller --resources and fetch datas via api.
controller:
public function index()
{
$articles = Article::all();
return response()->json([
"articles" => $articles
], 200);
}
public function show($id)
{
$article = Article::whereId($id)->first();
return response()->json([
"article" => $article
], 200);
}
also set the update function too.
public function update(Request $request, $id)
{
$view = Article::find($id);
$view->update($request->where("view_count"));
return response()->json(["message" => "view_count updated"]);
}
I set the api routes:
Route::get('/articles', 'ArticlesController#index');
Route::get('/articles/{id}', 'ArticlesController#show');
Route::get('/articles/{id}', 'ArticlesController#update');
And finally in Vue.js
<p class="button">
<i #click.prevent="count" class="fas fa-chevron-right"></i>
</p>
data(){
return {
view: 0,
};
},
methods: {
count: function(){
axios.post("/api/articles" + this.item.id).then(response => {
this.view++;
})
window.location.href = "pages/" + this.item.id
}
}
it's counting but not update the col. also, when I refresh the page of course it will start to count from 0... it's not really efficient way to it. what is the best and right way to do it?
Thank you.
Not: By the way I am fetching and iterating api in the parent component:
<div class="listWrap" :key="item.id" v-for="item in filterArticles">
<list :item="item" />
</div>
your workflow to update views is wrong.
first, we should change our uri method of the update method to GET like below :
Route::get('/articles/update/{id}', 'ArticlesController#update');
then, our update method within ArticlesController to increment view_count value:
public function update(int $id)
{
// i have changed the $view by $article
$article = Article::find($id);
$article->view_count++;
$article->save();
return response()->json(["message" => "view_count updated", 201]);
}
and within our Vue component, we should update the URI of the update method and the HTTP method name because we should use the same HTTP verb in both client and server sides.
<p class="button">
<i #click.prevent="count" class="fas fa-chevron-right"></i>
</p>
<script>
export default {
// as you are using parent/child relationship betwen components, you should use props.
props: { item: Object },
data(){
return {
view: 0,
};
},
methods: {
count: function(){
axios.get(`/api/articles/update/${this.item.id}`).then(response => {
this.view++;
})
window.location.href = "pages/" + this.item.id;
}
}
}
</script>

How To Display My Invoice Data In Invoice Template

I'm using Laravel 5.7 & VueJs 2.5.* ...
I have invoices table, i need to display specific invoice in a new component, so user can see whatever invoice he wants or print that invoice.
I don't know how to do that, i'm just playing around, if you could help me out, i'll be very grateful to you.
<router-link> to the component
<router-link to="/ct-invoice-view" #click="openInvoice(ctInvoice)">
<i class="fas fa-eye fa-lg text-blue"></i>
</router-link>
Displaying Customer information here like this:
<div class="col-sm-4 invoice-col">
<address v-for="ctInvoice in ctInvoices" :key="ctInvoice.id">
<strong>Customer Info</strong><br>
Name: <span>{{ ctInvoice.customer.customer_name }}</span>
Invoice view component data() & method{}
data() {
return {
ctInvoices: {},
customers: null
};
},
methods: {
openInvoice(ctInvoice) {
axios
.get("api/ct-invoice/show/" + this.viewInvoice)
.then(({
data
}) => (this.ctInvoices = data.data));
},
Image for Better Understanding
You need to look at Dynamic Route matching: https://router.vuejs.org/guide/essentials/dynamic-matching.html#reacting-to-params-changes
Then you need to use axios.get in invoice views beforeMount function where this.$route.params.id will hold the invoice ID you want to load if the link is applied like so:
<router-link :to="`/ct-invoice-view/${ctInvoice.id}`">
<i class="fas fa-eye fa-lg text-blue"></i>
</router-link>
Alternatively...
I suggest not navigating away from the list, it can be irritating for users having filtered the list then returning to it to look at more invoices and having to filter again unless the filter options and current results are sticky
There are a number of ways of doing this and they are lengthy to example, Typically I would make proper use of a modal and the invoice view load the data on display but to get you started a basic in page solution to experiment with, then try adapting in a reusable modal component later:
<button #click="showInvoice = ctInvoice.id">
<i class="fas fa-eye fa-lg text-blue"></i>
</button>
data() {
return {
loading: false,
invoice: {},
customers: null
};
},
computed: {
showInvoice: {
get: function() {
return this.invoice.hasOwnProperty('id');
},
set: function(value) {
if(value === false) {
this.invoice = {};
return;
}
// could check a cache first and push the cached item into this.invoice else load it:
this.loading = true;
axios.get("api/ct-invoice/show/" + value).then(response => {
// you could push the invoice into a cache
this.invoice = response.data;
}).cache(error => {
// handle error
}).finally(() => {
this.loading = false;
});
}
}
}
In view-invoice component have a close button with bind #click="$emit('close')"
Check this article for how $emit works: https://v2.vuejs.org/v2/guide/components-custom-events.html
<div v-if="loading" class="loading-overlay"></div>
<view-invoice v-if="showInvoice" :invoice="invoice" #close="showInvoice = false" />
<table v-else>....</table>
Hide the table when displaying the invoice, experiment with using v-show instead of v-if upon loosing table content state.
Inside your invoice view, property called invoice will contain the invoice data.
Check this article for how to use props: https://v2.vuejs.org/v2/guide/components-props.html
Hint: The #close listens to the $emit('close')
Could also make use of when switching between table and invoice view.
https://v2.vuejs.org/v2/guide/transitions.html
#MarcNewton
I did something like this, it's working for me, can u just review it for me:
<router-link> to the Invoice View component
<router-link v-bind:to="{name: 'ctInvoiceView', params: {id: ctInvoice.id}}">
<i class="fas fa-eye fa-lg text-blue"></i>
</router-link>
Getting Data of Specific Invoice ID Like This:
created: function() {
axios
.get("/api/ct-invoice/" + this.$route.params.id)
.then(({
data
}) => {
console.log(data);
this.form = new Form(data);
})
.catch(error => {
console.log(error.response);
});
},

Vue.js + Require.js extending parent

I'm new to Vue.js but trying to get to grips with it. So far it has gone well and I've got quite far but I am stuck extending a parents template.
I am trying to make dashboard widgets that extend a default widget layout (in Boostrap). Please note that the below code is using Vue, Require, Underscore & Axios.
Parent file - _Global.vue
<template>
<div class="panel panel-default">
<div class="panel-heading">
<b>{{ widgetTitle }}</b>
<div class="pull-right">
<a href="#" v-on:click="toggleMinimized"
v-bind:title="(isMinimized ? 'Show widget' : 'Hide widget')">
<i class="fa fa-fw" v-bind:class="isMinimized ? 'fa-plus' : 'fa-minus'"></i>
</a>
</div>
</div>
<div class="panel-body" v-if="!isMinimized">
<div class="text-center text-muted" v-if="!isLoaded">
<i class="fa fa-spin fa-circle-o-notch"></i><br />
</div>
<parent v-if="isLoaded">
<!-- parent content should appear here when loaded -->
</parent>
</div>
</div>
</template>
<script>
export default {
// setup our widget props
props: {
'minimized': {
'default': false,
'required': false,
'type': Boolean
}
},
// define our data
data: function () {
return {
widgetTitle: 'Set widget title in data',
isLoaded: false,
isMinimized: this.$props.minimized
}
},
// when vue is mounted, open our widget
mounted: function () {
if(!this.isMinimized) {
this.opened();
}
},
// define our methods
methods: {
// store our widget state to database
storeWidgetState: function () {
// set our data to send
let data = {
'action' : 'toggleWidget',
'widget' : this.$options._componentTag,
'state' : !this.isMinimized
};
// post our data to our endpoint
axios.post(axios.endpoint, data);
},
// toggle our minimized data
toggleMinimized: function (e) {
// prevent default
e.preventDefault();
// toggle our minimized state
this.isMinimized = !this.isMinimized;
// trigger opened if we aren't minimized
if(!this.isMinimized) this.opened();
// save our widget state to database
this.storeWidgetState();
},
// triggered when opened from being minimized
opened: function () {
console.log('opened() method is where all widget logic should be placed');
}
}
}
</script>
Child file - Example.vue
Should extend _Global.vue using mixins and then display content within .panel-body
<template>
<div>
I want this content to appear inside the .panel-body div
{{ content }}
<img v-bind:src="image.src" v-bind:alt="image.alt"
v-if="image.src" class="img-responsive" style="margin: 0 auto" />
</div>
</template>
<script>
// import our widgets globals
import Global from './_Global.vue'
export default {
components: {
'parent': {
// what can I possibly put here??
}
},
// use our global mixin for all widgets
mixins: [Global],
// setup our methods for this widget
methods: {
opened: _.debounce(function () {
// make sure this can only be opened once
if(this.hasBeenOpened) return;
this.hasBeenOpened = true;
// temporarily allow axios to make external requests
let axiosHeaders = axios.defaults.headers.common;
let vm = this;
axios.defaults.headers.common = {};
axios.get('https://yesno.wtf/api')
.then(function (res) {
// set our content
vm.content = null;
// set our image content
vm.image.src = res.data.image;
vm.image.alt = res.data.answer;
})
.catch(function (err) {
// set our error text
vm.content = String(err);
})
.then(function () {
// this will always hit..
vm.isLoaded = true;
});
// restore our axios headers for security
axios.defaults.headers.common = axiosHeaders;
}, 300)
},
// additional data
data: function () {
return {
// set our widgets title
widgetTitle: 'Test title',
// logic for the specific widget
hasBeenOpened: false,
content: 'Loaded and ready to go...',
image: {
src: false,
alt: null
}
};
},
}
</script>
Currently my parent template is just completely overwriting my child view. The only way I can get it to work is by explicitly defining the template parameter inside components -> parent: {} but I don't want to have to do that...?
Ok, thanks to Gerardo Rosciano for pointing me the in right direction. I've used to slots to come up with an eventual solution. We then access parent methods and data attributes just to get everything working as it should.
Example.vue - our example widget
<template>
<div>
<widget-wrapper>
<span slot="header">Example widget</span>
<div slot="content">
<img v-bind:src="image.src" v-bind:alt="image.alt"
v-if="image.src" class="img-responsive" style="margin: 0 auto" />
{{ content }}
</div>
</widget-wrapper>
</div>
</template>
<script>
// import our widgets globals
import WidgetWrapper from './_Widget.vue'
export default {
// setup our components
components: {
'widget-wrapper': WidgetWrapper
},
// set our elements props
props: {
'minimized': {
'type': Boolean,
'default': false,
'required': false
}
},
// setup our methods for this widget
methods: {
loadContent: _.debounce(function () {
// make sure this can only be opened once
if(this.hasBeenOpened) return;
this.hasBeenOpened = true;
// temporarily allow axios to make external requests
let axiosHeaders = axios.defaults.headers.common;
let vm = this;
axios.defaults.headers.common = {};
axios.get('https://yesno.wtf/api')
.then(function (res) {
// set our content
vm.content = null;
// set our image content
vm.image.src = res.data.image;
vm.image.alt = res.data.answer;
})
.catch(function (err) {
// set our error text
vm.content = String(err);
})
.then(function () {
// this will always hit..
vm.isLoaded = true;
});
// restore our axios headers for security
axios.defaults.headers.common = axiosHeaders;
}, 300)
},
// additional data
data: function () {
return {
// global param for parent
isLoaded: false,
// logic for the specific widget
hasBeenOpened: false,
content: 'Loaded and ready to go...',
image: {
src: false,
alt: null
}
};
},
}
</script>
_Widget.vue - our base widget that gets extended
<template>
<div class="panel panel-default">
<div class="panel-heading">
<b><slot name="header">Slot header title</slot></b>
<div class="pull-right">
<a href="#" v-on:click="toggleMinimized"
v-bind:title="(minimized ? 'Show widget' : 'Hide widget')">
<i class="fa fa-fw" v-bind:class="minimized ? 'fa-plus' : 'fa-minus'"></i>
</a>
</div>
</div>
<div class="panel-body" v-if="!minimized">
<div class="text-center text-muted" v-if="!isLoaded">
<i class="fa fa-spin fa-circle-o-notch"></i><br />
Loading...
</div>
<div v-else>
<slot name="content"></slot>
</div>
</div>
</div>
</template>
<script>
export default {
// get loaded state from our parent
computed: {
isLoaded: function () {
return this.$parent.isLoaded;
}
},
// set our data element
data: function () {
return {
minimized: false
}
},
// when the widget is mounted, trigger open state
mounted: function () {
this.minimized = this.$parent.minimized;
if(!this.minimized) this.opened();
},
// methods to manipulate our widget
methods: {
// save our widget state to database
storeWidgetState: function () {
// set our data to send
let data = {
'action' : 'toggleWidget',
'widget' : this.$parent.$options._componentTag,
'state' : !this.minimized
};
// post this data to our endpoint
axios.post(axios.endpoint, data);
},
// toggle our minimized state
toggleMinimized: function (e) {
// prevent default
e.preventDefault();
// toggle our minimized state
this.minimized = !this.minimized;
// trigger opened if we aren't minimized
if(!this.minimized) this.opened();
// save our widget state to database
this.storeWidgetState();
},
// when widget is opened, load content
opened: function () {
// make sure we have a valid loadContent method
if(typeof this.$parent.loadContent === "function") {
this.$parent.loadContent();
} else {
console.log('You need to define a loadContent() method on the widget');
}
}
}
}
</script>

Categories

Resources