I have the following template:
<template>
<div>
<h1>Users from SQL Server!</h1>
<p>This component demonstrates fetching data from the server.</p>
<table v-if="userlist.length" class="table">
<thead>
<tr>
<th>Id</th>
<th>First</th>
<th>Last</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<!-- nb: These items must match the case they are passed in with (the Json passed back from .net Core serialises to lowerCamelCase) otherwise they will
show nothing and no error is raised! -->
<tr v-for="item in userlist">
<td>{{ item.userid }}</td>
<td>{{ item.firstname }}</td>
<td>{{ item.lastname }}</td>
<td>{{ item.createdonDate }}</td>
</tr>
</tbody>
</table>
<p v-else><em>Loading...</em></p>
</div>
This displays a table with no data. This is because the data passed in from the server has a different case to that used in the template items. If I fix them for example:
item.userId
item.firstName
item.lastName
item.createdOnDate
Then it works and data is displayed. The issue for me is that no error is returned. I am learning Vue using the .Net Core SPA template as a starter. But it took me a while to realise what I was doing wrong. If this was a model in the razor view it would have blown with a helpful error.
Is there a way to raise an error for this kind of thing?
I do have the Chrome Vue extension installed and realised the problem when I looked at the data there. But I was stumped for a while.
UPDATE 1: Thanks #ndpu for your solution but I am having trouble fitting it into my project. I have a boot.ts file like this:
import 'bootstrap';
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use((VueRouter) as any);
Vue.config.devtools = true;
const routes = [
{ path: '/', component: require('./components/home/home.vue.html') },
{ path: '/counter', component:
require('./components/counter/counter.vue.html') },
{ path: '/fetchdata', component:
require('./components/fetchdata/fetchdata.vue.html') },
{ path: '/users', component: require('./components/users/users.vue.html') },
{ path: '/user', component: require('./components/user/user.vue.html') }
];
new Vue({
el: '#app-root',
router: new VueRouter({ mode: 'history', routes: routes }),
render: h => h(require('./components/app/app.vue.html'))
});
Where do I put the Object.prototype.safeGet? In there or in my component templates? Nowhere seems to work.
UPDATE 2:
I got it to work by putting the code from #ndpu into app.ts like this:
import Vue from "vue";
import { Component } from 'vue-property-decorator';
#Component({
components: {
MenuComponent: require('../navmenu/navmenu.vue.html')
}
})
export default class AppComponent extends Vue {
}
Object.defineProperty(Object.prototype, 'safeGet', {
get: function () {
return this;
}
});
Simplest way: define additional property to Object with getter that will only return himself.
/* eslint no-extend-native: ["error", { "exceptions": ["Object"] }] */
Object.defineProperty(Object.prototype, 'safeGet', {
get: function () {
return this;
}
});
So, by adding safeGet to any data attribute, you can be sure that you will get TypeError if attribute is undefined:
this:
<td>{{ item.userid.safeGet }}</td>
will produce exception (if actual property name is userId):
TypeError: Cannot read property 'safeGet' of undefined
FIDDLE: http://jsfiddle.net/yMv7y/2785/
Also, you can define simple method in object prototype to check property existence:
UPDATE: i couldn't make it work in 'complex' application with modules, webpack etc - vue trying to make added to Object.prototope method reactive. Didnt know why it is work in simple case like in applied fiddle.
Object.prototype.safeGet = function() {
var val, args = Array.prototype.slice.call(arguments);
var currLvlObj = this;
for (var i=0; i < args.length; i++) {
val = currLvlObj = currLvlObj? currLvlObj[args[i]] : undefined;
if (val === undefined) {
throw Error('property with name ' + args[i] + ' is undefined');
}
}
return val;
}
and use it like this:
<td>{{ item.safeGet('userid') }}</td>
this call should throw error (if actual property name is userId): Error: property with name userid is undefined
PS: nested objects properties can be accessed by passing all property names as arguments. For example, to access 'userId' in {'data': {'userId': 0}}:
<td>{{ item.safeGet('data', 'userid') }}</td>
FIDDLE: http://jsfiddle.net/yMv7y/2778/
Related
This is my clientMutations.js file. I am using gql from #apollo-client.
import { gql } from "#apollo/client";
const DELETE_CLIENT = gql`
mutation deleteClient($id: ID!) {
deleteClient(id: $id) {
id
name
email
phone
}
}
`;
Here is the component that uses the query.
import { DELETE_CLIENT } from "../mutations/clientMutations";
export default function ClientRow({ client }) {
const { deleteClient } = useMutation(DELETE_CLIENT, {
variables: { id: client.id },
});
return (
<tr>
<td>{client.name}</td>
<td>{client.email}</td>
<td>{client.phone}</td>
<td>
<button className="btn btn-danger btn-sm" onClick={deleteClient}>
<FaTrash />
</button>
</td>
</tr>
);
}
i am getting Invariant Violation: Argument of undefined passed to parser was not a valid GraphQL DocumentNode. You may need to use 'graphql-tag' or another method to convert your operation into a document in my browser console.
I was able to solve this by restarting react. I had also made a mistake by using object destructuring rather than using array destructuring here:
const [ deleteClient ] = useMutation(DELETE_CLIENT, {
variables: { id: client.id },
});
I am working on my first Sveltekit app project, converting from a React based project. I got stuck with a bit of a tricky method inside an object inside a store. Before I refactor the entire data structure to something perhaps more intuitive ( this is still a data structure derived from the React way of doing things ) , I wanted to understand how to do this properly in case I need it again.
An object stores some meta data about a dataset.
const dataSetsIndex = [
{id: ':sample:alphabet',
permanent: true,
metadata: {
title: 'Alphabetic Letter Frequency',
source: 'https://observablehq.com/#d3/bar-chart-transitions'
},
attachments: {
data: import ('/src/dataSets/alphabet.json'),
}
}
];
export default dataSetsIndex;
There would be more objects with the { id: permanent: metadata: { title: source: } attachments: { data: ()=> } } structure in this dataSetsIndex component prop.
But when my program eventually tries to access the data from an external JSON ti display on a route in Sveltekit , I can't seem to find a way to make that Promise from the import('/src/dataSets/alphabet.json') method return.
Following the docs, I tried an interface that destructures the data and stores it in a writable - the data in the JSON file is fields:[] , rows:[]
import DataSets from "../dataSets/dataSetsIndex.js";
import {writable} from "svelte/store";
export const dataSetsStore = writable([]);
let destructedDataSets = () => {
const dataSets = DataSets.map( ( dataset, index ) =>
{
return {
id: index,
title: dataset.metadata.title,
source: dataset.metadata.source,
fields: dataset.attachments.data().then(
(success) => { return success.fields},
(fail) => {return fail})
}
}
)
dataSetsStore.set(dataSets);
};
destructedDataSets();
then bringing that in to a route which is reactive
<script>
import {dataSetsStore} from "../stores/dataSetsStore.js"
</script>
{#each $dataSetsStore as metadataObject}
<div>
{metadataObject.title.toUpperCase()}
{metadataObject.fields}
</div>
{/each}
only displays ALPHABETIC LETTER FREQUENCY [object Promise]
What am I missing here?
OK, so I figured it out and this is working. I looked at this related post to help me understand the role of {#await} in the context of this particular structure... my code excerpt below uses Bulma to draw up a table for the results
<script>
import {dataSetsStore} from "../stores/dataSetsStore.js"
</script>
{#each $dataSetsStore as metadataObject}
{#await metadataObject.importDataFrom()}
<p>loading...</p>
{:then theFields}
<table class="table">
<thead>{metadataObject.title.toUpperCase()}</thead>
{#each theFields.fields as f}
<th>
<abbr class="has-background-success is-size-5 p-1" title={f.name}>
{f.name.trim().slice(0,10)}
</abbr>
<span class="has-text-info is-size-8">
{f.type}
</span>
</th>
{/each}
</table>
{:catch error}
<p>Something went wrong: {error.message}</p>
{/await}
{/each}
Employee.vue Component
<tr role="row" class="odd" v-for="(employee, index) in employees" :key="index">
<td>{{ index+1 }}</td>
<td><router-link :to="/employee-profile/+employee.id">{{ employee.name }}</router-link></td>
</tr>
I am sending employee id from here to EmployeeDetails.vue by routes in app.js
let routes = [
{ path: '/employee', component: require('./components/office/Employee.vue').default },
{ path: '/employee-details/:id', component: require('./components/office/EmployeeDetails').default },
]
Here is my EmployeeDetails.vue component
<script>
export default {
data() {
return {
employees:{},
}
},
mounted() {
let id = this.$route.params.id
axios.get('api/employee-details/'+id)
.then(response => {
this.employees = response.data;
});
}
}
</script>
Here is api.php file that I have called route through API resources
Route::get('employee-details/{id}', 'API\EmployeeController#employeeDetails');
and Here is my Controller EmployeeController.php where I have called function for return data
public function employeeDetails($id)
{
return DB::table('employees')->where('id', $id)->get();
}
But the problem is: Data is not showing and return a error message in my console. Error is given below. Actually I want How can I solve this error.
app.js:81221 [Vue warn]: Error in render: "TypeError: Cannot read property '0' of undefined"
found in
---> <EmployeeProfile> at resources/js/components/office/EmployeeProfile.vue
<Root>
app.js:82484 TypeError: Cannot read property '0' of undefined
It seems the id you are passing in the router link is incorrect.
It should be :
<router-link :to="`/employee-profile/${employee.id}`">{{ employee.name }}</router-link>
I am trying to update multiple records at the same time which requires me to have an array of id's being sent with my form data. I am using Vue.js and sending the data from my vuex store via axios.
The data is being sent from my vue component like so:
methods: {
...mapActions(['updateCheckedEngagements']),
updateChecked() {
this.updateCheckedEngagements({
engagements: this.checkedEngagements,
assigned_to: this.engagement.assigned_to,
status: this.engagement.status
})
},
}
The this.checkedEngagements is collecting the array of id's I will be updating. However, since I am using vuex to actually send the data, I am doing the following and it hasn't been something I have needed to do previously, so I am trying to get clarity on if this is the correct way. Here is the method in my vuex store
updateCheckedEngagements(context, engagement, checkedEngagements) {
axios.patch('/engagementsarray', {
engagements: checkedEngagements,
assigned_to: engagement.assigned_to,
status: engagement.status,
})
.then(response => {
context.commit('updateCheckedEngagements', response.data)
})
.catch(error => {
console.log(error.response.data)
})
},
Now the issue is passing the the 3rd parameter in the method right here:
updateCheckedEngagements(context, engagement, checkedEngagements)
I am not sure it is actually sending the array of id's. I do know that the id's are being collected from the form when I do a console.log(this.checkedEngagements). however, I feel as though I am missing something when I send it to vuex... any help would be greatly appreciated...
So I came to this solution.
...mapActions(['updateCheckedEngagements']),
updateChecked() {
this.updateCheckedEngagements({
engagements: this.checkedEngagements.engagements,
assigned_to: this.checkedEngagements.assigned_to,
status: this.checkedEngagements.status
})
},
My store is like this
updateCheckedEngagements(context, checkedEnagements) {
axios.patch('/engagementsarray', {
engagements: checkedEnagements.engagements,
assigned_to: checkedEnagements.assigned_to,
status: checkedEnagements.status
})
.then(response => {
console.log(response)
context.commit('updateCheckedEngagements', response.data)
})
.catch(error => {
console.log(error.response.data)
})
},
I was having issue trying to collect array of id's and using same object to send data with. This was because I was not being explicit enough with my naming. When I would nest the checkedEngagements:[] array in my engagement object it would reference the v-for I was looping through instead of the object. To see what I am talking about I will post the v-for loop
<tbody>
<tr v-for="(engagement, index) in engagementFilter" :key="index">
<th scope="row"><input type="checkbox" :value="engagement.id" v-model="checkedEngagements.engagements"></th>
<th>{{ engagement.client.last_name}}, {{ engagement.client.first_name}} & {{ engagement.client.spouse_first_name}}</th>
<td>{{ engagement.status }}</td>
<td>{{ engagement.assigned_to }}</td>
<td>{{ engagement.return_type }}</td>
<td>{{ engagement.year }}</td>
</tr>
</tbody>
So because the v-for was using engagement I could not also use engagement in my v-model. Instead of using v-model="enagagement.checkedEngagements" I used v-model="checkedEngagements.engagements"
While console logging an array passed from blade to vue I get this error.
In index.blade I have this
<books :books="{{ $books }}"></books>
And my Books.vue is
<template>
<!-- <tbody class="tbody">
<book-row v-for="book in books" :key="book.id"></book-row>
</tbody> -->
</template>
<script>
import BookRow from './BookRow.vue';
export default {
props: ['books'],
components: {BookRow},
data() {
return {
}
},
mounted() {
console.log(this.books);
},
}
What is the problem?
I would appreciate any help.
in your blade file remove :
<books books="{{ $books }}"></books>
But, instead of using the above method i would suggest you to use axios or fetch inside vue. Get data via api.