How to globally override properties of a 3rd party component in VueJS - vue.js

I am using a component from a library which provides properties to set some texts:
props: {
placeholder: { type: String, default: "Select something" },
// ... More text properties here
}
Usage
// Shows "Select something" when no value selected
<vue-multiselect></vue-multiselect>
// To show something else, you can specify the prop
<vue-multiselect placeholder="Another message here"></vue-multiselect>
All across the application we will be using the same texts, and of course, we would like to avoid repeating it everywhere.
// Shows "This is the default text" when no value selected
<vue-multiselect></vue-multiselect>
Given that we have no control on the component, how can we set those property defaults globally?
Vue plugin? Vue directive?
I am quite ok with most basic vue concepts (custom components, events, binding, etc.) but as I don't know plugins and directives I am stuck on that one.

Related

How to access only Vue.js components elements using a global mixin?

Trying to create a plugin with global mixin which would automatically look for specific element and change its attributes.
export default {
// called by Vue.use(ThisPlugin)
install(Vue, options) {
Vue.mixin({
created() {
console.log($("div").length); // get rid of jQuery and global content
},
});
},
};
As this is called on every vue component I want to limit content mixin accesses with similar like el parameter in directives or like components have element querySelector (this.$el.querySelector("div")) and to replace jquery usage. Is my approach correct and how would I access only components contents in a mixin?
Want to skip directives as those would need to modify tons of existing components, rather introduce a plugin for a component.

Is it possible to access a HTML element using a ref in the data section of a component with Vue 2?

Is it possible to access $refs from the data section of a Vue (2.6.12) component e.g.
flickingPlugins: [new Arrow({
prevElSelector: '.overview-arrow-prev',
nextElSelector: '.overview-arrow-next',
parentEl: this.$refs['overview-arrows'], // _this is undefined
})],
The reason I'm asking is because of the requirements of the Flicking slider/carousel component. This component has an additional arrow plugin, for arrows to appear on either end to control sliding content left and right. The plugin docs (3rd example) provide a Vue example that looks like this:
data() {
return {
plugins: [new Arrow({ parentEl: document.body })]
}
}
The parentEl attribute is a way to specify where your custom arrow control elements will be located, and requires a HTMLElement value. I've always been told that element access should be done using refs, but how would you do that in this case?
As far as I'm aware and the lifecycle documentation reads, this is not possible due to the design of Vue.js.
Created
[...] At this stage, the instance has finished processing the options which means the following have been set up: data observation, computed properties, [...]. However, the mounting phase has not been started, and the $el property will not be available yet.
So you will have to wait until your component has been mounted in order to access the element and its references.
Why not try an approach like this:
{
// ...
data () {
// no access to $el and $refs
return {
plugins: []
}
},
mounted () {
// here you have access to $el and $refs
this.plugins.push(new Arrow({ parentEl: /* ... */ }))
},
}

Dynamically changing chart types with Vue Chart.js

I'm trying to use Vue Chart.js to implement a chart selector to visualise various data.
Structure of application:
ChartPicker.vue (allow user to pick chart, create data, use dynamic component key to re-render component)
ChartWrapper.vue (receives props and passes them on, creates mixin for dynamic chart type)
ChartRender.vue (simply renders chart)
In the chart render component you usually you need to do 'extends: Bar', 'extends: Line' etc, therefore, requiring a ChartRender component type for each chart type. I found a neat solution that passes in the chart type to the chart mixins, then the final chart render makes no reference to chart type (not quite clear how this works even after looking at vue-chart.js code). This is the example I based my code on (it has no chart type selection):
https://codesandbox.io/s/vue-template-original-1czfi
So, I tried to extend functionality of that example to add a chart selector. It's working to an extent on chart type change: data changes, components re-render but the chart type doesn't change (even though it's being passed to the mixin dynamically)
I have a running example here:
https://codesandbox.io/s/vue-chart-issue-v2-twg3o
I've spent nearly a week trying to figure this out with no joy. I could create a workaround to use a separate ChartRender component for each chart type (e.g. ChartRenderBar, ChartRenderLine etc) but it moves away from DRY, so would rather not.
If anybody could help, I'd be VERY appreciative,
It is possible to dynamically update your chart type with vue-chartjs. The way I did it is by accessing the options in the chart itself and replacing it with the prop I get in which says which chart type it should become and then do an update on the chart. It is not the most elegant solution but it works.
<script>
import { Line, mixins } from 'vue-chartjs';
const { reactiveProp } = mixins;
export default {
extends: Line,
name: "LineChart",
mixins: [reactiveProp],
props: {
options: { type: Object },
chartType: { type: String }
},
mounted () {
this.renderChart(this.chartData, this.options);
},
watch: {
options: {
deep: true,
handler () {
this.$data._chart.options = this.options;
this.updateChart();
}
},
chartType (newVal) {
this.$data._chart.config.type = newVal;
this.updateChart()
}
},
methods: {
updateChart () {
this.$data._chart.update();
},
}
}
</script>
in Vuejs, it is not possible to change mixins after the component being created, and because of this I've used a wrapper component in my other solution, so I can pass a mixin dynamically before the component being created, but unfortunately, there is no way to have some kind of reactive mixin.
my solution for you current situation would be something like that:
https://codesandbox.io/s/vue-template-y5wsw
in the above solution, I have created two components, BarChart and LineChart
to switch between those dynamically, I am using one of the most awesome features in vuejs, Dynamic Component
and of course to avoid duplicating the data source, you can use vuex to share data between multiple components, or you can have the data in the parent page and access your dataset or options like
this.$parent['whatever data property in your parent component']
Hope you found this helpful.

Injecting property defaults into 3rd party Vue component

I am using a datepicker component in my project. Basic usage would be like this:
date-picker(language="fr" v-model="date")
There are several attributes which will get repeated each time we need to use a date picker: language for instance.
So I would like to be able to simply do that when a date picker is needed.
date-picker(v-model="date")
And that would default to fr for the language property of the 3rd party library.
Here is what I have tried:
A custom component which extends the Datepicket component: Not that great as I need to define a template which contains the original date picker component. That translates to a superfluous wrapper component
A plugin? I can only inject properties to the global Vue instance. (pretty new to Vue)
Mixin does not apply as I would need to change the 3rd party component
Not sure how you extended the component. But this should be elegant enough?
for e.g.
Vue.component("extended-datepicker", {
extends: vuejsDatepicker,
props: {
format: {
default: "yyyy MMM(MM) dd"
},
language: {
default: fr
}
}
});
demo: https://jsfiddle.net/jacobgoh101/5917nqv8/2/
Update for the problem where "single file components are required to provide a template tag"
A Vue component is essentially a JavaScript object with certain properties.
You don't always need to use .vue single file component. In this case, you can just create a .js file that export an object.
For e.g. this ExtendedDatepicker.js would be a valid Vue component
import Datepicker from "vuejs-datepicker";
export default {
extends: Datepicker,
props: {
format: {
default: "yyyy MMM(MM) dd"
}
}
};
demo: https://codesandbox.io/s/9kn29053r

How know if Object passed by prop is changed in Vuejs

How can i know if my object retrivied by props is changed or not?
Example.
I have an object passed by props like:
object:{
id: 1,
list: [{..},{..}],
propertyExample: true,
message: "I know that You will change this input"
}
And in my html frontend I have an input that change value of message or another property like:
<input type="text" v-model="object.message" />
And I would notify when my "entire original object" (that passed by prop) is changed. If I use watch deep the problem As documentation says is:
Note: when mutating (rather than replacing) an Object or an Array, the
old value will be the same as new value because they reference the
same Object/Array. Vue doesn’t keep a copy of the pre-mutate value.
So I have an object retrieved by props, so I should "disable" save button if object is equals to "original" or "enable" if object is different so if I make an update in frontend like modify property.
so If I enter in a page with my component I have original object like above described, and my save button is disabled because the "object" is not changed.
I would enable my save button if I change one of the properties of my object.
so example if I add a object in a property list array described, or if I change property message, or if I add a new property.
Watch function will be called when one of property in props object has been changed.
You can also use "v-bind" to pass all the properties of the object as props:
so
<demo v-bind="object"></demo>
will be equivalent to
<demo :id="object.id" :list="object.list" :propertyExample:"object.propertyExample" :message="object.message"></demo>
Then you can watch message prop individually for changes.
Edit
You can also use Vue Instance Properties.
There may be data/utilities you’d like to use in many components, but you don’t want to pollute the global scope. In these cases, you can make them available to each Vue instance by defining them on the prototype:
Vue.prototype.$appName = 'My App'
Now $appName is available on all Vue instances, even before creation. If we run:
new Vue({
beforeCreate: function () {
console.log(this.$appName)
}
})
Add watcher to that passed prop. and do something when changed.
watch: {
passedProp(changedObject) {
//do something...
change the variable which stands for enabling the "SAVE" button
}
}
OR if you are not using webpack/babel
watch: {
passedProp: function(changedObject) {
//do something...
change the variable which stands for enabling the "SAVE" button
}
}