I am new to Vuex and Nuxt.
I would like to use vuex to fetch dropbox filestructure and store them.
Here is my code. the console.log seems to work fine. it prints out something like below.
But the structure still turns out to be [] when i use in index.vue
[ { '.tag': 'file',
name: 'Document.docx',
path_lower: '/posts/document.docx',
path_display: '/posts/Document.docx',
id: 'id:H_6Dhj1r7cEAAAAAAAAXlQ',
client_modified: '2018-09-02T14:23:05Z',
server_modified: '2018-09-02T14:23:06Z',
rev: '5e5cab150',
size: 11366,
content_hash: 'd26bb0382752820694d31f42e82e31ef72bed683b90e02952ea09125264d4124' },
{ '.tag': 'file',
name: '2013-5-17-first-post.md',
path_lower: '/posts/2013-5-17-first-post.md',
path_display: '/posts/2013-5-17-first-post.md',
id: 'id:H_6Dhj1r7cEAAAAAAAAXlg',
client_modified: '2018-09-02T14:25:38Z',
server_modified: '2018-09-02T14:25:38Z',
rev: '6e5cab150',
size: 136,
content_hash: '3b8d60de425e8280d55e45d7359cd3290abc5bc3b0bb6831b09a6da0d3cb6a12' } ]
the code is like below
import "isomorphic-fetch"
import {
Dropbox
} from "dropbox";
import {
DropboxTeam
} from "dropbox";
export const state = () => ({
structure: []
});
export const mutations = {
setStucture(state, structure) {
state.structure = structure.slice();
console.log(state.structure);
// console.log(structure.slice());
}
};
export const actions = {
async nuxtServerInit({ commit }) {
let accessToken = "XXXXX"
let dropbox = new Dropbox({
accessToken: accessToken
});
dropbox.filesListFolder({path: '/posts'})
.then(response => {
const structure = response.entries;
commit("setStucture", structure);
})
.catch(error => {
console.log(error);
});
}
};
Can I get some help. Thank you!
add await to dropbox.filesListFolder({path: '/posts'}) like below turned out to be the right answer.
await dropbox.filesListFolder({path: '/posts'})
Related
My async actions do not run correctly. Im new to Vue and JS and I am not sure what is happening here. I placed some confirm() dialogs within my code, to see which line passed and which not.
Within the ScanView.vue I call my addProduct action. I get the confirm dialog saying "addProduct" and dispatch calles the next callAPI action where I get the "callAPI" confirm dialog but nothing more. Seems like fetch() isnt working at all, because no any other dialog is shown. What am I doing wrong?
ScanView.vue
export default defineComponent({
name: "Home",
methods: {
scanEan() {
// QR Code Scanner Logic
this.$store.dispatch("addProduct", ean);
}
});
main.js
const store = new Vuex.Store({
state: {
products: [{
name: 'Produkt',
ean: '123',
amount: '1',
smallImageUrl: 'smImage',
mediumImageUrl: 'mdImage',
largeImageUrl: 'lgImage',
expiration: []
}]
},
mutations: {
addProduct(state, product) {
state.products.unshift(product);
}
},
actions: {
addProduct(context, ean) {
confirm("addProduct: " + ean);
context.dispatch('callAPI', ean);
},
callAPI(context, ean) {
confirm("callAPI: ");
fetch("https://world.openfoodfacts.org/api/v0/product/" + ean + ".json") //
.then(response => {
confirm("reesponse");
return response.json();
}
) //
.then(data => {
confirm("data: " + data);
context.dispatch('saveProduct', data);
});
},
saveProduct(context, data) {
confirm("saveProduct: ");
const name = data.product.product_name;
const ean = data.code;
const smImage = data.product.image_front_thumb_url;
const mdImage = data.product.image_front_small_url;
const lgImage = data.product.image_front_url;
const expiration = new Array();
const date = new Date(data.product.expiration_date);
expiration.push(date);
const product = new Product(
name,
ean,
smImage,
mdImage,
lgImage,
expiration
)
confirm("Produktdata: " + product);
context.commit('addProduct', product);
}
}
});
app.use(store);
EDIT
I build a simulate button for better testing. QR Scanning does not work in Browser.
Result It does work in Browser. But not on my emulator or android device. Seems like fetch() isnt the right way with ionic-vue. If I catch the error I got TypeError: Failed to fetch...
<template>
<button #click="simulateScan">Simulate Scan</button>
</template>
<script>
export default {
methods: {
simulateScan() {
this.$store.dispatch('addProduct', 737628064502);
}
}
};
</script>
Final Solution
fetch() does not work on android. You have to use something like cordova-http, capacitor-http, ionic-http or else. I used capacitorcommunity-http.
npm install #capacitor-community/http
npx cap sync
import { Http } from '#capacitor-community/http';
[...]
callAPI(context, ean) {
var eanurl = "https://world.openfoodfacts.org/api/v0/product/" + ean + ".json";
Http.get({ url: eanurl}) //
.then(response => {
return response.data;
}
) //
.then(data => {
console.log(data);
context.dispatch('saveProduct', data);
}).catch(error => confirm(error));
},
[...]
See EDIT Below
I have massively improved over my last question, but I am stuck again after some days of work.
Using Vue, Vue-router, Vuex and Vuetify with the Data on Googles Could Firestore
I want to update my data live, but i cannot find a way to do this.
Do i need to restructure, like moving products and categories into one collection?
Or is there any bind or query magic to get this done.
As you can see below, it loads the data on click quite well, but I need the live binding 'cause you could have the page open and someone could sell the last piece (amountLeft = 0). (And a lot of future ideas).
My data structure is the following:
categories: {
cat_food: {
name: 'Food'
parentCat: 'nC'
},
cat_drinks: {
name: 'Food'
parentCat: 'nC'
},
cat_beer: {
name: 'Beer'
parentCat: 'cat_drinks'
},
cat_spritz: {
name: 'Spritzer'
parentCat: 'cat_drinks'
},
}
products: {
prod_mara: {
name: 'Maracuja Spritzer'
price: 1.5
amountLeft: 9
cat: ['cat_spritz']
},
prod_capp: {
name: 'Cappuccino'
price: 2
cat: ['cat_drinks']
},
}
The categories and the products build a tree. The GIF shows me opening the categories down to show a product. You see that it's a product when you have a price tag.
You can see there are two categories that have the same parent (cat_drinks).
The product prod_capp is also assigned to the category and shown side by side to the categories.
I get the data currently this way:
catsOrProd.js
import { catsColl, productsColl } from '../firebase'
const state = {
catOrProducts: [],
}
const mutations = {
setCats(state, val) {
state.catOrProducts = val
}
}
const actions = {
// https://vuefire.vuejs.org/api/vuexfire.html#firestoreaction
async bindCatsWithProducts({ commit, dispatch }, CatID) {
if (CatID) {
// console.log('if CatID: ', CatID)
await Promise.all([
catsColl.where('parentCat', '==', CatID).orderBy('name', 'asc').get(),
productsColl.where('cats', 'array-contains', CatID).orderBy('name', 'asc').get()
])
.then(snap => dispatch('moveCatToArray', snap))
} else {
// console.log('else CatID: ', CatID)
await Promise.all([
catsColl.where('parentCat', '==', 'nC').orderBy('name', 'asc').get(),
productsColl.where('cats', 'array-contains', 'nC').orderBy('name', 'asc').get()
])
.then(snap => dispatch('moveCatToArray', snap))
}
},
async moveCatToArray({ commit }, snap) {
const catsArray = []
// console.log(snap)
await Promise.all([
snap[0].forEach(cat => {
catsArray.push({ id: cat.id, ...cat.data() })
}),
snap[1].forEach(cat => {
catsArray.push({ id: cat.id, ...cat.data() })
})
])
.then(() => commit('setCats', catsArray))
}
}
export default {
namespaced: true,
state,
actions,
mutations,
}
This is a part of my vue file that is showing the data on screen. I have left out the unnecessary parts.
To open everything a have a route with props and clicking on the category sends the router to the next category. (this way i can move back with browser functionality).
Sale.vue
<template>
...........
<v-col
v-for="catOrProduct in catOrProducts"
:key="catOrProduct.id"
#click.prevent="leftClickProd($event, catOrProduct)"
#contextmenu.prevent="rightClickProd($event, catOrProduct)">
....ViewMagic....
</v-col>
............
</template>
<script>
.........
props: {
catIdFromUrl: {
type: String,
default: undefined
}
},
computed: {
// https://stackoverflow.com/questions/40322404/vuejs-how-can-i-use-computed-property-with-v-for
...mapState('catOrProducts', ['catOrProducts']),
},
watch: {
'$route.path'() { this.bindCatsWithProducts(this.catIdFromUrl) },
},
mounted() {
this.bindCatsWithProducts(this.catIdFromUrl)
},
methods: {
leftClickProd(event, catOrProd) {
event.preventDefault()
if (typeof (catOrProd.parentCat) === 'string') { // when parentCat exists we have a Category entry
this.$router.push({ name: 'sale', params: { catIdFromUrl: catOrProd.id } })
// this.bindCatsWithProducts(catOrProd.id)
} else {
// ToDo: Replace with buying-routine
this.$refs.ProductMenu.open(catOrProd, event.clientX, event.clientY)
}
},
}
</script>
EDIT 24.09.2020
I have changed my binding logic to
const mutations = {
setCatProd(state, val) {
state.catOrProducts = val
},
}
const actions = {
async bindCatsWithProducts({ commit, dispatch }, CatID) {
const contain = CatID || 'nC'
const arr = []
catsColl.where('parentCat', '==', contain).orderBy('name', 'asc')
.onSnapshot(snap => {
snap.forEach(cat => {
arr.push({ id: cat.id, ...cat.data() })
})
})
productsColl.where('cats', 'array-contains', contain).orderBy('name', 'asc')
.onSnapshot(snap => {
snap.forEach(prod => {
arr.push({ id: prod.id, ...prod.data() })
})
})
commit('setCatProd', arr)
},
}
This works, as the data gets updated when I change something in the backend.
But now i get an object added everytime something changes. As example i've changed the price. Now i get this:
I don't know why, because i have the key field set in Vue. The code for the rendering is:
<v-container fluid>
<v-row
align="center"
justify="center"
>
<v-col
v-for="catOrProduct in catOrProducts"
:key="catOrProduct.id"
#click.prevent="leftClickProd($event, catOrProduct)"
#contextmenu.prevent="rightClickProd($event, catOrProduct)"
>
<div>
<TileCat
v-if="typeof(catOrProduct.parentCat) == 'string'"
:src="catOrProduct.pictureURL"
:name="catOrProduct.name"
/>
<TileProduct
v-if="catOrProduct.isSold"
:name="catOrProduct.name"
... other props...
/>
</div>
</v-col>
</v-row>
</v-container>
Why is this not updating correctly?
From the Vuefire docs, this is how you would subscribe to changes with Firebase only:
// get Firestore database instance
import firebase from 'firebase/app'
import 'firebase/firestore'
const db = firebase.initializeApp({ projectId: 'MY PROJECT ID' }).firestore()
new Vue({
// setup the reactive todos property
data: () => ({ todos: [] }),
created() {
// unsubscribe can be called to stop listening for changes
const unsubscribe = db.collection('todos').onSnapshot(ref => {
ref.docChanges().forEach(change => {
const { newIndex, oldIndex, doc, type } = change
if (type === 'added') {
this.todos.splice(newIndex, 0, doc.data())
// if we want to handle references we would do it here
} else if (type === 'modified') {
// remove the old one first
this.todos.splice(oldIndex, 1)
// if we want to handle references we would have to unsubscribe
// from old references' listeners and subscribe to the new ones
this.todos.splice(newIndex, 0, doc.data())
} else if (type === 'removed') {
this.todos.splice(oldIndex, 1)
// if we want to handle references we need to unsubscribe
// from old references
}
})
}, onErrorHandler)
},
})
I would generally avoid any unnecessary dependencies, but according to your objectives, you can use Vuefire to add another layer of abstraction, or as you said, doing some "magic binding".
import firebase from 'firebase/app'
import 'firebase/firestore'
const db = firebase.initializeApp({ projectId: 'MY PROJECT ID' }).firestore()
new Vue({
// setup the reactive todos property
data: () => ({ todos: [] }),
firestore: {
todos: db.collection('todos'),
},
})
Before I was using lightstep/opentelemetry-exporter-js, I can use my own exporters and Lightstep exporter at same time.
import { CollectorTraceExporter } from '#opentelemetry/exporter-collector';
import { NodeTracerProvider } from '#opentelemetry/node';
import { BatchSpanProcessor, ConsoleSpanExporter } from '#opentelemetry/tracing';
import { LightstepExporter } from 'lightstep-opentelemetry-exporter';
const initTracer = () => {
const serviceName = 'server-trace-service';
const tracerProvider = new NodeTracerProvider({
plugins: {
http: {
enabled: true,
path: '#opentelemetry/plugin-http',
},
},
});
tracerProvider.addSpanProcessor(new BatchSpanProcessor(new ConsoleSpanExporter()));
tracerProvider.addSpanProcessor(
new BatchSpanProcessor(
new CollectorTraceExporter({
serviceName,
})
)
);
tracerProvider.addSpanProcessor(
new BatchSpanProcessor(
new LightstepExporter({
serviceName,
token: 'myToken',
})
)
);
tracerProvider.register();
};
However, just saw lightstep/opentelemetry-exporter-js is deprecated and replaced by lightstep/otel-launcher-node.
I checked the source code of it and the demo, it looks like it is a "framework" on top of OpenTelemetry.
const {
lightstep,
opentelemetry,
} = require('lightstep-opentelemetry-launcher-node');
const sdk = lightstep.configureOpenTelemetry({
accessToken: 'YOUR ACCESS TOKEN',
serviceName: 'locl-ex',
});
sdk.start().then(() => {
const tracer = opentelemetry.trace.getTracer('otel-node-example');
const span = tracer.startSpan('test-span');
span.end();
opentelemetry.trace.getTracerProvider().getActiveSpanProcessor().shutdown();
});
Is it possible to simply use it as one of OpenTelemetry exporters?
lightstep-opentelemetry-launcher-node basically bundles the required things for you for easier configuration so this is not an exporter. If you were to simply replace the "LightstepExporter" with "OpenTelemetry Collector Exporter" in your code you can simply do this
import { CollectorTraceExporter } from '#opentelemetry/exporter-collector';
tracerProvider.addSpanProcessor(
new BatchSpanProcessor(
new CollectorTraceExporter({
url: 'YOUR_DIGEST_URL',
headers: {
'Lightstep-Access-Token': 'YOUR_TOKEN'
}
})
)
);
The default YOUR_DIGETS_URL from lightstep/otel-launcher-node is https://ingest.lightstep.com:443/api/v2/otel/trace
I'm using Bootstrap vue table with contentful's API and could use some help with my code. I'm attempting to use a for loop to iterate over an array and get the property values. The console.info(episodes); call prints out each iteration for the var episodes, but now how do I bind this to my variable episodes. Using return only returns one result even outside of the for each loop. Any help or suggestions on another implementation is greatly appreciated. Full Template below.
<template>
<div>
<h1>Bootstrap Table</h1>
<b-table striped responsive hover :items="episodes" :fields="fields"></b-table>
</div>
</template>
<style>
</style>
<script>
import axios from "axios";
// Config
import config from "config";
// Vuex
import store from "store";
import { mapGetters, mapActions } from "vuex";
// Services
import { formatEntry } from "services/contentful";
// Models
import { entryTypes } from "models/contentful";
// UI
import UiEntry from "ui/Entry";
import UiLatestEntries from "ui/LatestEntries";
const contentful = require("contentful");
const client = contentful.createClient({
space: "xxxx",
environment: "staging", // defaults to 'master' if not set
accessToken: "xxxx"
});
export default {
name: "contentful-table",
data() {
return {
fields: [
{
key: "category",
sortable: true
},
{
key: "episode_name",
sortable: true
},
{
key: "episode_acronym",
sortable: true
},
{
key: "version",
sortable: true
}
],
episodes: []
};
},
mounted() {
return Promise.all([
// fetch the owner of the blog
client.getEntries({
content_type: "entryWebinar",
select: "fields.title,fields.description,fields.body,fields.splash"
})
])
.then(response => {
// console.info(response[0].items);
return response[0].items;
})
.then(response => {
this.episodes = function() {
var arrayLength = response.length;
var episodes = [];
for (let i = 0; i < arrayLength; i++) {
// console.info(response[i].fields.title + response[i].fields.splash + response[i].fields.description + response[i].fields.body );
var episodes = [
{
category: response[i].fields.title,
episode_name: response[i].fields.splash,
episode_acronym: response[i].fields.description,
version: response[i].fields.body
}
];
// episodes.forEach(category => episodes.push(category));
console.info(episodes);
}
return episodes;
};
})
.catch(console.error);
}
};
</script>
You can use the map method on the response array to return all the elements.
In your current example you keep re-setting the episodes variable, instead of the push() you actually want to do. The map method is still a more elegant way to solve your problem.
this.episodes = response.map((item) => {
return {
category: item.fields.title,
episode_name: items.fields.splash,
episode_acronym: item.fields.description,
version: item.fields.body
}
})
You can update the last then to match the last then below
]).then(response => {
return response[0].items;
})
.then((response) => {
this.episodes = response.map((item) => {
return {
category: item.fields.title,
episode_name: items.fields.splash,
episode_acronym: item.fields.description,
version: item.fields.body
};
});
})
.catch(console.error)
You do have an unnecessary second then, but I left it there so that you could see what I am replacing.
Subscriptions with Nexus are undocumented but I searched Github and tried every example in the book. It's just not working for me.
I have cloned Prisma2 GraphQL boilerplate project & my files are as follows:
prisma/schema.prisma
datasource db {
provider = "sqlite"
url = "file:dev.db"
default = true
}
generator photon {
provider = "photonjs"
}
generator nexus_prisma {
provider = "nexus-prisma"
}
model Pokemon {
id String #default(cuid()) #id #unique
number Int #unique
name String
attacks PokemonAttack?
}
model PokemonAttack {
id Int #id
special Attack[]
}
model Attack {
id Int #id
name String
damage String
}
src/index.js
const { GraphQLServer } = require('graphql-yoga')
const { join } = require('path')
const { makeSchema, objectType, idArg, stringArg, subscriptionField } = require('#prisma/nexus')
const Photon = require('#generated/photon')
const { nexusPrismaPlugin } = require('#generated/nexus-prisma')
const photon = new Photon()
const nexusPrisma = nexusPrismaPlugin({
photon: ctx => ctx.photon,
})
const Attack = objectType({
name: "Attack",
definition(t) {
t.model.id()
t.model.name()
t.model.damage()
}
})
const PokemonAttack = objectType({
name: "PokemonAttack",
definition(t) {
t.model.id()
t.model.special()
}
})
const Pokemon = objectType({
name: "Pokemon",
definition(t) {
t.model.id()
t.model.number()
t.model.name()
t.model.attacks()
}
})
const Query = objectType({
name: 'Query',
definition(t) {
t.crud.findManyPokemon({
alias: 'pokemons'
})
t.list.field('pokemon', {
type: 'Pokemon',
args: {
name: stringArg(),
},
resolve: (parent, { name }, ctx) => {
return ctx.photon.pokemon.findMany({
where: {
name
}
})
},
})
},
})
const Mutation = objectType({
name: 'Mutation',
definition(t) {
t.crud.createOnePokemon({ alias: 'addPokemon' })
},
})
const Subscription = subscriptionField('newPokemon', {
type: 'Pokemon',
subscribe: (parent, args, ctx) => {
return ctx.photon.$subscribe.pokemon()
},
resolve: payload => payload
})
const schema = makeSchema({
types: [Query, Mutation, Subscription, Pokemon, Attack, PokemonAttack, nexusPrisma],
outputs: {
schema: join(__dirname, '/schema.graphql')
},
typegenAutoConfig: {
sources: [
{
source: '#generated/photon',
alias: 'photon',
},
],
},
})
const server = new GraphQLServer({
schema,
context: request => {
return {
...request,
photon,
}
},
})
server.start(() => console.log(`🚀 Server ready at http://localhost:4000`))
The related part is the Subscription which I don't know why it's not working or how it's supposed to work.
I searched Github for this query which results in all projects using Subscriptions.
I also found out this commit in this project to be relevant to my answer. Posting the related code here for brevity:
import { subscriptionField } from 'nexus';
import { idArg } from 'nexus/dist/core';
import { Context } from './types';
export const PollResultSubscription = subscriptionField('pollResult', {
type: 'AnswerSubscriptionPayload',
args: {
pollId: idArg(),
},
subscribe(_: any, { pollId }: { pollId: string }, context: Context) {
// Subscribe to changes on answers in the given poll
return context.prisma.$subscribe.answer({
node: { poll: { id: pollId } },
});
},
resolve(payload: any) {
return payload;
},
});
Which is similar to what I do. But they do have AnswerSubscriptionPayload & I don't get any generated type that contains Subscription in it.
How do I solve this? I think I am doing everything right but it's still not working. Every example on GitHub is similar to above & even I am doing the same thing.
Any suggestions?
Edit: Subscriptions aren't implemented yet :(
I seem to have got this working despite subscriptions not being implemented. I have a working pubsub proof of concept based off the prisma2 boilerplate and Ben Awad's video tutorial https://youtu.be/146AypcFvAU . Should be able to get this up and running with redis and websockets to handle subscriptions until the prisma2 version is ready.
https://github.com/ryanking1809/prisma2_subscriptions
Subscriptions aren't implemented yet.
I've opened up an issue to track it.
I'll edit this answer as soon as it's implemented in Prisma 2.