Vue.JS infinite re-rendering on data change - vue.js

I am rendering a bunch of HTML divs (I've named them cells) based on Vue data.
The colour of the cell is dictated by a hex code in the data.
On mouse over, a function should run to change the colour of the particular cell.
Here is how the data looks and how the function works at the moment:
new Vue({
el: '#app',
data: {
cells: [
{i: 1, hex: '#111111'},
{i: 2, hex: '#222222'},
{i: 3, hex: '#ffffff'}
]
},
methods: {
mouseOver: function (e){
this.cells[e.toElement.id].hex = (Math.floor(Math.random() * 16777215)).toString(16);
}
}
})
And here is the code for the v-for which renders the (in this case) 3 cells:
<div class='cell'
v-for="cell in cells"
:id="cell.i"
:key="cell.i"
:style="{ backgroundColor: cell.hex}"
#mouseenter="mouseOver">
</div>
However this does not work.
[Vue warn]: You may have an infinite update loop in a component render function.
(found in <Root>)
I theorise that this is because Vue re-renders all of the cells every time the data changes, and since the component is new, the function runs again.
However I'm new to Vue and not sure how to go about solving this!
Help much appreciated!

You forgot to add # at the beginning of your hex color code and you forgot to decrease the index for accessing the array by one.
Furthermore, e.toElement seems to be unsupported by some browsers. Rather use e.target
What is the difference between Event.target, Event.toElement and Event.srcElement?
this.cells[e.target.id-1].hex = '#' + (Math.floor(Math.random() * 16777215)).toString(16);
Working fiddle: https://jsfiddle.net/TobiObeck/qfpkvn9L/1/

Related

Vuetify v-treeview doesn't delete node

I am a beginner in vuejs and vuetify and I try to add and remove nodes in a treeview.
When I do a .push(item) the treeview updates but when I delete an item from my array, the treeview does not update.
Here is an example of my code
My treeview
<v-treeview
[...]
:items="formatedItems"
item-key="slug"
[...]
>
[...]
</v-treeview>
formatedItems is declared in the data like this :
data: () => ({
[...]
formatedItems: [{
id: 0,
slug: null,
name: i18n.t(****),
children: []
}]
[...]
}),
This code correctly adds a child in my treeview :
this.formatedItems[0].children.push({
id: element.id,
slug: element.slug,
name: element.name,
children: []
});
On the other hand, this code removes the item from my array but doesn't update the treeviews :
_.remove(this.formatedItems[0].children, function(n) {
return n.slug == element.slug;
});
this.formatedItems = this.formatedItems; // I tried this to trigger a potential magical update of vuejs but nothing...
I don't know what to try anymore, that's why I come to ask you the question:
.push being a build-in function and knowing that I use a lodash function for deletion, is there something I didn't understand about the use of vuejs that causes my treeview not to be updated?
Thank you for your attention
Looks like the problem somewhere else. I've tried a basic example and all works fine.
Except I used filter method for deletion.
codepen.io/DavidGolodetsky/pen/eYZMbgm

l-rectangle component does not update

I am using the l-rectangle in a Vue leaflet project.
I am creating a rectangle like so:
<l-rectangle :bounds="rectangle"></l-rectangle>
which displays the rectangle on my map doing this in the .js-file:
new Vue({
el: '#app',
data: function() {
return {
rectangle: [[69.81310023846743, 16.929931640625004],[69.11310023846743, 16.129931640625004]]
}
}
});
I have created a map click event, in this function I am changing the coordinates of the rectangle array in order to make the rectangle change size/shape. But nothing is happening (the function gets called but the rectangle does not change):
clickEvent:function(event)
{
var point = [event.latlng.lat,event.latlng.lng];
this.rectangle[0] = this.rectangle[0];
this.rectangle[1] = point;
}
Thanks for any help and guidance!
As per Vue documentation, the reactivity for array will not work if you set value for a particular index, Instead you can use some array operation methods which are mentioned in Vue documentation
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
the following above methods makes the array reactive
The click event can be replaced with
clickEvent:function(event)
{
var point = [event.latlng.lat,event.latlng.lng];
this.rectangle.splice(0, 1, this.rectangle[0]);
this.rectangle.splice(1, 1, point);
}

Vuejs2 - How to update the whole object from the server not losing reactivity?

I have a list of objects that can be updated from the database.
So, when I load the list, objects have only id and name.
When I click on an object I load other fields that can be of any length - that's why I don't load them with the objects in the list.
I found that when I update an object it can be difficult to keep reactivity https://v2.vuejs.org/v2/guide/reactivity.html so I need to find some workaround.
this code works almost okay:
axios.get('/api/news', item.id).then(function(response){
if (response){
Object.assign(item, response.data.item);
}
});
But the problem is the fields that have not been presented from the beginning is not 100% reactive anymore. What is going on is a new field has been updated reactively only when I change another, previous one. So, if I show 2 text field with old and new properties, if I change the second property, the field will not be updated until I change the first one.
I got item object from the component:
data () {
return {
items: [],
}
},
and
<div v-for="item in items" #click="selectItem(item)" >
<span>{{item.name}}</span>
</div>
Then item's been passed to the function selectItem.
What is the proper pattern to load new fields and keep them reactive? (NB: it's not the case when I can assign field by field - I want to reuse the same code no matter which object it is, so I need so the solution for updating an object at a time without listing all new fields.)
Note. This code works inside the component.
Completely revised post: Ok, the example you give uses an array, which has its own caveats, in particular that you can't directly set values like vm.items[indexOfItem] = newValue and have it react.
So you have to use Vue.set with the array as the first argument and the index as the second. Here's an example that adds a name property to object items and then uses Vue.set to set the item to a new object created by Object.assign.
new Vue({
el: '#app',
data: {
items: [{
id: 1,
other: 'data'
}, {
id: 2,
other: 'thingy'
}]
},
methods: {
selectItem(parent, key) {
const newObj = Object.assign({}, parent[key], {
name: 'some new name'
});
Vue.set(parent, key, newObj);
setTimeout(() => {parent[key].name = 'Look, reactive!';}, 1500);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="item, index in items" #click="selectItem(items, index)">
<span>{{item.name || 'empty'}}</span>
</div>
<pre>
{{JSON.stringify(items, null, 2)}}
</pre>
</div>
Have a look at Change-Detection-Caveats Vue cannot detect property addition or deletion if you use "normal assign" methods.
You must use Vue.set(object, key, value)
Try something like the following:
axios.get('/api/news', item.id).then(function(response){
if (response){
let item = {}
Vue.set(item, 'data', response.data.item)
}
});
Than item.data would than be reactiv.
Can simply use Vue.set to update this.item reactively
axios.get('/api/news', item.id).then(function(response){
if (response){
this.$set(this, "item", response.data.item);
}
});

Vue.js - Highmaps - Redraw map on series change

I have a highmaps 'chart' and the only thing that I want is to redraw the whole map inside an external function. Let me explain better. The map draws itself immediatly when the page loads up but I fetch some data from an external service and set it to a variable. Then I would like to just redraw the chart so that the new data appears in the map itself. Below is my code.
<template>
<div>
<highmaps :options="chartOptions"></highmaps>
</div>
</template>
<script>
import axios from 'axios';
import HighCharts from 'vue-highcharts';
import json from '../map.json'
let regions = [];
export default {
data: function () {
return {
chartOptions: {
chart: {
map: json, // The map data is taken from the .json file imported above
},
map: {
/* hc-a2 is the specific code used, you can find all codes in the map.json file */
joinBy: ['hc-key', 'code'],
allAreas: false,
tooltip: {
headerFormat: '',
pointFormat: '{point.name}: <b>{series.name}</b>'
},
series: [
{
borderColor: '#a0451c',
cursor: 'pointer',
name: 'ERROR',
color: "red",
data: regions.map(function (code) {
return {code: code};
}),
}
],
}
},
created: function(){
let app = this;
/* Ajax call to get all parameters from database */
axios.get('http://localhost:8080/devices')
.then(function (response) {
region.push(response.parameter)
/* I would like to redraw the chart right here */
}).catch(function (error){
console.error("Download Devices ERROR: " + error);
})
}
}
</script>
As you can see I import my map and the regions variable is set to an empty array. Doing this results in the map having only the borders and no region is colored in red. After that there is the created:function() function that is used to make the ajax call and retrieve data. After that I just save the data pushing it into the array and then obviously nothing happens but I would like to redraw the map so that the newly imported data will be shown. Down here is the image of what I would like to create.
If you have any idea on how to implement a thing like this or just want to suggest a better way of handling the problem, please comment.
Thanks in advance for the help. Cheers!
After a few days without any answer I found some marginal help online and came to a pretty satisfying conclusion on this problem so I hope it can help someone else.
So the first thing I did was to understand how created and mounted were different in Vue.js. I used the keyword created at first when working on this project. Because of that, inside this function, I placed my ajax call that gave me data which I then loaded inside the 'chart' by using the .addSeries method of the chart itself.
To reference the chart itself I used this: let chart: this.$refs.highcharts.chart. This searches for the field refs in any of your components/html elements and links it to the variable. So in the html there was something like this:
<template>
<div>
<highmaps :options="chartOptions" ref="highcharts"></highmaps>
</div>
</template>
The real problem was that the chart didn't even start rendering while all this process was going on so I changed the created keyword with mounted which means that it executes all the code when all of the components are correctly mounted and so my chart would be already rendered.
To give you (maybe) a better idea of what I am talking about I will post some code down below
mounted: function(){
let errorRegions = [];
let chart = this.$refs.highcharts.chart;
axios.get('localhost:8080/foo').then(function(response)
{
/* Code to work on data */
response.forEach(function(device){
errorRegions.push(device);
}
chart.addSeries({
name: "ERROR",
color: "red",
data: errorRegions
}
/* ...Some more code... */
})
}
And this is the result (have been adding some more series in the same exact manner)
Really hoping I have been of help to someone else. Cheers!

Using dijit.InlineEditBox with dijit.form.Select

I'm trying to use a dijit.form.Select as the editor for my dijit.InlineEditBox. Two problems / unexpected behavior seem to occur:
Inconsistently, the InLineEditBox doesn't have the initial value set as selected
Consistently, after selecting a choice, the value that should be hidden is shown instead of the label.
The width isn't set to 130px
Here's working code: http://jsfiddle.net/mimercha/Vuet8/7/
The jist
<span dojoType="dijit.InlineEditBox" editor="dijit.form.Select"
editorParams="{
options: [
{label:'None',value:'none'},
{label:'Student',value:'stu'},
{label:'Professor',value:'prof',selected:true},
],
style:'width:1000px;',
}"
editorStyle="width: 1000px;"
>
</span>
Any help is greatly appreciated! Thanks!
Okay, after a few MORE hours struggling with the mess that is dijit.InlineEditBox, I think I have the solution to the remaining issue (#2).
EDIT: My first solution to #2 is still flawed; the implementation at http://jsfiddle.net/kfranqueiro/Vuet8/10/ will never return the actual internal value when get('value') is called.
EDIT #2: I've revamped the solution so that value still retains the real (hidden) value, keeping displayedValue separate. See if this works better:
http://jsfiddle.net/kfranqueiro/Vuet8/13/
First, to recap for those who weren't on IRC:
Issue #1 was happening due to value not being properly set as a top-level property of the InlineEditBox itself; it didn't pick it up properly from the wrapped widget.
Issue #3 was happening due to some pretty crazy logic that InlineEditBox executes to try to resolve styles. Turns out though that InlineEditBox makes setting width particularly easy by also exposing it as a top-level numeric attribute. (Though IINM you can also specify a percentage as a string e.g. "50%")
Now, issue #2...that was the killer. The problem is, while InlineEditBox seems to have some logic to account for widgets that have a displayedValue attribute, that logic is sometimes wrong (it expects a displayedValue property to actually exist on the widget, which isn't necessarily the case), and other times missing entirely (when the InlineEditBox initializes). I've worked around those as best I could in my own dojo.declared extensions to InlineEditBox and the internal widget it uses, _InlineEditor - since generally it's a good idea to leave the original distribution untouched.
It's not pretty (neither is the underlying code I dug through to understand and come up with this), but it seems to be doing its job.
But man, this was rather interesting. And potentially pertinent to my interests as well, as we have used this widget in our UIs as well, and will be using it more in the future.
Let me know if anything backfires.
hm...
<span dojoType="dijit.InlineEditBox" editor="dijit.form.Select"
editorParams="{
options: [
{label:'None',value:'none'},
{label:'Student',value:'stu'},
{label:'Professor',value:'prof',selected:true},**<<<<** and this comma is for?
],
style:'width:1000px;',**<<<<** and this comma is for?
}"
editorStyle="width: 1000px;"
>
</span>
Also, when using dijit.form.Select, selected value is not attr "selected" but value.
And if you enter prof inside <span ...blah > prof </span> than your proper selected option will be selected ;)
Dijit select checks for VALUE, not attr.
This may be fixed in recent Dojo - see http://bugs.dojotoolkit.org/ticket/15141 - but using 1.7.3 I found this worked:
In my app directory, at the same level as dojo, dijit and dojox, I created a file InlineSelectBox.js which extends InlineEditBox with code to set the HTML on the associated domNode from the value of the Dijit, and which wires up that code to the onChange() event:
define(["dijit/InlineEditBox",
"dijit/form/Select",
"dojo/on",
"dojo/_base/declare",
"dojo/_base/array"
],
function(InlineEditBox, Select, on, declare, array){
return declare(InlineEditBox, {
_setLabel: function() {
array.some(this.editorParams.options, function(option, i){
if (option.value == this.value) {
this.domNode.innerHTML = option.label;
return true;
}
return false;
}, this);
},
postMixInProperties: function(){
this.inherited(arguments);
this.connect(this, "onChange", "_setLabel");
},
postCreate: function(){
this.inherited(arguments);
this._setLabel();
}
});
});
Then, in my view script:
require(["dojo/ready",
"app/InlineSelectBox",
"dijit/form/Select"
],
function(ready, InlineSelectBox, Select){
ready(function(){
// Add code to set the options array
var options = [];
// Add code to set the initial value
var initialValue = '';
var inlineSelect = new InlineSelectBox({
editor: Select,
editorParams: {options: options},
autoSave: true,
value: initialValue
}, "domNodeToAttachTo");
});
});
I was dealing with this situation a few months ago, and not finding a resolution i made my own algorithm.
I put a div with an event on Onclick that build programatically a Filtering Select on that div with the store i want to use.
function create(id,value){
var name = dojo.byId(id).innerHTML;
dojo.byId(id).parentNode.innerHTML = '<div id="select"></div>';
new dijit.form.FilteringSelect({
store: store,
autoComplete: true,
invalidMessage:"Invalid Selection",
style: "width: 80px;",
onBlur: function(){ },
onChange: function(){ },
required: true,
value: value,
disabled: false,
searchAttr: "name",
id: "status"+id,
name: "status"
},"select");
dijit.byId('status'+id).focus();
}
I used the onBlur event to destroy the widget and the onchange to save by xhr the new value.
The focus is below because the onBlur was not working properly.
note: the function is not complete.