Selected value is not displayed in the select option in VueJS project - javascript

I build a shift planner as part of a larger vuejs 2 project, with a Cloud Firestore database, and when I try to set a work shift for a specific day for any employee, selecting the shift from a predefined list, the shift is saved in the database as expected, but it is not displayed on the dropdown as being selected. Here is my code:
<template>
<div>
<select v-model="selectedShift" #change="saveShift">
<option :value="{start:'00:00',end:'00:00',name:'',abreviation:'',duration:0,adjustedReason:''}"></option>
<option v-for="shift in shifts" :key="shift.name" :value="shift">{{ shift.abreviation }}</option>
</select>
</div>
<script>
import {db} from '#/firebase/init'
import randomstring from 'randomstring';
export default {
props: ['employee','date'],
data(){
return{
shifts: [],
selectedShift: '',
shift: {
start:'00:00',
end:'00:00'
},
//in order to get a unique name for each modal
modalName: '',
modalNameSent: ''
}
},
methods:{
saveShift(){
let id = `${this.employee.id}${this.date}`
db.collection('shifts').doc(id).update(this.selectedShift)
}
},
created(){
//retrieve defined shifts from db
db.collection('shifts_definition').where('service_id','==',this.employee.service_id).get().then(docs=>{
docs.forEach(doc=>{
this.shifts.push(doc.data())
})
})
let id = `${this.employee.id}${this.date}`;
let ref = db.collection('shifts').doc(id)
ref.onSnapshot(doc=>{
if(!doc.exists){
ref.set({
uid: randomstring.generate({length: 12,charset: 'alphabetic'}),
service_id: this.employee.service_id,
employee_id: this.employee.id,
adjustedReason:'',
date: this.date,
start:'00:00',
end:'00:00',
duration:0,
name:'',
abreviation:'',
})
}else{
this.selectedShift = doc.data()
this.shift = doc.data()
this.shift.id = doc.id
this.modalName = doc.data().uid
this.modalNameSent = '#'+doc.data().uid
}
});
}
}
</script>

Related

How to display specific row in vuejs api

I just want to ask how to display/fetch data from API to my textbox
when you click the edit button in a specific row table. It will display it's own id and other details. I'm so sorry to post my code like this, I don't know how to do it because it gives me errors.
Raw Code :
data : {
students : []
}
methods: {
async editStudents(edit) {
let id = "621ecc95817b5aeb5783aebe"
let a = await
this.$axios.get(`https://api.qa.sampleapi.com/students/${id}`)
console.log(a.data.data)
}
It will give me that specific item but how to do it with a for loop.
Sample code :
editStudent(edit) {
let studentid = id
let a = await
this.$axios.get(`https://api.qa.sampleapi.com/students/${studentid}`)
for(let i = 0; i < this.students.length; i++) {
if(edit.studentid === this.students[i].studentid) {
this.textbox1 = this.students[i].studentid;
}
}
}
As per my understanding I came up with below solution. Please let me know if it works as per your requirement or not.
Demo :
new Vue({
el:"#app",
data:{
students: [{
id: 1,
name: 'Student 1'
}, {
id: 2,
name: 'Student 2'
}, {
id: 3,
name: 'Student 3'
}]
},
methods: {
editStudent(id) {
console.log(id); // You will get the student ID here
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul class="list">
<li v-for="student in students" :key="student.id">
{{ student.name }}
<button #click="editStudent(student.id)">Edit</button>
</li>
</ul>
</div>

Unable to update per page count from the frontend (Laravel, InertiaJS, VueJS)

I am migrating my current application from Laravel Livewire to Laravel InertiaJS VueJS. Currently I am stuck at setting the per page count from the front end and paginate the data accordingly. Currently I am using Laravel's default pagination along with the custom pagination component for VueJS and it works seamlessly. I just want to set the $per_page as per the input, the variable is set to 5 by default, in the index method of the controller. Below is the code structure and the logic. Please help me achieve this in the way InertiaJS is meant to be used.
UserController.php
public function index(Request $request)
{
$per_page = \Request::get('per_page') ?: 5;
$query = User::select('id', 'name', 'email', 'role_id', 'created_at');
$users = $query->paginate($per_page);
return Inertia::render('Backend/Management/AudienceManagement/Users/Index', [
'users' => $users
]);
}
Users/Index.vue
<template>
<input-group borderless paddingless inline>
<input-select #change="setPerPage($event)" id="perPage" placeholder="Per Page">
<option value="5">5</option>
<option value="10">10</option>
</input-select>
</input-group>
</template>
<script>
import {
Inertia
} from '#inertiajs/inertia'
export default {
props: {
users: {
type: Object
}
},
data() {
return {
sortField: '',
sortDirection: ''
}
},
methods: {
setPerPage(event) {
console.log(event.target.value);
this.users.per_page = event.target.value;
Inertia.reload({
only: ['users.data']
});
},
}
}
</script>

How to load Bootstrap-vue "b-table" row data dynamically using "_showDetails"?

I have bootstrap-vue "b-table" used in my Vue page. Each row has an "view-details" button, that shows additional information about the selected row. I was looking for examples that can send request to backend when user clicks for view-details, that expands the row and shows details retrieved from backend. The "_showDetails" option from bootstrap-vue table seems limited as the examples all use the data that was already loaded along with the main tableland using this way would overload the page as my data for each row is too big.
Are there any examples or even other libs that support such functionality?
You can do this with bootstrap-vue without any problems.
Create a method that gets called when you click your "view details" button, this method will call your backend and insert the data onto your item. Once the data has been retrieved you set _showDetails to true on the item, which will open the details.
You could also open it immediately and show a loading message while the data is retrieved, that's up to you.
new Vue({
el: '#app',
created() {
// Get initial data
fetch('https://reqres.in/api/users')
.then(response => response.json())
.then(json =>
/* Map and use only some of the data for the example */
this.items = json.data
.map(user => {
return {
id: user.id,
first_name: user.first_name,
last_name: user.last_name
}
}))
},
data() {
return {
items: [],
fields: ['id', 'first_name', 'last_name', {
key: 'actions',
label: ''
}]
}
},
methods: {
toggleDetails(item) {
if (item._showDetails) { // if details are open, close them
item._showDetails = false
} else if (item.details) { // if details already exists, show the details
this.$set(item, '_showDetails', true)
} else {
fetch(`https://reqres.in/api/users/${item.id}`)
.then(response => response.json())
.then(json => {
const user = json.data;
item.details = {
email: user.email,
avatar: user.avatar
}
this.$set(item, '_showDetails', true)
})
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.5.0/dist/bootstrap-vue.min.js"></script>
<link href="https://unpkg.com/bootstrap-vue#2.5.0/dist/bootstrap-vue.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap#4.3.1/dist/css/bootstrap.min.css" rel="stylesheet" />
<div id="app">
<b-container>
<b-table :items="items" :fields="fields">
<template v-slot:cell(actions)="{ item }">
<b-btn #click="toggleDetails(item)">
Show details
</b-btn>
</template>
<template v-slot:row-details="{ item : { details: { email, avatar }}}">
<b-card>
<b-img :src="avatar" fluid></b-img>
{{ email }}
</b-card>
</template>
</b-table>
</b-container>
</div>

How to have parent data be updated by child component with multiple values

Code below.
I think I'm missing a crucial piece here. I've been through the docs and watched the entire vue2 step by step. Everything is making sense so far but I'm stuck on what seems to be a core piece. Any help would be appreciated. If this is totally wrong, please let me know, I'm not married to any of this stuff.
Desired functionality: There is an order Vue instance and it has line items.
On order.mounted() we hit an api endpoint for the order's data, including possible existing line items. If there are existing line items, we set that order data (this.lineitems = request.body.lineitems or similar). This part works fine and I can get the order total since the orders' line items are up to date at this point.
Each line item is an editable form with a quantity and a product . If I change the quantity or product of any line item, I want the child line-item component to notify the parent component that it changed, then the parent will update its own lineitems data array with the new value, and preform a POST request with all current line item data so the server side can calculate the new line item totals (many specials, discounts, etc). This will return a full replacement array for the order's line item data, which in turn would passed down to the line items to re-render.
Problems:
The line-items components "update..." methods are feeling obviously wrong, but my biggest issue is understanding how to get the parent to update its own line items data array with the new data. for instance
​
lineitems = [
{id: 1000, quantity: 3, product: 555, total: 30.00},
{id: 1001, quantity: 2, product: 777, total: 10.00}
]
If the second line item is changed to quantity 1, how do I get the parent's lineitems data to change to this? My main problem is that I don't know how the parent is suppose to know which of its own lineitems data array need to be modified, and how to grab the data from the changed child. I assume it came in via an event, via emit, but do I now need to pass around the primary key everywhere so I can do loops and compare? What if its a new line item and there is no primary key yet?
Mentioned above, I'm using the existing line item's DB primary key as the v-for key. What if I need a "new lineitem" that appends a blank lineitem below the existing ones, or if its a new order with no primary keys. How is this normally handled.
Is there a best practice to use for props instead of my "initial..." style? I assume just using $emit directly on the v-on, but I'm not sure how to get the relevant information to get passed that way.
This seems like the exact task that VueJS is suited for and I just feel like I keep chasing my tail in the wrong direction. Thanks for the help!
LineItem
Vue.component('line-item', {
props: ["initialQuantity", "initialProduct", "total"],
data () {
return {
// There are more but limiting for example
quantity: initialQuantity,
product: initialProduct,
productOptions = [
{ id: 333, text: "Product A"},
{ id: 555, text: "Product B"},
{ id: 777, text: "Product C"},
]
}
},
updateQuantity(event) {
item = {
quantity: event.target.value,
product: this.product
}
this.$emit('update-item', item)
},
updateProduct(event) {
item = {
quantity: this.quantity,
product: event.target.value
}
this.$emit('update-item', item)
}
template: `
<input :value="quantity" type="number" #input="updateQuantity">
<select :value="product" #input="updateProduct">
<option v-for="option in productOptions" v-bind:value="option.id"> {{ option.text }} </option>
</select>
Line Item Price: {{ total }}
<hr />
`
})
Order/App
var order = new Vue({
el: '#app',
data: {
orderPK: orderPK,
lineitems: []
},
mounted() {
this.fetchLineItems()
},
computed: {
total() {
// This should sum the line items, like (li.total for li in this.lineitems)
return 0.0
},
methods: {
updateOrder(item) {
// First, somehow update this.lineitems with the passed in item, then
fetch(`domain.com/orders/${this.orderPK}/calculate`, this.lineitems)
.then(resp => resp.json())
.then(data => {
this.lineitems = data.lineitems;
})
},
fetchLineItems() {
fetch(`domain.com/api/orders/${this.orderPK}`)
.then(resp => resp.json())
.then(data => {
this.lineitems = data.lineitems;
})
},
},
template: `
<div>
<h2 id="total">Order total: {{ total }}</h2>
<line-item v-for="item in lineitems"
#update-item="updateOrder"
:key="item.id"
:quantity="item.quantity"
:product="item.product"
:total="item.total"
></line-item>
</div>
`
})
Here's a list of problems in your attempt that would prevent it from displaying anything at all i.e.
quantity: initialQuantity, - surely you meant quantity: this.initialQuantity, ... etc for all the other such data
missing } for computed total
your line-item template is invalid - you have multiple "root" elements
And then there's some minor issues:
you want the #change handler for the select, not #input, if your code ran, you'd see the difference,
Similarly you want #change for input otherwise you'll be making fetch requests to change the items every keystroke, probably not what you'd want
So, despite all that, I've produced some working code that does all you need - mainly for my own "learning" though, to be fair :p
// ******** some dummy data and functions to emulate fetches
const products = [
{ id: 333, text: "Product A", unitPrice: 10},
{ id: 555, text: "Product B", unitPrice: 11},
{ id: 777, text: "Product C", unitPrice: 12},
];
let dummy = [
{id: 1, quantity:2, product: 333, total: 20},
{id: 2, quantity:3, product: 777, total: 36},
];
const getLineItems = () => new Promise(resolve => setTimeout(resolve, 1000, JSON.stringify({lineitems: dummy})));
const update = items => {
return new Promise(resolve => setTimeout(() => {
dummy = JSON.parse(items);
dummy.forEach(item =>
item.total = parseFloat(
(
item.quantity *
(products.find(p => p.id === item.product) || {unitPrice: 0}).unitPrice *
(item.quantity > 4 ? 0.9 : 1.0)
).toFixed(2)
)
);
let res = JSON.stringify({lineitems: dummy});
resolve(res);
}, 50));
}
//********* lineItem component
Vue.component('line-item', {
props: ["value"],
data () {
return {
productOptions: [
{ id: 333, text: "Product A"},
{ id: 555, text: "Product B"},
{ id: 777, text: "Product C"},
]
}
},
methods: {
doupdate() {
this.$emit('update-item', this.value.product);
}
},
template: `
<p>
<input v-model="value.quantity" type="number" #change="doupdate()"/>
<select v-model="value.product" #change="doupdate()">
<option v-for="option in productOptions" v-bind:value="option.id"> {{ option.text }} </option>
</select>
Line Item Price: {{ '$' + value.total.toFixed(2) }}
</p>
`
})
//********* Order/App
const orderPK = '';
var order = new Vue({
el: '#app',
data: {
orderPK: orderPK,
lineitems: []
},
mounted() {
// initial load
this.fetchLineItems();
},
computed: {
carttotal() {
return this.lineitems.reduce((a, {total}) => a + total, 0)
}
},
methods: {
updateOrder(productCode) {
// only call update if the updated item has a product code
if (productCode) {
// real code would be
// fetch(`domain.com/orders/${this.orderPK}/calculate`, this.lineitems).then(resp => resp.json())
// dummy code is
update(JSON.stringify(this.lineitems)).then(data => JSON.parse(data))
.then(data => this.lineitems = data.lineitems);
}
},
fetchLineItems() {
// real code would be
//fetch(`domain.com/api/orders/${this.orderPK}`).then(resp => resp.json())
// dummy code is
getLineItems().then(data => JSON.parse(data))
.then(data => this.lineitems = data.lineitems);
},
addLine() {
this.lineitems.push({
id: Math.max([this.lineitems.map(({id}) => id)]) + 1,
quantity:0,
product: 0,
total: 0
});
}
},
template: `
<div>
<h2 id="total">Order: {{lineitems.length}} items, total: {{'$'+carttotal.toFixed(2)}}</h2>
<line-item v-for="(item, index) in lineitems"
:key="item.id"
v-model="lineitems[index]"
#update-item="updateOrder"
/>
<button #click="addLine()">
Add item
</button>
</div>
`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
</div>
note: there may be some inefficient code in there, please don't judge too harshly, I've only been using vuejs for a week

vue.js computed property not triggered

Vue JS computed property is not triggered With this markup
<!-- language: lang-html -->
<p>£{{plant_price}}</p>
<div v-if="selected.plant.variations.length > 0 ">
<select v-model="selected.plant.selected_variation" class="form-control">
<!-- inline object literal -->
<option v-for="(variation, i) in selected.plant.variations" :selected="variation.id == selected.plant.selected_variation ? 'selected' : ''":value="variation.id">
{{variation.name}}
</option>
</select>
</div>
<!-- language: lang-js -->
var app = new Vue({
el: '#vueApp',
data: {
selected: {
type: {a: '' , b: ''},
vehicle: '',
plant: {
}
},
computed: {
plant_price: function() {
if (this.selected.plant.variations.length > 0 ) {
var variant = _.find(this.selected.plant.variations, {id: this.selected.plant.selected_variation });
return variant.price;
} else {
return this.selected.plant.price;
}
}
...
selected.plant is populated by clicking on a plant - triggering the updateSelected method.
<div class="col-sm-4" v-for="(plant, i) in step2.plants">
<div v-on:click="updateSelected(plant)" ....
methods: {
updateSelected: function(plant) {
this.selected.plant = plant; // selected plant
if (this.selected.plant.variations.length > 0 ) {
this.selected.plant.selected_variation = this.selected.plant.variations[0].id; // set the selected ID to the 1st variation
I have checked through the debugger, and can see that all the correct properties are available.
selected:Object
type:Object
vehicle: "Truck"
plant:Object
id:26
price:"52"
regular_price:"100"
selected_variation:421
variations:Array[2]
0:Object
id:420
name:"small"
price:52000
regular_price:52000
1:Object
etc...
I have a computed property, which should update the plant_price based on the value of selected.plant.selected_variation.
I grab selected.plant.selected_variation and search through the variations to retrieve the price. If no variation exists, then the plant price is given.
I have a method on each product to update the selected plant. Clicking the product populates the selected.plant and triggers the computed plant_price to update the price (as the value of selected.plant.selected_variation has changed).
However, the computed plant_price is not triggered by the select. Selecting a new variant does what its supposed to, it updates selected.plant.selected_variation. Yet my plant_price doesn't seem to be triggered by it.
So I refactored my code by un-nesting selected.plant.selected_variation. I now hang it off the data object as
data = {
selected_variation: ''
}
and alter my computer property to reference the data as this.selected_variation. My computed property now works??? This makes no sense to me?
selected.plant.selected_variation isn't reactive and VM doesn't see any changes you make to it, because you set it after the VM is already created.
You can make it reactive with Vue.set()
When your AJAX is finished, call
Vue.set(selected, 'plant', {Plant Object})
There're two ways you can do it, what you are dealing with is a nested object, so if you want to notify the changes of selected to the others you have to use
this.$set(this.selected, 'plant', 'AJAX_RESULT')
In the snippet I used a setTimeout in the created method to simulate the Ajax call.
Another way you can do it is instead of making plant_price as a computed property, you can watch the changes of the nested properties
of selected in the watcher, and then update plant_price in the handler, you can check out plant_price_from_watch in the snippet.
Vue.component('v-select', VueSelect.VueSelect);
const app = new Vue({
el: '#app',
data: {
plant_price_from_watch: 'not available',
selected: {
type: {a: '' , b: ''},
vehicle: "Truck"
}
},
computed: {
plant_price() {
return this.setPlantPrice();
}
},
watch: {
selected: {
handler() {
console.log('changed');
this.plant_price_from_watch = this.setPlantPrice();
},
deep: true
}
},
created() {
setTimeout(() => {
this.$set(this.selected, 'plant', {
id: 26,
price: '52',
regular_price: '100',
selected_variation: 421,
variations: [
{
id: 420,
name: "small",
price: 52000,
regular_price: 52000
},
{
id: 421,
name: "smallvvsvsfv",
price: 22000,
regular_price: 22000
}
]
})
}, 3000);
},
methods: {
setPlantPrice() {
if (!this.selected.plant) {
return 'not available'
}
if (this.selected.plant.variations.length > 0 ) {
const variant = _.find(this.selected.plant.variations, {id: this.selected.plant.selected_variation });
return variant.price;
} else {
return this.selected.plant.price;
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
<p>£{{plant_price}}</p>
<p>£{{plant_price_from_watch}}</p>
<div v-if="selected.plant && selected.plant.variations.length > 0 ">
<select v-model="selected.plant.selected_variation" class="form-control">
<!-- inline object literal -->
<option v-for="(variation, i) in selected.plant.variations" :selected="variation.id == selected.plant.selected_variation ? 'selected' : ''":value="variation.id">
{{variation.name}}
</option>
</select>
</div>
</div>

Categories