Vue and data from REST API - vue.js

While playing around with vue.js I noticed some strange behavior while trying to display on a page data from an API, but here's the strange thing :
using vue 2.0.0, i can see the "Title", but I have an error in dev console [see printscreen]
using the latest vue version, i can't see the "Title" [and I have the same error in the printscreen]
Is it normal, or?
Source code :
template:
'<div>'+
'Form with id = {{id}}'+
'<br/>'+
'has title = {{item.details.Title}}'+
'</div>',
data: function(){
return {
id: '',
item: {}
}
},
created: function() {
this.get()
},
methods: {
get: function() {
var self = this
id = window.location.hash
id = id.replace('#/whatever/','')
axiosInstance.get('/thebackendresource/'+id) // <--- make http calls etc
.then(function (response) {
self.id = id
self.item = response.data
console.log(self.item)
}).catch(function (error) {
console.log(error)
}
);
}
}

You are getting this error, because when you are fetching data from axiosinstance, that time item.details is null, and when it tries to render it throws this error.
Once the api call is completed, it updates the the DOM and in turn re-renders the DOM, so you can see item.details.Title rendered.
You need to add a null check to prevent this error, which can be easily done using v-if, like follwoing:
template:
'<div>'+
'Form with id = {{id}}'+
'<br/>'+
'<span v-if="item.details"> has title = {{item.details.Title}}'+
'</span>' +
'</div>',

Related

How can I put a data inside the <template> in a component with Vue

I am trying to do a pagination but I can not put the dynamic total I am doing like this:
<v-pagination v-model="currentPage"
:page-count="total"
:classes="bootstrapPaginationClasses"
:labels="paginationAnchorTexts"
></v-pagination>
How you can see the total os in the :page-count, it is a dynamic total because I am getting data from database, my vue code is this one:
<script>
import vPagination from 'vue-plain-pagination';
export default {
created() {
this.getPosts();
},
methods: {
getPosts() {
fetch('/api/bank')
.then(response => response.json() )
.then(json => {
this.posts = json.data.data;
this.total = json.data.last_page;
this.current_page = json.data.current_page;
});
}
},
components: { vPagination },
data: function() {
return {
postsSelected: "",
posts: [],
currentPage: 1,
total: this.total,
bootstrapPaginationClasses: {
ul: 'pagination',
li: 'page-item',
liActive: 'active',
liDisable: 'disabled',
button: 'page-link'
},
paginationAnchorTexts: {
first: 'Primera',
prev: '«',
next: '»',
last: 'Última'
}
}
}
}
</script>
How you can see I am using fetch to get the data from database and then I am split it in different information like total and the I am using this information inside the data: function() {}.
How you can tell total it's like this: total: this.total because I want to get the total number but when I do that I am getting this error:
[Vue warn]: Invalid prop: type check failed for prop "pageCount". Expected Number with value NaN, got Undefined
and I think that it is because:
total: this.total in the data function() {} is bad or:
how can I put the dynamic variable total inside the
How could I fix it?
Thanks!
If you want to know the data retrieved from the API, you can console log the data returned like this:
getPosts() {
fetch('/api/bank')
.then(response => response.json() )
.then(json => {
console.log(json.data)
this.posts = json.data.data;
this.total = json.data.last_page;
this.current_page = json.data.current_page;
});
}
Also, you should not have data attribute and props attribute with the same name! So change the total data attribute to another name and initialize it with a value of 0 instead.
In fact, you don't need to care about passing the Prop total at all as your method getPosts is not dependent on the Prop! So you may just have total: 0 in data and that should fix your issues

I have event duplication after action was moved in store object

In my laravel 5.8 / vue 2.5.17 / vuex^3.1.0 I have a problem that with dialog opened I have event duplication.
I have an event for item deletion :
In my vue file:
...
mounted() {
bus.$on('dialog_confirmed', (paramsArray) => {
if (paramsArray.key == this.deleteFromUserListsKey(paramsArray.user_list_id)) {
this.runDeleteFromUserLists(paramsArray.user_list_id, paramsArray.index);
}
})
bus.$on('onUserListDeleteSuccess', (response) => {
this.is_page_updating = false
this.showPopupMessage("User lists", 'User\'s list was successfully deleted!', 'success');
})
bus.$on('onUserListDeleteFailure', (error) => {
this.$setLaravelValidationErrorsFromResponse(error.message);
this.is_page_updating = false
this.showRunTimeError(error, this);
this.showPopupMessage("User lists", 'Error adding user\'s list !', 'error');
})
}, // mounted() {
methods: {
confirmDeleteUserList(user_list_id, user_list_title, index) {
this.confirmMsg("Do you want to exclude '" + user_list_title + "' user list ?", {
key: this.deleteFromUserListsKey(user_list_id), user_list_id: user_list_id, index: index
}, 'Confirm', bus);
}, //confirmDeleteUserList(id, user_list_title, index) {
deleteFromUserListsKey(user_list_id) {
return 'user_list__remove_' + user_list_id;
},
runDeleteFromUserLists(user_list_id, index) {
this.$store.dispatch('userListDelete', { logged_user_id : this.currentLoggedUser.id, user_list_id : user_list_id } );
}, // runDeleteFromUserLists() {
and in resources/js/store.js :
state : {
...
userLists: [],
...
actions : {
userListDelete(context, paramsArray ) {
axios({
method: ( 'delete' ),
url: this.getters.apiUrl + '/personal/user-lists/' + paramsArray.user_list_id,
}).then((response) => {
let L = this.getters.userLists.length
for (var I = 0; I < L; I++) {
if (response.data.id == this.getters.userLists[I].id) {
this.getters.userLists.splice(this.getters.userLists.indexOf(this.getters.userLists[I]), 1)
context.commit('refreshUserLists', this.getters.userLists);
break;
}
}
bus.$emit( 'onUserListDeleteSuccess', response );
}).catch((error) => {
bus.$emit('onUserListDeleteFailure', error);
});
}, // userListDelete(context, paramsArray ) {
confirmMsg (based on https://github.com/euvl/vue-js-modal )is defined in my mixing :
confirmMsg: function (question, paramsArray, title, bus) {
this.$modal.show('dialog', {
title: title,
text: question,
buttons: [
{
title: 'Yes',
default: true, // Will be triggered by default if 'Enter' pressed.
handler: () => {
bus.$emit('dialog_confirmed', paramsArray);
this.$modal.hide('dialog')
}
},
{
title: '', // Button title
handler: () => {
} // Button click handler
},
{
title: 'Cancel'
}
]
})
},
it worked ok, until I moved userListDelete method from my vue file into store.js.
As a result on 1st event item is deleted ok, the the second item raise error that item was not found and I do not know event is doubled...
How to fix it ?
UPDATED BLOCK :
I still search for valid decision :
I uploaded live demo at :
http://178.128.145.48/login
demo#demo.com wdemo
http://178.128.145.48/websites-blogs will be opened.
Please, try to go to “User's lists” by link at top left menu https://prnt.sc/nq4qiy
and back several times. When on “User's lists” page I try to delete 1 user list it is deleted, but I got several messages
and url in “network” section of my browser : https://imgur.com/a/4ubFB0g
Looks like events are duplicated. And looks like that is move between pages number of guplications is raised.
Why and how to fix it ?
I use #click.prevent in triggering the event to show confirm delete message.
There is “ Add Demo Data” to add more demo rows.
Thanks!
Well, it is quite obvious.
Take a closer look at the Vue component lifecycle diagram.
Your component is mounted each time you enter a route.
So, bus.$on inside your mounted block executed each time you enter this route.
I suggest you move bus event handlers to some other location. For example app.js/ App.vue mounted hook or directly into the store. Since all you do inside handler is calling store actions.

Getting reactivity from watch in Vue.js

Trying to make a component in Vue.js, which first shows image via thumbnail, loading full image in background, and when loaded, show full image.
The thing which does not work, component does not react on change of showThumb flag in watch section. What is wrong?
Vue.component('page-image',
{
props: ['data'],
template:
'<img v-if="showThumb == true" v-bind:src="thumbSrc"></img>'+
'<img v-else v-bind:src="fullSrc"></img>',
data: function()
{
return { thumbSrc: '', fullSrc: '', showThumb: true };
},
watch:
{
data: function()
{
this.thumbSrc = data.thumbImg.url;
this.fullSrc = data.fullImg.url;
this.showThumb = true;
var imgElement = new Image();
imgElement.src = this.fullSrc;
imgElement.onload = (function()
{
this.showThumb = false; // <<-- this part is broken
} );
}
}
} );
Note: there is a reason why I do it via 2 img tags - this example is simplified.
Your onload callback will have a different scope than the surrounding watch function, so you cannot set your data property like this. Change it to an arrow function to keep scope:
imgElement.onload = () =>
{
this.showThumb = false;
};
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

How do I operate the m.withAttr tutorials code?

A contrived example of bi-directional data binding
var user = {
model: function(name) {
this.name = m.prop(name);
},
controller: function() {
return {user: new user.model("John Doe")};
},
view: function(controller) {
m.render("body", [
m("input", {onchange: m.withAttr("value", controller.user.name), value: controller.user.name()})
]);
}
};
https://lhorie.github.io/mithril/mithril.withAttr.html
I tried the above code does not work nothing.
It was the first to try to append the following.
m.mount(document.body, user);
Uncaught SyntaxError: Unexpected token n
Then I tried to append the following.
var users = m.prop([]);
var error = m.prop("");
m.request({method: "GET", url: "/users/index.php"})
.then(users, error);
▼/users/index.php
<?php
echo '[{name: "John"}, {name: "Mary"}]';
Uncaught SyntaxError: Unexpected token n
How do I operate the m.withAttr tutorials code?
Try returning m('body', [...]) from your controller.
view: function (ctrl) {
return m("body", [
...
]);
}
render should not be used inside of Mithril components (render is only used to mount Mithril components on existing DOM nodes).
The example is difficult to operate because it's contrived, it's not meant to be working out-of-the-box. Here's a slightly modified, working version:
http://jsfiddle.net/ciscoheat/8dwenn02/2/
var user = {
model: function(name) {
this.name = m.prop(name);
},
controller: function() {
return {user: new user.model("John Doe")};
},
view: function(controller) {
return [
m("input", {
oninput: m.withAttr("value", controller.user.name),
value: controller.user.name()
}),
m("h1", controller.user.name())
];
}
};
m.mount(document.body, user);
Changes made:
m.mount injects html inside the element specified as first parameter, so rendering a body element in view will make a body inside a body.
Changed the input field event to oninput for instant feedback, and added a h1 to display the model, so you can see it changing when the input field changes.
Using m.request
Another example how to make an ajax request that displays the retrieved data, as per your modifications:
http://jsfiddle.net/ciscoheat/3senfh9c/
var userList = {
controller: function() {
var users = m.prop([]);
var error = m.prop("");
m.request({
method: "GET",
url: "http://jsonplaceholder.typicode.com/users",
}).then(users, error);
return { users: users, error: error };
},
view: function(controller) {
return [
controller.users().map(function(u) {
return m("div", u.name)
}),
controller.error() ? m(".error", {style: "color:red"}, "Error: " + controller.error()) : null
];
}
};
m.mount(document.body, userList);
The Unexpected token n error can happen if the requested url doesn't return valid JSON, so you need to fix the JSON data in /users/index.php to make it work with your own code. There are no quotes around the name field.

Ember.js - Reload paginated list from API after model deletion (and probably insertion)

I have a paginated list of models which comes from my REST api.
I use the api's pagination metadata to get the total number of records from my DB, and see if there are previous or next pages.
If I delete one of the records on the current page, I would like several things to happen:
- The first record of the next page should appear in the current page (if it exists).
- My metadata to be updated. (Total and pagination data)
The direct transitionToRoute method on the controller is not doing any of that. I believe an API reload of the current page is the way to go, but it doesn't seem implemented as far as I know.
I have managed to get the result I want by doing self.transitionToRoute('index') followed by another self.transitionToRoute to the page I want reloaded... This is downright horrible and makes me cringe. There must be a better way!
Here is some of the relevant code:
//router.js
App.Router.map(function(){
this.resource('jobs', function(){
this.resource('job', { path:'/:job_id' }, function(){
this.route('edit');
});
this.route('create');
});
});
//mixins.js
App.PaginatedListController = Ember.Mixin.create({
queryParams: ['page'],
page: 1,
total: function(){
return this.get('meta').count;
}.property('meta'),
previousAPIPage: function(){
return this.get('meta').previous;
}.property('meta'),
nextAPIPage: function(){
return this.get('meta').next;
}.property('meta'),
hasPreviousPage: function(){
return this.get('previousAPIPage') ? true : false;
}.property('previousAPIPage'),
hasNextPage: function(){
return this.get('nextAPIPage') ? true : false;
}.property('nextAPIPage'),
actions:{
previousPage: function(){
this.decrementProperty('page');
this.transitionToRoute({
queryParams: {
page: this.get('page')
}
});
},
nextPage: function(){
this.incrementProperty('page');
this.transitionToRoute({
queryParams: {
page: this.get('page')
}
});
},
},
});
//controllers\jobController.js
App.JobsController = Ember.ArrayController.extend(App.PaginatedListController, {
sortProperties: ['name'],
sortAscending: true,
meta: function(){
return this.store.metadataFor('job');
}.property('model.#each')
});
App.JobController = Ember.ObjectController.extend({
needs: ['jobs'],
deleteMode: false,
actions: {
delete: function(){
// our delete method now only toggles deleteMode's value
this.toggleProperty('deleteMode');
},
cancelDelete: function(){
// set deleteMode back to false
this.set('deleteMode', false);
},
confirmDelete: function(){
var self = this;
this.set('deleteMode', false);
this.get('model').destroyRecord().then(function(response) {
var qParams = self.get('controllers.jobs').get('queryParams');
//This is where it hurts
self.transitionToRoute('index');
//This is what I want reloaded
self.transitionToRoute('jobs', {queryParams:qParams});
});
},
}
});