Access this.<module> from within Nuxt plugin - javascript

Is there any way I can access a JS API exposed by a Nuxt module from within a client-side plugin?
Context: I'm using Buefy/Bulma, which is loaded like this in nuxt.config.js:
modules: [
['nuxt-buefy', {css: false}],
],
Buefy exposes this.$buefy.<etc> which is accessible from components.
But I want to access this API from within a client-side plugin, utils.js, which is loaded like this:
plugins: ['~/plugins/utils.js'],
And utils.js itself:
export default ({app}, inject) => {
inject('myUtil', (msg, isErr) => {
app.$buefy; //<-- undefined
....
I assume this is an ordering issue, i.e. Buefy is being loaded after my plugin or something. I can't do this in a static JS file loaded via meta.scripts as that won't have access to the app (I assume).
Anything I can do here?

Try this in your plugin js file
import { ToastProgrammatic as toast } from 'buefy';
export default (_, inject) => {
inject('appHelper', {
displayToast(duration = null, msg = null, position = null, type = null) {
toast.open({
duration: duration || 3000,
message: msg || 'Update Successful',
position: position || 'is-bottom-left',
type: type || 'is-success',
});
},
});
};

Related

Vue prefetch data from separate backend

I have some queries from an API-Server that returns a json object that will be static over a user session, but not static forever.
It's a one-pager with Vue router.
How can I achieve that I:
can access this.myGlobals (or similar eg window.myGlobals) in all components, where my prefetched json-data from API-Server is stored.
My approach that is already working is to embed help.js via a mixin.
Oddly enough, I get hundreds of calls to this query. At first I thought that it only happened in the frontend and is chached, but the requests are actually sent hundreds of times to the server. I think it is a mistake of my thinking, or a systematic mistake.
i think the problem is, that the helper.js is not static living on the vue instance
main.js:
import helpers from './helpers'
Vue.mixin(helpers)
helpers.js:
export default {
data: function () {
return {
globals: {},
}
}, methods: {
//some global helper funktions
},
}, mounted() {
let url1 = window.datahost + "/myDataToStore"
this.$http.get(url1).then(response => {
console.log("call")
this.globals.myData = response.data
});
}
}
log in console:
call
SomeOtherStuff
(31) call
SomeOtherStuff
(2) call
....
log on server:
call
call
call (pew pew)
My next idea would be to learn vuex, but since its a easy problem, im not sure if i really need that bomb ?
You can use plugin to achieve this.
// my-plugin.js
export default {
install (Vue, options) {
// start fetching data right after install
let url1 = window.datahost + "/myDataToStore"
let myData
Vue.$http.get(url1).then(response => {
console.log("call")
myData = response.data
})
// inject via global mixin
Vue.mixin({
computed: {
myData () {
return myData
}
}
})
// or inject via instance property
Vue.prototype.$myData = myData
// or if you want to wait until myData is available
Vue.prototype.$myData = Vue.$http.get(url1)
.then(response => {
console.log("call")
myData = response.data
})
}
}
and use it:
Vue.use(VueResource)
Vue.use(myPlugin)

location is not defined error in react + next js?

I am trying to send some text on basic of hosted url (where my build is deployed).but i am getting this error
ReferenceError: location is not defined
here is my code
https://codesandbox.io/s/laughing-mendel-pf54l?file=/pages/index.js
export const getStaticProps = async ({ preview = false, previewData = {} }) => {
return {
revalidate: 200,
props: {
//req.host
name: location.hostname == "www.google.com" ? "Hello" : "ccccc"
}
};
};
Can you show your imports, because it could be that you are importing router from 'next/client'
Assuming that you are using functional-based component
You need to import router as follows:
import {useRouter} from "next/router";
in your function body:
const router = useRouter();
getStaticProps() is executed at build time in Node.js, which has no location global object – Location is part of the browser API. Additionally, because the code is executed at build time, the URL is not yet known.
Change getStaticProps to getServerSideProps (see documentation). This will mean the function is called at runtime, separately for each request.
From the context object passed to getServerSideProps, pull out the Node.js http.IncomingMessage object.
On this object, look for the Host header.
export const getServerSideProps = async ({ req }) => {
return {
props: {
name: req.headers.host === "www.google.com" ? "Hello" : "ccccc"
}
};
};
Note:
I also changed == to ===, as it's generally advised to use the latter. The former can produce some unexpected results because of silent type conversions.
I also removed revalidate, as this is not applicable to getServerSideProps().

How can I get dynamic data from within gatsby-config.js?

Consider the following code within gatsby-config.js:
module.exports = {
plugins: [
{
resolve: `gatsby-source-fetch`,
options: {
name: `brands`,
type: `brands`,
url: `${dynamicURL}`, // This is the part I need to be dynamic at run/build time.
method: `get`,
axiosConfig: {
headers: { Accept: "text/csv" },
},
saveTo: `${__dirname}/src/data/brands-summary.csv`,
createNodes: false,
},
},
],
}
As you can see above, the URL for the source plugin is something that I need to be dynamic. The reason for this is that the file URL will change every time it's updated in the CMS. I need to query the CMS for that field and get its CDN URL before passing to the plugin.
I tried adding the following to the top of gatsby-config.js but I'm getting errors.
const axios = require("axios")
let dynamicURL = ""
const getBrands = async () => {
return await axios({
method: "get",
url: "https://some-proxy-url-that-returns-json-with-the-csv-file-url",
})
}
;(async () => {
const brands = await getBrands()
dynamicURL = brands.data.summary.url
})()
I'm assuming this doesn't work because the config is not waiting for the request above to resolve and therefore, all we get is a blank URL.
Is there any better way to do this? I can't simply supply the source plugin with a fixed/known URL ahead of time.
Any help greatly appreciated. I'm normally a Vue.js guy but having to work with React/Gatsby and so I'm not entirely familiar with it.
I had similar requirement where I need to set siteId of gatsby-plugin-matomo dynamically by fetching data from async api. After searching a lot of documentation of gatsby build lifecycle, I found a solution.
Here is my approach -
gatsby-config.js
module.exports = {
siteMetadata: {
...
},
plugins: {
{
resolve: 'gatsby-plugin-matomo',
options: {
siteId: '',
matomoUrl: 'MATOMO_URL',
siteUrl: 'GATSBY_SITE_URL',
dev: true
}
}
}
};
Here siteId is blank because I need to put it dynamically.
gatsby-node.js
exports.onPreInit = async ({ actions, store }) => {
const { setPluginStatus } = actions;
const state = store.getState();
const plugin = state.flattenedPlugins.find(plugin => plugin.name === "gatsby-plugin-matomo");
if (plugin) {
const matomo_site_id = await fetchMatomoSiteId('API_ENDPOINT_URL');
plugin.pluginOptions = {...plugin.pluginOptions, ...{ siteId: matomo_site_id }};
setPluginStatus({ pluginOptions: plugin.pluginOptions }, plugin);
}
};
exports.createPages = async function createPages({ actions, graphql }) {
/* Create page code */
};
onPreInit is a gatsby lifecycle method which is executing just after plugin loaded from config. onPreInit lifecycle hook has some built in methods.
store is the redux store where gatsby is storing all required information for build process.
setPluginStatus is a redux action by which plugin data can be modified in redux store of gatsby.
Here the important thing is onPreInit lifecycle hook has to be called in async way.
Hope this helps someone in future.
Another approach that may work for you is using environment variables as you said, the URL is known so, you can add them in a .env file rather than a CSV.
By default, Gatsby uses .env.development for gatsby develop and a .env.production for gatsby build command. So you will need to create two files in the root of your project.
In your .env (both and .env.development and .env.production) just add:
DYNAMIC_URL: https://yourUrl.com
Since your gatsby-config.js is rendered in your Node server, you don't need to prefix them by GATSBY_ as the ones rendered in the client-side needs. So, in your gatsby-config.js:
module.exports = {
plugins: [
{
resolve: `gatsby-source-fetch`,
options: {
name: `brands`,
type: `brands`,
url: process.env.DYNAMIC_URL, // This is the part I need to be dynamic at run/build time.
method: `get`,
axiosConfig: {
headers: { Accept: "text/csv" },
},
saveTo: `${__dirname}/src/data/brands-summary.csv`,
createNodes: false,
},
},
],
It's important to avoid tracking those files in your Git repository since you don't want to expose this type of data.

Action dispatched automatically returns html response, but when run manually it returns JSON response

I have an action called fetchUserPermissions which returns a permission set from an api endpoint through axios. This action is run trough another action called init, which is run automatically trough the utility dispatchActionForAllModules which basically looks for the init action in all modules/submodules and runs it. This part seems to work fine, but it seems like the endpoint returns html (?) instead of a JSON response. The returned response looks something like:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Title</title> </head> <body> <noscript> <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <script type="text/javascript" src="http://0.0.0.0:8080/bundle.js" ></script> </body> </html>
It's important to know that i run Vue through Django using the webpack_loader package. The above html is the index.html that serves Vue.
However, when i dispatch the action in the created() hook of an component it works as expected, and returns something like { "id": 1, "is_staff": true, "is_superuser": true, "permissions": [ "view_user" ], "group_permissions": [ "has_users_export" ] }.
The dispatchActionForAllModules utility is basically copy paste from the vue-enterprise-bolierplate repo.
How can i make sure it returns a JSON response as expected?
state/store.js
import Vue from 'vue'
import Vuex from 'vuex'
import dispatchActionForAllModules from '#/utils/dispatch-action-for-all-modules'
import modules from './modules'
Vue.use(Vuex)
const store = new Vuex.Store({
modules,
strict: process.env.NODE_ENV !== 'production',
})
export default store
// automatically run the `init` action for every module,
// if one exists.
dispatchActionForAllModules('init')
state/modules/index.js
import camelCase from 'lodash/camelCase'
const modulesCache = {}
const storeData = { modules: {} }
;(function updateModules() {
const requireModule = require.context(
// Search for files in the current directory.
'.',
// Search for files in subdirectories.
true,
// Include any .js files that are not this file or a unit test.
/^((?!index|\.unit\.).)*\.js$/
)
// For every Vuex module...
requireModule.keys().forEach((fileName) => {
const moduleDefinition =
requireModule(fileName).default || requireModule(fileName)
// Skip the module during hot reload if it refers to the
// same module definition as the one we have cached.
if (modulesCache[fileName] === moduleDefinition) return
// Update the module cache, for efficient hot reloading.
modulesCache[fileName] = moduleDefinition
// Get the module path as an array.
const modulePath = fileName
// Remove the "./" from the beginning.
.replace(/^\.\//, '')
// Remove the file extension from the end.
.replace(/\.\w+$/, '')
// Split nested modules into an array path.
.split(/\//)
// camelCase all module namespaces and names.
.map(camelCase)
// Get the modules object for the current path.
const { modules } = getNamespace(storeData, modulePath)
// Add the module to our modules object.
modules[modulePath.pop()] = {
// Modules are namespaced by default.
namespaced: true,
...moduleDefinition,
}
})
// If the environment supports hot reloading...
if (module.hot) {
// Whenever any Vuex module is updated...
module.hot.accept(requireModule.id, () => {
// Update `storeData.modules` with the latest definitions.
updateModules()
// Trigger a hot update in the store.
require('../store').default.hotUpdate({ modules: storeData.modules })
})
}
})()
// Recursively get the namespace of a Vuex module, even if nested.
function getNamespace(subtree, path) {
if (path.length === 1) return subtree
const namespace = path.shift()
subtree.modules[namespace] = {
modules: {},
namespaced: true,
...subtree.modules[namespace],
}
return getNamespace(subtree.modules[namespace], path)
}
export default storeData.modules
state/modules/users.js
import axios from 'axios'
export const state = {
requestUserPermissions: [],
}
export const mutations = {
'FETCH_USER_PERMISSIONS' (state, permissions) {
state.requestUserPermissions = permissions
},
}
export const actions = {
init: ({ dispatch }) => {
dispatch('fetchUserPermissions')
},
fetchUserPermissions: ({ commit }) => {
axios.get('user/permissions/').then(result => {
console.log(result.data)
commit('FETCH_USER_PERMISSIONS', result.data)
}).catch(error => {
throw new Error(`API ${error}`)
})
},
}
utils/dispatch-action-for-all.modules.js
import allModules from '#/state/modules'
import store from '#/state/store'
export default function dispatchActionForAllModules(actionName, { modules = allModules, modulePrefix = '', flags = {} } = {}) {
// for every module
for (const moduleName in modules) {
const moduleDefinition = modules[moduleName]
// if the action is defined on the module
if (moduleDefinition.actions && moduleDefinition.actions[actionName]) {
// dispatch the action if the module is namespaced, if not
// set a flag to dispatch action globally at the end
if (moduleDefinition.namespaced) {
store.dispatch(`${modulePrefix}${moduleName}/${actionName}`)
} else {
flags.dispatchGlobal = true
}
}
// if there are nested submodules
if (moduleDefinition.modules) {
// also dispatch action for these sub-modules
dispatchActionForAllModules(actionName, {
modules: moduleDefinition.modules,
modulePrefix: modulePrefix + moduleName + '/',
flags,
})
}
}
// if this is at the root ant at least one non-namespaced module
// was found with the action
if (!modulePrefix && flags.dispatchGlobal) {
// dispatch action globally
store.dispatch(actionName)
}
}
The computed property in the component gets the data from store directly, like:
permissions() {
return this.$store.state.users.requestUserPermissions
}

autoComplete.js - how to resolve autoComplete is not defined

I want to use autocomplete.js in my application.
I have installed the package using yarn add #tarekraafat/autocomplete.js. I am using webpack 4.28 to bundle the javascript files and have require("#tarekraafat/autocomplete.js/dist/js/autoComplete"); to import the package into the application and placed the bundled file at the bottom before the closing BODY tag.
In my custom.js file, I am creating a new instance of autoComplete as follows:
new autoComplete({
data: {
src: async () => {
document.querySelector("#autoComplete_results_list").style.display = "none";
document.querySelector("#autoComplete").setAttribute("placeholder", "Loading...");
const source = await fetch("/employee/search");
const data = await source.json();
return data;
},
key: "name"
},
selector: "#autoComplete",
placeHolder: "type employee name to search...",
threshold: 0,
searchEngine: "strict",
highlight: true,
dataAttribute: { tag: "value", value: "" },
maxResults: Infinity,
renderResults: {
destination: document.querySelector("#autoComplete"),
position: "afterend"
},
onSelection: feedback => {
document.querySelector(".selection").innerHTML = feedback.selection.food;
document
.querySelector("#autoComplete")
.setAttribute("placeholder", `${event.target.closest(".autoComplete_result").id}`);
console.log(feedback);
}
});
However, on running the application, I am getting an error Uncaught ReferenceError: autoComplete is not defined with a reference to the location where I am creating the new instance.
I have read the getting started documentation and looked at the demo code and I can't figure out what I am missing. How do I resolve the error?
Your problem is in your import, you are not import the autoComplete correctly, so when you using the new autoComplete you are having error.
Change the require("#tarekraafat/autocomplete.js/dist/js/autoComplete"); to import autoComplete from '#tarekraafat/autocomplete.js';, put this on top of your file, right after jquery or something
Write your code inside
$(document).ready(function(){
// Write your Code Here
});

Categories