So I'm starting out with Vue JSONForms and I'm trying to create a bare-bones custom text renderer. I know there JSONForms has the vue-vanilla package, but I want to understand what are the basics needed for a custom renderer because later on I will need to do much more customization to each custom renderer I create. Here is what I have so far:
<template>
<v-input />
</template>
<script lang="ts">
import { ControlElement, JsonFormsRendererRegistryEntry, rankWith, isStringControl } from '#jsonforms/core'
import { useJsonFormsControl, RendererProps } from '#jsonforms/vue'
import { defineComponent } from 'vue'
const renderersText = defineComponent({
name: 'renderers-text',
setup (props: RendererProps<ControlElement>) {
return useJsonFormsControl(props)
},
})
export default renderersText
export const entry: JsonFormsRendererRegistryEntry = {
renderer: renderersText,
tester: rankWith(1, isStringControl),
}
</script>
But I'm getting a r.tester is not a function error. Any idea what this means and/or what I need to fix? Thanks in advance!
Related
I am new to Pinia, and am having trouble setting up just a basic store. I have been following Pinia's own documentation, and cannot seem to read any state whatsoever from the vue component I'm mapping to the Pinia store.
In my app I have:
import { createPinia } from 'pinia';
export default function initApp(el) {
let app = createApp(MenuApp);
app.use(router).use(createPinia()).mount(el);
}
I set up a super basic Pinia store, just to get started:
import {defineStore} from 'pinia';
export const useTestPiniaStore = defineStore('testStore', {
state: () => {
return {
name: 'bob'
}
},
})
In my vue component I have:
<template>
<div class="menu-page">
<h1>{{name}}</h1>
</div>
</template>
<script>
import { mapState } from 'pinia';
import useTestPiniaStore from '#store/modules/piniaStore';
export default {
computed: {
...mapState(useTestPiniaStore['name']),
}
}
</script>
Pinia appears in my Vue dev tools, but no stores appear within it, and I get the error
Cannot read properties of undefined (reading 'name')
I don't understand what I am doing wrong here. If anyone can give some pointers that would be so appreciated.
mapState() requires two arguments, but you've passed it only one.
The 1st argument should be useTestPiniaStore, and the 2nd should be an array of state properties to map (or an object). It looks like you're trying to reference name from useTestPiniaStore, which would be undefined.
Your computed prop should look like this:
<script>
import { mapState } from 'pinia'
import { useTestPiniaStore } from '#/store'
export default {
computed: {
...mapState(useTestPiniaStore, ['name']), 👈
},
}
</script>
demo
While rewriting my VueJs project in typescript, I came across a TypeScript error.
This is a part of the component that has a custom v-model.
An input field in the html has a ref called 'plate' and I want to access the value of that. The #input on that field calls the update method written below.
Typescript is complaining that value does not exist on plate.
#Prop() value: any;
update() {
this.$emit('input',
plate: this.$refs.plate.value
});
}
template:
<template>
<div>
<div class="form-group">
<label for="inputPlate" class="col-sm-2 control-label">Plate</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputPlate" ref="plate" :value="value.plate" #input="update">
</div>
</div>
</div>
</template>
You can do this:
class YourComponent extends Vue {
$refs!: {
checkboxElement: HTMLFormElement
}
someMethod () {
this.$refs.checkboxElement.checked
}
}
From this issue: https://github.com/vuejs/vue-class-component/issues/94
Edit - 2021-03 (Composition API)
Updating this answer because Vue 3 (or the composition API plugin if you're using Vue 2) has some new functions.
<template>
<div ref="root">This is a root element</div>
</template>
<script lang="ts">
import { ref, onMounted, defineComponent } from '#vue/composition-api'
export default defineComponent({
setup() {
const root = ref(null)
onMounted(() => {
// the DOM element will be assigned to the ref after initial render
console.log(root.value) // <div>This is a root element</div>
})
return {
root
}
}
})
</script>
Edit - 2020-04:
The vue-property-decorator library provides #Ref which I recommend instead of my original answer.
import { Vue, Component, Ref } from 'vue-property-decorator'
import AnotherComponent from '#/path/to/another-component.vue'
#Component
export default class YourComponent extends Vue {
#Ref() readonly anotherComponent!: AnotherComponent
#Ref('aButton') readonly button!: HTMLButtonElement
}
Original Answer
None of the above answers worked for what I was trying to do. Adding the following $refs property wound up fixing it and seemed to restore the expected properties. I found the solution linked on this github post.
class YourComponent extends Vue {
$refs!: {
vue: Vue,
element: HTMLInputElement,
vues: Vue[],
elements: HTMLInputElement[]
}
someMethod () {
this.$refs.<element>.<attribute>
}
}
son.vue
const Son = Vue.extend({
components: {},
props: {},
methods: {
help(){}
}
...
})
export type SonRef = InstanceType<typeof Son>;
export default Son;
parent.vue
<son ref="son" />
computed: {
son(): SonRef {
return this.$refs.son as SonRef;
}
}
//use
this.son.help();
This worked for me: use
(this.$refs.<refField> as any).value or (this.$refs.['refField'] as any).value
Avoid using bracket < > to typecast because it will conflict with JSX.
Try this instead
update() {
const plateElement = this.$refs.plate as HTMLInputElement
this.$emit('input', { plate: plateElement.value });
}
as a note that I always keep remembering
Typescript is just Javascript with strong typing capability to ensure type safety. So (usually) it doesn't predict the type of X (var, param, etc) neither automatically typecasted any operation.
Also, another purpose of the typescript is to make JS code became clearer/readable, so always define the type whenever is possible.
Maybe it will be useful to someone. It looks more beautiful and remains type support.
HTML:
<input ref="inputComment" v-model="inputComment">
TS:
const inputValue = ((this.$refs.inputComment as Vue).$el as HTMLInputElement).value;
In case of custom component method call,
we can typecast that component name, so it's easy to refer to that method.
e.g.
(this.$refs.annotator as AnnotatorComponent).saveObjects();
where AnnotatorComponent is class based vue component as below.
#Component
export default class AnnotatorComponent extends Vue {
public saveObjects() {
// Custom code
}
}
With Vue 3 and the Options API, this is what worked for me:
<script lang="ts">
import {defineComponent} from 'vue';
export default defineComponent({
methods: {
someAction() {
(this.$refs.foo as HTMLInputElement).value = 'abc';
},
},
});
</script>
The autocomplete doesn't bring the foo property from $refs because it's defined in the template, and apparently there's no information inferred from it.
However, once you force the casting of .foo to the HTML element type, everything works from there on, so you can access any element property (like .value, in the example above).
Make sure to wrap your exports with Vue.extend() if you are converting your existing vue project from js to ts and want to keep the old format.
Before:
<script lang="ts">
export default {
mounted() {
let element = this.$refs.graph;
...
After:
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
mounted() {
let element = this.$refs.graph;
...
I found a way to make it work but it is ugly in my opinion.
Feel free to give other/better suggestions.
update() {
this.$emit('input', {
plate: (<any>this.$refs.plate).value,
});
}
I spent a LONG time trying to find an answer to this using Vue 3, TypeScript with class components and (as it happens, although not relevant to this) TipTap. Found the answer from bestRenekton above which finally solved it, but it needed tweaking. I'm pretty sure this is TypeScript specific.
My child component has this at the start:
export default class WhealEditor extends Vue {
It includes this method (the one I want to call from the parent):
doThis(what: string) {
console.log('Called with ' + what)
}
And this right at the end:
export type EditorRef = InstanceType<typeof WhealEditor>
</script>
So this announces to any consumer of the child component that it can access it using the variable EditorRef. The parent component includes the child component in the template:
<WhealEditor ref="refEditor" />
The parent component then imports ref, and the child component and the exposed object:
import { ref } from 'vue'
import WhealEditor, { EditorRef } from './components/WhealEditor.vue'
I then have a method to get this object:
getEditor(): EditorRef {
// gets a reference to the child component
return this.$refs.refEditor as EditorRef
}
Finally, I can handle events - for example:
processButton(msg: string) {
// runs method in child component
this.getEditor().doThis(msg)
Like everything else to do with client script, it's so much harder than I expected!
I currently writing a financial application using Vue.js and Vuetify. I have a few component files and javascript files like
Dashboard.vue
Cashflow.vue
NetWorth.vue
stores.js <- Vue Vuex
I have some functions which I need to use across all the Vue.js and javascript files. Would it be possible for me to perhaps write a function library which can be used across all
the component and js files.
function moneyFormat(num)
function IRRCalc(Cashflow)
function TimeValueMoneyCalc(I,N,PV,FV,PMT)
function PerpetualAnnuityCalc(I,PV)
function CarLoanInstallment(V,N)
function HouseLoanInstallment(V,N)
I know in C it is very simple just #include<financial.h> was wondering is there something similar in javascript.
Thanks.
There are 3 ways to do this:
1/You can create a helper.js file and import it to .vue files
// helper.js
export default {
function moneyFormat(num) { // some logic}
}
// Dashboard.vue
<script>
import helper from "helper.js" //the path may change depends on where you put the js file
methods: {
useHelper(value) {
helper.moneyFormat(value)
}
}
</script>
2/Another way is bind the function to Vue prototype
in main.js
Vue.prototype.$moneyFormat= function moneyFormat(num) {}
then in Dashboard.vue just call this.$moneyFormat(num). No need to import anything
3/ Use mixins. You can search online on how to use this https://v2.vuejs.org/v2/guide/mixins.html
You can create a single JS file that holds all the helper/util methods, and then export them individually:
export function moneyFormat(num) { ... }
export function IRRCalc(Cashflow) { ... }
export function TimeValueMoneyCalc(I,N,PV,FV,PMT) { ... }
export function PerpetualAnnuityCalc(I,PV) { ... }
export function CarLoanInstallment(V,N) { ... }
export function HouseLoanInstallment(V,N) { ... }
Then, you can simply import individual methods as of when needed, i.e.:
import { CarLoanInstallment, HouseLoanInstallment } from '/path/to/helper/file';
This can be quite usefuly for tree-shaking when you're bundling with webpack, for example, so that you don't bundle unnecessary functions that are never used in your project.
You can use Mixin
In your main.js, add Vue.mixin:
import Vue from "vue";
import App from "./App.vue";
Vue.mixin({
methods: {
helloWorld() {
alert("Hello world");
}
}
});
new Vue({
render: h => h(App)
}).$mount("#app");
and then you can call helloWorld() method from your component script with this.helloWorld() or just helloWorld() from the template.
You also can use filters if the method is to apply common text formatting
In your main.js, add Vue.filter:
import Vue from "vue";
import App from "./App.vue";
Vue.filter("capitalize", function(value) {
if (!value) return "";
value = value.toString();
return value.charAt(0).toUpperCase() + value.slice(1);
});
new Vue({
render: h => h(App)
}).$mount("#app");
and then you can do {{ "some text" | capitalize }} to apply capitalize filter on "some text"
Example here: https://codesandbox.io/s/heuristic-dirac-esb45?file=/src/main.js:0-226
I want a review button on image, but I don't find attribute.
I set the imagePreviewMarkupShow = true but it didn't work.
Package here
My Template
<template>
<div id="app">
<file-pond
name="test"
ref="pond"
max-files="4"
label-idle="Drop files here..."
:allow-multiple="true"
accepted-file-types="image/jpeg, image/png"
:files="myFiles"
v-on:init="handleFilePondInit"
allowImagePreview ="false"
/>
</div>
</template>
My Script
import vueFilePond from 'vue-filepond';
import 'filepond/dist/filepond.min.css';
import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.css';
import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type';
import FilePondPluginImagePreview from 'filepond-plugin-image-preview';
import FilePondPluginImageOverlay from 'filepond-plugin-image-overlay';
// Create component
const FilePond = vueFilePond(FilePondPluginFileValidateType, FilePondPluginImagePreview,FilePondPluginImageOverlay);
export default {
name: 'app',
data: function() {
return { myFiles: [] };
},
methods: {
handleFilePondInit: function() {
console.log('FilePond has initialized');
// this.$refs.pond.getFiles();
// FilePond instance methods are available on `this.$refs.pond`
}
},
components: {
FilePond
}
};
How do I add that button?
I was also struggling with this problem.
The solution is to import CSS.
import 'filepond-plugin-image-overlay/dist/filepond-plugin-image-overlay.min.css'
This is not mentioned in Github.
I have a React application that uses vega-lite for data visualizations. I tried to use the official tooltip addon for vega (https://github.com/vega/vega-tooltip), however I have no idea how to do it in a React component.
This is the component I have:
import React from 'react';
import PropTypes from 'prop-types';
import VegaLite from 'react-vega-lite';
const VegaChart = ({data, spec}) => {
return(
<div className="vega-chart-wrapper">
<VegaLite spec={spec} data={data} />
</div>
)
}
VegaChart.propTypes = {
data: PropTypes.object.isRequired,
spec: PropTypes.object.isRequired
}
export default VegaChart;
From the documentation of vega-tooltip, it says that I can install the module and then I have to do this:
<!-- Placeholder for my scatter plot -->
<div id="vis-scatter"></div>
var opt = {
mode: "vega-lite",
};
vega.embed("#vis-scatter", vlSpec, opt, function(error, result) {
// result.view is the Vega View, vlSpec is the original Vega-Lite specification
vegaTooltip.vegaLite(result.view, vlSpec);
});
This looks like the standard way to integrate in a jQuery application, but this is not gonna work in a React component. Any ideas?
Your react-vega-lite must already include vega-lite. So
import * as vega from 'vega-lite';
And put the rest in componentDidMount of your VegaChart