Vue Element Table: undefined nested value crashes Chrome - vue.js

I'm using a Vue Element table to display a list of objects that has an object nested in it.
"main_list":{
"id": "1",
"user": {
"username": "Bob"
}
}
I can display the username using:
<el-table-column prop="user.username" label="User" sortable />
Every now and then this page will throw an error stating that it can not read value username of undefined. This will crash Google Chrome to the extent that you have to kill all Chrome processes in the task manager. Firefox does not crash this bad. I think it goes into an infinite loop
I get the data via API, so it might be that some of the user object does not have any usernames.
So far the the only solution that I have found (I'm not sure it is a solution) is:
<el-table-column prop="username" label="User">
<template
v-if="typeof scope.row.assigned_to !== 'undefined'"
slot-scope="scope"
>{{ scope.row.assigned_to.username }}</template>
</el-table-column>
Do you have any Idea why this crash happens?
Is there a better solution than mine?

Once you fetch the list of items, you should update each one of them so that every user has a defined username - even if it is an empty string. Something like
axios.get(url).then(result =>
this.items = result.data.map(item =>
{
if(!item.user) item.user = {};
if(!item.user.username) item.user.username = '';
return item;
});
You can also try something else:
<el-table-column label="User" sortable :sort-method="sortUsers">
<template slot-scope="tbl">
{{ (tbl.row.user || {}).username || 'UNKNOWN' }}
</template>
</el-table-column>
methods:
{
sortUsers(a, b)
{
const left = (a.user || {}).username || 'UNKNOWN';
const right = (b.user || {}).username || 'UNKNOWN';
if (left < right) return -1;
if (left > right) return 1;
return 0;
}
}

Related

Vue 2 Moving Window with Keyboard Navigation

I have created a list in my program. In this list, you press ArrowUp or ArrowDown to navigate between the items that are generated with a v-for in said list. When you use the arrows to navigate the list, a counter is used to know which item inside this list is the "selected" one. My current problem is: When you "select" an item that is outside your view, the Browser's Window doesn't follow it down, allowing the selection to keep "going down" this list, but the user isn't able to see the other selected items.
I've tried messing around with the focus() function, but I don't know how to focus() a specific item without using the ref atribute, which I also can't seem to implement since it's being generated with v-for.
Here's my current table:
<table class="table table-striped p-0 m-0" v-show="pedidos.length > 0">
<thead class="table-dark">
<tr class="">
<th>Cliente Emissor</th>
<th>Produto</th>
<!-- <th>Endereço</th> -->
<!-- <th>Valor</th> -->
<th>Data Emissão</th>
<th>Estado</th>
</tr>
</thead>
<tbody>
<template v-for="(pedido, index) in pedidos">
<itemPedido :pedido="pedido" :key="index" :endereco="endereco" :class="[(navegacaoAtual == index) ? 'teste' : '']" ref="teste"></itemPedido>
<itemEdit #atualizarListaPedidos="atualizarListaPedidos" #atualizarStatusModal="atualizarStatusModal" class="animation-dropdown" :pedido="pedido" :key="pedido.nome"></itemEdit>
</template>
</tbody>
</table>
Here's the current navigateWithArrows() function I've made:
navegarSetas(key) {
if (this.navegacaoSetasAtiva == false) {
this.navegacaoSetasAtiva = true
this.navegacaoAtual = 0
} else if (this.modalAtivado == false && this.navegacaoSetasAtiva == true) {
if (key == 'ArrowDown' && this.navegacaoAtual < this.pedidos.length - 1) {
this.navegacaoAtual++
Vue.nextTick().then(() => {
let teste1 = this.$refs.teste[this.navegacaoAtual]
teste1.$el.focus()
})
} else if (key == 'ArrowUp' && this.navegacaoAtual <= this.pedidos.length && this.navegacaoAtual > 0) {
this.navegacaoAtual--
} else if (key == 'Enter') {
let pedidoSelecionado = this.pedidos[this.navegacaoAtual].id
Event.$emit('changeShow', pedidoSelecionado)
}
}
},
All help is appreciated. Thank you!
After taking a break and resuming my search, I stumbled upon something that did exactly what I wanted: the scrollIntoView() function.
https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView

Dynamically Tally Child Elements by Classname in Vue

I have a page that allows a user to drag/drop images into pre-defined DIVs, then I tally up the total value of the images based on their class name. What I am trying to do is get vue to read the values from each outer div.answer and get the class names of the child images.
My source code is:
<div
is="box-answers"
v-for="box in boxes.slice().reverse()"
v-bind:key="box.id"
v-bind:level="box.level"
v-bind:hint="box.hint"
></div>
<script>
Vue.component('box-answers', {
props: ['level','hint'],
template: '<div class="droppable answer :id="level" :title="hint"></div>'
});
new Vue({
el: '#mainapp',
data: {
boxes: [
{ id: 1, level: 'baselevel-1', hint: 'x 1' },
{ id: 2, level: 'baselevel-2', hint: 'x 20' },
{ id: 3, level: 'baselevel-3', hint: 'x 400' },
{ id: 4, level: 'baselevel-4', hint: 'x 8,000' },
{ id: 5, level: 'baselevel-5', hint: 'x 160,000' }
]
}
</script>
This converts to the follow HTML (the nested DIVs and SPANs are user-possible entries by dragging):
<div id="baselevel-5" class="droppable answer" title="x 160,000">
<div><img src="images/line.gif" alt="Five" class="imgfive"></div>
<span><img src="images/dot.gif" alt="One" class="imgone"></span>
</div>
...
<div id="baselevel-1" class="droppable answer" title="x 1">
<span><img src="images/line.gif" alt="One" class="imgone"></span>
</div>
Currently, I have jQuery/JavaScript calculating the point values using the following:
$(function(j) {
var arAnswers = Array(1);
count = 0; //
j("div.answer").each(function( idx ) {
currentId = j(this).attr('id');
ones = 0;
fives = 0;
if ( j("#" + currentId).children().length > 0 ) {
ones = j("#" + currentId).children().find("img.imgone").length * 1;
fives = j("#" + currentId).children().find("img.imgfive").length * 5;
arAnswers[count] = ones + fives; //Tally box value
count++;
}
});
});
I would like Vue to perform similar iteration and addition to return total value of ones and fives found based on the image classname.
Currently, you are approaching this problem as a pure-play DOM operation. If that is what you need then you can simply use $refs:
<!-- NOTICE ref -->
<div ref="boxAnswers"
is="box-answers"
v-for="box in boxes.slice().reverse()"
v-bind:key="box.id"
v-bind:level="box.level"
v-bind:hint="box.hint">
</div>
Inside your high-level component, you will have a function like:
function calculate() {
// NOTICE $refs
const arAnswers = this.$refs.boxAnswers.map((x) => {
// $el is the DOM element
const once = x.$el.querySelectorAll('img.imgone').length * 1;
const fives = x.$el.querySelectorAll('img.imgfive').length * 5;
return once + fives
});
return arAnswers;
}
But this is not the correct Vue way of doing things. You have to think in terms of events and data model (MVVM - don't touch DOM. DOM is just a representation of your data model). Since, you have a drag-n-drop based application, you have to listen for drag, dragstart, dragend and other drag events. For example:
<!-- NOTICE drop event -->
<div #drop="onDropEnd(box, $event)"
is="box-answers"
v-for="box in boxes.slice().reverse()"
v-bind:key="box.id"
v-bind:level="box.level"
v-bind:hint="box.hint">
</div>
Your onDropEnd event handler will look like:
function onDrop(box, $event) {
// box - on which box drop is happening
// $event.data - which image is being dropped
// Verify $event.data is actually the image you are intending
if ($event.data === 'some-type-image') {
// Do the counting manipulations here
// ... remaining code
}
}
This is not a complete code as I don't know other components. But it should help you with the required direction.

Vue v-for changing from version 1 to version 2

I'm learning Vue.js and found this fiddle that does exactly what I want to do.
Here is the fiddle: https://jsfiddle.net/os7hp1cy/48/
I integrated this and am getting this error:
invalid expression: v-for="user in users | filterBy searchKey | paginate"
So I've done some digging and I see it has changed from version 1 to 2. However, I don't know how to fix this.
<li v-for="user in users | filterBy searchKey | paginate">{{ user.name }}</li>
I would like to replace this with something that Vue 2 will support and will work the same way.
As of Vue version 2, filters can only be used inside text interpolations ({{ }} tags). See the documentation for migrating from Vue version 1.
You can use a computed property to filter the users and use that computed property in the v-for directive instead:
computed: {
filteredUsers: function() {
let key = this.searchKey.toUpperCase();
return this.users.filter((user) => {
return user.name.toUpperCase().indexOf(key) !== -1
})
},
paginatedUsers: function() {
var list = this.filteredUsers;
this.resultCount = list.length
if (this.currentPage >= this.totalPages) {
this.currentPage = this.totalPages
}
var index = this.currentPage * this.itemsPerPage
return list.slice(index - 1, index - 1 + this.itemsPerPage)
}
}
<li v-for="user in paginatedUsers">{{ user.name }}</li>
Also, when using v-for to generate a range of numbers like you do for your page numbers, Vue version to starts the index at 1 instead of 0. So, you'll need to update the logic depending on a starting index of 0 as well.
Here's a working fiddle.

How can I add 2 condition in one class vue.js 2?

My vue component is like this :
<template>
<a :class="'btn ' + [respond == 'responseFound' ? ' btn-yellow' : ' btn-default', type == 1 ? ' btn-block' : ' btn-xs center-block']">
...
</a>
</template>
I try like that, but it does not work?
You can use :class="[array, of, classes]" syntax:
<a :class="['btn', (respond === 'responseFound' ? 'btn-yellow' : 'btn-default'), (type === 1 ? 'btn-block' : 'btn-xs center-block')]">
As a bonus you don't have to worry about adding the leading spaces, Vue will handle it.
Just to keep things clean in view/template/markup, move your conditions to computed properties:
<template>
<a :class="['btn', getRespondClass, getTypeClass]">
</template>
<script>
export default {
data () {
return {
respond: '',
type: ''
}
},
computed: {
getRespondClass () {
return this.respond === 'responseFound' ? 'btn-yellow' : 'btn-default'
},
getTypeClass () {
return this.type === 1 ? 'btn-block' : 'btn-xs center-block'
}
}
}
</script>
Pretty sure the current showed answer says how you can add multiple classes on 1 condition. But if you want to have multiple conditions for your class added you can simply just do it like this:
:class="{'classname': condition_one && condition_two}"
Or you can do this:
:class="[ condition1 | condition2 ? 'className' : '']"
This checks for either of the conditions to be true. If you want to check for both replace | with &&. But if you have nothing in the else class, I think #Marnix's answer is cleaner.

Check if value exists in vuejs

I have data : 1,2,3,4,4,5 & my code like this:
<div id="looping" v-for="display in editlistAssesments">
{{display.test_id}}
</div>
My code if in php such as like this
$temp_id = array();
foreach($data as $data){
if(in_array($data ->test_id,$temp_id)){
echo" 1: no";
echo" 2: no";
echo" 3: no";
echo" 4: yes"; //because he have same value
echo" 5: no";
$temp_id[] = $data ->test_id;
}
}
how I can do that in loop vueJs..??
From my point of view, the best way is:
<div id="looping"
v-for="display in editlistAssesments">
<span v-if="typeof display.test_id !== 'undefined'">
{{display.test_id}}
</span>
</div>
Because if you use v-if="display.test_id" and the test_id value is 0 (boolean comparation) you will never see the display.text_id.
You can use also this another condition to check if is null or undefined: display.test_id != null (loose equality operator)
As far as I understand, you want to check if value is in array and then render it accordingly?
If so, you need a Vue custom filter. Something like this will do the trick:
var vm = new Vue({
el: 'body',
data: {
editlistAssesments: [1,2,3,4,4,5]
},
filters: {
ifInArray: function (value) {
return this.editlistAssesments.indexOf(value) > -1 ? 'Yes' : 'No';
}
},
});
And then use it like this:
<div id="looping" v-for="display in editlistAssesments">
<span v-text="display.test_id | ifInArray"></span>
<!-- bind Vue value to html element is better practice -->
</div>
Check docs for more information:
http://vuejs.org/guide/custom-filter.html