Display array from json data to cards - html

So, im a little bit lost here and i need some help.
I have a json that come from the server with data that i dont know.
Based on that i found a solution to display the data on html here on SO:
https://stackoverflow.com/a/50352965/9721446
But the problem is that each "item" is an entry from array, so if i ngfor array, it outputs each line as an item, and i want the item to be all entries of each result.
heres the html:
<ng-container *ngFor="let item of singleArray | paginate: { itemsPerPage:411, currentPage: p} ">
<!-- All the entries -->
<div class="w3-container">
<!-- Table view-->
<table class="center">
<tr *ngIf="!item.tag.includes('URL') && !item.tag.includes('linkChal')">
<td><div class="col-xs-auto thick">{{item.tag.toLowerCase() | translate}}</div></td>
<td class="tab">{{item.value}}</td>
</tr>
<tr *ngIf="item.tag.includes('URL')">
<td>Link da entrada: </td>
<td> - Ver mais -</td>
</tr>
<tr *ngIf="item.tag.includes('linkChal')">
<td>Link do Challenge: </td>
<td> - Challenge -</td>
</tr>
</table>
<div style="background-color: #ff7d2a">
<ul *ngIf=" item.tag.includes('---------')"><p>New Entry</p></ul>
</div>
</div>
</ng-container>
Ts:
for(let i in res)
{
//array with entities from json
this.entity.push(i);
for(let j in res[i])
{
let val = Number(j)+1;
this.cont.push(i +" - nº: " + val );
this.singleArray.push({
tag: i,
value: val
});
for(let t in res[i][j])
{
this.test.push(t);
this.cont.push(t +" - "+ this.responseList[i][j][t]) ;
if(t.split(".",2)[1] === "CompositeId")
{
this.test.push("URL:");
//Get the id
this.cont.push(this.moduleName + "/" + t.split(".",2)[0] + "/" + this.responseList[i][j][t].match(/=(.*)_/)[1]);
//debugger;
this.singleArray.push({
tag: "URL:",
value: this.moduleName + "/" + t.split(".",2)[0] + "/" + this.responseList[i][j][t].match(/=(.*)_/)[1]
});
}
else if(t.split(".",2)[1] === "Challenge")
{
this.singleArray.push({
tag: "linkChal",
value: this.moduleName + "/" +t.split(".",2)[1] + "/" + this.responseList[i][j][t].match(/=(.*)_/)[1]
});
}
else {
this.singleArray.push({
tag: t,
value: this.responseList[i][j][t]
});
}
}
this.test.push("\n");
this.cont.push("\n");
this.singleArray.push({
tag: "---------\n",
value: "--------\n"
});
//it ends an item here
}
}
Heres the output i have with that:
Each one line is an entry from the array, the big question is, how to transform all lines/entries until "New Entry" and made an single item to ngfor and display data into a card that i already have..)
I've tried to create an array and push the singleArray into it (hoping each entry of that new array was an item that i want), at the end of for(let j in res[i]) on .ts but it just repeated all the entries creating a bunch of entries..
here, at the end of that for, i've tried to push an array with something, then ngfor it (it gives me the number items that i want, but then i dont have the results to access them..)
Has anyone had this problem before?
thanks in advance
Edit: here's what singleArray looks like:

Your best bet here is to follow the single responsibility principal and separate the concerns of each class.
Stop trying to do this all in the view and separate out the responsibility of formatting the data and the problem will seem much simpler.
Make a new class to define the model you want your view to use
Have your view implement this new ideal model that you control
Generate some test data to make get this looking like what you want
Create a new class who's entire responsibility is to turn the external model from the api response into this new internal model
json2ts may help generate a better external model from the response, but it may not be of much use in this case
Once you have done the above, based on your sample output, it should be fairly simple to convert from the external model into the internal model. It's hard to convey this, but assuming the hyphens are the item separator you could simply do something like the following:
const externalItems = // data from api
const internalItems = [];
let currentInternalItem = {};
externalItems.forEach(item => {
if (item.tag.startsWith('---------')) {
internalItems.push(currentInternalItem);
currentInternalItem = {};
} else {
currentInternalItem[item.tag] = item.value;
}
});
This would group the array back into an object that you can use in your view.

I think I'm complicating too much.. The objective here is to display what comes from JSON into specific locations, like a card, with header and content, to better display the results.
I have a service that gives me a JSON, that i never knows what inside, that depends on the search term and can bring much information. For example:
If the term is "Idea":
If the term is Challenge:
My .ts file is only console.log what comes from the api.
ngOnInit() {
var setting = {setting to connect the server..}
enter code here
$.ajax(settings).done((rest) => this.response(rest));
}
response(res){
console.log(res);
}
How can i display that data the way i want?
Sorry for the long post, and for not beeing objective on the main question.

Related

Show a table with specific array content in angular

In my app.component.ts i created the array "data", which consists of a few strings separated by comma.
I parse it into json and with console.log i am able tto clearly present it the way i'd like to doing this:
const jsonData = Papa.parse(content, {skipEmptyLines: true,});
jsonData.data.forEach(function(data){
console.log(data[0], "|" , data[1] , "|" , data[2] , "|" , data[3]);
}
Its basically going through each line and then selecting the three data contents i'd like to be shown.
I want to display the array that way on my website in my app.component.html, but only the first three entries, because it has countless ones...
I've tried using the data as a json element
<td>{{data.entry1}}</td>
But i didn't succeed. Its quite a simple task but im really stuck here. Could anyone explain why I dont get it?
An easy way is to create an array that holds the data you would like to show.
ts
partialData = [];
yourFunction = () => {
:
jsonData.data.forEach(function(column) {
const line = [];
for (let i = 0; i < 4; i++) {
line.push(column[i]);
};
partialData.push(line);
});
}
html
<tr *ngFor="let line of partialData">
<td *ngFor="let column of line">
{{column}}
</td>
</tr>

Svelte virtual list component - function not working after filtering list

I am using the virtuallist component in a svelte project. I have added filtering to the list. My issue is that a function in my project stops working when I filter the list, I'm assuming because the list item is not yet in the dom when filtered?
The project converts medical units from metric units to international units using two inputs. Changing one input automatically converts the other.
Before filtering, everything works well with conversion but after entering a item name, (e.g. Type Zinc), the input conversion fails in the filtered items. No conversion occurs.
I've looked into afterUpdate as an option but not sure how to implement it.
---------Added Info -------------------
The issue is with list items not yet in view. Try typing "zinc" and then changing the input values of Zinc (fails) vs typing Acetone (item already in view) and changing those inputs (it works).
Here is a working REPL
The script:
<script>
import VirtualList from './VirtualList.svelte';
import unitsH from './data.js';
let searchTerm = "";
let start;
let end;
$: filteredList = unitsH.filter(item => item.name.toLowerCase().indexOf(searchTerm) !== -1);
function setBothFromSIH(value, i) {
const {factor, siValue} = unitsH[i];
unitsH[i].siValue = +value;
unitsH[i].usValue = +(value / factor).toFixed(2);
}
function setBothFromUSH(value, i) {
const {factor, usValue} = unitsH[i];
unitsH[i].usValue = +value;
unitsH[i].siValue = +(value * factor).toFixed(2);
}
</script>
With simplified html code:
<VirtualList items={filteredList} bind:start bind:end let:item >
<div class="border" style="overflow-x: scroll;"> <div><div>
<div class="name">{item.name}</div>
<span>Specimen: {item.specimen} </span>
<span> Conversion Factor: {item.factor} </span>
</div>
<div>
<label>US Range:{item.conventionalRange} {item.conventionalUnit}</label>
<input name="us{filteredList.indexOf(item)}" value={item.usValue} on:input="{e => setBothFromUSH(e.target.value, filteredList.indexOf(item))}" type=number placeholder=" US">
</div>
<div>
<label>SI Range: {item.siRange} {item.siUnit}</label>
<input name="si{filteredList.indexOf(item)}" value={item.siValue} on:input="{e => setBothFromSIH(e.target.value, filteredList.indexOf(item))}" type=number placeholder="SI">
</div></div> </div>
</VirtualList>
<p>showing items {start}-{end}</p>
Thanks for any help in getting this to work!
It's a small issue with your filter. You convert the product name to lower case but not the filter term ;) If you enter acetone instead of Acetone, then it works. The fix:
$: filteredList = unitsH.filter(item => item.name.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1);
Edit:
The issue with not calling the function for some filtered element is that you display the filteredList but still do the lookup on the unitsH list. Change it to this and it works:
function setBothFromSIH(value, i) {
const {factor, siValue} = filteredList[i];
filteredList[i].siValue = +value;
filteredList[i].usValue = +(value / factor).toFixed(2);
}
function setBothFromUSH(value, i) {
const {factor, usValue} = filteredList[i];
filteredList[i].usValue = +value;
filteredList[i].siValue = +(value * factor).toFixed(2);
}
Happy hacking!
Your problem is caused by using the wrong index, in the change handler you pass the index of the item in filteredIndex but then you use that one to change the item on that index in the array unitsH.
You can see that by:
- start anew
- note the value for Acetaminophen (index 0)
- search zinc
- change value of zinc (index 0 in filtered list)
- clear search
->> acetaminophen has changed because that is index 0 in unitsH
You can easily solve this by passing in the index of the original array instead:
<input name="si{filteredList.indexOf(item)}" value={item.siValue} on:input="{e => setBothFromSIH(e.target.value, unitsH.indexOf(item))}" type=number placeholder="SI">
However, if you move the markup for each item to a seperate component you can vastly simplify this by directly interacting with the properties instead of trying to change them in the array.

VueJs - Updating class with a setInterval function not working [duplicate]

I'm new to Vuejs. Made something, but I don't know it's the simple / right way.
what I want
I want some dates in an array and update them on a event. First I tried Vue.set, but it dind't work out. Now after changing my array item:
this.items[index] = val;
this.items.push();
I push() nothing to the array and it will update.. But sometimes the last item will be hidden, somehow... I think this solution is a bit hacky, how can I make it stable?
Simple code is here:
new Vue({
el: '#app',
data: {
f: 'DD-MM-YYYY',
items: [
"10-03-2017",
"12-03-2017"
]
},
methods: {
cha: function(index, item, what, count) {
console.log(item + " index > " + index);
val = moment(this.items[index], this.f).add(count, what).format(this.f);
this.items[index] = val;
this.items.push();
console.log("arr length: " + this.items.length);
}
}
})
ul {
list-style-type: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.11/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<div id="app">
<ul>
<li v-for="(index, item) in items">
<br><br>
<button v-on:click="cha(index, item, 'day', -1)">
- day</button>
{{ item }}
<button v-on:click="cha(index, item, 'day', 1)">
+ day</button>
<br><br>
</li>
</ul>
</div>
EDIT 2
For all object changes that need reactivity use Vue.set(object, prop, value)
For array mutations, you can look at the currently supported list here
EDIT 1
For vuex you will want to do Vue.set(state.object, key, value)
Original
So just for others who come to this question. It appears at some point in Vue 2.* they removed this.items.$set(index, val) in favor of this.$set(this.items, index, val).
Splice is still available and here is a link to array mutation methods available in vue link.
VueJS can't pickup your changes to the state if you manipulate arrays like this.
As explained in Common Beginner Gotchas, you should use array methods like push, splice or whatever and never modify the indexes like this a[2] = 2 nor the .length property of an array.
new Vue({
el: '#app',
data: {
f: 'DD-MM-YYYY',
items: [
"10-03-2017",
"12-03-2017"
]
},
methods: {
cha: function(index, item, what, count) {
console.log(item + " index > " + index);
val = moment(this.items[index], this.f).add(count, what).format(this.f);
this.items.$set(index, val)
console.log("arr length: " + this.items.length);
}
}
})
ul {
list-style-type: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.11/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<div id="app">
<ul>
<li v-for="(index, item) in items">
<br><br>
<button v-on:click="cha(index, item, 'day', -1)">
- day</button> {{ item }}
<button v-on:click="cha(index, item, 'day', 1)">
+ day</button>
<br><br>
</li>
</ul>
</div>
As stated before - VueJS simply can't track those operations(array elements assignment).
All operations that are tracked by VueJS with array are here.
But I'll copy them once again:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
During development, you face a problem - how to live with that :).
push(), pop(), shift(), unshift(), sort() and reverse() are pretty plain and help you in some cases but the main focus lies within the splice(), which allows you effectively modify the array that would be tracked by VueJs.
So I can share some of the approaches, that are used the most working with arrays.
You need to replace Item in Array:
// note - findIndex might be replaced with some(), filter(), forEach()
// or any other function/approach if you need
// additional browser support, or you might use a polyfill
const index = this.values.findIndex(item => {
return (replacementItem.id === item.id)
})
this.values.splice(index, 1, replacementItem)
Note: if you just need to modify an item field - you can do it just by:
this.values[index].itemField = newItemFieldValue
And this would be tracked by VueJS as the item(Object) fields would be tracked.
You need to empty the array:
this.values.splice(0, this.values.length)
Actually you can do much more with this function splice() - w3schools link
You can add multiple records, delete multiple records, etc.
Vue.set() and Vue.delete()
Vue.set() and Vue.delete() might be used for adding field to your UI version of data. For example, you need some additional calculated data or flags within your objects. You can do this for your objects, or list of objects(in the loop):
Vue.set(plan, 'editEnabled', true) //(or this.$set)
And send edited data back to the back-end in the same format doing this before the Axios call:
Vue.delete(plan, 'editEnabled') //(or this.$delete)
One alternative - and more lightweight approach to your problem - might be, just editing the array temporarily and then assigning the whole array back to your variable. Because as Vue does not watch individual items it will watch the whole variable being updated.
So you this should work as well:
var tempArray[];
tempArray = this.items;
tempArray[targetPosition] = value;
this.items = tempArray;
This then should also update your DOM.
Observe object and array reactivity here:
https://v2.vuejs.org/v2/guide/reactivity.html

How can I link paths so results are updated in dom-repeat

I am developing an appointment booking system. It basically consists of a set of Polymer custom elements arranged as follows (indented elements are in the template of the element rather than actually organised as shown)
<my-appointments>
<person-appointment booking="{{booking}}>
<booking-type type="{{booking.type}}">
<div>[[booking.type]]</div>
</booking-type>
</person-appointment>
<appointment-day booking="{{booking}}>
<template is="dom-repeat" items="{{appointments}} as="{{appointment}}">
<div>[[appointment.type]]</div>
</template>
</appointment-day>
</appointment>
inside the <appointment-day> element booking is defined as an "Object" property and appointments as an "Array". As a booking is made, the booking object is spliced into the appointments array at the correct place.
At the same time I use the linkPaths function to join path 'booking' to path 'appointments.n' (where n is 0, 1, 2 etc for where in the appointments array booking is situated)
This is the code that does this
if (foundAppointment) {
//found where to insert appointment, so do so
this.splice(path, j, 0, this.booking);
this.linkPaths('booking', path + '.' + j);
this.linkedBooking = true;
break;
}
Not shown is a mechanism inside <booking-type> to update the type property. SO when I update the type property using this mechanism, the visual representation changes inside the <person-appointment> element but it does not change inside the dom-repeat. I can check that the object located at appointents[n] IS updated, but the display is not updated.
I presume I haven't properly linked booking to the appropriate appointment entry. BUT how should I achieve this
Polymer does not observe the change of sub-properties of appointments.
Try to Force data system to pick up array the mutations with the code below:
// Force data system to pick up the **array** mutations
var array = this.appointments;
this.appointments= [];
this.appointments= array;
Try to add this to your code.
if (foundAppointment) {
//found where to insert appointment, so do so
this.splice(path, j, 0, this.booking);
this.linkPaths('booking', path + '.' + j);
this.linkedBooking = true;
// Force data system to pick up array mutations
var array = this.appointments;
this.appointments= [];
this.appointments= array;
break;
}
Or to Force data system to pick up the Object mutations with the code below:
// Force data system to pick up array mutations
var object = this.appointment;
this.appointment= [];
this.appointment= object;
I'm only guessing that the problem is in binding to array items.
Polymer has special rules for that.
Here is one example of array binding: Plunk
<template is="dom-repeat" items="[[first4People(people, people.*)]]">
<div>
Index: <span>[[index]]</span>
<br>
First as this:[[arrayItem(people.*, index, 'first')]]
<br>
First not as this: <span>[[item.first]]</span>
</div>
<br><br>
</template>
Simplified online example of the problem would help to understand the issue better.

Mandrill Editable Template: mc:edit link href

I'm trying to use Mandrill templates to send order tracking emails.
Using the mc:edit works well for simple text like <span mc:edit="ship_id">ship_id</span><br>
I was wondering if there was a way to pass the href link in a variable i.e. tracking_url
<a class="mcnButton " title="Track order" href=tracking_url target="_blank" style="font-weight: bold;text-align: center;">Track Order</a>
I'm using Djrill for Django and here's the code which sends the email so far, and I'd like to add tracking_url as a template_content variable or something similar
msg = EmailMessage(subject="Track your order", from_email="admin#example.com", to=[user.email])
msg.template_name = "order-sent"
msg.template_content = {'order_id' : order_id, 'order_date' : order_date, 'order_type' : order_type, 'first_name' : user.first_name, 'last_name' : user.last_name, 'phone' : user.info.phone,
'd_street' : d.street, 'd_zipcode' : d.zipcode, 'd_city' : d.city, 'd_country' : d.country}
msg.send()
It seems possible using the AddGlobalVariable method (read here) but I can't figure out how to use it..
I have an Email Dispatcher that uses MandripApp to send normal emails (as SMTP) as well to send emails using the template.
I do not know how to pass what you are asking, as I'm not using mc:edit attributes any longer (as my users will never edit the template themselves, me or a developer will) but I can provide you help with the global variables.
Global variables are the same as Mailchimp vars, like *|EMAIL|* and this is what I do:
var mergeVars = Dictionary<string, string>();
mergeVars.Add("ORDER_ID", orderId);
mergeVars.Add("CUSTOMER_NAME", fullname);
mergeVars.Add("CUSTOMER_FNAME", fullname.Contains(" ") ? fullname.Split(' ')[0] : fullname);
mergeVars.Add("CUSTOMER_EMAIL", email);
for example, a hole table:
StringBuilder sb = new StringBuilder();
foreach (ProductInfo pi in products)
{
sb.Append("<tr>");
sb.AppendFormat("<td style=\"text-align:left;\"><img src=\"http://dynassets1.gavekortet.dk/{2}/products/trans/{1}_1.png\" alt=\"{0}\" /></td>", pi.Title, pi.ID, shopId);
sb.AppendFormat("<td style=\"text-align:left;\">{0} x {1}</td>", pi.Qty, pi.Title);
sb.AppendFormat("<td style=\"text-align:right;\">{0:N2}</td>", double.Parse(pi.CardValue));
sb.Append("</tr>");
}
mergeVars.Add("ITEMS_LIST", sb.ToString());
in my template in MandrillApp I simply have (for the table part):
<table style="width: 100%; padding: 0 30px;">
<thead>
<tr>
<th style="width:75px; text-align:left;">Gavekort</th>
<th style="width:75px; text-align:left;">Ordreoversigt</th>
<th style="width:75px; text-align:right;">Værdi (kr.)</th>
</tr>
</thead>
<tbody>
*|ITEMS_LIST|*
</tbody>
</table>
and in code you do:
var tmplMessage = new MandrillSendTemplateItem();
tmplMessage.key = password;
tmplMessage.message = new MessageItem();
// Email Destination
tmplMessage.message.to = new List<MessageToItem>();
tmplMessage.message.to.Add(new MessageToItem() { name = destinationName, email = destinationEmail, type = "to" });
tmplMessage.message.to.Add(new MessageToItem() { name = "Bruno Alexandre", email = "my_email#domain.com", type = "bcc" }); // always send me a copy so I know what's going on
// Global Variables
tmplMessage.message.global_merge_vars = new List<TemplateContentItem>();
tmplMessage.message.global_merge_vars.Add(
new TemplateContentItem() {
name = "TASKCOMPLETE",
content = DateTime.UtcNow.ToString("dd MMM yyyy HH:mm") });
// Global Variables passed in properties
if (properties != null)
{
foreach (var p in properties)
{
tmplMessage.message.global_merge_vars.Add(
new TemplateContentItem() { name = p.Key, content = p.Value });
}
}
and send the email.
I hope it helps you doing what you need.
Note that you only pass the name of the global variable in your code, but in the template you need to call it wrapping it with |* and *| so:
tmplMessage.message.global_merge_vars.Add(
new TemplateContentItem() {
name = "TASKCOMPLETE",
content = DateTime.UtcNow.ToString("dd MMM yyyy HH:mm") });
will be accessible in the template as:
<span class="completed">*|TASKCOMPLETE|*</span>
This post is pretty old but I thought I'd share my answer to this in case someone else stumbles upon here.
If you are using the Mandrill API, you need to actually send the variable values in the "global_merge_vars" or "merge_vars" keys. DO NOT use the template_content. Mandrill API was rather unintuitive this way.
So your content would remain the same with a variable:
*|ITEMS_LIST|*
Then your JSON body should have something like:
"global_merge_vars": [
{
"name": "ITEMS_LIST",
"content": "This is a list"
}
],
source: How to add params to all links in a mandrill template through API?