I am fetching some data using Apollo inside of Nuxt. Somehow, when navigating to that page I get an error of
Cannot read property 'image' of undefined
When I refresh the page, everything works as expected.
I have a found a few threads of people having similar issues but no solution seems to work for me :/
This is my template file right now:
/products/_slug.vue
<template>
<section class="container">
<div class="top">
<img :src="product.image.url"/>
<h1>{{ product.name }}</h1>
</div>
</section>
</template>
<script>
import gql from 'graphql-tag'
export default {
apollo: {
product: {
query: gql`
query Product($slug: String!) {
product(filter: { slug: { eq: $slug } }) {
slug
name
image {
url
}
}
}
`,
prefetch({ route }) {
return {
slug: route.params.slug
}
},
variables() {
return {
slug: this.$route.params.slug
}
}
}
}
}
</script>
Basically the $apolloData stays empty unless I refresh the page. Any ideas would be much appreciated
EDIT
Got one step closer (I think). Before, everything (image.url and name) would be undefined when navigating to the page for the first time.
I added:
data() {
return {
product: []
};
}
at the top of my export and now at least the name is always defined so if I remove the image, everything works as expected. Just the image.url keeps being undefined.
One thing I noticed (not sure how relevant) is that this issue only occurs using the , if I use a normal a tag it works but of course takes away the vue magic.
EDIT-2
So somehow if I downgrade Nuxt to version 1.0.0 everything works fine
I stumbled on this issue as well, and found it hidden in the Vue Apollo documents.
Although quite similar to the OP's reply, it appears the official way is to use the "$loadingKey" property.
It's quite confusing in the documents because there are so many things going on.
https://vue-apollo.netlify.com/guide/apollo/queries.html#loading-state
<template>
<main
v-if="!loading"
class="my-8 mb-4"
>
<div class="w-3/4 mx-auto mb-16">
<h2 class="mx-auto text-4xl text-center heading-underline">
{{ page.title }}
</h2>
<div
class="content"
v-html="page.content.html"
></div>
</div>
</main>
</template>
<script>
import { page } from "~/graphql/page";
export default {
name: 'AboutPage',
data: () => ({
loading: 0
}),
apollo: {
$loadingKey: 'loading',
page: {
query: page,
variables: {
slug: "about"
}
},
}
}
</script>
If you need to use a reactive property within vue such as a slug, you can do so with the following.
<template>
<main
v-if="!loading"
class="my-8 mb-4"
>
<div class="w-3/4 mx-auto mb-16">
<h2 class="mx-auto text-4xl text-center heading-underline">
{{ page.title }}
</h2>
<div
class="content"
v-html="page.content.html"
></div>
</div>
</main>
</template>
<script>
import { page } from "~/graphql/page";
export default {
name: 'AboutPage',
data: () => ({
loading: 0
}),
apollo: {
$loadingKey: 'loading',
page: {
query: page,
variables() {
return {
slug: this.$route.params.slug
}
}
},
}
}
</script>
I think it's only a problem of timing on page load.
You should either iterate on products, if you have more than one, or have a v-if="product != null" on a product container, that will render only once the data is fetched from GraphQL.
In that way you'll use the object in your HTML only when it's really fetched and avoid reading properties from undefined.
To fix this, you add v-if="!$apollo.loading" to the HTML container in which you're taying to use a reactive prop.
Related
While following the emberjs tutorial I couldn't get my component to pass the testing
(https://guides.emberjs.com/release/tutorial/simple-component/).
The tutorial expected the each helper to be placed in the
template file (app/templates/rentals.hbs) to send each element of the array. Instead of placing the each helper in the template file I placed it in the component file
(app/templates/component/rental-listing.hbs) so I wouldn't have to write it out each time I needed to place the rental list.
The error in testing was:
Promise rejected during "should display rental details": Cannot read property 'textContent' of null
This works in testing:
<article class="listing">
<a
onclick={{action "toggleImageSize"}}
class="image {{if this.isWide "wide"}}"
role="button"
>
<img src={{this.rental.image}} alt="">
<small>View Larger</small>
</a>
<div class="details">
<h3>{{this.rental.title}}</h3>
<div class="detail owner">
<span>Owner:</span> {{this.rental.owner}}
</div>
<div class="detail type">
<span>Type:</span> {{this.rental.category}}
</div>
<div class="detail location">
<span>Location:</span> {{this.rental.city}}
</div>
<div class="detail bedrooms">
<span>Number of bedrooms:</span> {{this.rental.bedrooms}}
</div>
</div>
</article>
While wrapping the previous code with this doesn't:
{{#each #rentalListings as |rental|}}
{{/each}}
Here's the test:
// #rental is correctly renamed to #rentalListings
module('Integration | Component | rental-listing', function (hooks) {
setupRenderingTest(hooks);
hooks.beforeEach(function () {
this.rental = EmberObject.create({
image: 'fake.png',
title: 'test-title',
owner: 'test-owner',
type: 'test-type',
city: 'test-city',
bedrooms: 3
});
});
test('should display rental details', async function(assert) {
await render(hbs`<RentalListing #rental={{this.rental}} />`);
assert.equal(this.element.querySelector('.listing h3').textContent.trim(), 'test-title', 'Title: test-title');
assert.equal(this.element.querySelector('.listing .owner').textContent.trim(), 'Owner: test-owner', 'Owner: test-owner');
});
test('should toggle wide class on click', async function(assert) {
await render(hbs`<RentalListing #rental={{this.rental}} />`);
assert.notOk(this.element.querySelector('.image.wide'), 'initially rendered small');
await click('.image');
assert.ok(this.element.querySelector('.image.wide'), 'rendered wide after click');
await click('.image');
assert.notOk(this.element.querySelector('.image.wide'), 'rendered small after second click');
});
});
Should I not be placing an each helper in a component or is my testing file not setup correctly?
I can try to create a twiddle if the problem is not clear enough.
Rentallistings you passed in each helper what's the computed value. It should be an array but what's being passed in test rentals is an object. So it will not run over an object hence no output
I am new to Vue, and I have played around with small vue applications, but this is the first project I've done using vue and webpack (vue-cli).
I have a django rest API set up, for which I then use vue to consume the API and display the data. The issue i'm having is when I want to do anything the templates aren't updating. The data is loaded in correctly, but if I try and do a #click event and then a v-show (to toggle displaying an item) it doesn't change the view.
Example,
lets say I have a list of pubs that I consume from the API.
I get them via axios, and store them in the data function in a array called pubs:
<script>
import axios from 'axios'
export default {
name: 'Pubs',
data () {
return {
pubs: [
{
pub_id:'',
name:'',
address:'',
showPub: false,
},
]
}
},
created: function () {
this.loadPubs();
},
methods: {
loadPubs: function () {
var vm = this;
axios.get('http://127.0.0.1:8000/api/pubs/')
.then(function (response) {
vm.pubs = response.data;
vm.pubs.forEach(function (pub) {
pub.showPub = true;
});
console.log("loaded");
})
.catch(function (error) {
this.pubs = 'An error occured.' + error;
});
},
togglePub: function (pub) {
pub.showPub = !pub.showPub;
console.log(pub.showPub);
console.log(pub);
return pub;
},
}
}
</script>
The template could be:
<template>
<div class="pubs">
<h1>VuePubs</h1>
<ul>
<li v-for="pub in pubs">
<section>
<h2 #click="togglePub(pub)">{{ pub.name }}</h2>
<section v-show="pub.showPub">
<h3>{{ pub.address }}</h3>
<h3>{{ pub.postcode }}</h3>
<h3>{{ pub.showPub }}</h3>
</section>
</section>
</li>
</ul>
</div>
</template>
I can see that the data is changing in the model, thanks to the vue-dev tools, but the template doesn't change. The section doesn't hide and the h3 tags don't update with the new showPub field.
Any help would be greatly appreciated!
I want to add tooltips onto a button in a component that can appear based on a set of results back from the server. (i.e. action buttons for delete, edit etc.)
I have created a “search” component that is rendering into the application and when a search button is clicked the server may return a number of rows into that same search component template.
so for example:
My-app/pods/factual-data/template.hbs
Contains:
…
{{#if results}}
<div class="row">
<div class="col-sm-3"><b>Factual ID</b></div>
<div class="col-sm-2"><b>Name</b></div>
<div class="col-sm-2"><b>Town</b></div>
<div class="col-sm-2"><b>Post Code</b></div>
<div class="col-sm-2"><b>Actions</b></div>
</div>
{{/if}}
{{#each result in results}}
<div class="row">
<div class="col-sm-3">{{result.factual_id}}</div>
<div class="col-sm-2">{{result.name}}</div>
<div class="col-sm-2">{{result.locality}}</div>
<div class="col-sm-2">{{result.postcode}}</div>
<div class="col-sm-2">
<button {{action "clearFromFactual" result.factual_id}} class="btn btn-danger btn-cons tip" type="button" data-toggle="tooltip" class="btn btn-white tip" type="button" data-original-title="Empty this Row<br> on Factual" ><i class="fa fa-check"></i></button>
</div>
</div>
{{/each}}
…
However I cannot get the tooltip code to function, due to an element insert detection/timing issue..
In the component
My-app/pods/factual-data/component.js
Contains:
...
didInsertElement : function(){
console.log("COMPONENT: didInsertElement");
Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent);
this.enableToolTips();
},enableToolTips: function() {
var $el = Ember.$('.tip');
console.log("TOOLTIP:", $el);
if($el.length > 0) {
$el.tooltip({
html:true,
delay: { show: 250, hide: 750 }
});
}
}
...
However it seems didInsertElement is only run when the component is first rendered, is there a different function that is called everytime something in the DOM is changed within a component?
I did try to use observes: i.e.
…
enableToolTips: function() {
var $el = Ember.$('.tip');
console.log("TOOLTIP:", $el);
if($el.length > 0) {
$el.tooltip({
html:true,
delay: { show: 250, hide: 750 }
});
}
}.observes('results')
…
Which does trigger when the results variable is changed however it is still triggering before the content is actually rendered. I am assuming this because is I manually run in the console Ember.$('.tip').tooltip() (after the button is displayed) then the tooltips work ok.
Any pointers on this issue?
Try
enableToolTips: function() {
Ember.run.scheduleOnce('afterRender', this, function() {
var $el = Ember.$('.tip');
console.log("TOOLTIP:", $el);
if($el.length > 0) {
$el.tooltip({
html:true,
delay: { show: 250, hide: 750 }
});
}
});
}.observes('results')
Checking Ember.Component API there are two hooks that can do that
willClearRender : When component html is about to change.
willInsertElement : When old html is cleared and new one is going to be placed.
But you need to have a look on scheduleOnce.
Its worth noting that didInsertElement runs every time. But when it runs view was not updated. To solve that you need to run your code inside a Run Loop like this
didInsertElement : function(){
var self = this;
Ember.run.scheduleOnce('afterRender', this, function(){
//run tool tip here
self.$().find(".tip").tooltip({
});
});
}
I am trying to display the details of items in a list. This should be done by lazy loading the template (DOM for the details), because the template is very large and i've got many items in the list so a ng-show with ng-include is not working, since it is compiled into the DOM and makes the performance very bad.
After experimenting I figured out a solution, only working with a inline template. I am using a click handler to render the HTML with the detail-view directive to the DOM.
HTML
<div ng-controller="Ctrl">
{{item.name}} <button show-on-click item="item">Show Details</button>
<div class="detailView"></div>
<div ng-include="'include.html'"></div>
</div>
<!-- detailView Template -->
<script type="text/ng-template" id="detailView.html">
<p>With external template: <span>{{details.description}}</span></p>
</script>
Show On Click Directive
myApp.directive("showOnClick", ['$compile', '$parse', function($compile, $parse) {
return {
restrict: 'A',
scope: {
item: "=item"
},
link: function (scope, element, attrs) {
// Bind the click handler
element.bind('click', function() {
// Parse the item
var item = $parse(attrs.item)(scope);
// Find the element to include the details
var next = $(element).next('div.detailView');
// Include and Compile new html with directiv
next.replaceWith($compile('<detail-view details="item"></detail-view>')(scope));
});
}
};
}]);
Detail View Directive:
myApp.directive("detailView", ['$parse', '$templateCache', '$http', function($parse, $templateCache, $http) {
return {
restrict: 'E',
replace: true,
templateUrl: 'detailView.html', // this is not working
// template: "<div>With template in directive: <span>{{details.description}}</span></div>", // uncomment this line to make it work
link: function (scope, element, attrs) {
var item = $parse(attrs.details)(scope);
scope.$apply(function() {
scope.details = item.details;
});
}
};
}]);
Here is the full example on
Plunker
Is there a way to improve my solution, or what am I missing to load the external template?
Thanks beforehand!
You can also look at ng-if directive in Angular version 1.1.5 . ng-if would only render the html if condition is true. So this becomes
<div ng-controller="Ctrl">
{{item.name}} <button ng-if="showDetails" item="item" ng-click='showDetails=true'>Show Details</button>
<div class="detailView"></div>
<div ng-include="'include.html'"></div>
</div>
By just using ng-include:
<div ng-controller="Ctrl" ng-init="detailsViewTemplateSource='';">
{{item.name}}
<button ng-click="detailsViewTemplateSource = 'detailView.html'">
Show Details
</button>
<div ng-include="detailsViewTemplateSource"></div>
</div>
<!-- detailView Template -->
<script type="text/ng-template" id="detailView.html">
<p>With external template: <span>{{details.description}}</span></p>
</script>
I am creating a new Ember.Model with a return response from an ajax call.
This is the ajax method i have in the controller
createImageResource: function() {
var self = this,
fd = new FormData(),
newResource = {},
resources = self.get('controllers.resources.model'),
fileName = this.get('resource_image').name,
fileSize = this.get('resource_image').size,
fileType = this.get('resource_image').type;
fd.append('resource[resource_type]', 'image');
fd.append('resource[resource_name]', this.get('resource_name'));
fd.append('resource[source_id]', this.get('source_id'));
fd.append('resource_image[resource_image]', this.get('resource_image'));
fd.append('resource[resource_file_name]', fileName);
fd.append('resource[resource_file_type]', fileType);
fd.append('resource[resource_file_size]', fileSize);
this.set('isProcessingResource', true);
$.ajax({
url: '/resources',
method: 'POST',
dataType: 'json',
processData: false,
contentType: false,
data: fd,
}).then(function(newResourceData) {
newResource = Msmapp.Resource.create(newResourceData.resource);
resources.pushObject(newResource);
self.set('isProcessingResource', false);
self.transitionToRoute('resource', newResource);
});
},
This adds the new resource into the array of objects used by the resources controller. It puts it into the DOM like it should. The issue im having is each object is a link to the individual resource. All of the objects that existed on page load work fine. The object added to the list has the correct url and everything, it just doesnt do anything when you try to navigate.
Im not sure if there is something else i need to do in the .then() ?
This is the template
<section class="column_list">
<ul>
{{#each resource in controller }}
<li class="item">
{{#if resource.isLoading }}
{{spinner}}
{{else}}
{{#linkTo 'resource' resource }}
<img {{bindAttr src='resource.listAvatar'}} />
<div class='title'>{{ resource.resource_name }}</div>
{{/linkTo}}
{{/if}}
</li>
{{/each}}
</ul>
</section>
Since you're adding to resources, that's what you should be looping over too - {{#each resource in resources }}.
Either that or push onto your ArrayController instance directly - this. resources.pushObject(newResource);.