How can I reuse templates in Vue 2? - vuejs2

I have two forms (add and edit). All the fields in both of the forms are exactly the same (i.e. the templates are the same). Both the forms are accessible from the homepage through their respective buttons. How can I reuse a single Vue template for Add form as well as Edit form?
I am writing class based components in TypeScript.

You can create a component called customform or something, then declare props that will be passed into the component to specify what type it is (edit form, create form) then inside the form you'll need to write your logic, something like this:
<customForm :edit="true" :create="false" :data="data"></customForm>
Then in the form component:
<template>
<form class="customform">
<div class="row">
<label>Email: </label><input type="text" value="{{data.email || ''}}">
</div>
</form>
</template>
<script>
export default {
props: {
edit: {
type: Boolean,
default: false
},
create: {
type: Boolean,
default: false
},
data: {}
}
}
</script>
The Idea is to be able to render the form using props (e.g if edit is true then you will have to inject the values and change the action of the form, if create then the value of the fields will be empty and the action will be different...etc)

Related

How to change the language of whole application from the dropdown in Navbar component in vue

I created a language selection dropdown in my Navbar component. So here is my navbar component:
<div>
<h6>{{ translate("welcomeMsg")}} </h6>
<select name="lang" v-model="lang">
<option value="en">English</option>
<option value="de">Deutsch</option>
</select>
</div>
<script>
export default {
mixins: [en, de],
data() {
return {
lang: "en",
};
},
methods: {
translate(prop) {
return this[this.lang][prop];
}
}
}
</script>
So the parent of this component is an Index.vue which is main component in my application.
<div id="app">
<Topnav/>
<Navbar/>
<router-view></router-view>
<Footer/>
</div>
Currently, I am able to change the language in my Navbar component. So according to the selected value in the dropdown in Navbar component, welcomeMsg is changing. What I am trying to do is I want to put this pieve of code to TopBar "{{ translate("welcomeMsg")}} ", and according to the value of the dropdown in Navbar component, I want to change this value.
Can you help me with this or can you give me an idea how to do it?
If I understand you correctly, you want to use translate method inside Topnav component.
This method is however defined in Navbar, so it's not accessible in Topnav.
To use it elsewhere you could create a mixin with this method to import it to any component. I don't recommend this solution though as mixins are making the code messy.
Another solution is to create a component with translate method defined inside. Let this component do just that: translate a message passed by prop and render it inside some div:
<div>
{{ translatedMessage }}
</div>
<script>
mixins: [en, de],
props: {
message: {
type: String,
default: ''
},
language: {
type: String,
default: 'en'
}
},
computed: {
translatedMessage() {
return this[this.language][this.message];
}
}
</script>
You can reuse this component anywhere in the application. You would still need to pass a language prop somehow, possibly the solution would be to use vuex store to do this, since language is probably global for entire application.
For easier and more robust solutions I would use vue-i18n, which #Abregre has already suggested in his comment: https://stackoverflow.com/a/70694821/9463070
If you want a quick solution for a full-scale application and you don't have a problem with dependencies, you could try to use vue-i18n.
It's a plugin for vue that does exactly this for multi-locale websites/apps in Vue.
https://www.npmjs.com/package/vue-i18n
EDIT
Then in order to use it globally in your app, you should use vuex.
Keep the language selection state there and then wherever you want to use it, you make a computed function with the state.language getter.
The translate function should be a global registered filter
https://v2.vuejs.org/v2/guide/filters.html

how to be not hardcoded for Buefy type in vue.js

<b-checkbox v-model="selectedTiers" :native-value="i" type="checkType(i)" #input="update">
{{ $t('general.specificTier', { tier: i }) }}
</b-checkbox>
Hi everyone, I am using Buefy and Vue.js. I want the type to be flexible. That is why I am using the method here. according to different I, it outputs a different string. But type here doesn't recognize the method here. I also can't use string + string here.
Here is information about checkbox of buefy.
You can use v-bind directive to dynamically alter the attributes.
Here is an example to get your started:
<template>
<div id="app">
<!-- Example with string manipulation -->
<b-checkbox :type="`is-${type}`">TEST 1</b-checkbox>
<!-- Example by reading off compenent-data -->
<b-checkbox :type="checkboxType">TEST 2</b-checkbox>
</div>
</template>
<script>
export default {
name: "App",
components: {},
data() {
return {
type: 'success',
checkboxType: "is-success"
};
}
};
</script>
One last thing, you can still use a method there (just add : before the attribute name - like :type="checkType(i)"), but the function would only be evaluated once and any further data changes won't be reflected on the type attribute (won't update on data change)

Good practice for re-usable form component in Vue

I'm trying to write a re-usable form component for a "user". I'd like to be able to use it in both an "edit" and a "create" flow. I'd pass it a user object, it would validate as a user enters/modifies the user data and it would update the user object. The parent component (e.g EditUser, CreateUser) will do the actual saving.
How should I do this without mutating props? I've avoided using events so far because I'd need to fire one on every user input, passing a copy of the user object back up to the parent (I think).
EDIT: adding some (non-working) code to demonstrate. I can't share my exact code.
Parent component
<template>
<div >
<h1>header</h1>
<MyFormComponent
v-model="user"
>
</MyFormComponent>
<button>Save</button>
</div>
</template>
<script>
export default {
data(){
return {
user: {
name: 'User 1'
}
}
}
}
</script>
Form component
<template>
<form>
<MyFormInputComponent
v-model="user.name"
></MyFormInputComponent>
</form>
</template>
<script>
export default {
props: ['user'],
model: {
prop: 'user'
}
}
</script>
Thanks!
I don't know exactly your context, but this is how I use to do:
First, you don't need both components Parent and Child. You can do all you want inside Form Component.
To deal with the differences between create and edit modes, an option is computed property based on current route (if they are different according to create/edit operations).
Using this property, you decide if data will be fetched from API, if delete button will has shown, the title of the page and so on.
Here is an example:
async created() {
if (this.isEditMode) {
// fetch form data from API according to User ID and copy to a local form
},
},
computed: {
formTitle() {
return (this.isEditMode ? 'Update' : 'Create') + ' User';
},
}

Modify props.value from within child component

I am new to Vue and trying to build a "dropdown" component. I want to use it from a parent component like this:
<my-dropdown v-model="selection"></my-dropdown>
where selection is stored as data on the parent, and should be updated to reflect the user's selection. To do this I believe my dropdown component needs a value prop and it needs to emit input events when the selection changes.
However, I also want to modify the value from within the child itself, because I want to be able to use the dropdown component on its own (I need to modify it because otherwise the UI will not update to reflect the newly selected value if the component is used on its own).
Is there a way I can bind with v-model as above, but also modify the value from within the child (it seems I can't, because value is a prop and the child can't modify its own props).
You need to have a computed property proxy for a local value that handles the input/value values.
props: {
value: {
required: true,
}
},
computed: {
mySelection: {
get() {
return this.value;
},
set(v) {
this.$emit('input', v)
}
}
}
Now you can set your template to use the mySelection value for managing your data inside this component and as it changes, the data is emitted correctly and is always in sync with the v-model (selected) when you use it in the parent.
Vue's philosophy is: "props down, events up". It even says this in the documentation: Composing Components.
Components are meant to be used together, most commonly in
parent-child relationships: component A may use component B in its own
template. They inevitably need to communicate to one another: the
parent may need to pass data down to the child, and the child may need
to inform the parent of something that happened in the child. However,
it is also very important to keep the parent and the child as
decoupled as possible via a clearly-defined interface. This ensures
each component’s code can be written and reasoned about in relative
isolation, thus making them more maintainable and potentially easier
to reuse.
In Vue, the parent-child component relationship can be summarized as
props down, events up. The parent passes data down to the child via
props, and the child sends messages to the parent via events. Let’s
see how they work next.
Don't try to modify the value from within the child component. Tell the parent component about something and, if the parent cares, it can do something about it. Having the child component change things gives too much responsibility to the child.
You could use the following pattern:
Accept 1 input prop
Have another variable inside your data
On creation, white the incoming prop into your data variable
Using a watcher, watch the incoming prop for changes
On a change inside your component, send the change away
Demo:
'use strict';
Vue.component('prop-test', {
template: "#prop-test-template",
props: {
value: { // value is the default prop used by v-model
required: true,
type: String,
},
},
data() {
return {
dataObject: undefined,
// For testing purposes:
receiveData: true,
sendData: true,
};
},
created() {
this.dataObject = this.value;
},
watch: {
value() {
// `If` is only here for testing purposes
if(this.receiveData)
this.dataObject = this.value;
},
dataObject() {
// `If` is only here for testing purposes
if(this.sendData)
this.$emit('input', this.dataObject);
},
},
});
var app = new Vue({
el: '#app',
data: {
test: 'c',
},
});
<script src="https://unpkg.com/vue#2.0.1/dist/vue.js"></script>
<script type="text/x-template" id="prop-test-template">
<fieldset>
<select v-model="dataObject">
<option value="a">a</option>
<option value="b">b</option>
<option value="c">c</option>
<option value="d">d</option>
<option value="e">e</option>
<option value="f">f</option>
<option value="g">g</option>
<option value="h">h</option>
<option value="i">i</option>
<option value="j">j</option>
</select>
<!-- For testing purposed only: -->
<br>
<label>
<input type="checkbox" v-model="receiveData">
Receive updates
</label>
<br>
<label>
<input type="checkbox" v-model="sendData">
Send updates
</label>
<!--/ For testing purposed only: -->
</fieldset>
</script>
<div id="app">
<prop-test v-model="test"></prop-test>
<prop-test v-model="test"></prop-test>
<prop-test v-model="test"></prop-test>
</div>
Notice that this demo has a feature that you can turn off the propagation of the events per select box, so you can test if the values are properly updated locally, this is of course not needed for production.
If you want to modify a prop inside a component, I recommend passing a "default value" prop to your component. Here is how I would do that
<MyDropdownComponent
:default="defaultValue"
:options="options"
v-model="defaultValue"
/>
And then there are 2 options to how I would go from there -
Option 1 - custom dropdown element
As you're using custom HTML, you won't be able to set a selected attribute. So you'll need to be creative about your methods.
Inside your component, you can set the default value prop to a data attribute on component creation. You may want to do this differently, and use a watcher instead. I've added that to the example below.
export default {
...
data() {
return {
selected: '',
};
},
created() {
this.selected = this.default;
},
methods: {
// This would be fired on change of your dropdown.
// You'll have to pass the `option` as a param here,
// So that you can send that data back somehow
myChangeMethod(option) {
this.$emit('input', option);
},
},
watch: {
default() {
this.selected = this.default;
},
},
};
The $emit will pass the data back to the parent component, which won't have been modified. You won't need to do this if you're using a standard select element.
Option 2 - Standard select
<template>
<select
v-if="options.length > 1"
v-model="value"
#change="myChangeMethod"
>
<option
v-for="(option, index) of options"
:key="option.name"
:value="option"
:selected="option === default"
>{{ option.name }}</option>
</select>
</template>
<script>
export default {
...
data() {
return {
value: '',
};
},
methods: {
// This would be fired on change of your dropdown
myChangeMethod() {
this.$emit('input', this.value);
},
},
};
</script>
This method is definitely the easiest, and means you need to use the default select element.
You could use a custom form input component
Form Input Components using Custom Events
Basically your custom component should accept a value prop and emit input event when value changes

Update parent model from child component Vue

I have a very small app that has a donation form. The form walks the user through the steps of filling in information. I have a main component, which is the form wrapper and the main Vue instance which holds all of the form data (model). All of the child components are steps within the donation process. Each child component has input fields that are to be filled out and those field will update the parent model so that I have all of the form data in the parent model when I submit the form. Here is how the components are put together:
<donation-form></donation-form> // Main/Parent component
Inside the donation-form component:
<template>
<form action="/" id="give">
<div id="inner-form-wrapper" :class="sliderClass">
<step1></step1>
<step2></step2>
<step3></step3>
</div>
<nav-buttons></nav-buttons>
</form>
</template>
Right now, I am setting the data from the inputs in each child component and then I have a watch method that is watching for fields to update and then I am pushing them to the $root by doing this...
watch: {
amount() {
this.$root.donation.amount = this.amount;
}
}
The problem is that one of my steps I have a lot of fields and I seem to be writing some repetitive code. Also, I'm sure this is not the best way to do this.
I tried passing the data as a prop to my child components but it seems that I cannot change the props in my child component.
What would be a better way to update the root instance, or even a parent instance besides add a watch to every value in my child components?
More examples
Here is my step2.vue file - step2 vue file
Here is my donation-form.vue file - donation-form vue file
You can use custom events to send the data back.
To work with custom events, your data should be in the parent component, and pass down to children as props:
<step1 :someValue="value" />
and now you want to receive updated data from child, so add an event to it:
<step1 :someValue="value" #update="onStep1Update" />
your child components will emit the event and pass data as arguments:
this.$emit('update', newData)
the parent component:
methods: {
onStep1Update (newData) {
this.value = newData
}
}
Here is a simple example with custom events:
http://codepen.io/CodinCat/pen/QdKKBa?editors=1010
And if all the step1, step2 and step3 contain tons of fields and data, you can just encapsulate these data in child components (if the parent component doesn't care about these row data).
So each child has its own data and bind with <input />
<input v-model="data1" />
<input v-model="data2" />
But the same, you will send the result data back via events.
const result = this.data1 * 10 + this.data2 * 5
this.$emit('update', result)
(again, if your application becomes more and more complex, vuex will be the solution.
Personally I prefer having a generic function for updating the parent, when working with forms, instead of writing a method for every child. To illustrate – a bit condensed – like this in the parent:
<template lang="pug">
child-component(:field="form.name" fieldname="name" #update="sync")
</template>
<script>
export default {
methods: {
sync: function(args) {
this.form[args.field] = args.value
}
}
}
</script>
And in the child component:
<template lang="pug">
input(#input="refresh($event.target.value)")
</template>
<script>
export default {
props: ['field', 'fieldname'],
methods: {
refresh: function(value) {
this.$emit('update', {'value': value, 'field': this.fieldname});
}
}
}
</script>
For your case you can use v-model like following:
<form action="/" id="give">
<div id="inner-form-wrapper" :class="sliderClass">
<step1 v-model="step1Var"></step1>
<step2 v-model="step2Var"></step2>
<step3 v-model="step3Var"></step3>
</div>
<nav-buttons></nav-buttons>
</form>
v-model is essentially syntax sugar for updating data on user input events.
<input v-model="something">
is just syntactic sugar for:
<input v-bind:value="something" v-on:input="something = $event.target.value">
You can pass a prop : value in the child components, and on change of input field call following which will change the step1Var variable.
this.$emit('input', opt)
You can have a look at this answer where you can see implementation of such component where a variable is passed thouugh v-model.