Injecting property defaults into 3rd party Vue component - vue.js

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

Related

Replaceable paths for vue component using httpVueLoader

Consider that instead of literal component url I want to use variable in component loader for the component path (vue2 + httpVueLoader not as node CLI)
.... html page .....
const myapp = new Vue({
el: '#app',
components: {
'my-component': httpVueLoader(this.component_path)
},
data() {
return {
component_path: my-path
}
},
....
});
where my-path is populated from other JavaScript part on the page external to shown vue code but nevertheless visible to vue.
Is this doable? and how it is doable if it is doable?
If someone asks as to why I need this - the answer is simple: I have different paths in different implementations and want to reuse same code for the logic and do not want every time to change paths for each implementation manually because they can be only static literal path string.
wider question is: when component code part is evaluated in vue rendering life-time - before or after assigning main vue script data? In other words are components loaded first and then data or data is loaded first and then components get loaded.

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.

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.

Vue.js Create a helper class to call your methods globally

I have just started my first project with Vue.js, I have managed to do a lot of basic things and now I am trying to structure the project. I want to achieve the highest possible code reuse. One of the most frequent cases of my application is going to be showing messages of different types, confirmation, information, etc. For this reason, I want to create a mechanism that allows me to launch these messages globally, regardless of where I call them.
As far as I have been able to advance, I have opted for the following variant:
1- I have created a directory called classes in my src directory.
2- I have created a file called MessageBox.js inside classes directory with the following content:
import Vue from 'vue';
export default class MessageBox extends Vue {
confirm() {
return alert('Confirm');
}
information() {
return alert('Information');
}
}
I define it like this because I want to call these methods globally as follows:
MessageBox.confirm();
I am really new to Vue.js and I was wondering if there is any other way to achieve the results I am looking for in a more efficient way .... or .. maybe more elegant?
Thank you very much in advance..
There are at least 2 ways of going about this:
Event bus
Rely on Vue.js internals to create a simple EventBus. This is a design pattern used in Vue.js.
Create a file and add the following lines to it
import Vue from 'vue';
const EventBus = new Vue();
export default EventBus;
Create your component that takes care of displaying global dialogs. This is usually registered at the top of the tree, so it can cover the entire real estate.
Import the event bus import EventBus from 'event_bus' and then register for the new events
EventBus.$on('SHOW_CONFIRM', (data) => {
// business logic regarding confirm dialog
})
Now you can import it in any component that wants to fire an event like so
EventBus.$emit('SHOW_CONFIRM', confirmData);
Vuex
You can also use vuex to store global data regarding dialogs and add mutations to trigger the display of the dialogs.
Again, you should define a component that takes care of displaying and push it towards the top of the visual tree.
Note: in both cases you should handle cases in which multiple dialog need to be shown at the same time. Usually using a queue inside the displaying component works.
It's an antipattern in modern JavaScript to merge helper functions that don't rely on class instance into a class. Modules play the role of namespaces.
Helper functions can be defined as is:
messageBox.js
export function confirm() {
return alert('Confirm');
}
They can be imported and used in component methods. In case they need to be used in templates, they can be assigned to methods where needed one by one:
Some.vue
import { confirm } from './util/messageBox';
export default {
methods: { confirm }
}
Or all at once:
import * as messageBox from './util/messageBox';
export default {
methods: { ...messageBox }
}
Helpers can be also be made reusable as Vue mixins:
messageBox.js
...
export const confirmMixin = {
methods: { confirm };
}
export default {
methods: { confirm, information };
}
And used either per component:
Some.vue
import { confirmMixin } from './util/messageBox';
export default {
mixins: [confirmMixin]
}
Or globally (isn't recommended because this introduces same maintenance problems as the use of global variables):
import messageBoxMixin from './util/messageBox';
Vue.mixin(messageBoxMixin);

Access the component name from a Vue directive

I want to create a custom Vue directive that lets me select components on my page which I want to hydrate. In other words, this is what I want to archive
I render my Vue app on the server (ssr)
I attach a directive to some components, like this:
<template>
<div v-hydrate #click="do-something"> I will be hydrated</div>
</template>
I send my code to the client and only those components that have the v-hydrate property will be hydrated (as root elements) on the client.
I want to achieve this roughly this way:
I will create a directives that marks and remembers components:
import Vue from "vue";
Vue.directive("hydrate", {
inserted: function(el, binding, vnode) {
el.setAttribute("data-hydration-component", vnode.component.name);
}
});
My idea is that in my inserted method write a data-attribute to the server-rendered element that I can read out in the client and then hydrate my component with.
Now I have 2 questions:
Is that a feasible approach
How do I get the component name in el.setAttribute? vnode.component.name is just dummy code and does not exist this way.
PS: If you want to know why I only want to hydrate parts of my website: It's ads. They mess with the DOM which breaks Vue.
I could figure it out:
import Vue from "vue";
Vue.directive("hydrate", {
inserted: function(el, binding, vnode) {
console.log(vnode.context.$options.name); // the component's name
}
});
I couldn't get the name of my single file components using the previously posted solution, so I had a look at the source code of vue devtools that always manages to find the name. Here's how they do it:
export function getComponentName (options) {
const name = options.name || options._componentTag
if (name) {
return name
}
const file = options.__file // injected by vue-loader
if (file) {
return classify(basename(file, '.vue'))
}
}
where options === $vm.$options