Javascript template literals - retrieve subcollection sets - javascript

I have data I need to integrate in a string using Template literals. I'm managining to do it except for when the data is of the type Collection.set.
My data is made of a table called Company with multiple records. I need to loop inside it to retrieve the data for each company( this bit is OK and already works), but then inside each company one of the attribute/column called company_tags is of type Collection.set So i need to loop (sort of loop inside the loop) to retrieve the data in order to inject it inside a Template literal, and this part I am failing to do it.
Javascript code
var html = "";
result.forEach(function(msg) {
console.log(msg);
console.log(msg.company_tags);
msg.company_tags.forEach(function(value) {
console.log(value);
});
html += `
<tr id="company-1234" data-id="1234">
<td class="name">
<h2>This is the company name ${msg.company_name}</h2> for which they agree to send an email to ${msg.company_email}
<td class="location">
<h3> ${msg.company_location_city}</h3>
</td>
<td class="tags">
<h2>yhis is one of the company tags: <b>${msg.company_tags[i]} </b> and it looks nice</h2>
</td>
</tr>
`;
});
document.getElementById("target").innerHTML = html;
Of course above the part <h2>This are the company tags: ${msg.company_tags[i]} does not work.
I made several attempts I show further below at reaching this Collection set but failed.
The global data called "msg" in the code above comes from a third party. To show you the structure of the data I need to parse/loop/retrieve look like here are the results of the console.log you see on the code above:
The data is the following: this is one of the Company record shown by the console.log in the code above:
console.log(msg) outputs
c {createdAt: Wed Jul 04 2018 16:49:56 GMT+0200 (CEST), updatedAt: Mon Jul 09 2018 15:26:25 GMT+0200 (CEST), _metadata: d}
company_email: "companyemail1#example.com"
company_name: "example name"
company_location : "Dublin"
company_tags: Set(3)
updatedAt: and so on...
d {_isLocked: false, _readyPromise: Promise, _deferred: null, _root: c, _state: 0, …}
console.log(msg.company_tags) outputs something I am not sure to fully understand. Seems like what is called Collection set.
Set(3) {"tag1", "improvement", "sexy"}
size : 3
__persistedSize__ : 3
__persistedState__:
{tag1: "tag1", improvement: "improvement", sexy: "sexy"}
__proto__ : Set
[[Entries]] : Array(3)
> 0 : "tag1"
> 1 : "improvement"
> 2: "sexy"
length : 3
Today when I my javascript code (see above) operates, I manage to have in my html the right data shown for the columns of "string "type. So the part below works fine for me:
<h2>This is the company name ${msg.company_name}</h2> for which they agree to send an email to ${msg.company_email}
BUT the part below with the column of type Collection.set does not work and shows "undefined"
<td class="tags">
<h2>This is one of the company tags: ${msg.company_tags[i]}
</td>
It's like I need to loop again (inside the other loopForEach).
Some attempts that failed:
var html = "";
result.forEach(function(msg) {
html += `
<tr id="company-67851" data-id="67851">
<td class="name">
<h2>This is the company name ${msg.company_name}</h2> for which they agree to send an email to ${msg.company_email}
<td class="location">
<h3> ${msg.company_location_city}</h3>
</td>
<td class="tags">
<h2>This is one of the company tags: <b>${ msg.company_tags.forEach(function(value) {
<h2>value</h2>
</div>
</a>
});
} </h2> and it looks nice
</td>
</tr>
`;
});
document.getElementById("target").innerHTML = html;
I also tried
${ msg.company_tags.forEach( (k, v) => `<h2>${v}</h2>`).join(" ") }
and also
${msg.company_tags.map(company_tag => <h2>This is one of the company tags: ${company_tag}</h2> and it looks nice`).join('')}
but I'm always getting UNDEFINED error or even sometimes
Uncaught (in promise): TypeError: .map is not a function
Nothing has been working so far. It's a little over my head as a javascript rookie.
My goals is that for example I see in the html for the example of record I gave above:
<td class="tags">
<h2>This is one of the company tags: <b>tag1</b> and it looks nice</h2>
<h2>These are the company tags: <b>imrovement</b> and it looks nice</h2>
<h2>These are the company tags: <b>sexy</b> and it looks nice</h2>
</td>

Seems like you need to convert msg.company_tags set into array:
var company_tags = []
msg.company_tags.forEach((k,v) => company_tags.push(v))
Then you should be able to map not forEach over it, as only map returns from function, forEach is used for side effects.
Also you can fetch generating of company tags into separate function.
${company_tags.map(company_tag => `<h2>This is one of the company tags: ${company_tag}</h2> and it looks nice`).join('')}

Related

How to access nested elements in array from the front-end using knockout JS?

I got a data from back-end and used parts of it in the front-end.
It looks like this
2021-22:
NewYork: {PriceHeaders: Array(9), Comment: 'qwert', Pricings: Array(11)}
Paris: {PriceHeaders: Array(2), Comment: null, Pricings: Array(3)}
2020-21:
Washington: {PriceHeaders: Array(19), Comment: 'fdsfdsf', Pricings: Array(121)}
Berlin: {PriceHeaders: Array(21), Comment: null, Pricings: Array(143)}
number of elements and names of elements may change every time.
My goal is to access every city in this list.
Meaning :
this code
data: Object.keys(pricings()), as: '_propertykey'
gives me 2021-22(and 2020-22 if I use it in foreach).
I want to access NewYork, Paris etc.. Subelements.
div looks like this
<div data-bind="foreach: { data: Object.keys(pricings()), as: '_propertykey', afterRender: tableRenderedHandler}">
</div>
*pricings() is the list that contains all that data.
EDIT : I used console.log($context). This is the data displayed under $root
UPDATE : As suggested, I changed my code like this :
<div data-bind="foreach: { data: pricings(), as: '_propertykey', afterRender: pricingTableRenderedHandler}">
<div data-bind="foreach: { data: Object.keys(_propertykey), as: '_propkey'}">
<div data-bind="text: console.log($parent[_propkey])"></div>
<div data-bind="text: console.log($parent[_propkey].Comment)"></div>
First log gives me whole object and second one is undefined.
So one way could be to just use nested foreach bindings
# First iterate over the "root-keys 2021-22 ..."
data-bind="foreach: pricings()"
# Then iterate the keys of the "root keys"
data-bind="foreach: Object.keys($data)"
# Then u can dynamically acces data from Paris, NewYork
data-bind="text: $parent[$data].Comment"
data-bind="foreach: $parent[$data].PriceHeaders"
# To just print e.g "NewYork"
data-bind="text: $data"
Another way would be to transform and flatten your data to arrays where no data is stored in keys

Accessing Image Arrays via JSON with AngularJS

UPDATE:
I can now do what I wanted to do, sort of. I can click on the thumb and the large image will display in the lighbox. A second a tag with the same data-lightbox name uses another ng-repeat as etee suggested.
As you can see from the code it is not perfect and I would still like to do it properly. I alos managed to use a nested forEach to get to the 'large' array in the JSON file, and they show in the console.log but I don't know how to get them from there to the original lighbox so it displays the set, and you'll see the current way shows the same image twice.
Here is some of the JSON file:
[
{
"name":"Cedar Run",
"album_name":"CedarRun",
"img":[
{
"thumb":["cedarRun.jpg", "cedarRun.jpg","cedarRun.jpg"]
},
{
"large":["IMG_6001.jpg","IMG_6009.jpg"]
}
],
"cabin_id":"17",
"sleeps":"2",
"description":"1 Room",
"number_of_bedrooms":"1",
"bedrooms":
{
"bedroom1":"1 Queen",
"bedroom2":"",
"bedroom3":"",
"bedroom4":""
},
"pull_out":"",
"living_room":"",
"bathrooms":"",
"bathroom_info":"Bath with Tub/Shower",
"other_info":"",
"winter_info":"Winter RR Only",
"winter_rate":"198.90",
"summer_info":"Summer RR Only",
"summer_rate":"198.90",
"cabin_description":"Quaint cabin perfect for two. A unique location with a view of the horseback rides going by throughout the day. Also has one small dorm refrigerator and working fireplace with firewood supplied by the ranch."
},
{
"name":"Pioneer",
"album_name":"Pioneer",
"img":[
{
"thumb": ["pioneer.jpg"]
},
{
"large":["IMG_8561.jpg","IMG_8567.jpg", "IMG_8568.jpg"]
}
],
"cabin_id":"25",
"sleeps":"2",
"description":"1 Room",
"number_of_bedrooms":"1",
"bedrooms":
{
"bedroom1":"1 Queen",
"bedroom2":"",
"bedroom3":"",
"bedroom4":""
},
"pull_out":"",
"living_room":"",
"bathrooms":"",
"bathroom_info":"Bath with Tub/Shower",
"other_info":"",
"winter_info":"Winter RR Only",
"winter_rate":"198.90",
"summer_info":"Summer RR Only",
"summer_rate":"198.90",
"cabin_description":"One cozy cabin built in the log style with country charm in mind. Located along the hillside of the sledding hill overlooking the ranch. Also has one small dorm refrigerator and working fireplace with firewood supplied by the ranch."
}
]
The AngularJS
<table class="table table-hover">
<tr ng-repeat="x in cabins | filter:search:true">
<td>
<div>
<a data-lightbox="{{x.name}}" data-title="{{x.name}}" href="_img/_lodging/{{x.album_name}}/1200w/{{x.img[1].large[0]}}">
<img class="cabin-thumb-main" ng-src="_img/_lodging/cabin_tn/{{x.img[0].thumb[0]}}">
</a>
<a ng-repeat="y in x.img[1].large" data-lightbox="{{x.name}}" data-title="{{x.name}}" href="_img/_lodging/{{x.album_name}}/1200w/{{y}}" ></a>
<td>
<h3><strong>{{x.name}}</strong></h3>
<span>{{x.description}}</span><span><small> (Sleeps {{x.sleeps}})</small></span>
<h4>{{x.other_info}}</h4>
<br>
<h4>Description</h4>
<p>{{x.cabin_description}} </p>
<h5>Bed & Bath</h5>
<p>{{x.bedrooms.bedroom1}}</p>
<p>{{x.bedrooms.bedroom2}}</p>
<p>{{x.bedrooms.bedroom3}}</p>
<p>{{x.bedrooms.bedroom4}}</p>
<p>{{x.pull_out}}</p>
<p>{{x.bathroom_info}}</p>
<br>
</tr>
</table>
The call using ng-app
$scope.search = {};
$http.get('_js/lodging.json').then(function (response) {
$scope.cabins = response.data;
$scope.images = []
angular.forEach($scope.cabins, function(item){
angular.forEach(item.img, function(i){
var large_img = i.large;
// return large_img;
console.log(large_img);
})
})
});
Link to Test Page
<div ng-repeat="img in x.img[1].large">
<p>{{img}}</p>
</div>
Try this and here is the working plunker
Now since you have fetched the images you can add them to ng-src.

Meteor / Blaze: updating list properties with minimal redraw

I'm encountering serious performance issues related to Blaze redraw. I know that my data structure is at fault but I can't find a way to fix it. This is a bit of a long one: I'll do my best to lay it out clearly.
The goal / the problem
I want to create a schedule view where users can schedule tests. Users should be able to drag and drop test slots, but the resulting redraw is so slow that the thing is unworkable.
The data
I have a Sittings collection which stores details about testing times. Each sitting has a list of scheduleGroups (i.e. exam1 and exam2 happen on Tuesday, exam3 and exam4 happen on Wednesday) The structure looks more or less like this:
var sitting = {
"_id" : "sittingId",
"name" : "Amazing Sitting",
"locationIds" : [
"room1_id",
"room2_id"
],
"startDate" : ISODate("2015-07-01T04:00:00Z"),
"endDate" : ISODate("2015-07-02T04:00:00Z"),
"scheduleGroups" : [
{
"_id" : "dEb3mC7H8RukAgPG3",
"name" : "New group",
"booths" : [
{
"boothNumber" : 1,
"slots" : [
{
"examId" : "exam1",
"dayNumber" : 1,
"startTime" : {
"hour" : 8,
"minutes" : 0
},
"endTime" : {
"hour" : 9,
"minutes" : 30
},
"candidateId" : "odubbD42kHDcR8Egc",
"examinerIds" : [
"GQmvyXbcmMckeNKmB",
"GfoBy4BeQHFYNyowG"
]
}
]
}
]
"locationId" : "room1_id"
}],
"examIds" : [
"exam1",
"exam2",
"exam3"
]
};
The HTML
Sitting is defined as global context in Router.current().data hook. This is part of the redraw problem. Anyway, the following HTML produces the following layout (img below).
<template name='scheduler'>
<div class='container'>
<div class='tabpanel'>
<div class='tab-content' id='scheduler-content'>
{{#each getScheduleGroups}}
<div class='container-fluid schedule-wrapper'>
<div class='row'>
<div class='scheduler-inner-box placeholder-box'></div>
{{#each booths}}
<div class='group-column-head scheduler-inner-box'>{{boothNumber}}</div>
{{/each}}
</div>
<!-- Sittings can take place over several days, normally 2 days -->
{{#each sittingDays}}
<div class='day-wrapper'>
<h3 class='text-center'>Day {{this}}</h3>
<!-- This helper returns a list of start times: [June 1 9AM, June 1 915AM....] -->
{{#each getScheduleTimes ..}}
<div class='row time-row'>
<div class='group-row-head scheduler-inner-box'>
<span class='box-content'>{{this}}</span>
</div>
<!-- Loop through each booth, so that each slot has a square for that boot
i.e. June 1 9AM has space for booth 1, and booth 2, and booth 3, etc
-->
{{#each ../../booths}}
<!-- Paper starting now tells us if there is a slot scheduled to begin at this time or wher its empty-->
<div class='scheduler-inner-box {{#unless paperStartingNow ../.. ..}}open-booth{{/unless}}'>
{{#with paperStartingNow ../.. .. boothNumber}}
<!--
getSlotStyle calculates height and colour, depending on how long the paper is and whether
it's ready to go: i.e. does it have a candidate and the right number of examiners?
-->
<div class='paper-tile tile' {{getSlotStyle this ../boothNumber 'paper'}}>
<span class='slot-name'>{{getSlotName}}</span>
</div>
{{#if slotHasCandidate}}
<div class='candidate-tile tile' {{getSlotStyle this ../boothNumber 'candidate'}}>
<span class='slot-name candidate-name'>{{getCandidateDisplay}}</span>
</div>
{{/if}}
{{#each slotExaminers}}
<div class='examiner-tile tile' {{getSlotStyle .. ../../boothNumber 'examiner' index}}>
<span class='slot-name examiner-name'>{{profile.name}}</span>
</div>
{{/each}}
{{/with}}
</div>
{{/each}}
</div>
{{/each}}
</div>
{{/each}}
</div>
{{/each}}
</div>
</div>
</div>
The Issue
When a user drops a slot (purple tile) onto an empty space, or drags a candidate or examiner to another tile, I update the collection and let Meteor redraw. However the whole thing redraws and I can't find a way to isolate.
Here's the code for moving a whole slot, candidates examiners and all.
moveSlot: function (originalBoothNumber, originalSlot, dropBoothNumber, newSlot) {
check( originalBoothNumber, Number );
check( originalSlot, Object );
check( dropBoothNumber, Number );
check( newSlot, Object );
//pull the old slot
var originalBooth = _.findWhere( this.booths, {boothNumber: originalBoothNumber} );
originalBooth.slots = _.without( originalBooth.slots, originalSlot );
//insert the new one
var dropBooth = _.findWhere( this.booths, {boothNumber: dropBoothNumber} );
dropBooth.slots.push( newSlot );
var modifier = {
$set: {}
};
modifier["$set"]["scheduleGroups." + this.getIndex() + ".booths"] = this.booths;
return Sittings.update({_id: this.getSitting()._id }, modifier);
},
Do I need to change the way I store Sitting.booths? If anyone can recommend a better data structure that will trigger a redraw of just the slot, I'd really appreciate it. I've found a hack that puts every slot into a session variable but there's got to be another way.
Thanks for your patience and your help,
db
Problem
It's because you store the whole set of slots inside one document of booth. Once you update a slot, the whole document is consider updated.
Solutions
IMHO, I don't think you need to change the structure, unless you have build a feature to search for slot or booth. You can set it up by create a series of reactiveVariable or a reactiveDictionary, your template should depends only on that reactive variable. Every time you updated, consider to sync between two of them, either manually or automatically.
PS. I'm not doing any affiliate with this site: https://www.sportsplus.me. But if you sign up -> home -> mlb -> anycontest -> click on plus, you can see their masterpiece infinite scroll with partly redraw. (Open your dev-console and watch the changes in element). They do exactly the same thing you're trying to do with minimum redraw.

How to handle array in angular js/HTML

I'm using a Web API to display values in HTML via Angular.
I have 4 attributes: Id, MovieName, Date and Cast. Cast is an array. I don't know how to handle the Cast attribute.
Controller.js
app.controller('MyController', function ($scope, MyService) {
$scope.getemploy = function () {
var promise = MyService.getMovies();
promise.then(function (pl) {
$scope.Movies= pl.data
},
function (error) {
$log.error('Some Prob', error);
});
}
GetMovies()--> will bring movie details(Cast attribute will have male, female lead names in the array).
HTML File :
<tr ng-repeat="mov in Movies">
<td>{{mov.Id}}</td>
<td>{{mov.MovieName}}</td>
<td>{{mov.Date}}</td>
<td>{{mov.Cast}}</td>
</tr>
But it's not working. I think I'll need some other way to handle Cast attributes, whether in html or angular.
My Json output for your reference:
[
{
"_movieId": 1,
"_moviename": "Olympus Has Fallen",
"_releaseDate": 2013,
"_cast": [
"Gerard Butler",
"Dylan McDermott",
"Aaron Eckhart",
"Angela Bassett"
]
}
Can anyone please help?
But its not working....
Because according to posted JSON your receive, HTML should be with different keys. Try this:
<tr ng-repeat="mov in Movies">
<td>{{mov._movieId}}</td>
<td>{{mov._moviename}}</td>
<td>{{mov._releaseDate}}</td>
<td>{{mov._cast.join(', ')}}</td>
</tr>
I used simple join method of array to render comma separated list of actors. If you need something more specific, you will need to make use of one more ngRepeat on mov._cast array (see Danny's answer).
I'm not sure what you mean by the Cast is not working, but I'm guessing it's just showing the JSON object in the HTML? You could show all the cast members in a list doing something like:
<tr ng-repeat="mov in Movies">
<td>{{mov.Id}}</td>
<td>{{mov.MovieName}}</td>
<td>{{mov.Date}}</td>
<td>
<ul>
<li ng-repeat="member in mov.Cast">{{ member }}</li>
</ul>
</td>
</tr>

KnockoutJS showing a sorted list by item category

I just started learning knockout this week and everything has gone well except for this one issue.
I have a list of items that I sort multiple ways but one of the ways I want to sort needs to have a different display than the standard list. As an example lets say I have this code
var BetterListModel = function () {
var self = this;
food = [
{
"name":"Apple",
"quantity":"3",
"category":"Fruit",
"cost":"$1",
},{
"name":"Ice Cream",
"quantity":"1",
"category":"Dairy",
"cost":"$6",
},{
"name":"Pear",
"quantity":"2",
"category":"Fruit",
"cost":"$2",
},{
"name":"Beef",
"quantity":"1",
"category":"Meat",
"cost":"$3",
},{
"name":"Milk",
"quantity":"5",
"category":"Dairy",
"cost":"$4",
}];
self.allItems = ko.observableArray(food); // Initial items
// Initial sort
self.sortMe = ko.observable("name");
ko.utils.compareItems = function (l, r) {
if (self.sortMe() =="cost"){
return l.cost > r.cost ? 1 : -1
} else if (self.sortMe() =="category"){
return l.category > r.category ? 1 : -1
} else if (self.sortMe() =="quantity"){
return l.quantity > r.quantity ? 1 : -1
}else {
return l.name > r.name ? 1 : -1
}
};
};
ko.applyBindings(new BetterListModel());
and the HTML
<p>Your values:</p>
<ul class="deckContents" data-bind="foreach:allItems().sort(ko.utils.compareItems)">
<li><div style="width:100%"><div class="left" style="width:30px" data-bind="text:quantity"></div><div class="left fixedWidth" data-bind="text:name"></div> <div class="left fixedWidth" data-bind="text:cost"></div> <div class="left fixedWidth" data-bind="text:category"></div><div style="clear:both"></div></div></li>
</ul>
<select data-bind="value:sortMe">
<option selected="selected" value="name">Name</option>
<option value="cost">Cost</option>
<option value="category">Category</option>
<option value="quantity">Quantity</option>
</select>
</div>
So I can sort these just fine by any field I might sort them by name and it will display something like this
3 Apple $1 Fruit
1 Beef $3 Meat
1 Ice Cream $6 Dairy
5 Milk $4 Dairy
2 Pear $2 Fruit
Here is a fiddle of what I have so far http://jsfiddle.net/Darksbane/X7KvB/
This display is fine for all the sorts except the category sort. What I want is when I sort them by category to display it like this
Fruit
3 Apple $1 Fruit
2 Pear $2 Fruit
Meat
1 Beef $3 Meat
Dairy
1 Ice Cream $6 Dairy
5 Milk $4 Dairy
Does anyone have any idea how I might be able to display this so differently for that one sort?
Your view shouldn't contain logic beyond that necessary to render it. Thus, your foreach binding
data-bind="foreach:allItems().sort(ko.utils.compareItems)" should become a computed observable.
You should move the <option> data into your model and take advantage of the options data-bind.
To address the actual question, you'll take advantage of template binding and containerless if binding.
The template binding will allow you to change the look/feel of the view based on the selected sort type. So 2 templates are available, the default-template which handles the regular display and the category-template specifically for category based rendering.
<script type="text/html" id="category-template">
<ul class="deckContents" data-bind="foreach:sortedItems">
<li>
<!-- ko if: $root.outputCategory($index()) -->
<div data-bind="text:category"></div>
<!-- /ko -->
<span class="indented" data-bind="text:name"></span>
<span class="indented" data-bind="text:cost"></span>
<span class="indented" data-bind="text:category"></span>
</li>
</ul>
The html usage: <div data-bind='template: { name: currentTemplate, data: $data}'></div> where currentTemplate is an computed observable that returns the template id based on sort type.
In some way or another you must assign priority to the categories. I have done this by declaring var categoryPriority = ["Fruit", "Meat", "Dairy"].
Have a look at my fiddle. I didn't address the fixedWidth used by the default-template so you'll need to handle the CSS styling to line it up the way you want.
Edit: Is there a way to dynamically add an item to the list and have it show up in the sorted list automatically?
Pushing a new item: When you want knockout to "notify" other elements then you don't want to read the observableArray by using allItems() before performing the push. Instead, you'll push into the observableArray using allItems.push which in-turn will cause knockout to trigger computed observables (that depend on this observable) to evaluate, subscriptions to execute, DOM elements to update ... etc.
Computed Dependencies: In order for a computed to "depend" on another observable it has to be read inside of the provided evaluator function. Since, sortedItems only reads sortType that is the only "trigger" for re-evaluation. Thus, changing the allItems.sort to allItems().sort causes sortedItems to evaluate whenever changes are made to allItems.
See How Dependency Tracking Works
By using sort method we can sort the list by ascending order.
I am taking your example and explain this.
food = [{
"name":"Apple",
"quantity":"3",
"category":"Fruit",
"cost":"$1",
},{
"name":"Ice Cream",
"quantity":"1",
"category":"Dairy",
"cost":"$6",
},{
"name":"Pear",
"quantity":"2",
"category":"Fruit",
"cost":"$2",
},{
"name":"Beef",
"quantity":"1",
"category":"Meat",
"cost":"$3",
},{
"name":"Milk",
"quantity":"5",
"category":"Dairy",
"cost":"$4",
}];
self.allItems = ko.observableArray(food);
Here you have an array. By using below code to sort this list
self.allItems.sort(function (left, right) {
return left.name() == right.name() ? 0 : (left.name() < right.name() ? -1 : 1)
});
like wise if u want to sort by cost ..is the same..Replace name to cost..u will get it.

Categories

Resources