Our team is converting an app from Vue 2 to 3 and I am working on the final steps.
I am investigating whether we can convert our mixin files to composables. I have yet to find any documentation to support whether you can use composables with optionsAPI.
I have tried a little sample but I am seeing the limitations:
COMPOSABLE file useComposables:
import { ref, computed } from 'vue'
export default () => {
let first = ref('First')
let last = ref('Last')
let mycomputed = computed(() => {
return `${first.value} *** ${last.value}`
})
return {
first, mycomputed
}
}
COMPONENT:
import useComposables from '#/utils/useComposable'
created () {
let { first, mycomputed } = useComposables()
console.log('first', first.value)
console.log('mycomputed', mycomputed.value)
},
<template>
mycomputed {{ mycomputed }}
</template>
So, I see when I try to do interpolation on mycomputed computed variable in the template, the component doesn't have access to the computed variable because it is not in the computed option and doesn't belong to 'this'.
I can't seem to find any documentation to support using composables with options API.
Am I missing something or is this a no-go?
Thanks
OP achieved to solve that by using the following:
setup() {
let { first, mycomputed } = useComposables()
return {
first, mycomputed
}
},
Related
I wrote a vue3 component which uses the VirtualScroller from PrimeVue and I would like to scroll to the end of the scroller each time I'm adding new elements. For that, there is scrollInView method which is defined on the component and documented here
My code looks like this (it's typescript with vue-class-component and single file syntax):
<template>
...
<VirtualScroller :items="content" :itemSize="50" class="streamscroller" ref="streamscroller">
<template v-slot:item="{ item }">
<pre>{{ item }}</pre>
</template>
</VirtualScroller>
...
</template>
<script lang="ts">
...
import { ref, ComponentPublicInstance } from "vue";
import VirtualScroller from "primevue/virtualscroller";
...
#Options({
components: {
VirtualScroller,
...
},
})
export default class StreamResultViewer extends Vue {
streamscroller = ref<ComponentPublicInstance<VirtualScroller>>();
content: string [] = [ "No output" ];
...
mounted(): void {
...
console.debug("scroller mounted: ", this.streamscroller.value); // <=== here, already the value is indefined
}
onData(msg: string): void {
const lines = msg.split('\n');
const content = [...this.content, ...lines];
this.content = content;
console.debug("scroller: ", this.streamscroller.value); // <== always undefined
this.streamscroller.value?.scrollInView(this.content.length, 'to-end', 'smooth'); // <== so never called
}
...
The virtual scroller works well (I can add lines each time they arrives and the scroll bar moves...) but I can never call the scroll method because the ref is undefined...
I'd be very grateful for any clue...
Thank you
The only workaround I found is too use $refs like this:
onData(msg: string): void {
const lines = msg.split('\n');
const content = [...this.content, ...lines];
this.content = content;
const scroller = this.$refs.streamscroller as VirtualScroller;
scroller.scrollInView(this.content.length, 'to-end', 'smooth');
}
This way, I am able to call the scrolling method and it works fine.
If someone can explain how it should work normally with ref<T>() in the vue-class-component + typescript mode, I'd be glad to hear that.
i am new in vue 3 and trying to make two data binding, but it's not working correctly. I did setup() and create a reactive data. Bind it to the children via v-model. Then $emit the updated data back to the parent. But parent's reactive data not applied 'till code re-run on save command.
parent script:
export default defineComponent({
setup(){
const activeCard = reactive({id:null, travelType:null})
return {activeCard}
},
})
parent template:
<pin-card v-for="(pinCard, idx) in categoryPinCards"
:key="idx"
v-model="activeCard"
:pinCard="pinCard"></pin-card>
child script
export default defineComponent({
props:{
modelValue:Object,
},
methods:{
makePinActive(id:number, travelType:null) {
this.$emit("update:modelValue", {id: id, travelType: travelType})
},
}
})
I followed the steps but, something is missing?
It seems reactive props can't be used by v-model in Vue 3 (while refs don't have this problem).
A workaround is to add a handler for the update:modelValue event that Object.assigns the new value:
<pin-card
v-for="(pinCard, idx) in categoryPinCards"
:key="idx"
:modelValue="activeCard" 👈
#update:modelValue="setActiveCard($event)" 👈
:pinCard="pinCard"
></pin-card>
export default {
setup() {
return {
//...
setActiveCard(eventData) {
Object.assign(activeCard, eventData)
}
}
}
}
demo
Is there a way with Sinon to have a negative match? Specifically that an object does not have a given property?
Thanks!
There isn't currently a built-in matcher for that.
Sinon allows you to create custom matchers so you could create your own, here is one for doesNotHave based on the built-in has matcher:
import * as sinon from 'sinon';
const doesNotHave = (prop) => sinon.match(function (actual) {
if(typeof value === "object") {
return !(prop in actual);
}
return actual[prop] === undefined;
}, "doesNotHave");
test('properties', () => {
const obj = { foo: 'bar' };
sinon.assert.match(obj, sinon.match.has('foo')); // SUCCESS
sinon.assert.match(obj, doesNotHave('baz')); // SUCCESS
})
I've just realized that it's possible to specify undefined in the object's shape to make the check:
sinon.assert.match(actual, {
shouldNotExists: undefined
});
Not completely sure if it's 100% valid, but seems to do the job.
You cannot use sinon for this, you have to use something like chai.
You would do:
cont { expect } = require("chai");
expect({ foo: true }).to.not.have.keys(['bar']);
https://runkit.com/embed/w9qwrw2ltmpz
I'm trying to make a Meteor helper non-reactive with this code:
let titleNonReactive;
Template.articleSubmission.onCreated(function () {
this.autorun(function() {
titleNonReactive = Template.currentData().title;
});
});
Template.articleSubmission.helpers({
titleNonreactive: function() {
return titleNonReactive;
}
});
However the resulting output is still reactive. If I save a new value in the background, it's automatically updated on the frontend where I'm displaying the result of this helper with {{ titleNonreactive }}.
How can I fix this?
This is likely caused by your Blaze data context (will need to see your Template code to confirm), but here's a possible solution that doesn't involve using Tracker.nonreactive. Since you want the value of titleNonreactive to not be reactive, you can just use a standard local / non-reactive variable to store a copy of the original reactive title. For example:
import { Template } from 'meteor/templating';
import { articles } from '/imports/api/articles/collection';
import './main.html';
let originalTitle;
Template.body.onCreated(function onCreated() {
this.autorun(() => {
const article = articles.findOne();
if (article && !originalTitle) {
originalTitle = article.title;
}
});
});
Template.body.helpers({
article() {
return articles.findOne();
},
titleNonreactive() {
return originalTitle;
}
});
Then in your Template:
<ul>
{{#with article}}
<li>Reactive Title: {{title}}</li>
<li>Non-Reactive Title: {{titleNonreactive}}</li>
{{/with}}
</ul>
In Facebook react.js, you can compose component within component, or maybe mix and match.
I'm wondering if twitter flight can do the same thing. if so, can anyone gives me an example?
this is what I have so far:
define(function (require) {
var defineComponent = require('flight/lib/component'),
infoInput = require('component/info_input');
return defineComponent(inputSection, infoInput);
function inputSection () {
this.after('initialize', function() {
infoInput.doSomehting();
});
};
});
and my info_input.js is defined below:
define(function (require) {
var defineComponent = require('flight/lib/component');
return defineComponent(infoInput);
function infoInput() {
this.after('initialize', function() {
});
this.doSomething = function() {
alert('I will do something');
};
};
});
This is what mixins are for.
Flight Components are enriched mixins.
From doc/component_api.md
It comes with a set of basic functionality such as event handling and Component registration. Each Component definition mixes in a set of custom properties which describe its behavior.
Read more about Components.
So the answer to your question is Yes.
I guess that what you are doing is legit, although I've never done it before.
I'd rather move the shared logic to a Mixin or attach the two components to the same element and let them talk via events:
component/input_section.js
this.after('initialize', function () {
this.trigger('uiSomethingRequired');
});
component/info_input.js
this.after('initialize', function () {
this.on('uiSomethingRequired', this.doSomething);
});
Solution mentioned by G.G above works!
We may go a step ahead to trigger events on restricted scope instead of document:
component/input_section.js
this.after('initialize', function () {
this.$node.closest(this.attr.parentClass).trigger('uiSomethingRequired');
});
component/info_input.js
this.after('initialize', function () {
this.on(this.$node.closest(this.attr.parentClass), 'uiSomethingRequired', this.doSomething);
});