How to make dynamic props for element UI - javascript

I am working on a Element-UI datepicker component which I wrapped into my own needs.
I want to add one or more props to a default value.
pickerOptions: {
type: Object,
default: () => ({
addedProp: '',
defaultProp: 'firstDayOfWeek: 1',
}),
},
How can I make addedProp be dynamic. How can I setup this prop inside a component. defaultProp should always be active as default.
addedProp should be like toggleable, only if need.

I'm not sure if I understand you correctly, but you can change your pickerOptions to be a computed data, like here: https://jsfiddle.net/Lczj0ndp/1/
data() {
return { dynamicValue: '' };
}
computed: {
pickerOptions1() {
return {
someOption: this.dynamicValue,
...
}
}
and then you can make one or more of your options dynamic.
More about computed data you can read here.

Fixed it with:
computed: {
combinedPickerOptions() {
return {
firstDayOfWeek: 1,
...this.pickerOptions,
};
},
},
Solution is the spread operator. Now i can set "firstDayOfWeek" as default and set more options if need.

Related

Vue, filter property on Observable

I'm pretty new to Vue, what I'm doing now is the following.
I receive an Item prop in my component, I spread this Item prop out over a Form data object that's defined in my component (as to have reactivity)
data() {
return {
form: {}
}
mounted () {
this.form = {
...this.item,
translations: { ...this.item.translations }
}
},
Now my local form data holds the information, including reactive translations, right?
Next thing I try to do is filter this data, but then it's failing me.
If I console.log(this.form). It is an Observable (see screenshot)
Is there a way to filter, reduce, map on this 'Observable'?
Am I doing 'reactivity' the right way?
Try clone/deepClone, before assigning the item to this.form.
You can access props from data() directly.
data() {
return {
form: {
...this.item,
translations: { ...this.item.translations }
}
}
},
computed: {
getForm() {
// use filter/map method here, e.g.
// return this.form.filter((item) => { ... })
}
}

Reactivity in depth in data

I need some help. I have a component where I pass a prop and I need to assign a variable from my data to the prop variable, but in a reactive way. I cant modify the child it only accepts Booleans. The problem is that Vue initializates the data, but the disabled attribute isnt reactive. I know that if I pass an object to the disabled attribute that will be reactive, but I cant.
data() {
let editmode = true;
return {
EditMode: editmode,
schema: [
{
disabled: !editmode,
}
]
}
}
In the future I need to edit the value of EditMode and I want to that edit to be passed to my child component. I pass the schema variable to the child.
The attribute disabled is not reactive because he is not receiving a referenced variable to pass it reference across the data structure, basically you is saying disabled: !true, that evaluated to disabled: false, and "false" is just false, not a reference from the variable because a boolean is not referenceable, a object is (by example, and this is one of reasons to data hook return a object)! And if you change the editmode variable nothing is gona to happen ... Rethink your data structure and edit what your need putting these data in a object to be reactive! Hope it helps.
If you really need two separated variables, a workaround is use a watcher to detect when editMode changes and populate the changed value to another variable in data..
<template>
<div>
<button #click="change">Change Edit Mode {{editMode}}</button>
<child-component :isEditing="!schema.disabled"></child-component>
</div>
</template>
<script>
export default {
name: "test2",
components: {
'child-component': {
template: "<div>Editing? {{isEditing}}</div>",
props: {
isEditing: {
required: true,
type: Boolean
}
}
}
},
data(){
return {
editMode: true,
schema: {
disabled: false
}
}
},
methods: {
change(){
this.editMode = !this.editMode;
}
},
watch: {
editMode(n){
this.schema.disabled = !n;
}
}
}
</script>
Thanks for your response! I think I havent described my problem enough. I understood that a variable isnt referencable only the objects. I post the answer I got in the Vue forum, in the hope I can help someone else, because this solved my problem. Solution
<template>
<form-generator :schema="schema">
</template>
<script>
Data() {
return {
EditMode: false,
},
methods: {
Edit() {
this.EditMode = true.
}
},
computed: {
schema() {
return [
{
type: "MInput",
disabled: !this.EditMode,
}
]
}
},
</script>

Vue.js 2: Cant useTwo-way Computed Property in combination with vuex

I cant get Two-way Computed Property in combination with vuex to work.
If there are input changes I want to set getIsUnsavedData to true and "copy"/commit the changes into a new variable $store.state.authenticatedUser.changedData. After there is any change I want the input to get() its value from $store.state.authenticatedUser.changedData instead of $store.state.authenticatedUser.data to display the change.
At fist everything works like expected. If there are input changes, the changed value will be replicated in the $store.state.authenticatedUser.changedData property. The getIsUnsavedData value changes to true and the get() points to the replicated data.
There is only one bug left. Suddenly the computed property never changes although the vuex store is updating correctly. The set() function still gets called, but the get() doesn't .
<ui-textbox #change="valueChanged" v-model="userName"></ui-textbox>
// ...
computed: {
userName: {
get() {
return this.$store.getters.getIsUnsavedData ? this.$store.state.authenticatedUser.changedData.name : this.$store.state.authenticatedUser.data.name
},
set(value) {
this.$store.commit('setChangedUserData', {
key: 'name',
value: value
})
}
}
},
methods: {
valueChanged() {
this.$store.commit('setUnsavedState', true)
}
},
// ....
Try to use mine library
https://github.com/yarsky-tgz/vuex-dot
it's done for such situations, to make code footprint of setters/getters in your code much less.
<template>
<form>
<input v-model="name"/>
<input v-model="email"/>
</form>
</template>
<script>
import { takeState } from 'vuex-dot';
export default {
computed: {
...takeState('user')
.expose(['name', 'email'])
.dispatch('editUser')
.map()
}
}
</script>
(updated to reflect Andor's input)
v-model can point to a computed property but it needs to handle work a bit differently than when it just refers to data.
vue's doc
applying the section on two-way computed properties:
<ui-textbox v-model="userName"></ui-textbox>
// ...
computed: {
userName: {
get () {
return this.$store.state.obj.userName
},
set (value) {
this.$store.commit('updateUserName', value)
}
}
}
I might be missing something about what you're doing but this is how I'd start to solve the problem.

Merge Vue props with default values

I have an options prop in my Vue component that has a default value.
export default {
props: {
options: {
required: false,
type: Object,
default: () => ({
someOption: false,
someOtherOption: {
a: true,
b: false,
},
}),
},
},
};
If the options object is passed as a prop to the component, the default value is replaced. For example, when passed { someOption: true }, now the options object contains only that value.
How can I pass a partial object and override the default values with the given values instead of replacing the whole object?
I've encountered a similar problem recently and used Object.assign
Here is the docs from mozilla https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
A concrete usage of your case would be something like that:
props: {
options: {
required: false,
type: Object,
default: () => ({}),
},
},
data(){
mergedOptions:{},
defaultOptions:{
someOption: false,
someOtherOption: {
a: true,
b: false,
},
}
},
mounted(){
//you will have the combined options inside mergedOptions
Object.assign(this.mergedOptions,this.defaultOptions,this.options)
}
By doing this, you will override only the properties that passed via props. Don't know if it's the most efficient way but it's very understandable and neat :)
So if you pass in as props :options={someOption:true} the merged options will be equivalent to:
{
someOption: true,
someOtherOption: {
a: true,
b: false,
},
}
EDIT: If you need your data to be reactive, you might want to have a computed.
computed: {
mergedOptions(){
return {
...this.defaultOptions,
...this.options
}
}
}
You will actually never want to modify props within components. If you do, you break one-way-dataflow of parent/child components and your code will be hard to reason about what is affecting what.
Lifted right from the Vue docs, the correct solution is to either (1) use an initial prop or (2) a computed value, so your app can be reactive and respect parent components, and you can rest easy and kick your feet up :)
Both solutions assume your template will use opts for options...
Solution 1: Use an initial prop (defaults and options):
props: ['options', 'defaults'],
data: function () {
var opts = {}
Object.assign(opts, this.defaults, this.options)
return {
opts: opts
}
}
Solution 2: Define a computed property so your component can react to prop changes:
props: ['options', 'defaults'],
computed: {
opts: function () {
let opts = {}
Object.assign(opts, this.defaults, this.options)
return opts
}
}
A quick thought experiement will show, if a parent component changes your input props, your component can properly react.

What's the correct way to pass props as initial data in Vue.js 2?

So I want to pass props to an Vue component, but I expect these props to change in future from inside that component e.g. when I update that Vue component from inside using AJAX. So they are only for initialization of component.
My cars-list Vue component element where I pass props with initial properties to single-car:
// cars-list.vue
<script>
export default {
data: function() {
return {
cars: [
{
color: 'red',
maxSpeed: 200,
},
{
color: 'blue',
maxSpeed: 195,
},
]
}
},
}
</script>
<template>
<div>
<template v-for="car in cars">
<single-car :initial-properties="car"></single-car>
</template>
</div>
</template>
The way I do it right now it that inside my single-car component I'm assigning this.initialProperties to my this.data.properties on created() initialization hook. And it works and is reactive.
// single-car.vue
<script>
export default {
data: function() {
return {
properties: {},
}
},
created: function(){
this.data.properties = this.initialProperties;
},
}
</script>
<template>
<div>Car is in {{properties.color}} and has a max speed of {{properties.maxSpeed}}</div>
</template>
But my problem with that is that I don't know if that's a correct way to do it? Won't it cause me some troubles along the road? Or is there a better way to do it?
Thanks to this https://github.com/vuejs/vuejs.org/pull/567 I know the answer now.
Method 1
Pass initial prop directly to the data. Like the example in updated docs:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
But have in mind if the passed prop is an object or array that is used in the parent component state any modification to that prop will result in the change in that parent component state.
Warning: this method is not recommended. It will make your components unpredictable. If you need to set parent data from child components either use state management like Vuex or use "v-model". https://v2.vuejs.org/v2/guide/components.html#Using-v-model-on-Components
Method 2
If your initial prop is an object or array and if you don't want changes in children state propagate to parent state then just use e.g. Vue.util.extend [1] to make a copy of the props instead pointing it directly to children data, like this:
props: ['initialCounter'],
data: function () {
return {
counter: Vue.util.extend({}, this.initialCounter)
}
}
Method 3
You can also use spread operator to clone the props. More details in the Igor answer: https://stackoverflow.com/a/51911118/3143704
But have in mind that spread operators are not supported in older browsers and for better compatibility you'll need to transpile the code e.g. using babel.
Footnotes
[1] Have in mind this is an internal Vue utility and it may change with new versions. You might want to use other methods to copy that prop, see How do I correctly clone a JavaScript object?.
My fiddle where I was testing it:
https://jsfiddle.net/sm4kx7p9/3/
In companion to #dominik-serafin's answer:
In case you are passing an object, you can easily clone it using spread operator(ES6 Syntax):
props: {
record: {
type: Object,
required: true
}
},
data () { // opt. 1
return {
recordLocal: {...this.record}
}
},
computed: { // opt. 2
recordLocal () {
return {...this.record}
}
},
But the most important is to remember to use opt. 2 in case you are passing a computed value, or more than that an asynchronous value. Otherwise the local value will not update.
Demo:
Vue.component('card', {
template: '#app2',
props: {
test1: null,
test2: null
},
data () { // opt. 1
return {
test1AsData: {...this.test1}
}
},
computed: { // opt. 2
test2AsComputed () {
return {...this.test2}
}
}
})
new Vue({
el: "#app1",
data () {
return {
test1: {1: 'will not update'},
test2: {2: 'will update after 1 second'}
}
},
mounted () {
setTimeout(() => {
this.test1 = {1: 'updated!'}
this.test2 = {2: 'updated!'}
}, 1000)
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app1">
<card :test1="test1" :test2="test2"></card>
</div>
<template id="app2">
<div>
test1 as data: {{test1AsData}}
<hr />
test2 as computed: {{test2AsComputed}}
</div>
</template>
https://jsfiddle.net/nomikos3/eywraw8t/281070/
I believe you are doing it right because it is what's stated in the docs.
Define a local data property that uses the prop’s initial value as its initial value
https://vuejs.org/guide/components.html#One-Way-Data-Flow
Second or third time I run into that problem coming back to an old vue project.
Not sure why it is so complicated in vue, but it can we done via watch:
export default {
props: ["username"],
data () {
return {
usernameForLabel: "",
}
},
watch: {
username: {
immediate: true,
handler (newVal, oldVal) {
this.usernameForLabel = newVal;
}
},
},
Just as another approach, I did it through watchers in the child component.
This way is useful, specially when you're passing an asynchronous value, and in your child component you want to bind the passed value to v-model.
Also, to make it reactive, I emit the local value to the parent in another watcher.
Example:
data() {
return {
properties: {},
};
},
props: {
initial-properties: {
type: Object,
default: {},
},
},
watch: {
initial-properties: function(newVal) {
this.properties = {...newVal};
},
properties: function(newVal) {
this.$emit('propertiesUpdated', newVal);
},
},
This way I have more control and also less unexpected behaviour. For example, when props that passed by the parent is asynchronous, it may not be available at the time of created or mounted lifecycle. So you can use computed property as #Igor-Parra mentioned, or watch the prop and then emit it.
Following up on Cindy's comment on another answer:
Be carful. The spread operator only shallow clones, so for objects
that contain objects or arrays you will still copy pointers instead of
getting a new copy.
Indeed this is the case. Changes within objects inside arrays will still propagate to your components even when a spread operator is employed.
Here was my solution (using Composition API):
setup() {
properties = ref([])
onMounted(() => {
properties.value = props.initialProperties.map((obj) => ({ ...obj }));
})
}
This worked to set the values and prevent them from getting changed, even if the data was changed in the parent component.

Categories

Resources