I'm have a project in vuejs, Vuetify and the following library to generate forms dynamically using annotated JSON Schemas:
https://koumoul-dev.github.io/vuetify-jsonschema-form/latest/about
The library is great and do exactly what I need, generate form from JSON scheme, I can even add my own component, and show it on a form, the only missing thing is I cant get the slot component data into the model.
you can find my example here:
https://codepen.io/eran-levi/pen/jOqjroa
This is how I added the component:
<template slot="rating" slot-scope="{key, value, update, schema }" class="field">
<star-rating :value="value" #rating-selected="update($event, key)" :show-rating="false" />
</template>
and in the Schema JSON:
rating: {
type: "number",
title: "Rating",
minimum: 0,
maximum: 5
}
as you can see, once you hit the "Show Data" button, you can see the results of each field you edit, beside the value of the new star component I have added..
what am I missing here?
Thank you.
You are missing your update method.
I added the updateRating method and bound it to the #rating-selected event.
Here is a working fiddle: https://jsfiddle.net/9pjdxht6/1/
vue-star-rating component:
<star-rating :value="value" #rating-selected="updateRating" :show-rating="false" />
updateRating method:
updateRating: function(rating){
this.model.rating = rating;
}
Hope it help you.
Related
I have a problem to translate the information with vue i18n that is called through "v-for", all the data from the template is translated without problem, but those that I export through arrays and scripts do not render
i am using vue-i18n: version 7.8.1
You're only ever setting the helpTitles property when your component is created.
I would suggest using $t() in your templates instead of within data(). Then it will automatically react to changes.
I honestly don't think using an array from the translation file is a great idea. I'd be more inclined to add them with their own keys, just like your question and info translation keys, eg
helpStartedTitle: "GETTING STARTED - MODEL",
helpMembersTitle: "MEMBERS",
helpAccountTitle: "ACCOUNT",
//etc
You could then set up the keys in your data like this
data: () => {
const keys = [
"helpStarted",
"helpMembers",
"helpAccount",
"helpPayment",
"helpSocial",
"helpFraud",
"helpSupport",
"helpStudio"
]
return {
helpInfo: keys.map((key, id) => ({
id,
title: `general.help.${key}Title`,
question: `general.help.${key}`,
answer: `general.help.${key}Info`
}))
}
}
then in your template
<div v-for="help in helpInfo" :key="help.id">
<div :id="help.id" class="help-subtitle">{{ $t(help.title) }}:</div>
<HelpItem
:question="$t(help.question)"
:answer="$t(help.answer)"
:num="help.id"
/>
</div>
Even better would be to just pass the translation keys through to your HelpItem component and use $t() with it.
<HelpItem
:question="help.question"
:answer="help.answer"
:num="help.id"
/>
and in the HelpItem component
export default {
name: "HelpItem",
props: {
question: String,
answer: String,
num: Number
},
// etc
}
<!-- just guessing here -->
<h1>{{ $t(question) }}</h1>
<p>{{ $t(answer) }}</p>
FYI, I've corrected answear to answer throughout.
My goal is to allow the user to dynamically build a form from different components, based on what they choose in a select element. For example, they can choose to add a Heading, then maybe a Paragraph, then another Heading, etc. Each "part" is a separate Component.
I know this sort of thing has been asked before, but I'm only day 2 into Vue and I think I'm 90% of the way there - I'm just missing something. What I believe I'm stuck on is how to add a component to my app's data to allow Vue to render it out.
Here is the relevant markup:
<div id="creator">
<template v-for="part in existingParts">
<component :is="part"></component>
</template>
<select class = "custom-select" id = "new-part-chooser" v-model="newPart" v-on:change="addPart">
<option disabled value = "">Add a new part</option>
<option
v-for="part in possibleParts"
v-bind:value="part.toLowerCase()"
>{{ part }}</option>
</select>
<?php
// These simply bring in the templates for the components
// I know this isn't standard practice but... one thing at a time
include 'component-heading.html';
include 'component-paragraph.html';
?>
</div>
and my javascript file:
Vue.component("part-heading",{
data:function(){
return {
text: ""
}
},
template:"#component-heading-template"
});
Vue.component("part-paragraph",{
data:function(){
return {
text: ""
}
},
template:"#component-paragraph-template"
});
const Creator = new Vue({
el:"#creator",
data:{
newPart:"",
possibleParts:[
"Heading",
"Paragraph"
],
existingParts:[]
},
methods:{
addPart:function(e){
/*** This is where I'm stuck - what do I push here? ***/
this.existingParts.push();
}
}
});
I've read through the docs and Google'd the hell out of the topic, but every setup seems to be just different enough that I can't figure out how to apply it.
I was missing the fact that in the markup, the :is directive causes Vue to create a component that matches the name of the element in existingParts. So push()-ing "part-heading" into existingParts causes Vue to render an instance of the "part-heading" component.
The updated, working code is:
this.existingParts.push('part-'+this.newPart);
I have a component myHello:
<temlate>
<div>
<h2>Hello</h1>
<p>world</p>
</div>
</template>
And main component:
<h1>my hello:</h1>
<my-hello><my-hello>
After rendering shows this:
<h1>my hello:</h1>
<div>
<h2>Hello</h1>
<p>world</p>
</div>
How to delete <div> ?
With VueJS, every component must have only one root element. The upgrade guide talks about this. If it makes you feel better, you are not alone. For what it's worth the components section is a good read.
With the myriad of solutions to your problem, here is one.
component myHello:
<temlate>
<h2>Hello</h1>
</template>
component myWorld:
<temlate>
<p>world</p>
</template>
component main
<h1>my hello:</h1>
<my-hello><my-hello>
<my-world><my-world>
Vue gives you the tools to do so by creating templates or you can do it by having a parent div with two parent divs as children. Reset the data from the data function. Stick with convention (create templates). It's hard to get used to use Vue when you have a jQuery background. Vue is better
Ex.
data () {
message: 'My message'
}
When you click a button to display a new message. Clear the message or just set the message variable with a new value.
ex. this.message = 'New Message'
If you like to show another div. you should used the if - else statement and add a reactive variable. Ex. showDivOne
data () {
message: 'My message'
showDivOne: true,
showDivTwo: false
}
Add this reactive variables to the parent divs that corresponds to the div.
When clicking the button, you should have a function like...
methods: {
buttonClick () {
this.showDivOne = false
this.showDivTwo = true
}
}
I think you can use v-if directive to controll. Add a flag to controll status to show or hide
When should I use a directive vs a component in vue.js? I'm implementing some stuff from Bootstrap and it looks like I could do it either way (I'm starting with the dropdown menu).
I get the feeling that a directive is more for manipulating the dom on a single element, while components are for packaging a bunch of data and/or dom manipulation. Is this a good way to look at it?
This Stack Overflow question is the #1 result to the Google query "vue directive vs component". Saurshaz’s answer is currently the accepted one and it’s very wrong in Vue 2.0. I imagine this is leading a lot of people astray so I'm going to weigh in here.
The answer to “should I use a directive or a component in Vue” is almost always a component.
Do you want to have reusable HTML? I.e. reusable widgets? Then use a component. Do you want two of these widgets to have discrete data? Then use a component. The data of one will NOT override the data of another. Maybe that was true in Vue 1.0, I don't know. But it's absolutely not true in Vue 2.0. In Vue 2.0, your components have a data function that returns a unique set of data. Consider this real-life of a Vue dropdown that has an HTML markup similar to the UI Bootstrap dropdown:
<template>
<span class="dropdown sm-dropdown" #click="toggle" :class="{'open': isOpen}">
<a class="dropdown-toggle">
<span class="special-field">{{ label }}</span>
</a>
<ul class="dropdown-menu">
<li v-for="choice in choices">
<a #click.prevent="click(choice)">{{ choice.label }}</a>
</li>
</ul>
</span>
</template>
<script>
export default {
name: 'Dropdown',
props: ['label', 'options', 'onChange'],
data() {
return {
choices: this.options,
isOpen: false
}
},
methods: {
click(option) {
this.onChange(option);
},
toggle() {
this.isOpen = !this.isOpen;
}
}
}
</script>
Now in a parent component, I can do something like this:
<template>
<div class="container">
<dropdown
label="-- Select --"
:options="ratingChoices"
:onChange="toggleChoice"
>
</dropdown>
<dropdown
label="-- Select --"
:options="ratingChoices"
:onChange="toggleChoice"
>
</dropdown>
</div>
</template>
<script>
import Dropdown from '../dropdown/dropdown.component.vue';
export default {
name: 'main-directive',
components: { Dropdown },
methods: {
toggleChoice(newChoice) {
// Save this state to a store, e.g. Vuex
}
},
computed: {
ratingChoices() {
return [{
value: true,
label: 'Yes'
}, {
value: false,
label: 'No'
}]
}
}
}
</script>
There's a decent amount of code here. What's happening is we're setting up a parent component and inside that parent component we have two dropdowns. In other words, the dropdown component is being called twice. The point I'm trying to make in showing this code is this: when you click on the dropdown, the isOpen for that dropdown changes for that directive and for that directive only. Clicking on one of the dropdowns does not affect the other dropdown in any way.
Don't choose between components or directives based on whether or not you're wanting discrete data. Components allow for discrete data.
So when would you want to choose a directive in Vue?
Here are a couple of guidelines that'll hopefully get you thinking in the right direction.
You want to choose a directive when you're wanting to extend the functionality of HTML components and you suspect that you’re going to need this extendability across multiple components and you don't want your DOM to get deeper as a result. To understand what I mean by this, let's look at the directives that Vue provides out of the box. Take its v-for directive for instance. It allows you to loop through a collection. That's very useful and you need to be able to do that in any component you want, and you don't want the DOM to get any deeper. That's a good example of when a directive is the better choice.[1]
You want to choose a directive when you want a single HTML tag to have multiple functionality. For example, an element that both triggers an Ajax request and that has a custom tooltip. Assuming you want tooltips on elements other than Ajax-triggering elements, it makes sense to split these up into two different things. In this example I would make the tooltip a directive and the Ajax feature driven by a component so I could take advantage of the built-in #click directive that’s available in components.
1 A footnote for the more curious. In theory v-for could have been made as a component, but doing so would have required a deeper-than-necessary DOM every time you wanted to use v-for as well as a more awkward syntax. If Vue had chosen to make a component out of it, instead of this:
<a v-for="link in links" :href="link.href">link.anchor</a>
The syntax would have had to have been this:
<v-for items="link in links">
<a :href="link.href">link.anchor</a>
</v-for>
Not only is this clumsy, but since the component code would have needed to implement the <slot></slot> syntax in order to get the innerHTML, and since slots cannot be immediate children of a <template> declaration (since there's no guarantee that slot markup has a single node of entry at its top level), this means there would have to be a surrounding top-level element in the component definition for v-for. Hence the DOM would get deeper than necessary. Directive was unequivocally the right choice here.
I think of it this way:
Components define widgets - these are sections of html that have behavior associated with them.
Directives modify behavior of sections of html (which may or may not be widgets).
I think this difference is better explained with two examples.
Components: are wrappers that are best suited when you need to insert (or add) your own HTML tags over something to render it. E.g. a widget, a custom button, etc where you would need to add some HTML tags to show it properly.
Directives: don't add tags but rather give you direct access to the HTML tag (to which you have added the directive). This gives you access to modify the attributes of that HTML element directly. E.g. initializing a tooltip, set css styles, bind to an event, etc.
Reusability is a reason for using directives,
While Components are also creating reusable 'widgets', two components in the same html system would overwrite the previous ones 'data', So think of directives in a case like this.
Another point worth thinking of is - Can user be using it via HTML only after some instructions ?
I'm using https://vue-multiselect.js.org in my Vue component. I am using AJAX to update the select list option, and I see in the inspector that there are 10 values.
But when I actually click into the multiselect list, I only see a subset of these items and I can't figure out why.... Here are my multi-select props/events:
<multiselect
v-model="organization"
:allow-empty="true"
:loading="loading"
:options="organizationSearch"
:preserve-search="true"
#search-change="searchOrganizations"
#select="organizationSelected"
#remove="organizationDeselected"
placeholder="Search Organizations"
label="name"
track-by="id"
class="multiselect my-3"
>
</multiselect>
I figured it out. I dug into the component code a bit and found that all the options WERE in the options prop, but there were only 3 in the filteredOptions computed field. I added
:internal-search="false" into my component and then this fixed it.
I think the ultimate issue here had to do with conflict between the internal filtering, and the dynamic update of the options I was doing via AJAX.
Below is the definition of filteredOptions in the actual component definition.
It could be more helpful if you expand the options array in the inspector, so we could see what properties are presented in the objects.
track-by is used to identify the option within the options list thus it’s value has to be unique.
You set track-by="id" and label="name", so your options array should look something like this:
options: [
{ id: 1, name: 'Option #1' },
{ id: 2, name: 'Option #2' },
{ id: 3, name: 'Option #3' },
],
Make sure that all the items in your options array have an id property and it is unique for all of them, because the items with the same id will not appear in the multiselect list.
This depends on the structure of your options.
It shows that they are objects when they should probably be literal values.