So to explain why I need to do that :
I've got a masonry of photos and I need to add an index to each of those photos.
The thing is I can't relie on the index of my v-for because sometimes it is on purpose an "empty case".
|---|---|---|
| | 2 | 3 |
| 1 |---|---|
| | 4 | 5 |
|---|---|---|
| 6 | | 8 |
|---| 7 |---|
| 9 | | 10|
|---|---|---|
The code looks like this.
<div v-for="(row, i) in rphotos" :key="i">
<div v-for="(p, j) in row" :key="i*nbCol+j">
<div v-if="p"
:data-index="index"
class="g-item">
<!-- Increment index there -->
</div>
<div v-else class="g-item g-empty" />
</div>
</div>
However due to vuejs reactivity, my index will always be updated.
An other solution could perhaps be to check through a computed if on the row, g-item is not too a g-empty however I'm not even sure it is possible and yet dont see how precisely I could do this.
My best bet would be to increment a value on display as it is done in PHP template rendering like Smarty or Blade. But since javascript frameworks is meant to be reactive is there a way of doing that ?
Any idea is welcomed.
It's not clear what your data looks like, but you can set an index to each item in rphotos before displaying them so you can then access that index inside the v-for with p.index:
<div v-for="(row, i) in rphotosFiltered">
<div v-for="(p, j) in row">
<div v-if="p.index !== null" :data-index="p.index"></div>
<div v-else/>
</div>
</div>
data(): {
return {
rphotos: [
[
{index: 1, item1: ''},
{index: null, item2: ''}
],
[
{index: 3, item3: ''}
]
]
}
},
computed: {
rphotosFiltered() {
// modify "this.rphotos" list here the way you need it
// return it
}
}
Incrementing a variable during render requires using a method, which breaks reactivity. For this reason, I recommend calculating the index of each image before rendering. In general, the rendering process should be just that--rendering the data that is already there.
If you can't modify the array of image data to include an index property, you could use separate array or hash to store the index values. If the array of image data might be changed by code that is outside your component, you can use a watch to update the index values when the image data changes. In this case also be sure to use the created or mounted hook to initialize the index values when the component is loaded.
UPDATE
Instead of initializing the index values using the created or mounted hook, simply set the immediate property of the watch. This forces it to run when the component is loaded as well as when the watched value changes.
Related
I have an issue rendering parameters in Angular 8, I get datas from API that I need to render in divs that matchs those datas but I'm having an issue where the datas shows in every divs, here's what it looks like :
Here, "Drops","Misc","Network" are the main divs that need to render the lower-categories.
Altought, what I want is for example, to have only "Drops Aluminium" inside the main "Drops", only "VANNE" inside "Misc" and "Main" in "Network". The lower categories should only renders when they have their ids inside the main id ( see picture 2 below ).
What I have tried :
Binding the values inside the divs, since all main and lower categories have ids like so :
Here is a stackblitz example : https://stackblitz.com/edit/angular-me2ppb?file=src%2Fapp%2Fapp.component.ts
Thank you in advance for your time and help, it's much appreciated !
If I understand correctly, could you just solve it with a nested loop in the template?
<div *ngFor="let main of total_by_level | keyvalue">
{{label_name[main.key]}}
<div *ngFor="let sub of main.value | keyvalue">
{{label_name[sub.key]}}
</div>
</div>
This would result in:
Network
Main
Drops
Drops Aluminium
Misc
VANNE
You have 2 options here:
Adapt your HTML to loop around total_by_level and query label_name appropriately
Build the output in code
It looks like you have attempted both, and so are open to either. Personally, I prefer to do as much as possible in the code and keep the HTML as dumb as possible, so I would take approach 2.
In ngOnInit() (which should be where you do any initial processing), I would build an array based on the structure on total_by_level.
output: any[];
ngOnInit() {
this.output = Object.keys(this.total_by_level).map(levelKey => {
const child = this.total_by_level[levelKey];
return {
level: {
label: this.label_name[levelKey]
},
children: Object.keys(child).map(childKey => ({
label: this.label_name[childKey],
value: child[childKey]
}))
};
});
}
It then becomes simple to bind to this array in your HTML:
<div *ngFor="let item of output">
{{item.level.label}}
<div *ngFor="let child of item.children">
{{child.label}}
{{child.value}}
</div>
</div>
You are dealing with some odd data structures, and I'm not sure of your terminology, so I have guessed a little bit here. You can take the concept of this idea and work with it. I am also assuming that there is only ever 1 nested child in total_by_level.
DEMO: https://stackblitz.com/edit/angular-upqdex
How do you bind a template containing tds within a tr?
I have a table that I want column sections to be dynamically added/removed/rearranged.
My tds are grouped into categories, the user can pick categories, and then the sections of the table should be pieced together with if/then logic using the category ids.
I'm having trouble binding my category templates into tr. I've tried:
setting the component selector to "app-componentName" and inputting the component using <app-componentName></app-componentName>
setting the component selector to "[app-componentName]" and inputting the component using <template app-componentName></template>
but neither works correctly. Using the first option, all of the data from my category is put into one td. Using the second option, none of my categories show up at all.
Here's the gist...
table-row.component.ts
...
var selectedCategories = [
{
id: 2,
name: "billing address"
},
{
id: 1,
name: "name"
}
];
...
table-row.component.html
<ng-container *ngFor="let category of selectedCategories">
<app-name-cells [person]="person" *ngIf="category.id === 1">
<app-billing-address-cells [person]="person" *ngIf="category.id === 2">
</ng-container>
name-cells.component.html
<td>{{user.firstName}}</td>
<td>{{user.middleName}}</td>
<td>{{user.lastName}}</td>
billing-address-cells.component.html
<td>{{user.billingAddress.line1}}</td>
<td>{{user.billingAddress.line2}}</td>
<td>{{user.billingAddress.city}}</td>
<td>{{user.billingAddress.state}}</td>
<td>{{user.billingAddress.zip}}</td>
Result
//full address in first cell, full name in second cell
|| 123 Main St New York NY 12345 | John A Doe ||
Expected Result
// each part of each component in it's own cell
|| 123 Main St | New York | NY | 12345 | John | A | Doe ||
Edit - Added StackBlitz:
See code example: StackBlitz
The user would like to have the grid lines copied with the grid data when he copies from the web page. Is that possible ?
currently the result of copying is something like:
name subject
lolo math
fofo history
what I want to have is:
| name | subject |
---------------
| lolo | math |
---------------
| fofo | history |
I googled that but I couldn't find a good solution for that, and I'm not experienced in front-end tricks.
You can do something like this:
function changeCopiedText(event) {
event.preventDefault();
// get original text from selection
var originaltext = window.getSelection();
// call your function to add the lines to the original text
var textwithlines = addTableLines(originaltext);
// add the new text to the clipboard
if (window.clipboardData) {
window.clipboardData.setData('Text', textwithlines);
}
}
// capture the copy event
document.addEventListener('copy', changeCopiedText);
But you will have to think about the best way to add the lines in addTableLines, according to your needs.
You need to create a copy event to your table:
//Here we assume that your table has an id and it is stored in myTable
document.getElementById(myTable).addEventListener('copy', function(e){
e.preventDefault();
e.clipboardData.setData("Text", parseClipboard(e.clipboardData.getData("Text")));
});
In the example above parseClipboard is assumed to handle your text in the clipboard and convert it as you will. You would want to split the text with newline as separator, add your | characters at the start and end of the line for each item (line) and then join them with the separator of newline---------------newline
My solution was a mix of the solutions above use Clipboard.js and parse the copied text:
new Clipboard('.clipboard-btn', {
text: function (trigger) {
var originaltext = $(trigger).next('table').closest("#my-tbl").text();
return addTableLines(originaltext);
}
});
I'm still writing addTableLines because the split is not detecting the new lines.
So basically I have this quiz app im working on using angular and I want to tally up the amount of times the right answer is entered. I already made it so the words 'CORRECT' are displayed by the question if they type the right answer in the text box, but I want to see how many times that happens. Here is my code
div ng-repeat="q in questions">
<span>{{ q.question }}</span><br>
<input type="text" ng-model="q.ans" name="email" placeholder="">
<div ng-show="q.ans===q.answer">CORRECT!</div>
<div>
so basically questions is just an array with a question string and answer string. I want to see at the end how many are correct. So I'm thinking, I added in a correct property to the question objects that has a default of 0 which could mean wrong, and change when its right to 1.
Now how would I make it change from the html page here when someone types the right answer? like if correct is shown, if the ng-show is right, then that value would be 1, if not, it'd be 0.
thanks for any assistance. Wondering if I could do this in real time instead of having a 'check' button at the end.
EDIT: okay I looking around the ng-if directive, would it somehow be possible to add like
<div ng-if="q.ans===q.answer">{{ q.correct = 1 }} </div>
or somehow execute that q.correct = 1 (meaning that answer is correct) if the ng-if block is run?
Make a filter for counting the correct answers
// app is your module
app.filter('correctCount', function() {
return function(questions) {
return questions.reduce(function(count, q) {
return count + (q.ans === q.answer ? 1 : 0);
}, 0);
};
})
Then you can display the total in your template
Total: {{questions | correctCount | number}}
Demo ~ http://plnkr.co/edit/br3fxHQ8q04ajZj6Fxch?p=preview
An alternative to reduce that might be easier to understand is...
return questions.filter(function(q) {
return q.ans === q.answer;
}).length;
On my (Cordova, jQuery-Mobile) iOS mobile application I receive some JSON values on the first page.
To store them I use arrays.
To pass them and reuse them on the second page I use a each function, count up an integer(i) on every run and push the received data into several arrays, the integer(i) I store in a DIV in data-id. (<div data-id'"+i+"'>)
As soon as the User navigates to the second page by tapping on one of the DIVs I use the integer(i) in data-id to get every value from the arrays and display on page 2.
Example
Database:
ID NAME SURNAME AGE USERID
1 Michael Douglas 44 uid3242
2 Eric Cartman 11 uid9275
3 Felix Woodspuck 38 uid3852
4 Amadeus Mozart 158 uid1120
Script:
ID = [];
NAME = [];
SURNAME = [];
AGE = [];
USERID = [];
for example - If the user clicks on the DIV with data-id=2
iInt = $(this).attr('data-id'); // iInt=2
$("#abc").text(NAME[iInt];) // NAME[2]; --> Eric
$("#xyz").text(SURNAME[iInt];) // SURNAME[2]; --> Cartman
...
This is just an example, please ignore the values.
But in my App I am using a lot of variables on a lot of pages, and it gets a little messy in code, so what I am wondering about is if there is a more efficent way to save the values I get from JSON to use them on the second page? How do you do it?
Thank you.
I've been tucking the JSON string into localstorage, then pulling it out later and restoring to an object.
localStorage.setItem("jsondata",mytextvar);
Then later:
var obj = JSON.parse(localStorage.getItem("jsondata"));