Vue computed property functions are never called - vue.js

I trying to do my own Google Science Journal, but web version with MQTT protocol, but my code have so many bugs.
Some clarification about my code:
google.charts.load is calling in the parent component, then callback make a call for the child component
time props is an interval, change between true and false every second for collect data.
I need to watch changes of a data with multiple dependencies.
How my code works?
When a new instance is created, appear an empty google chart
The chart start to show data every second.
The tittle for the chart change when a new tab is selected and the chart data is erased.
This code has a problem, is the order in which the functions are called (generate some problems with google chart), but I fixed it, moving functions from computed to methods and calling in order.
I just want to know why this computed functions don't work, I mean, they aren't never call.
<template>
<v-card>
<v-tabs
v-model="active_tab"
background-color="red lighten-2"
dark
show-arrows
>
<v-tab
v-for="(n) in tabs"
:key="n.id"
>
{{n.text}}
<v-icon>{{n.icon}}</v-icon>
</v-tab>
</v-tabs>
<div ref="char_div"></div>
</v-card>
</template>
<script>
import debounce from 'debounce'
export default {
name: 'charts',
props:{
time: {
type: Boolean,
default: false
}
},
data(){
return {
chart: null,
options: null,
value: 0,
previus: null,
data: null,
tabs: [
{id: 0, text: "Aceleración lineal", unit: "(m/s²)", icon: ""},
{id: 1, text: "Aceleración en x", unit: "(m/s²)", icon: ""},
{id: 2, text: "Aceleración en y", unit: "(m/s²)", icon: ""},
{id: 3, text: "Aceleración en z", unit: "(m/s²)", icon: ""},
{id: 4, text: "Velocidad lineal", unit: "(m/s)", icon: ""},
{id: 5, text: "Luz ambiental", unit: "(lux)", icon: ""},
{id: 6, text: "Intensidad sonora", unit: "(dB)", icon: ""},
{id: 7, text: "Tono", unit: "(Hz)", icon: ""},
{id: 8, text: "Barómetro", unit: "(hPa)", icon: ""},
{id: 9, text: "Brujula", unit: "grados", icon: ""},
{id: 10, text: "Magnetómetro", unit: "µT", icon: ""},
{id: 11, text: "Humedad", unit: "%", icon: ""},
{id: 12, text: "Temperatura ambiente", unit: "°C", icon: ""}
],
active_tab: 0
}
},
computed: {
newData: function(){
switch (this.active_tab) {
case 0:
this.value = Math.sqrt(this.$state.lin_acel.x^2 + this.$state.lin_acel.y^2 + this.$state.lin_acel.z^2)
break
case 1:
this.value = this.$state.lin_acel.x
break
case 2:
this.value = this.$state.lin_acel.y
break
case 3:
this.value = this.$state.lin_acel.z
break
case 4:
if (this.previus != null){
//var cons = Math.sqrt(this.$state.lin_acel.x^2 + this.$state.lin_acel.y^2 + this.$state.lin_acel.z^2)
var ax = this.$state.lin_acel.x,
ay = this.$state.lin_acel.y,
az = this.$state.lin_acel.z
var nuevo = Date.now()
var vx = ax * ((nuevo - this.previus)/1000),
vy = ay * ((nuevo - this.previus)/1000),
vz = az * ((nuevo - this.previus)/1000)
//this.value += (cons)*((nuevo - this.previus)/1000)
this.value += Math.sqrt(vx^2 + vy^2 + vz^2)
this.previus = nuevo
}else{
this.value = Math.sqrt(this.$state.lin_acel.x^2 + this.$state.lin_acel.y^2 + this.$state.lin_acel.z^2)
this.previus = Date.now()
}
case 5:
this.value = this.$state.lux
break
case 6:
this.value = this.$state.noise
break
case 7:
this.value = this.$state.noise
break
case 8:
this.value = this.$state.presion
break
case 9:
var vector = Math.sqrt(this.$state.magneto.x^2 + this.$state.magneto.y^2 + this.$state.magneto.z^2)
break
case 10:
this.value = Math.sqrt(this.$state.magneto.x^2 + this.$state.magneto.y^2 + this.$state.magneto.z^2)
break
default:
this.value = 0
break
}
},
newOptions(){
console.log("new options")
this.options = {
tittle: this.tabs[this.active_tab].text,
crosshair: {orientation: 'vertical', trigger: 'both'},
legend: 'none',
hAxis: {
format:'mm:ss'
}
}
},
drawchart(){
console.log("chart is drawing")
this.chart.draw(this.data, google.charts.Line.convertOptions(this.options));
},
},
watch: {
time: {
immediate: false,
handler(){
this.addData()
}
},
active_tab: {
inmediate: false,
handler(){
this.updatetable()
}
}
},
methods: {
addData(){
this.data.addRows([[new Date(Date.now()),0]])
},
updatetable(){
this.data = null
this.data = new google.visualization.DataTable(
{cols:
[{label: 'tiempo', id: 'x', type: 'date'},
{label: String(this.tabs[this.active_tab].text), id: 'y', type: 'number'}]})
}
},
mounted() {
this.chart = new google.charts.Line(this.$refs.char_div)
this.newOptions
this.updatetable()
this.drawchart
}
}
</script>
<style>
</style>

As Phil says, "Computed property functions are not meant to alter any data properties and are supposed to return a value". Is because computed watchs changes only in return, for example:
//Computed watch this
return this.a + this.b
//{...}
//Not this
this.c = this.a + this.b
I found here and in Vue forums ways to improve this code:
If you need to watch a data with multiple dependencies, you can do the next:
computed: {
multi(){
return [this.a, this.b].join()
}
},
watch: {
multi: {
handler(): {
this.c = this.a + this.b
}
}
}
I thing the best I can find solution was:
created(){
this.$watch(
(this) => (vm.x, vm.y, vm.z, Date.now()),
function () {
// Executes if `x`, `y`, or `z` have changed.
}
)
}
This last also return the function unwatch(), it stop the watcher property, if I do this.watcher = this.$watch(/*data and callback here*/), then I can do this.watcher() stop watcher and make new one, perfect for improve performance of this code.

Related

Vuejs track input field

I need to check whether my input field is empty or not.
Logic
if form.name has value, use increase function
if form.name is empty, use decrease function
do not use increase, decrease functions on each character that user inputs or removes
Code
<el-form-item label="Product name *">
<el-input v-model="form.name"></el-input>
</el-form-item>
methods: {
increase() {
this.percentage += 8.3;
if (this.percentage > 100) {
this.percentage = 100;
}
},
decrease() {
this.percentage -= 8.3;
if (this.percentage < 0) {
this.percentage = 0;
}
},
}
any idea?
Update
Script
data() {
return {
form: {
name: '', // require
slug: '',
price: '', // require
supplier_id: '', // require
new_price: '',
base_price: '',
sku: '',
qty: 1, // require
active: '', // require
photo: '',
photos: [],
shortDesc: '',
longDesc: '',
origin: '',
tags: [],
brand_id: '', // require
categories: [],
user_id: '',
seoTitle: '',
seoTags: '',
seoPhoto: '',
seoDescription: '',
variations: [],
options: [],
condition: '', // require
isbn: '',
ean: '',
upc: '',
height: '',
weight: '',
lenght: '',
width: '', // require
},
}
},
methods: {
onSubmit(e) {
e.preventDefault();
axios.post('/api/admin/products/store', this.form)
.then(res => {
// do my things
})
.catch(function (error) {
console.log('error', error);
});
},
}
HTML
<el-form ref="form" :model="form" label-width="120px" enctype="multipart/form-data">
// my inputs (listed in form part in script above)
<el-button type="primary" #click="onSubmit" native-type="submit">Create</el-button>
</el-form>
One possible solution would be to use #focus and #blur events to check if form.name has a value before increasing or decreasing, this would be fired on focus or on blur events, so you will not have the methods fired on each character input or deletion.
for example:
<el-form-item label="Product name *">
<el-input #focus="checkName" #blur="checkName" v-model="form.name"></el-input>
</el-form-item>
methods: {
checkName() {
//If form.name has a value then run increase method, otherwise run decrease method
!!this.form.name ? this.increase() : this.decrease()
},
increase() {
this.percentage += 8.3;
if (this.percentage > 100) {
this.percentage = 100;
}
},
decrease() {
this.percentage -= 8.3;
if (this.percentage < 0) {
this.percentage = 0;
}
},
}
You can see a working fiddle HERE
UPDATE
Alright so i did follow the rules you state on your question, and i didn't know you wanted to get the percentage of completion of the form, so in order to do that, i would suggest to use a computed property, you can read more about computed properties in the VueJS Documentation, this way the percentage is calculated based on the criteria we can give it, and only if the form has values.
computed: {
formProgress: function () {
let totalInputs = Object.keys(this.form).length;
let filledInputs = 0;
Object.values(this.form).forEach(val => {
if (!!val){
filledInputs++;
}
});
return (filledInputs/totalInputs)*100
}
},
As you can see in one single computed property you can handle the complex logic and return the value reactively, to explain it better, i'm counting the lenght of the form object, to get total number of inputs in your form, so it's important to have all your form data inside the form data object, then i convert that object to an array to iterate it, and i check if each property has a value on it, if does it, i add 1 to the filledInputs counter, and finally just return a simple math to get the percentage. please check the new Fiddle here to see it in action:
FORM PROGRESS FIDDLE
If you have any other doubt just let me know.
UPDATE 2:
All right in order to only count for specific inputs for the form progress, i have modified the code to work based on an array that contains the names of the properties that are required. here is the full code:
data() {
return {
form: {
name: '',
lastName: null,
categories: [{}],
},
requiredFields: ['name', 'categories']
};
},
computed: {
formProgress: function () {
let totalInputs = this.requiredFields.length;
let filledInputs = 0;
Object.entries(this.form).forEach(entry => {
const [key, val] = entry;
if (this.requiredFields.includes(key)){
switch (val.constructor.name) {
case "Array":
if (val.length !== 0){
if (Object.keys(val[0]).length !== 0){
filledInputs++;
}
}
break
case "Object":
if (Object.keys(val).length !== 0){
filledInputs++;
}
break
default:
if (!!val){
filledInputs++;
}
}
}
});
return Number((filledInputs/totalInputs)*100).toFixed(1)
}
},
And here is the updated FIDDLE
As you can see now i'm using Object.entries to get the key and value of the form object, so you can have a single form object to send to your backend, this way i'm checking first if the key is in the required fields, and if has a value, so all you need to do is update the requiredFields data array with the same names as your inputs data property to make the validation work, also there is a validation depending if is array, array of objects, or object, that way it will validate input on each data type.
Hope this works for you.

Vuejs: Cannot set property 'profile_picture' of undefined

I'm really confused whats wrong with my code. Can someone tell me what I did wrong here.
data() {
return {
users: [],
}
},
methods:{
moveData(response){
for(var x=0;x<response.data.data.length; x++){
this.users[x].profile_picture = response.data.data[x].profile_picture;
this.users[x].age = response.data.data[x].age;
this.users[x].intro = response.data.data[x].introMessage;
this.users[x].name = response.data.data[x].userName;
}
// eslint-disable-next-line
console.log('Users',this.users);
}
}
as the error suggests that this.users[x] is undefined. the simple solution would be to initialize this.users[x] with some empty object just like
for(var x=0;x<response.data.data.length; x++){
this.users[x] = {};
this.users[x].profile_picture = response.data.data[x].profile_picture;
this.users[x].age = response.data.data[x].age;
this.users[x].intro = response.data.data[x].introMessage;
this.users[x].name = response.data.data[x].userName;
}
As you using this.users[x], it always undefined because your array length is 0. So here one way more you can use .map() array to modify the field name and directly assign response to your users in moveData.
By use of Map array with spread operator
const response = [{
profile_picture: 'https://image.flaticon.com/icons/svg/149/149452.svg',
age: 25,
introMessage: 'Hello',
userName: 'test105'
}, {
profile_picture: 'https://image.flaticon.com/icons/svg/149/149452.svg',
age: 18,
introMessage: 'HI',
userName: 'demo105'
}]
console.log(response.map(({age,profile_picture,...r}) => Object.create({
age,
profile_picture,
name: r.userName,
intro: r.introMessage
})));
Modification in your code, use push method of array
const response = {
data: {
data: [{
profile_picture: 'https://image.flaticon.com/icons/svg/149/149452.svg',
age: 25,
introMessage: 'Hello',
userName: 'test105'
}, {
profile_picture: 'https://image.flaticon.com/icons/svg/149/149452.svg',
age: 18,
introMessage: 'HI',
userName: 'demo105'
}]
}
}
let users = [];
for (var x = 0; x < response.data.data.length; x++) {
users.push({
profile_picture: response.data.data[x].profile_picture,
age: response.data.data[x].age,
intro: response.data.data[x].introMessage,
name: response.data.data[x].userName
});
}
console.log(users)

FullCalendar 4.0 within a Vue application

I am using fullCalendar v4.0 with no jquery. I have initialized it like this
<div id="calendar"></div>
In data object I have this.
calendar: null,
config: {
plugins: [ interactionPlugin, dayGridPlugin, timeGridPlugin, listPlugin, momentPlugin],
axisFormat: 'HH',
defaultView: 'timeGridWeek',
allDaySlot: false,
slotDuration: '00:60:00',
columnFormat: 'dddd',
titleFormat: 'dddd, MMMM D, YYYY',
defaultDate: '1970-01-01',
dayNamesShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
eventLimit: true,
eventOverlap: false,
eventColor: '#458CC7',
firstDay: 1,
height: 'auto',
selectHelper: true,
selectable: true,
timezone: 'local',
header: {
left: '',
center: '',
right: '',
},
select: (event) => {
this.selectCalendar(event)
},
header: false,
events: null
}
}
while having a calendar in data variable, Now I can render() and destroy() it from anywhere.
But I am having an issue for handling Calendar events:
Such as
select: (event) => {
this.selectCalendar(event)
}
I have defined another method in methods: {} as selectCalendar() to call it in select but I am getting an error as
Uncaught TypeError: Cannot read property 'selectCalendar' of undefined
I want to do few operations on select, eventClick, eventDrop, eventResize, but I am unable to call a method within the config.
Also is there any way possible to define select or any method as
select: this.selectCalendar
So that it will just straight send an event to the defined method?
I have tried vue-fullcalendar but it doesn't work for my cause. Any help will be thankful.
Vue v.2.5.21
I am using vue full calendar, you can handle event of fullcalendar like code below
<full-calendar :event-sources="eventSources" #event-selected="myEventSelected"></full-calendar>
export default{
methods:{
caculateSomething(event){
//do st here
},
myEventSelected(event){
//do st here
this.caculateSomething(event)
console.log(event)
}
}
}
This is how I sorted this out.
in html <div id="calendar"></div>
in your data() => {}
calendar: null,
config: {
plugins: [ interactionPlugin, dayGridPlugin, timeGridPlugin, listPlugin, momentPlugin],
axisFormat: 'HH',
defaultView: 'timeGridWeek',
allDaySlot: false,
slotDuration: '00:60:00',
columnFormat: 'dddd',
columnHeaderFormat: { weekday: 'short' },
defaultDate: '1970-01-01',
dayNamesShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
eventLimit: true,
eventOverlap: false,
eventColor: '#458CC7',
firstDay: 1,
height: 'auto',
selectHelper: true,
selectable: true,
timezone: 'UTC',
header: {
left: '',
center: '',
right: '',
},
header: false,
editable: true,
events: null
}
Don't define any select, resize or dropEvent in config for the first time, but then the part where you are going to render the calendar do something like this
if (this.calendar == null) {
console.log(this.schedule)
let calendarEl = document.getElementById('calendar');
let calendarConfig = this.config
let select = (event) => {
this.selectCalendar(event)
}
let eventClick = (event) => {
this.clickCalendar(event)
}
let eventDrop = (event) => {
this.dropCalendar(event)
}
let eventResize = (event) => {
this.resizeCalendar(event)
}
calendarConfig.select = select;
calendarConfig.eventClick = eventClick;
calendarConfig.eventDrop = eventDrop;
calendarConfig.eventResize = eventResize;
this.calendar = new Calendar(calendarEl, calendarConfig);
this.calendar.render();
this.renderEvents()
}
Now you can handle those events of FullCalendar in your own methods.
Also having the calendar in this.calendar gives you the power to destroy it from anywhere, in the methods: {}
In FullCalendar 4.0 things have been changes but quite simpler.
these are the methods attached to FullCalendar Events
selectCalendar(event) {
this.calendar.addEvent(event)
},
clickCalendar(event) {
if (window.confirm("Are you sure you want to delete this event?")) {
let findingID = null
if (event.event.id === "") {
findingID = event.el
} else {
findingID = event.event.id
}
let removeEvent = this.calendar.getEventById( findingID )
removeEvent.remove()
}
},
dropCalendar(event) {
},
resizeCalendar(event) {
},
destroyCalendar() {
if (this.calendar != null) {
this.calendar.destroy();
this.calendar = null
}
}
when an event is added by you. You can find it through el in an event, but the custom events should have a unique ID. through which you will find and delete it.

How to set up initialRange on Vue-rangedate-picker-winslow?

While using Vue Rangedate Picker I hit a roadblock trying to configure the prop Initial Range (the initial range that the component spits before the user even select any other range).
Have managed to setup other props like "caption" and "preset ranges" but the initRange is complaining about it not being an Object and being a function.
On my template:
<date-picker v-bind="datePicker" initRange="datePicker.presetRanges.last7Days" #selected="onDateSelected" i18n="EN" ></date-picker>
On my data:
datePicker: {
initRange: {
start: '1505862000000',
end: '1505872000000'
},
captions: {
title: 'Choose Date/Period',
ok_button: 'Apply'
},
presetRanges: {
today: function () {
const n = new Date()
const startToday = new Date(n.getFullYear(), n.getMonth(), n.getDate() + 1, 0, 0)
const endToday = new Date(n.getFullYear(), n.getMonth(), n.getDate() + 1, 23, 59)
return {
label: 'Today',
active: false,
dateRange: {
start: startToday,
end: endToday
}
}
},
last7Days: function () {
const n = new Date()
const weekAgo = new Date(n.getFullYear(), n.getMonth(), n.getDate() - 7, 24, 0)
const endToday = new Date(n.getFullYear(), n.getMonth(), n.getDate() + 1, 0, 0)
return {
label: 'Last 7 Days',
active: 'false',
dateRange: {start: weekAgo, end: endToday}
}
},
On my methods:
methods: {
onDateSelected: function (daterange) {
let that = this;
that.selectedDate = daterange;
let UnixStart = Math.round((Date.parse(that.selectedDate.start)));
let UnixEnd = Math.round((Date.parse(that.selectedDate.end)));
},
How can I solve this?
https://github.com/bliblidotcom/vue-rangedate-picker/issues/71
I leave the comments with this link. you will find it. should work for you.

Rally Cumulative Flow Diagram with Points

I'm looking to try and do a cumulative flow diagram by story points in rally with their newer API/SDK and found some sample code on their GitHub page RallyAnalytics GitHub
So after some work I have it working to some degree but don't understand or can find any documentation for how to configure this more. It looks like the report being generated is doing count and not the PlanEstimate which I tried to add in fieldsToSum. How can I get it to sum the PlanEstimate field by c_KanbanState and not just give me a count of stories that matched the c_KanbanState for that week? Sample code below minus the minified code from GitHub.
var userConfig = {
title: 'Cumulative Flow Diagram',
debug: false,
trace: false,
// asOf: "2012-11-01", // Optional. Only supply if want a specific time frame. Do not send in new Date().toISOString().
granularity: 'week',
fieldsToSum: ['PlanEstimate'],
scopeField: "Project", // Supports Iteration, Release, Tags, Project, _ProjectHierarchy, _ItemHierarchy
scopeValue: 'scope',
scopeData: {
StartDate: new Date("2012-12-01T07:00:00.000Z"),
EndDate: new Date(new Date()),
Name: ""
},
//fieldNames: ['count', 'PlanEstimate']
kanbanStateField: 'c_KanbanState',
chartSeries: [
{name: 'To Do'},
{name: 'Dev Ready'},
{name: 'In Dev'},
{name: 'Peer Review'},
{name: 'QA Ready'},
{name: 'QA Done'},
{name: 'Accepted'}
]
}
(function() {
var charts = {};
var visualizer;
var nameToDisplayNameMap;
createVisualization = function(visualizationData) {
if (typeof visualizationData !== "undefined" && visualizationData !== null) {
categories = visualizationData.categories;
series = visualizationData.series;
charts.lowestValueInLastState = visualizationData.lowestValueInLastState;
charts.chart = new Highcharts.Chart({
chart: {
renderTo: 'chart-container',
defaultSeriesType: 'column',
zoomType: 'x'
},
legend: {
enabled: true
},
credits: {
enabled: false
},
title: {
text: userConfig.title
},
subtitle: {
text: userConfig.scopeData.Name
},
xAxis: {
categories: categories,
tickmarkPlacement: 'on',
tickInterval: Math.floor(categories.length / 12) + 1,
title: {
text: userConfig.granularity.slice(0, 1).toUpperCase() + userConfig.granularity.slice(1) + 's'
}
},
yAxis: [
{
title: {
text: 'Total Points',
},
min: charts.lowestValueInLastState
}
],
tooltip: {
formatter: function() {
point = this.point
s = point.series.name + ': <b>' + point.y + '</b><br \>';
if (point.x == point.series.data.length - 1) {
s += point.category.slice(0, point.category.length - 1) + ' to-date';
} else {
s += point.category;
}
return s;
}
},
plotOptions: {
series: {
events: {
legendItemClick: function(event) {
if (this.chart.series.length == this.index + 1) {
if (!this.visible) {
this.chart.yAxis[0].setExtremes(charts.lowestValueInLastState);
} else {
this.chart.yAxis[0].setExtremes(0);
};
};
return true;
}
}
}
},
series: series
}); // end of chart
} else {
// Put a spinner in the chart containers until first fetch returns
$('#chart-container')
.html('<img height="20px" src="https://rally1.rallydev.com/slm/js-lib/ext/2.2/resources/images/default/grid/loading.gif"></img>')
.attr("style", "text-align:center");
};
};
$(document).ready(function() {
visualizer = new CFDVisualizer(charts, userConfig, createVisualization);
});
})();
You may be using a slightly older version because the latest doesn't have the fieldsToSum parameter in the config, but you can upgrade the chart to sum PlanEstimate by chaging a few lines in the CFDVisualizer.coffee to this:
#config.lumenizeCalculatorConfig.metrics = [
{f: 'groupBySum', field: 'PlanEstimate', groupByField: #config.kanbanStateField, allowedValues: allowedValues}
]
from:
#config.lumenizeCalculatorConfig.metrics = [
{f: 'groupByCount', groupByField: #config.kanbanStateField, allowedValues: allowedValues}
]
You should probably also change the axis label in the cfd.html.
If this proves too difficult to accomplish (CoffeeScript may be unfamiliar), let me know and I'll post a new version to GitHub.