How to insert a function into a directive Vue 3? - vue.js

I need to write a specific global directive that should perform the same function in different hooks.
How can I implement something like this
export default {
directives: {
widthAsChild: {
widthAsChild(el) {
el.style.width = getComputedStyle(el.firstChild).getPropertyValue(
"width"
);
},
mounted(el) {
widthAsChild(el);
window.addEventListener("resize", () => {
widthAsChild(el);
});
},
},
},
}

The function has to be declared outside the directive's declaration object. One way to do that is to use an IIFE that contains a local method that you can can reuse in the returned directive declaration:
export default {
directives: {
widthAsChild: (() => {
const widthAsChild = el => {
el.style.width = getComputedStyle(el.firstChild).getPropertyValue("width");
}
return {
mounted(el) {
widthAsChild(el);
window.addEventListener("resize", () => {
widthAsChild(el);
});
},
}
})()
}
}
demo 1
Alternatively, you could move that to a separate file:
// #/directives/widthAsChild.js
const widthAsChild = el => {
el.style.width = getComputedStyle(el.firstChild).getPropertyValue('width')
}
export default {
mounted(el) {
widthAsChild(el)
window.addEventListener('resize', () => {
widthAsChild(el)
})
}
}
// MyComponent.vue
import widthAsChild from '#/directives/widthAsChild'
export default {
directives: {
widthAsChild,
}
}
demo 2

Related

Test if imported method is called

I'm trying to test if a composable's method is called from my store. Here's what I have:
/stores/ui:
import { defineStore } from 'pinia';
import { useUtils } from '#/composables/utils';
const { sendEvent } = useUtils();
interface State {
tab: string,
}
export const useUIStore = defineStore('ui', {
state: (): State => ({
tab: 'Home',
}),
actions: {
setTab(tabName: string) {
this.tab = tabName;
sendEvent('navigation', `click_${tabName}`);
},
}
})
#/composables/utils:
export function useUtils() {
const sendEvent = (name: string, value: string) => {
// do stuff
};
return {
sendEvent
};
}
And here's my test file:
import { setActivePinia, createPinia } from 'pinia'
import { useUIStore } from '#/stores/ui';
import { useUtils } from '#/composables/utils';
describe('', () => {
let uiStore;
beforeEach(() => {
setActivePinia(createPinia());
uiStore = useUIStore();
})
it('Sets the active tab', () => {
let sendEvent = jest.spyOn(useUtils(), 'sendEvent');
expect(uiStore.tab).toBe('Home');
uiStore.setTab('help');
expect(uiStore.tab).toBe('help');
expect(sendEvent).toHaveBeenCalledTimes(1);
});
})
I've also tried mocking the import:
jest.mock(
'#/composables/utils',
() => {
function useUtils() {
return {
sendEvent: jest.fn(),
}
}
return {
useUtils
};
},
{ virtual: true },
);
import { useUtils } from '#/composables/utils';
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
What's the correct way of adding a spy to sendEvent ?
In your test file - mock the utils:
const mockSendEvent = jest.fn();
jest.mock("#/composables/utils", () => ({
useUtils: () => ({
sendEvent: mockSendEvent,
}),
}));
and then update your test to use the mock:
it('Sets the active tab', () => {
expect(uiStore.tab).toBe('Home');
uiStore.setTab('help');
expect(uiStore.tab).toBe('help');
expect(mockSendEvent).toHaveBeenCalledTimes(1);
});

vue-emoji-picker : import const from vuex module not yielding desired result

I am trying to add custom emoji set to vue-emoji-picker
based https://codepen.io/DCzajkowski/pen/gObWjEQ
I have implemented this with partial success. I get everything loaded, except "RECENT" not added to the emoji list. Any help is greatly appreciated.
//my store/index.js
import recentEmojis from "./modules/recentEmojis";
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
recentEmojis,
},
// store/modules/recentEmojis/index.js
export const defaultEmojis = {
"Frequently used": {
thumbs_up: "+1",
red_hreat: "heart",
},
People: {
},
Nature: {
},
Objects: {
},
Places: {
},
Symbols: {
},};
const getName = (emoji) => {
console.log("getName");
const emojiMap = Object.values(defaultEmojis).reduce(
(a, b) => ({ ...a, ...b }),
{});
const object = Object.entries(emojiMap).find(
// eslint-disable-next-line no-unused-vars
([_name, value]) => value === emoji
);
return object ? object[0] : null;
};
export default {
namespaced: true,
defaultEmojis,
state: {
recentlyUsedEmojis: [],
},
mutations: {
recentlyUsedEmojis: (state, recentlyUsedEmojis) =>
(state.recentlyUsedEmojis = recentlyUsedEmojis),
},
actions: {
addEmojiToRecent: ({ state, commit }, emoji) => {
const name = getName(emoji);
const rest = state.recentlyUsedEmojis
.map(
// eslint-disable-next-line no-unused-vars
([_name, value]) => value
)
.includes(emoji)
? state.recentlyUsedEmojis.filter(
// eslint-disable-next-line no-unused-vars
([_name, value]) => value !== emoji
)
: state.recentlyUsedEmojis.length > 5
? state.recentlyUsedEmojis.slice(0, -1)
: state.recentlyUsedEmojis;
commit("recentlyUsedEmojis", [[name, emoji], ...rest])},},
getters: {
recentlyUsedEmojis: (state) =>
state.recentlyUsedEmojis.length
? { Recent: Object.fromEntries(state.recentlyUsedEmojis) }
: {},
},
}
//in my vue instance # src/pages/edtor.default.vue
...
<emoji-picker :emoji-table="emojis" #emoji="append" :search="search">
....
<script> import axios from "axios";
import EmojiPicker from "vue-emoji-picker"
import { defaultEmojis } from "../../store/modules/recentEmojis/index.js" // <<<<
export default { name: "ABCD", components: { EmojiPicker, },
...
computed: { emojis()
{ return { ...this.$store.getters.recentlyUsedEmojis, ...defaultEmojis } }, },
......
methods: {
append(emoji)
{ this.input += emoji this.$store.dispatch("recentEmojis/addEmojiToRecent", emoji) },
}
replaced
...this.$store.getters.recentlyUsedEmojis,
with
...this.$store.getters['recentEmojis/recentlyUsedEmojis']

Why can't I use dragula in Vue3 setup but mounted?

When I use dragula in vue3 setup. It isn't working. Like this:
setup() {
const dragFrom = ref(null);
const dragTo = ref(null);
onMounted(() => {
dragula([dragFrom, dragTo], {
copy: (el) => {
console.log(el);
return true;
},
accepts: () => {
return true;
},
});
});
return { dragFrom, dragTo };
}
But this way can be successful:
mounted() {
const dragFrom = this.$refs.dragFrom;
const dragTo = this.$refs.dragTo;
dragula([dragFrom, dragTo], {
copy: function (el, source) {
console.log(el);
return true;
},
accepts: function (el, target) {
return true;
},
});
}
Both methods are based on vue3.What's wrong?
Your issue comes from the fact that you are not accessing the value of the ref, i.e. dragFrom.value and dragTo.value when passing them into the dragula() function. Remember that when you create a reactive and mutable ref object, you will need to access its inner value using the .value property.
This should therefore work:
setup() {
const dragFrom = ref(null);
const dragTo = ref(null);
onMounted(() => {
// Ensure you access the VALUE of the ref!
dragula([dragFrom.value, dragTo.value], {
copy: (el) => {
console.log(el);
return true;
},
accepts: () => {
return true;
},
});
});
return { dragFrom, dragTo };
}
See proof-of-concept on this demo CodeSandbox I've created: https://uwgri.csb.app/

Nuxt - composition api and watchers

I am trying to watch for some warnings in my component
import VueCompositionApi, { watch } from '#vue/composition-api';
import useWarning from '#composables/warnings';
Vue.use(VueCompositionApi);
setup () {
const { activeWarnings } = useWarning();
watch(activeWarnings, () => {
console.log('called inside on update')
});
}
In my composition function I just push into the reactive array to simulate warning.
import { reactive } from '#vue/composition-api';
export default function useWarnings () {
const activeWarnings = reactive([]);
setInterval(() => {
fakeWarning();
}, 3000);
function fakeWarning () {
activeWarnings.push({
type: 'severe',
errorCode: 0,
errorLevel: 'red',
}
);
}
return { activeWarnings };
Does this not work in Vue 2 at all? Is there a workaround? activeWarnings does update in my component - I see the array filling up but this watcher is never called.
I am using https://composition-api.nuxtjs.org/
Since activeWarnings is an array you should add immediate:true option :
const { activeWarnings } = useWarning();
watch(activeWarnings, () => {
console.log('called inside on update')
},{
immediate:true
});
or
const { activeWarnings } = useWarning();
watch(()=>activeWarnings, () => {
console.log('called inside on update')
},{
immediate:true
});
But i recommend the following syntax :
import { reactive,toRef } from '#vue/composition-api';
export default function useWarnings () {
const state= reactive({activeWarnings :[]});
setInterval(() => {
fakeWarning();
}, 3000);
function fakeWarning () {
state.activeWarnings.push({
type: 'severe',
errorCode: 0,
errorLevel: 'red',
}
);
}
return { activeWarnings : toRef(state,'activeWarnings')};
then :
const { activeWarnings } = useWarning();
watch(activeWarnings, () => {
console.log('called inside on update')
},{
immediate:true
});

(Vue.js) In computed, I can't use data

export default {
data () {
return {
test: true
}
},
computed: {
test2: {
get: () => {
return console.log(this.test)
},
set: () => {
console.log(this.test)
}
}
}
But the Error occurred TypeError: Cannot read property 'test' of undefined
I want to use data in computed. but I can't
Help Me!
You should not be using arrow functions in the getter and setters, because that refers to the lexical this:
Note that if you use an arrow function with a computed property, this won’t be the component’s instance
So, update your code:
export default {
data () {
return {
test: true
}
},
computed: {
test2: {
get: function () {
return console.log(this.test)
},
set: function () {
console.log(this.test)
}
}
}
this here refers to the function not vue instance ,you can make this:
test2: {
let This = this
get: () => {
return console.log(This.test)
},
set: () => {
console.log(This.test)
}
}