I'm trying to write a simple function in the methods. It should create an array out of JSON data.
The JSON object is called page with another object called images containing height, orientation etc.
setup () {
return {
page: usePage(),
}
},
methods: {
getOrientations() {
this.page.images.forEach((item) => {
console.log(item
})
}
Unfortunately it throws an undefined is not an object error. Logging only this.page however prints the whole object.
Does this.page.images not work when using it inside a method? Because it works when using it "inline" in HTML.
Thanks for any tips!
You are currently using the Composition API (setup function) alongside the options-based API (methods object). Although it is possible, it is not recommended (take a look at their motivation). Your methods should stand in the setup function:
setup () {
const page = usePage()
page.images.forEach((item) => {
console.log(item)
})
return {
page,
}
},
Of course, you could still modularize this code using functions.
setup() {
// ...
const getOrientations = () => {
page.images.forEach((item) => {
console.log(item)
})
}
// And make it available to your template
return {
// ...
getOrientations
}
}
Related
Am trying to provide test authors with a fluent PageModel api in TestCafe, like:
await MyApp // a Page Model class instance
.navigateTo(xyz) // clicks a button to navigate to a specific part in my app
.edit() // clicks the edit button
.setField(abc, 12.34)
.save()
.changeStatus('complete');
I had all the individual methods working as async methods that can be awaited individually, but that makes the code quite unreadable and as a result error prone.
However, whatever way I attempt to make the api fluent, it results in the following error:
Selector cannot implicitly resolve the test run in context of which it
should be executed. If you need to call Selector from the Node.js API
callback, pass the test controller manually via Selector's .with({ boundTestRun: t }) method first. Note that you cannot execute
Selector outside the test code.
The trick into making a fluent async api is imho switching from async functions to regular functions as methods and have those methods return a thenable 'this' value. And in order to prevent the await oscillating, the 'then' function needs to be removed once called (and then reinstalled when
A very basic example that reproduces the issue can be seen below:
import { Selector } from 'testcafe'
class MyPage {
queue: [];
async asyncTest() {
return await Selector(':focus').exists;
}
queuedTest() {
this.then = (resolve, reject) => {
delete this.then; // remove 'then' once thenable gets called to prevent endless loop
// calling hardcoded method, in a fluent api would processes whatever is on the queue and then resolve with something
resolve(this.asyncTest());
};
// In a real fluent api impl. there would be code here to put something into the queue
// to execute once the 'then' method gets called
// ...
return this;
}
}
fixture `Demo`
.page `https://google.com`;
test('demo', async () => {
const myPage = new MyPage();
console.log('BEFORE')
await myPage.asyncTest();
console.log('BETWEEN')
await myPage.queuedTest(); // Here it bombs out
console.log('AFTER')
});
Note that the sample above isn't showcasing a fluent api, it just demonstrates that calling methods that use Selectors through the 'then' function (which imho is key to creating a fluent api) results in the aforementioned error.
Note: I know what the error means and that the suggestion is to add .with({boundTestRun: t}) to the selector, but that would result in required boilerplate code and make things less maintainable.
Any thoughts appreciated
P.
In your example, a selector cannot be evaluated because it does not have access to the test controller (t). You can try to avoid directly evaluating selectors without assertion.
Here is my example of the chained Page Model (based on this article: Async Method Chaining in Node):
Page Model:
import { Selector, t } from 'testcafe';
export class MyPage {
constructor () {
this.queue = Promise.resolve();
this.developerName = Selector('#developer-name');
this.submitButton = Selector('#submit-button');
this.articleHeader = Selector('#article-header');
}
_chain (callback) {
this.queue = this.queue.then(callback);
return this;
}
then (callback) {
return callback(this.queue);
}
navigateTo (url) {
return this._chain(async () => await t.navigateTo(url));
}
typeName (name) {
return this._chain(async () => await t.typeText(this.developerName, name));
}
submit () {
return this._chain(async () => await t.click(this.submitButton));
}
checkName (name) {
return this._chain(async () => await t.expect(this.articleHeader.textContent).contains(name));
}
getHeader () {
this._chain(async () => console.log(await this.articleHeader.textContent));
return this;
}
}
Test:
import { MyPage } from "./page-model";
fixture`Page Model Tests`;
const page = new MyPage();
test('Test 1', async () => {
await page
.navigateTo('http://devexpress.github.io/testcafe/example/')
.typeName('John')
.submit()
.checkName('John')
.getHeader();
});
Hi everyone here is the mixin code I wrote as I want to use this for default.vue and error.vue layout. I am trying to avoid duplicating code in two layout.
export default {
provide () {
return {
copyRight: this.getCopyrightText,
email: this.getEmail,
socials: this.getSocials
}
},
computed: {
getMenu () {
return this.store.getters['general/getMenu'].menu
},
getSocials () {
return this.store.getters['general/getSocialDetails']
},
getCopyrightText () {
return this.store.getters['general/getCopyRight']
},
getEmail () {
return this.store.getters['general/getEmail']
}
},
middleware: 'load-menu-items'
}
This is what I get: Cannot read property 'length' of undefined
What am I doing wrong?
In your component I assume you're using .length on the data you're receiving from the getter method, which is probably where the error occurs.
First of all you should debug to see if your getter is actually working as expected. Try this and look at output in console for every getter computed property. If undefined is printed to the console you'll get the error you posted if you're using .length on it
getEmail () {
let data = this.store.getters['general/getEmail'];
console.log(data);
return data;
}
If you post the component which is using this mixin maybe I can help you further.
I'm creating a blog in my nuxt-app that pulls data from contentful ive been following this tutorial, now I can get that all right, but I cant seem to get both context and the environment variables I set up to return from the asyncData argument
I have created a json file like so..
.contentful.json
{
"CTF_BLOG_POST_ITEM": "...",
"CTF_BLOG_POST": "...",
"CTF_SPACE_ID": "...",
"CTF_CDA_ACCESS_TOKEN":"..."
}
and then in my nuxt.config.js
env: {
CTF_SPACE_ID: config.CTF_SPACE_ID,
CTF_CDA_ACCESS_TOKEN: config.CTF_CDA_ACCESS_TOKEN,
CTF_BLOG_POST_ITEM: config.CTF_BLOG_POST_ITEM,
CTF_BLOG_POST: config.CTF_BLOG_POST
}
now basically in my component I've been trying to do this
asyncData(context, {env}) {
return Promise.all([
client.getEntries({
'content_type': env.CTF_BLOG_POST_ITEM,
order: '-sys.createdAt'
})
]).then(([posts]) => {
console.log(context);
return {
posts: posts.items
}
})
},
but when I run this I get cannot read property CTF_BLOG_POST_ITEM of undefined, if I take context out of the arguments this works, and vice versa if I take the {env} I get the context.
How can I get both??
Thanks
The primary (1st) argument of asyncData() is the context object. env is a property of the context object. You could access it as context.env without the use of object restructuring assignment. Your example could be rewritten in the following way without the use of object restructuring assignment:
asyncData(context) {
return Promise.all([
client.getEntries({
'content_type': context.env.CTF_BLOG_POST_ITEM,
order: '-sys.createdAt'
})
]).then(([posts]) => {
console.log(context);
console.log(context.env);
return {
posts: posts.items
}
})
},
The signature asyncData(context, {env}) { is incorrect because you are adding a second argument, {env}, which does not reference the aforementioned context object in any way. If you only need env from context, you can use object restructuring assignment to extract this property in the following way (as you mentioned works when you remove the 1st argument:
asyncData({env}) {
return Promise.all([
client.getEntries({
'content_type': context.env.CTF_BLOG_POST_ITEM,
order: '-sys.createdAt'
})
]).then(([posts]) => {
console.log(context);
console.log(context.env);
return {
posts: posts.items
}
})
},
If you need additional context object properties, using object destructuring assignment. Tt would look like:
asyncData({env, params, req, res}) {
Otherwise, you can just access properties such as context.env, context.params, etc by simply passing context as the first/primary argument with restructuring.
Hopefully that helps!
I'm trying to wait for certain strings in a sort of dictionary containing all the text for buttons, sections, labels etc.
I start out by sending a list of default strings to a controller that registers all the strings with my CMS in case those specific values do not already exist. After that I return a new object containing my "dictionaries", but with the correct values for the current language.
I run the call with an event listener that triggers a dispatch() on window.onload, and then add the data to a Vuex module state. I then add it to a computed prop.
computed: {
cartDictionary() {
return this.$store.state.dictionaries.myDictionaries['cart']
}
}
So now here's the problem: In my template i try to get the values from the cartDictionaryprop, which is an array.
<h2 class="checkout-section__header" v-html="cartDictionary['Cart.Heading']"></h2>
But when the component renders, the prop doesn't yet have a value since it's waiting for the AJAX call to finish. And so of course I get a cannot read property of undefined error.
Any ideas on how to work around this? I would like to have the dictionaries accessible through a global object instead of passing everything down through props since it's built using atomic design and it would be insanely tedious.
EDIT:
Adding more code for clarification.
My module:
const dictionaryModule = {
namespaced: true,
state: {
dictionaries: []
},
mutations: {
setDictionaries (state, payload) {
state.dictionaries = payload
}
},
actions: {
getDictionaries ({commit}) {
return new Promise((resolve, reject) => {
Dictionaries.init().then(response => {
commit('setDictionaries', response)
resolve(response)
})
})
}
}
}
My Store:
const store = new Vuex.Store({
modules: {
cart: cartModule,
search: searchModule,
checkout: checkoutModule,
filter: filterModule,
product: productModule,
dictionaries: dictionaryModule
}
})
window.addEventListener('load', function () {
store.dispatch('dictionaries/getDictionaries')
})
I think you can watch cartDictionary and set another data variable.
like this
<h2 class="checkout-section__header" v-html="cartHeading"></h2>
data () {
return {
cartHeading: ''
}
},
watch: {
'cartDictionary': function (after, before) {
if (after) {
this.cartHeading = after
}
}
}
Because this.$store.state.dictionaries.myDictionarie is undefined at the the begining, vuejs can't map myDictionarie['core']. That's why your code is not working.
You can do this also
state: {
dictionaries: {
myDictionaries: {}
}
}
and set the dictionaries key values during resolve.
I also would have liked to see some more of your code, but as i can't comment your questions (you need rep > 50), here it goes...
I have two general suggestions:
Did you setup your action correctly? Mutations are always synchronous while actions allow for asynchronous operations. So, if you http client returns a promise (axios does, for example), you should await the result in your action before calling the respective mutation. See this chapter in the official vuex-docs: https://vuex.vuejs.org/guide/actions.html
You shouldn't be using something like window.onload but use the hooks provided by Vue.js instead. Check this: https://v2.vuejs.org/v2/guide/instance.html#Lifecycle-Diagram
EDIT: As a third suggestion: Check, whether action and mutation are called properly. If they are handled in their own module, you have to register the module to the state.
Per documentation here
If page A pushes page B, it can send a function as a prop or data that modifies page A’s context. This way, whenever we want to send anything to page A from page B, the latter just needs to call the function and pass some arguments:
// Page A
this.$emit('push-page', {
extends: pageB,
onsNavigatorProps: {
passDataBack(data) {
this.dataFromPageB = data;
}
}
});
I am following this idea. Doing something similar with this.$store.commit
I want to push AddItemPage and get the returned value copied to this.items
//Parent.vue
pushAddItemPage() {
this.$store.commit('navigator/push', {
extends: AddItemPage,
data() {
return {
toolbarInfo: {
backLabel: this.$t('Page'),
title: this.$t('Add Item')
}
}
},
onsNavigatorProps: {
passDataBack(data) {
this.items = data.splice() //***this*** is undefined here
}
}
})
},
//AddItemPage.vue
...
submitChanges()
{
this.$attrs.passDataBack(this, ['abc', 'xyz']) // passDataBack() is called, no issues.
},
...
Only problem is this is not available inside callback function.
So i can't do this.items = data.splice()
current context is available with arrow operator.
Correct version:
onsNavigatorProps: {
passDataBack: (data) => {
this.items = data.splice()
}
}