Checking variable status from another JavaScript module using export default - javascript

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;
}
}

Related

How to get separate "global" module per instance?

Is it possible get individual "global" module for each instance?
import MyModule from './index.js';
const first = new MyModule();
const second = new MyModule();
// PLEASE SEE EXAMPLE BELOW FOR LOGS
// expect to log for both:
// 1. "in entry: true"
// 2. "in init: true"
// but second module logs:
// 1. "in entry: false"
// 2. "in init: false"
Issue being here that both share globals and 1. instance changes property to false.
What are my options to have in individual globals module per instance?
PS! I have tens of thousands of lines of legacy code. Would be 100x better if I didn't need to change/remove/refactor globals.js contents nor these imports import globals from './globals.js';. Nevertheless, give all your ideas - I need to fix it, even if I need to change a lot.
Code for example above / minimal reproducible example
index.js
import globals from './globals.js';
import init from './init.js';
export default function MyModule (conf) {
console.log('in entry:', globals.state1);
init();
}
globals.js
export default {
state1: true,
};
init.js
import globals from './globals.js';
export default function init () {
console.log('in init:', globals.state1);
globals.entry1 = false;
}
Since an object is passed by reference you need to create a copy each time you use globals.
A good way is to export a function like:
/* globals.js */
const globals = { // Maybe wrap the object in Object.freeze() to prevent side effects
state1: true,
};
export default () => ({ ...globals });
You can then call the function to create a copy each time you need it.
/* init.js */
import getGlobals from './globals.js';
export default function init () {
const globals = getGlobals();
console.log('in init:', globals.state1);
globals.entry1 = false;
}

Vue3 Multiple Instances of Composition API Data Store

Simplifying a real-life situation...
Let's say I have a webapp with two columns. The same component used in both columns. The functionality uses data storage and functions created in a separate composition api js file, made available to the component by importing and then provide/inject. Works great.
But is there a way to write the js file with the composition api once, and then create multiple instances when it's imported to the Vue app? That way a separate instance can be sent to each component and they won't share the same data object. I know if you import the same file with multiple names...
import instanceone from "path";
import instancetwo from "path";
...they'll both share the same objects because it's importing the same file as two names, not two instances of the file.
Is there any way to achieve something like this? I'm interested in any setup that would achieve the end goal (not needing two copies of the file to achieve two independent usages). I took a flyer and thought maybe creating a single file that exports objects and functions, then two files that each import the appropriate pieces of that single file, and then let Vue import those two files might work...but nope, not so much.
Obviously there are plenty of other ways to do this, but I want to explore this possibility first. Preferably without making use of Vuex.
Thank you!
the following is one of the way to achieve this
/* composable_module.js */
import { ref, computed } from 'vue';
export const shared_var_1 = ref(0);
export const shared_var_2 = ref(0);
export function composable_variables() {
// will return separate instance of variables for each call
const var1 = ref(0);
const comp_var1 = computed(() => var1.value + shared_var_1.value);
// comp_var1 updates value when either var1 or shared_var_1 value gets updated
return { var1, comp_var1 };
}
usage as following
/* component_1.vue */
import { shared_var_1, shared_var_2, composable_variables } from 'composable_module.js';
/* other things needed for component or any file */
setup() {
const { var1, comp_var1 } = composable_variables();
/*
Do what you want to do with
shared_var_1, shared_var_2, var1, comp_var1
*/
// return whatever you wanted to use in template
return { shared_var_1, shared_var_2, var1, comp_var1 }
}
Here shared_var_1, shared_var_2 will act as vuex store values
and var1, comp_var1 will be separate for each function call
so can be used in multiple components as separate variable sharing common functionality but not value.
Within your 'path' composable you could define two states, then call the relevant state with something like:
const { getItem1, getItem2, setItem1, setItem2 } = (whichInstance) ? instanceOne : instanceTwo
You just need to define your whichInstance condition to determine which instance you want.
Your composable could be something like:
const stateOne = reactive({
item1: true,
item2: 1
})
const stateTwo = reactive({
item1: false,
item2: 2
})
export function instanceOne() {
let stateRef = toRefs(stateOne)
/* Getters */
const getItem1 = () => {
return stateRef.item1
}
const getItem2 = () => {
return stateRef.item2
}
/* Mutations */
const setItem1 = (value) => {
stateRef.item1.value = value
}
const setItem2 = (value) => {
stateRef.item2.value = value
}
return {
state: toRefs(stateOne),
getItem1,
getItem2,
setItem1,
setItem2
}
}
export function instanceTwo() {
let stateRef = toRefs(stateTwo)
/* Getters */
const getItem1 = () => {
return stateRef.item1
}
const getItem2 = () => {
return stateRef.item2
}
/* Mutations */
const setItem1 = (value) => {
stateRef.item1.value = value
}
const setItem2 = (value) => {
stateRef.item2.value = value
}
return {
state: toRefs(stateTwo),
getItem1,
getItem2,
setItem1,
setItem2
}
}
})

How to pass K6 Tool data modified in default function to teardown stage

K6 Tool is being used for our testing needs. For the sample snippet below when run with K6, we see that the change occurred in default function for data passed from setup is not affected and visible in tear down stage. Is there any other possible way to have this available so that we could utilize it for Test Data Management purposes during Load Tests?
import http from "k6/http";
import { group, check, sleep, fail } from "k6";
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
import { textSummary } from "https://jslib.k6.io/k6-summary/0.0.1/index.js";
const SLEEP_DURATION = 1;
// Global variables should be initialized.
var csvData = [{"Id":"P976O6RP8NSFN076RKLBSGK1Q8NZT7WA"},{"Id":"D4YJZFQJUPJTZWPLP2VB5M839SZBNNSO"}];
function removeId(arr, value) {
arr = arr.filter(function(item) {
return item.Id !== value
});
return arr;
}
export function setup() {
return JSON.parse(JSON.stringify(csvData));
}
export default function(data) {
let Id = csvData[0].Id;
data = removeId(data, Id);
console.log('ID deleted is: '+Id+' \nJson<data> in default :: '+JSON.stringify(data));
}
export function handleSummary(data) {
return {
//stdout: JSON.stringify(data),
};
}
export function teardown(data) {
//Teardown does not have modified data object updated in default function
console.log('\nJson<data> in teardown :: '+JSON.stringify(data));
}
No, this is (currently?) not possible. The setup data must not be modified by the default function. Think about it: this wouldn't work, if executed in a cloud or clustered environment, because the setup and default function might be executed on different machines.
Why do you want to modify the setup data?

Unable to access Vue.js global function through plugin

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

Mocking a function jest but jest calling original function

I have a function that returns true or false, lets call it myFunc
myFunc (){
if(something){return true}
else{return false}
}
that's what it does for sake of arg
I then call it somewhere else
if(myFunc()){
}else{
}
when I log it out, it continually comes out as false. however, when i have mocked it in my test like so:
const myMock = (myModule.myFunc = jest.fn())
myMock.mockReturnValue(true)
so why is it still coming back as false when I log it from the index file? or is that not quite how mocking works?
I'm guessing that myModule is the object you imported, and then you set the mock function on that object. But in the myModule file you are referencing that function directly, not through a module reference, right?
The proper way would probably be to move myFunc out of myModule. But if you want to keep it there, then you are going to have to partially mock myModule:
jest.mock('./myModule', () => {
return {
...jest.requireActual('./myModule'),
myFunc: jest.fn()
}
})
But seriously consider moving myFunc out of myModule, because partial mocking is difficult and confusing.
One way I found to solve my issue was to use a class instead.
Here is a sudo example:
Implementation
export class Location {
getLocation() {
const environment = this.getEnvironmentVariable();
return environment === "1" ? "USA" : "GLOBAL";
}
getEnvironmentVariable() {
return process.env.REACT_APP_LOCATION;
}
}
Test
import { Location } from "./config";
test('location', () => {
const config = new Location();
jest.spyOn(config, "getEnvironmentVariable").mockReturnValue("1")
const location = config.getLocation();
expect(location).toBe("USA");
});

Categories

Resources