Vue.js component model update - javascript

Im absolutely new in Vue framework and I need create reusable component with live BTC/LTC/XRP price
For live prices Im using Bitstamp websockets API. Here is example usage with jQuery - run this snippet, is really live.
var bitstamp = new Pusher('de504dc5763aeef9ff52')
var channel = bitstamp.subscribe('live_trades')
channel.bind('trade', function (lastTrade) {
$('p').text(lastTrade.price)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>
<h3>BTC/USD price</h3>
<p>loading...</p>
As you can see, its really simple. But, I need to use Vue.js component. So I created this, and its also fully functional:
var bitstamp = new Pusher('de504dc5763aeef9ff52')
Vue.component('live-price', {
template: '<div>{{price}}</div>',
data: function () {
return {
price: 'loading...'
}
},
created: function () {
this.update(this)
},
methods: {
update: function (current) {
var pair = current.$attrs.pair === 'btcusd'
? 'live_trades'
: 'live_trades_' + current.$attrs.pair
var channel = bitstamp.subscribe(pair)
channel.bind('trade', function (lastTrade) {
current.price = lastTrade.price
})
}
}
})
new Vue({
el: '.prices'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>
<section class="prices">
<live-price pair="btcusd"></live-price>
<live-price pair="ltcusd"></live-price>
<live-price pair="xrpusd"></live-price>
</section>
But, there is big BUT. Am I using Vue right way? WHERE IS IDEAL PLACE to run Pusher? In "created" or "mounted" method? In "computed"? In "watch"? Or where? Am i doing it right? I really dont known, I started with Vue ... today :(

Looks pretty good for your first day using Vue! I would just make a few changes.
The component is reaching out and using a global, bitstamp. Generally with components, you want them to be independent, and not reaching out of themselves to get values. To that end, declare the socket as a property that can be passed in to the component.
Likewise, the pair is passed in as a property, but you do not declare it and instead, use current.$attrs.pair to get the pair. But that's not very declarative and makes it harder for anyone else to use the component. Moreover, by making it a property, you can reference it using this.pair.
When using something like a socket, you should always remember to clean up when you are done using it. In the code below, I added the unsubscribe method to do so. beforeDestroy is a typical lifecycle hook to handle these kinds of things.
Computed properties are useful for calculating values that are derived from your components data: the channel you are subscribing to is a computed property. You don't really need to do this, but its generally good practice.
A Vue can only bind to a single DOM element. You are using a class .prices which works in this case because there is only one element with that class, but could be misleading down the road.
Finally, created is an excellent place to initiate your subscription.
console.clear()
var bitstamp = new Pusher('de504dc5763aeef9ff52')
Vue.component('live-price', {
props:["pair", "socket"],
template: '<div>{{price}}</div>',
data() {
return {
price: 'loading...',
subscription: null
}
},
created() {
this.subscribe()
},
beforeDestroy(){
this.unsubscribe()
},
computed:{
channel(){
if (this.pair === 'btcusd')
return 'live_trades'
else
return 'live_trades_' + this.pair
}
},
methods: {
onTrade(lastTrade){
this.price = lastTrade.price
},
subscribe() {
this.subscription = this.socket.subscribe(this.channel)
this.subscription.bind('trade', this.onTrade)
},
unsubscribe(){
this.subscription.unbind('trade', this.onTrade)
this.socket.unsubscribe(this.channel)
}
}
})
new Vue({
el: '#prices',
data:{
socket: bitstamp
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>
<section id="prices">
<live-price pair="btcusd" :socket="bitstamp"></live-price>
<live-price pair="ltcusd" :socket="bitstamp"></live-price>
<live-price pair="xrpusd" :socket="bitstamp"></live-price>
</section>

Rewrited - is it ok now?
var config = {
key: 'de504dc5763aeef9ff52'
}
var store = new Vuex.Store({
state: {
pusher: null
},
mutations: {
initPusher (state, payload) {
state.pusher = new Pusher(payload.key)
}
}
})
var livePrice = {
template: '#live-price',
props: ['pair'],
data () {
return {
price: 'loading...',
subscription: null
}
},
computed: {
channel () {
return this.pair === 'btcusd'
? 'live_trades'
: 'live_trades_' + this.pair
}
},
methods: {
onTrade (lastTrade) {
this.price = lastTrade.price
},
subscribe () {
this.subscription = this.$store.state.pusher.subscribe(this.channel)
this.subscription.bind('trade', this.onTrade)
},
unsubscribe () {
this.subscription.unbind('trade', this.onTrade)
this.$store.state.pusher.unsubscribe(this.channel)
}
},
created () {
this.subscribe()
},
beforeDestroy () {
this.unsubscribe()
}
}
new Vue({
el: '#prices',
store,
components: {
'live-price': livePrice
},
created () {
store.commit({
type: 'initPusher',
key: config.key
})
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.3.1/vuex.min.js"></script>
<section id="prices">
<live-price pair="btcusd"></live-price>
<live-price pair="ltcusd"></live-price>
<live-price pair="xrpusd"></live-price>
</section>
<template id="live-price">
<div>
{{price}}
</div>
</template>

Related

Vue component get and set conversion with imask.js

I am trying to use iMask.js to change 'yyyy-mm-dd' to 'dd/mm/yyyy' with my component however when I am setting the value I think it is taking the value before the iMask has finished. I think using maskee.updateValue() would work but don't know how to access maskee from my component.
I am also not sure if I should be using a directive to do this.
Vue.component("inputx", {
template: `
<div>
<input v-mask="" v-model="comp_date"></input>
</div>
`,
props: {
value: { type: String }
},
computed: {
comp_date: {
get: function () {
return this.value.split("-").reverse().join("/");
},
set: function (val) {
const iso = val.split("/").reverse().join("-");
this.$emit("input", iso);
}
}
},
directives: {
mask: {
bind(el, binding) {
var maskee = IMask(el, {
mask: "00/00/0000",
overwrite: true,
});
}
}
}
});
var app = new Vue({
el: "#app",
data: {
date: "2020-12-30"
}
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.12"></script>
<script src="https://unpkg.com/imask"></script>
<div id="app">
<inputx v-model="date"></inputx>
Date: {{date}}
</div>
The easiest way you can achieve this is by installing the external functionality on the mounted hook of your Vue component, instead of using a directive.
In this way you can store the 'maskee' object on your component's data object to later access it from the setter method.
Inside the setter method you can then call the 'updateValue' method as you hinted. Then, you can extract the processed value just by accessing the '_value' prop of the 'maskee' object.
Here is a working example:
Vue.component("inputx", {
template: `
<div>
<input ref="input" v-model="comp_date"></input>
</div>
`,
data: {
maskee: false,
},
props: {
value: { type: String },
},
computed: {
comp_date: {
get: function () {
return this.value.split("-").reverse().join("/");
},
set: function () {
this.maskee.updateValue()
const iso = this.maskee._value.split("/").reverse().join("-");
this.$emit("input", iso);
}
}
},
mounted(){
console.log('mounted');
const el = this.$refs.input;
this.maskee = IMask(el, {
mask: "00/00/0000",
overwrite: true,
});
console.log('maskee created');
}
});
var app = new Vue({
el: "#app",
data: {
date: "2020-12-30"
}
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.12"></script>
<script src="https://unpkg.com/imask"></script>
<div id="app">
<inputx v-model="date"></inputx>
Date: {{date}}
</div>

Vuejs: Passing SAVE function into CRUD component

I am struggeling with a proper solution which requires an advanced parent-child communication in vuejs. There can be many different parent components which has a logic how to save data. From the other side there will be only one child component which has a list of elements and a form to create new elements but it doesn't know how to save the data.
The question is: Is there any other way (better approach) to have the same functionality but to get rid of this.$refs.child links. For example I am wondering if I can just pass a function (SaveParent1(...) or SaveParent2(...)) to the child component. But the problem is the function contains some parent's variables which won't be available in child context and those variables could be changed during the runtime.
Just few clarifications:
The methods SaveParent1 and SaveParent2 in real life return
Promise (axios).
The child-component is like a CRUD which is used
everywhere else.
At the moment the communication looks like that: CHILD -event-> PARENT -ref-> CHILD.
Bellow is the example:
<div id="app">
<h2>😀Advanced Parent-Child Communication:</h2>
<parent-component1 param1="ABC"></parent-component1>
<parent-component2 param2="XYZ"></parent-component2>
</div>
Vue.component('parent-component1', {
props: { param1: { type: String, required: true } },
methods: {
onChildSubmit(p) {
// Here will be some logic to save the param. Many different parents might have different logic and all of them use the same child component. So child-component contains list, form and validation message but does not know how to save the param to the database.
var error = SaveParent1({ form: { p: p, param1: this.param1 } });
if (error)
this.$refs.child.paramFailed(error);
else
this.$refs.child.paramAdded(p);
}
},
template: `<div class="parent"><p>Here is parent ONE:</p><child-component ref="child" #submit="onChildSubmit"></child-component></div>`
});
Vue.component('parent-component2', {
props: { param2: { type: String, required: true } },
methods: {
onChildSubmit(p) {
// Here is a different logic to save the param. In prictice it is gonna be different requests to the server.
var error = SaveParent2({ form: { p: p, param2: this.param2 } });
if (error)
this.$refs.child.paramFailed(error);
else
this.$refs.child.paramAdded(p);
}
},
template: `<div class="parent"><p>Here is parent TWO:</p><child-component ref="child" #submit="onChildSubmit"></child-component></div>`
});
Vue.component('child-component', {
data() {
return {
currentParam: "",
allParams: [],
errorMessage: ""
}
},
methods: {
submit() {
this.errorMessage = "";
this.$emit('submit', this.currentParam);
},
paramAdded(p) {
this.currentParam = "";
this.allParams.push(p);
},
paramFailed(msg) {
this.errorMessage = msg;
}
},
template: `<div><ol><li v-for="p in allParams">{{p}}</li></ol><label>Add Param: <input v-model="currentParam"></label><button #click="submit" :disabled="!currentParam">Submit</button><p class="error">{{errorMessage}}</p></div>`
});
function SaveParent1(data) {
// Axios API to save data. Bellow is a simulation.
if (Math.random() > 0.5)
return null;
else
return 'Parent1: You are not lucky today';
}
function SaveParent2(data) {
// Axios API to save data. Bellow is a simulation.
if (Math.random() > 0.5)
return null;
else
return 'Parent2: You are not lucky today';
}
new Vue({
el: "#app"
});
There is also a live demo available: https://jsfiddle.net/FairKing/novdmcxp/
Architecturally I recommend having a service that is completely abstract from the component hierarchy and that you can inject and use in each of the components. With this kind of component hierarchy and architecture it is easy to run into these issues. It is important to abstract as much functionality and business logic from the components as possible. I think of components in these modern frameworks just merely as HTML templates on steroids, which should at most act as controllers, keeping them as dumb and as thin as possible so that you don't run into these situations. I do not know vue.js so I cannot give you the technical solution but hope this indication helps
I think I have found a solution. So no two ways communication. I can just pass a method and the child will do everything without communicating with parent. I am happy with that I am marking it as an answer. Thanks everyone for your help.
Let me please know what do you think guys.
Bellow is my solution:
<div id="app">
<h2>😀Advanced Parent-Child Communication:</h2>
<parent-component1 param1="ABC"></parent-component1>
<parent-component2 param2="XYZ"></parent-component2>
</div>
Vue.component('parent-component1', {
props: { param1: { type: String, required: true } },
computed: {
saveFunc() {
return function(p) { SaveParent1({ form: { p: p, param1: this.param1 } }); }.bind(this);
}
},
template: `<div class="parent"><p>Here is parent ONE:</p><child-component :saveFunc="saveFunc"></child-component></div>`
});
Vue.component('parent-component2', {
props: { param2: { type: String, required: true } },
computed: {
saveFunc() {
return function(p) { SaveParent2({ form: { p: p, param2: this.param2 } }); }.bind(this);
}
},
template: `<div class="parent"><p>Here is parent TWO:</p><child-component :saveFunc="saveFunc"></child-component></div>`
});
Vue.component('child-component', {
props: {
saveFunc: { type: Function, required: true }, // This is gonna be a Promise in real life.
},
data() {
return {
currentParam: "",
allParams: [],
errorMessage: ""
}
},
methods: {
submit() {
this.errorMessage = "";
var error = this.saveFunc(this.currentParam);
if (error)
this.paramFailed(error);
else
this.paramAdded(this.currentParam);
},
paramAdded(p) {
this.currentParam = "";
this.allParams.push(p);
},
paramFailed(msg) {
this.errorMessage = msg;
}
},
template: `<div><ol><li v-for="p in allParams">{{p}}</li></ol><label>Add Param: <input v-model="currentParam"></label><button #click="submit" :disabled="!currentParam">Submit</button><p class="error">{{errorMessage}}</p></div>`
});
function SaveParent1(data) {
console.log(data);
// Axios API to save data
if (Math.random() > 0.5)
return null;
else
return 'Parent1: You are not lucky today';
}
function SaveParent2(data) {
console.log(data);
// Axios API to save data
if (Math.random() > 0.5)
return null;
else
return 'Parent2: You are not lucky today';
}
new Vue({
el: "#app"
});
The demo link: https://jsfiddle.net/FairKing/novdmcxp/126/

Vue.js global event not working

I've got
<component-one></component-one>
<component-two></component-two>
<component-three></component-three>
Component two contains component three.
Currently I emit an event in <component-one> that has to be caught in <component-three>.
In <component-one> I fire the event like this:
this.$bus.$emit('setSecondBanner', finalBanner);
Then in <component-three> I catch it like this:
mounted() {
this.$bus.$on('setSecondBanner', (banner) => {
alert('Caught');
this.banner = banner;
});
},
But the event is never caught!
I define the bus like this (in my core.js):
let eventBus = new Vue();
Object.defineProperties(Vue.prototype, {
$bus: {
get: () => { return eventBus; }
}
});
What could be wrong here? When I check vue-dev-tools I can see that the event has fired!
This is the working example for vue1.
Object.defineProperty(Vue.prototype, '$bus', {
get() {
return this.$root.bus;
}
});
Vue.component('third', {
template: `<div> Third : {{ data }} </div>`,
props:['data']
});
Vue.component('second', {
template: `<div>Second component <third :data="data"></third></div>`,
ready() {
this.$bus.$on('setSecondBanner', (event) => {
this.data = event.data;
});
},
data() {
return {
data: 'Defautl value in second'
}
}
});
Vue.component('first', {
template: `<div>{{ data }}</div>`,
ready() {
setInterval(() => {
this.$bus.$emit('setSecondBanner', {
data: 'Bus sending some data : '+new Date(),
});
}, 1000);
},
data() {
return {
data: 'Defautl value in first'
}
}
});
var bus = new Vue({});
new Vue({
el: '#app',
data: {
bus: bus
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.28/vue.js"></script>
<div id="app">
<second></second>
<first></first>
</div>
Have you tried registering the listener in created instead of mounted?
Also, why define the bus with defineProperties and not simply:
Vue.prototype.$bus = new Vue();

Dynamically binding title tag in vuejs

I'm new to vuejs.
I try to bind data on the title tag dynamically. I used vue-head to do this on a simple html page. I do not use webpack and npm.
This is how I bind the title tag :
var app = new Vue({
el: 'html',
head: {
title: function () {
return {
inner: this.remaining + ' Tâches',
separator: ' ',
complement: ' '
}
}
}
In the vue-head documentation, they suggest to do this :
methods: {
getAsyncData: function () {
var self = this
window.setTimeout(function () {
self.title = 'My async title'
self.$emit('updateHead')
}, 3000)
}
},
I also tried to set it in the watch prop, but it didn't work.
Here is my entire code : https://jsfiddle.net/5d70s0s6/1/
Thanks
Use a computed property.
computed: {
title: {
get() {
document.title = this.remaining
return this.remaining
},
set(val) {
document.title = val
}
}
}
You don't need to use <title>{{title}}</title>. If you change title in your Vue, it will be applied automatically to the page.
Also, you should not bind a Vue instance to html, head or body tags. Use regular elements only like <div id="app"></div> and set your Vue el: '#app'
Or you could use this:
data: {
title: '',
},
watch: {
title(val) {
document.title = val
}
}
Update:
While the code above can solve your problem. I created this tiny vue-title component that can be used in your project easily.
Example:
<vue-title>{{title}}</vue-title>

Run component method on load vue.js

hey I'm pretty new to Vue.js and I'm trying to accomplish what seems to be a simple thing but I'm, having trouble. Essentially, I need it so every time a component is loaded into the DOM, one of it's methods fire. Here is my current code, I've tried to use v-on:load but it doesn't seem to work.
Vue.component('graph', {
props:['graphId','graphData'],
template: '<canvas v-on:load="{{populateGraph()}}"></canvas>',
methods: {
initGraph: function () {
var settlementBalanceBarChart = new Chart(this.graphId, {
type: "bar",
data: settlementBalanceBarData,
options: settlementBalanceBarOptions
});
},
//this is the function I would like to run
populateGraph: function () {
alert('{{graphId}}');
}
}
});
var vm = new Vue({
el: "#app",
mounted: function(){
}
});
The same code functions fine if I use the v-on:click event
There are instance lifecycle hooks that you can use for that. For example:
Vue.component('graph', {
props:['graphId','graphData'],
template: '<canvas></canvas>',
created: function () {
alert('{{graphId}}');
},
methods: {}
});
You have to call the function prefixed by "this" as following:
var data =
{
cashiers: []
}
var vm = new Vue({
el: '#app',
data: data,
created: function () {
this.getCashiers();
},
methods: {
getCashiers: function () {
vm.cashiers = [];
}
}
});

Categories

Resources