Unable to access Vue.js global function through plugin - javascript

I'm trying to refactor some commonly used functions into a globally available Util plugin for my app. I followed the instructions from the docs and this question, but I'm not sure how to use it the functions in the template and Vue keeps complaining about an undefined method. Ideally I just want to call isEmpty from any child component.
util.js
export default {
install(Vue, options) {
Vue.isEmpty = function (object) {
return false // dummy function for now to check if method works
}
}
}
Also tried:
Util.install = function (Vue, options) {
Vue.isEmpty = function () {
...
}
// this doesn't work either
// Vue.prototype.$isEmpty = function (object) {
// return false
// }
}
main.js
import util from './components/shared/util.js'
import comp from './components/shared/myComponent.js'
// Vue.component('util', util) this doesn't work
Vue.use(util)
const app = new Vue({
...
components: {
comp
}).$mount('#app')
None of the below work. The error thrown is TypeError: Cannot read property 'isEmpty' of undefined
component template
<p v-if="util.isEmpty(license)" class="margin-0">N/A</p>
<p v-if="Vue.isEmpty(license)" class="margin-0">N/A</p>
<p v-if="isEmpty(license)" class="margin-0">N/A</p>

You are almost done, are missing of prototype. Try this:
utils.js
export default {
install(Vue, options) {
Vue.prototype.isEmpty = function (object) {
return false // dummy function for now to check if method works
}
}
}
Component
<p v-if="isEmpty(license)" class="margin-0">N/A</p>
Here a example: https://codesandbox.io/s/vue-template-tdx00

Related

TypeError: _API.default is not a constructor with Jest tests

I have an API class that I am trying to use in a React app.
// API file
class API {
...
}
export default API;
// Other file
import API from "utils/API";
const api = new API();
And I am getting the error:
TypeError: _API.default is not a constructor
But.. it seems like my default is set?
My Jest setup is like this:
"jest": {
"setupFiles": [
"./jestSetupFile.js"
],
"testEnvironment": "jsdom",
"preset": "jest-expo",
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|#react-native(-community)?)|expo(nent)?|#expo(nent)?/.*|#expo-google-fonts/.*|react-navigation|#react-navigation/.*|#unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|react-router-native/.*|#invertase/react-native-apple-authentication/.*)"
]
},
My strong guess is that this is due to a configuration of my babel, webpack or package.json.
What could be causing this?
Note, I want to be clear, this doesn't happen whatsoever in my main application, only in Jest testing
If I change it to a named export/import, I get this:
TypeError: _API.API is not a constructor
Extremely confusing behavior.
As mentioned by others, it would be helpful to see a minimum reproducible example.
However, there is one other possible cause. Are you mocking the API class in your test file at all? This problem can sometimes happen if a class is mistakenly mocked as an "object" as opposed to a function. An object cannot be instantiated with a "new" operator.
For example, say we have a class file utils/API like so:
class API {
someMethod() {
// Does stuff
}
}
export default API;
The following is an "incorrect" way to mock this class and will throw a TypeError... is not a constructor error if the class is instantiated after the mock has been created.
import API from 'utils/API';
jest.mock('utils/API', () => {
// Returns an object
return {
someMethod: () => {}
};
})
// This will throw the error
const api = new API();
The following will mock the class as a function and will accept the new operator and will not throw the error.
import API from 'utils/API';
jest.mock('utils/API', () => {
// Returns a function
return jest.fn().mockImplementation(() => ({
someMethod: () => {}
}));
})
// This will not throw an error anymore
const api = new API();
Trying adding "esModuleInterop": true, in your tsconfig.json. BY default esModuleInterop is set to false or is not set. B setting esModuleInterop to true changes the behavior of the compiler and fixes some ES6 syntax errors.
Refer the documentation here.
This was ultimately due to additional code inside the file that I was exporting the class from.
import { store } from "root/App";
if (typeof store !== "undefined") {
let storeState = store.getState();
let profile = storeState.profile;
}
At the top, outside my class for some functionality I had been working on.
This caused the class default export to fail, but only in Jest, not in my actual application.
You'll need to export it like this :
export default class API
You could try with:
utils/API.js
export default class API {
...
}
test.js
import API from "utils/API";
const api = new API();
I'm adding this because the issue I had presented the same but has a slightly different setup.
I'm not exporting the class with default, i.e.
MyClass.ts
// with default
export default MyClass {
public myMethod()
{
return 'result';
}
}
// without default, which i'm doing in some instances.
export MyClass {
public myMethod()
{
return 'result';
}
}
When you don't have the default, the import syntax changes.
In a (jest) test if you follow the convention where you do have export default MyClass(){};
then the following works.
const MOCKED_METHOD_RESULT = 'test-result'
jest.mock("MyClass.ts", () => {
// will work and let you check for constructor calls:
return jest.fn().mockImplementation(function () {
return {
myMethod: () => {
return MOCKED_METHOD_RESULT;
},
};
});
});
However, if you don't have the default and or are trying to mock other classes etc. then you need to do the following.
Note, that the {get MyClass(){}} is the critical part, i believe you can swap out the jest.fn().mockImplementation() in favour of jest.fn(()=>{})
jest.mock("MyClass.ts", () => ({
get MyClass() {
return jest.fn().mockImplementation(function () {
return {
myMethod: () => {
return MOCKED_METHOD_RESULT;
},
};
});
},
}));
So the issue is the way in which you access the contents of the class your mocking. And the get part allows you to properly define class exports.
I resolved this error by using below code.
jest.mock('YOUR_API_PATH', () => ({
__esModule: true,
default: // REPLICATE YOUR API CONSTRUCTOR BEHAVIOUR HERE BY ADDING CLASS
})
If you want to mock complete API class, please check the below snippet.
jest.mock('YOUR_API_PATH', () => ({
__esModule: true,
default: class {
constructor(args) {
this.var1 = args.var1
}
someMethod: jest.fn(() => Promise.resolve())
},
}));

Checking variable status from another JavaScript module using export default

I have two Javascript modules, one for my navigation, and one for route changes.
Inside of nav.js is simple if to check if the menu can be opened
const initNav = () => {
const openMenu = () => {
if (!menuIsOpen && isMobile) {
navItems.classList.add("is-menu-open");
menuIsOpen = true;
} else {
navItems.classList.remove("is-menu-open");
menuIsOpen = false;
}
}
}
initNav();
export default initNav;
Then, at the top of the other module I import
import initNav from './nav.js';
The question is, inside of my other module route.js, I need to be able to check if the menu is opened, and, if it is then close it, so I was going to use:
beforeLeave: function (data) {
if (menuIsOpen) {
navItems.classList.remove("is-menu-open");
menuIsOpen = false;
}
}
The console, however, says menuIsOpen is not defined.
I can't then this way check to see the status of this variable.
Am I able to do this another way, rather than combining the two modules into one very large js module?
The problem is that you're trying to reassign a variable that exists in multiple modules, but imported variables are read-only references - they can only be reassigned in their original module. So you can't just also export the menuIsOpen from nav.js because reassigning it in route.js would not be allowed.
You could have nav.js export a function that reassigns its menuIsOpen while also exporting menuIsOpen, but reassignable exports in general are unintuitive - nowhere else in Javascript does what looks like a reference to a primitive value seemingly reassign itself due to a different module reassigning the binding. Linters often forbid them.
A better alternative is for the main user of menuIsOpen to export setter and getter functions to interface with menuIsOpen. For example, you could do something like the following:
// nav.js
export const getMenuIsOpen = () => menuIsOpen;
export const setMenuIsOpen = (newVal) => menuIsOpen = newVal;
// ...
// route.js
import initNav, { getMenuIsOpen, setMenuIsOpen } from './nav.js';
// ...
beforeLeave: function (data) {
if (getMenuIsOpen()) {
navItems.classList.remove("is-menu-open");
setMenuIsOpen(false);
}
}
For anything more complicated than a single variable that needs to be accessed and changeable in multiple places, you can consider exporting an object instead, eg:
// nav.js
export const navState = {
menuIsOpen: false,
// other properties
};
// ...
// route.js
import initNav, { navState } from './nav.js';
// ...
beforeLeave: function (data) {
if (navState.menuIsOpen) {
navItems.classList.remove("is-menu-open");
navState.menuIsOpen = false;
}
}
Having to handle mutable state across multiple modules makes code a bit ugly. I prefer to avoid it when I can. Given the code in the question, since you already have a reference to navItems in both modules, you might be able to avoid having a persistent menuIsOpen variable at all if you simply check if the navItems class list has is-menu-open:
// nav.js
// ...
const openMenu = () => {
// defining menuIsOpen as a standalone variable isn't necessary,
// but you might find it makes the code more readable
const menuIsOpen = navItems.classList.contains('is-menu-open');
if (!menuIsOpen && isMobile) {
navItems.classList.add("is-menu-open");
} else {
navItems.classList.remove("is-menu-open");
}
}
// ...
// route.js
// ...
beforeLeave: function (data) {
const menuIsOpen = navItems.classList.contains('is-menu-open');
if (menuIsOpen) {
navItems.classList.remove("is-menu-open");
}
}
If you do need a standalone menuIsOpen variable in nav.js, you could have nav.js export a function which can close the menu and set menuIsOpen to false:
// nav.js
// ...
export const closeMenu = () => {
navItems.classList.remove("is-menu-open");
menuIsOpen = false;
};
// ...
// route.js
import initNav, { closeMenu } from './nav.js';
// ...
beforeLeave: closeMenu
(if beforeLeave really isn't doing anything else in your actual code other than checking if the class exists and removing it if so, you don't need to check if the class exists first - you can just remove it from the class list unconditionally - this can apply to all the other snippets in the answer too)
try getter/setter or export the variable menuIsOpen
Try like this
beforeLeave: (data) => {
if (menuIsOpen) {
navItems.classList.remove("is-menu-open");
menuIsOpen = false;
}
}

Access vue from imported file

I'm trying to access my Vue element from a required javascript file but i've failed so far... I want to take element-ui's el-form-item component's rule validator from outside. Maybe my logic is wrong but this way should work too (i guess)
Vue#2.5.13
element-ui#2.0.11
npm#5.6.0
Vue.use(...)
I tried Vue.use(...)
My javascript file.
const MyPlugin = {
install(Vue, options) {
Vue.prototype.usernameValidator = function (rule, value, callback) {
console.log(Vue);
console.log(this);
// ...
My console output:
console.log(Vue);
ƒ Vue$3(options) {
if ("development" !== 'production' &&
!(this instanceof Vue$3)
) {
warn('Vue is a constructor and should be called with the new keyword');
}
this._init(options);
}
console.log(this);
{"required":true,"field":"username","fullField":"username","type":"string"...}
beforeCreate
module.exports = function (rule, value, callback) {
console.log(this)
// ...
});
console.log(this);
{"required":true,"field":"username","fullField":"username","type":"string"...}
As I said my logic may wrong but I'm just curious can I make a method like this ?
Regards
Update
I'm calling it in register.blade.php
<el-form-item label="Username" prop="username" :rules="[{required:true, validator: usernameValidator, trigger:'blur,change'}]">
<el-input v-model="form.username" name="username"></el-input>
</el-form-item>
In app.js;
Vue.use(require('./plugins/usernameValidator'));
// ...
window.app = new Vue({
// ...
});
Where are you requireing the element? I'll detail out how I generally create plugins using your examples.
Define a plugin:
// my-plugin.js
import {FormItem} from 'element-ui'
const MyPlugin = {
install(Vue, options) {
Vue.prototype.usernameValidator = function (rule, value, callback) {
// use FormItem's validator
Then register the plugin via Vue.use:
// app.js
import MyPlugin from 'my-plugin'
// or require
const MyPlugin = require('my-plugin')
Vue.use(MyPlugin)
Later in a component, call the usernameValidator method defined in the plugin:
<template>
<input name="username" v-model="username" :class="isValidUsername ? 'borderGreen' : 'borderRed'" />
<div>{{ usernameFeedback }}</div>
</template>
export default {
beforeCreate () {
// what would you validate in here?
// this.usernameValidator(rule, value, callback)
},
data () {
return {
username: null,
usernameFeedback: null
}
},
computed: {
isValidUsername () {
// assuming the validator returns a boolean, valid/invalid
return this.usernameValidator(rule, this.username, this.validationCallback)
}
},
methods: {
validationCallback (feedback) {
this.usernameFeedback = feedback
}
}
}
If you have an example of how you require a component I can update my answer, but as it is you can access the plugin method in a component using this.

ES6 access exported 'default' instance from same file

I was wondering, when you export some data using the ES6 keyword export like so :
export default {
data: "Hello !"
}
How can you then from the same file, access that exact same exported data ?
EDIT: Of course, without declaring it before exporting the variable...
If you structure your file like that, you cannot.
Usually, you define functions and data that you want to expose before the export statement, and then reference them when you build the exported object/function.
Bad way:
export default {
data: 'Hello!',
myFn: function (str) {
console.log(str);
}
}
Good way:
var data = 'Hello!';
var myFn = function (str) {
console.log(str);
};
// code that uses data and myFn
// shorthand for { data: data, myFn: myFn }
export default { data, myFn };
Try the following
export const data = 'hello';
For my similar use case -- I wanted easy access to everything the ES6 module exported from within the same module just for debugging purposes. So:
export const data = 'hi'
export const someFunc = () => `important data: ${data}`
export default someFunc()
export const funcToDebug() => {
console.log(`what's going on here? ${myModule.exports.default}`)
}
var myModule = module
If you want to access internally the functions that you export, you should define them outside of the export statement. Like so:
const data = 'Hello !';
const someFunc = () => {
return data + ' Goodbye!'
}
export default {
data,
someFunc
}
Now the variables and functions can reference each other without being "stuck" inside the export function. For example the following will NOT work:
// WRONG!
export default {
data: 'Hello !',
someFunc: () => {
return data + ' Goodbye!';
}
}

Getting error when import instanceMethods function in Sequelize

I have created inside a file a function and export it using
export function foo(params...) {
// do something
}
inside the initialization of the model I import the function in this way:
import { foo } from "../path/..."
instanceMethods: {
foo: foo
}
the problem is the model is not initialized correctly. Do you know why?
the problem is the model is not initialized correctly
The following code works fine.
export function foo(params...) {
// do something
}
import { foo } from "./foo";
let instanceMethods = {
foo: foo
};
Error is elsewhere. Perhaps you meant to use = and used :? Note that if you don't use the variable it is erased form the output (ref).
#)-'--

Categories

Resources