Setting tabIndex nicely in VueJS when iterating over multiple objects - vuejs2

I have a couple of nested objects I need to iterate over to create inputs. I've dumbed it down to an example below. I would like to set tabIndexes in these inputs.
<div v-for="(iv, ik, ii) in {a: 'x', b: 'x'}" :key="ii">
<div v-for="(jv, jk, ji) in {a: 'y', b: 'y'}" :key="ji">
<div v-for="(kv, kk, ki) in {a: 'z', b: 'z', c: 'z'}" :key="ki">
<input type="text" :tabindex="(ii * 100) + (ji * 10) + ki" />
<label>{{(ii * 100) + (ji * 10) + ki}}</label>
</div>
</div>
</div>
What is the best way to be able to set my tabindex to 0, 1, 2, 3, etc? I found that setting a third arg on the v-for provides a numeric index, but what I've got seems a little convoluted. Is there a better way I can go about this?
The above results in output like the following:
[___________] 0
[___________] 1
[___________] 2
[___________] 10
[___________] 11
[___________] 12
[___________] 100
[___________] 101
[___________] 102
[___________] 110
[___________] 111
[___________] 112
Which works, but seems less than ideal. I know the tab key will work as intended if they're sequential and that gaps seem fine. But is there a cleaner way I could get 1 through 12 instead of the staggered numbers I've got? Basically like a running index (x++, etc) for each time I hit the ?
I tried setting an int in the 'data' and then a method to increment that, but quickly threw myself into an infinite re-rendering loop.
Thanks.

there is a trick to doing this, but it's a hack, and not exactly a best practice
Template:
{{numitems = 0 | hide}}
<div v-for="(iv, ik, ii) in {a: 'x', b: 'x'}" :key="ii">
<div v-for="(jv, jk, ji) in {a: 'y', b: 'y'}" :key="ji">
<div v-for="(kv, kk, ki) in {a: 'z', b: 'z', c: 'z'}" :key="ki">
<input type="text" :tabindex="numitems += 1" />
<label>{{(ii * 100) + (ji * 10) + ki}}</label>
</div>
</div>
</div>
script hide Filter definition
filters: {
hide: function(value){
return ''
}
}
you don't need the hide filter, but it ensures that you don't put anything into the template during definition. You can also define numitems in data. And use methods to reset and increment.
another option is setting up a computed value that uses the number you generate as an index to the incremental values without any gaps.
Whenever your objects' keys change, you can do the calculation, generating the keys either using your method, or using an object.
here's an example that uses ${ik}_${jk}_${kk} as the key
tabIndexVals(): {
let c = 0;
let o = {};
Object.keys(i).forEach(ik => {
Object.keys(i[ik]).forEach(jk => {
Object.keys(i[ik][jk]).forEach(kk => {
let key = `${ik}_${jk}_${kk}`;
o[key] = c;
c++;
})
})
})
return o;
}

Related

Totaling a Summary Line in Data Table

I hve a sumField method which totals values in a given column:
sumField (key) {
let total = 0
const sum = this.tableName.reduce((acc, cur) => {
return (total += +cur[key])
}, 0)
return sum
}
Inside my data table I call sumField to produce a rolling total of the values in a specific column of my data table:
<template v-slot:[`body.append`]="{headers}">
<tr class="summary">
<td v-for="(header,i) in headers" :key="i">
<div v-if="header.value == 'COL HEADER 1'">
{{ sumField('COL HEADER 1') }}</div>
<div v-else-if="header.value == 'COL HEADER 2'">
{{ sumField('COL HEADER 2') }}</div>
</td>
</tr>
</template>
This is presented on screen as an additional line of the data table, and the values change depending on the filters applied to the table.
Is there a way to sum the values calculated, and show this as a rolling total value also?
Got a solution which I hope might provide some help to others in the future!
Started by creating a new function:
sumTot (col1, col2) {
var one = col1
var two = col2
var tot = col1 + col2
return tot
}
Then I called sumTot giving the arguments of the individual sumField calls:
{{ sumTotal(sumField ('COL HEADER 1'), sumField ('COL HEADER 2')) }}
That gives me a dynamically updated running total value.

Angular Gridster-2 Redraggable and Resize

I am new to angular gridster 2
Could you please let me know if we are able to display the already developed charts using angular gridster 2 , I am also looking to see the same approach . As my UI has 5 different div blocks and inside there is a card being displayed in each div block ..
How can I use angular gridster to disaply these 5 cards with draggable and resizable features after loading the page..
<gridster [options]="options">
<div class="col-md-12 ml-auto mr-auto" >
<div class="row" > <!--[item]="item" *ngFor="let item of dashboard" gridster-item-->
<gridster-item class="col-lg-3 col-md-6 col-sm-6" [item]="item" *ngFor="let item of dashboard" >
<card1></card1>
<card2></card2>
</gridster-item>
</gridster>
export class dashboard implements oninit{
options={
draggable: {
enabled: true},
resizable: {
enabled: true
}
};
dashboard = [
{cols: 1, rows: 1, y: 0, x: 0}
];
}
Please explain what does the rows,columns, x and y represents .. How does it effect the UI if these values are changed..
Cols and Rows are your gridster layout design to allow gridster-items. If cols = 1 and rows = 1, you can put one gridster item only except maxItemRows and maxItemCols set greater than 1. The y and x are col and row index for gridster-item.
Example, cols = 5, rows = 5, and x = 3, y = 2, gridster-item will display start from column 3 and row 2.
Here are some articles about gridster.
https://medium.com/javascript-in-plain-english/drag-and-drop-dashboard-builder-with-angular-and-gridster-a07592e54ce2
https://developer.aliyun.com/mirror/npm/package/helio-angular-gridster

How to bind mobx-react-form with mobx-state-tree store

I have a form with three fields A, B and C. I am using mobx-react-form to handle the fields because it comes with onChange updates and validation baked in. I have set this up and it's working fine when I log out the values.
const View = inject('store')(observer(({ title }: Props) => {
return (
<div>
<form onSubmit={form.onSubmit}>
<label htmlFor={form.$('A').id}>
{form.$('A').label}
</label>
<input {...form.$('A').bind()} />
<label htmlFor={form.$('B').id}>
{form.$('B').label}
</label>
<input {...form.$('B').bind()} />
<label htmlFor={form.$('C').id}>
{form.$('C').label}
</label>
<input {...form.$('C').bind()} />
<button type="submit" onClick={form.onSubmit}>
Submit
</button>
</form>
</div>
);
}));
I will use the values from fields A, B and C to calculate additional values D and E which will appear in the app.
For state management, I am using mobx-state-tree to create the store.
export const Store = types.model('Store',
{
A: types.maybeNull(types.number),
B: types.maybeNull(types.number),
C: types.maybeNull(types.number),
D: types.maybeNull(types.number),
E: types.maybeNull(types.number),
})
.views(self => {
return {
getD: () => self.D,
getE: () => self.E
};
})
.actions(self => {
return {
setD: (A, B) => self.D = A + B,
setE: (B, C) => self.E = C - B,
resetStore: () => {
self.A = defaultState.A;
self.B = defaultState.B;
self.C = defaultState.C;
self.D = defaultState.D;
self.E = defaultState.E;
},
};
});
How can I bind the mobx-react-form fields A, B and C to corresponding values in the store so they update on change?
Well,
first of all,
I want you to notice , you are trying to connect 2 different state managers .
I like mobx-recat-form very much ! but you should consider that it
manages the state for you automatically .
You can bind yourself to onChange, and update D,E accordingly .
For Example:
<input onChange={e => {
form.$('B').set(e.target.value);
// handle D,E according to value
}} />
this is the most "direct" way to deal with it.
if you want to solve it in more "mobx" way ,
Do something likes this:
form.$('B')
.observe(({ form, field, change }) => {
// deal with D,E according to values
});
For further information I would take a look into:
https://foxhound87.github.io/mobx-react-form/docs/extra/mobx-events.html
And I would suggest to stick with this library(mobx-react-form) for controlling
the forms , and with direct hooks(1 st example) or by observing the form propagate changes to other values in other stores .

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.