How to pass my updated array into my .handelbars file - javascript

been stuck with this for a few days now.
In my application, when my listview.handelbars loads I am pulling my data from firebase. A number of objects (with data about rooms) that after this pull are being pushed in een array called allRooms[].
After that, the update compile passes this array on to my listview.handelbars file where everything gets put properly inside the html and handlebars tags. This is al working very fine and gives me the proper outcome in browser.
But then I need to be able to sort this objects by there properties -distance.
So I made a function to do that and i store the sorted objects in the array sortedRooms[];
So far so good. My question now is how do I pass this new sorted array on to my .handlebars file?
I tried to execute an update compile again inside my function sortRooms() and this worked but then i'm getting other problems (not able to go to detailview anymore for one).
Also important is that this sorted objects must only be shown by a button press. So at first i want to output the objects in the order they where added to firebase.
Here is my code
// JAVASCRIPT
ref.on("value", function (data) {
let rooms = data.val();
if (rooms === undefined || rooms === null) {
console.log('geen data');
} else {
let keys = Object.keys(rooms);
roomKeys.push(keys);
for (let i = 0; i < keys.length; i++) {
let k = keys[i];
Room = {
rentalPrice: rooms[k].rentalPrice,
warrant: rooms[k].warrant,
type: rooms[k].type,
surface: rooms[k].surface,
floors: rooms[k].floors,
numberOfPersons: rooms[k].numberOfPersons,
toilet: rooms[k].toilet,
douche: rooms[k].douche,
bath: rooms[k].bath,
kitchen: rooms[k].kitchen,
furnished: rooms[k].furnished,
address: rooms[k].address,
ownerKey: rooms[k].ownerKey,
lat: rooms[k].lat,
lon: rooms[k].lon,
image: rooms[k].image,
roomKey: keys[i],
adminName: rooms[k].adminName
}
allRooms.push(Room);
}
}
})
update(compile(studentListViewTemplate)({
allRooms,
}));
let sortedRooms = allRooms.sort((a, b) => a.distance - b.distance);
console.log(sortedRooms);
// Question ?? -> How to pass this sorted array on to my handelbars
// HANDLEBARS
{{#each allRooms }}
<div class="info-list" id="regularInfoList">
<h5 class="card-room-type-lv">{{ this.type }}</h5>
<div class="info-list-img">
<img src="{{ this.image }}" class="room-picture-list" alt="">
</div>
<div class="info-list-text">
<div class="sub-div">
<p class="card-titles-lv width"></p>
<p class="card-values-lv card-surface">{{ this.surface }}
m²</p>
</div>
<div class="sub-div">
<p class="card-titles-lv distance"></p>
<p class="card-values-lv card-distance">{{ this.distance }}
km</p>
</div>
<div class="sub-div">
<p class="card-titles-lv price"></p>
<p class="card-values-lv card-price">€ {{ this.rentalPrice
}}</p>
</div>
</div>
</div>
{{else}}
<p style="text-align:center">Nog geen koten om weer te geven</p>
{{/each}}

Recompile the template with allRooms inside the template being the sorted array. The sorting and updating should happen when the value arrives, so inside the callback:
ref.on("value", function (data) {
// no need for a for loop if you use Object.values
const allRooms = Object.values(data.val());
// sort mutates the array, it doesnt return a new one
allRooms.sort((a, b) => a.distance - b.distance);
// update afterwards.
update(compile(studentListViewTemplate)({
allRooms,
}));
});

Related

Loop not printing correct results on view

I'm trying make my Coordinates, Dispatcher and Captured At printout the correct totals that correspond to it's ID. Unfortunately, it's only assigning the first ID's totals in its HTML element. Having a bit of a braindead moment trying to fix this.
I know I'm missing something to identify the individual html instance, but I can't figure out how to make that happen.
HTML -
<div v-if="zoomLevel > 10" class="search-data">
<div><strong>Dispatcher:</strong> <span id="dispatcher">{{ dispatcher}}</span></div>
<div><strong>Lng/Lat:</strong> <span id="coordinates">{{ coordinates }}</span></div>
<div><strong>Captured At:</strong> <span id="capturedAt">{{ capturedAt }}</span></div>
</div>
JS -
export default {
data() {
return {
coordinates: '',
dispatcher: '',
capturedAt: '',
}
},
}
Method -
let totalTargets = Object.keys(e.source.data.features).length;
for (let i = 0; i < totalTargets; i++) {
console.log(e)
this.coordinates = e.source.data.features[i].geometry.coordinates.toString().replace("[", "").replace("]"," ").replace(",", ", ");
this.dispatcher = e.source.data.features[i].properties.name;
this.capturedAt = e.source.data.features[i].properties.capturedAt;
}
Console.log printout of method results
You need to iterate on the HTML, with v-for, create a reactive property to your list and can use like this:
<div v-if="zoomLevel > 10" class="search-data" v-for="(feature, index) in features" :key="index">
<div><strong>Dispatcher:</strong> <span id="dispatcher">{{ feature.dispatcher}}</span></div>
<div><strong>Lng/Lat:</strong> <span id="coordinates">{{ feature.coordinates }}</span></div>
<div><strong>Captured At:</strong> <span id="capturedAt">{{ feature.capturedAt }}</span></div>
</div>

SvelteKit: How to populate HTML with content from new array after page has already been loaded?

I am displaying a list of JSON objects containing user chats. If the user types something into the searchbar, the matching JSON objects from the original chats list are being pushed into the new filtered_chats array. I didn't include this code logic since this is working and filtered_chats is being populated. (Note: filtered_chats starts out as an empty array in the script tag. Maybe this is helpful)
The problem I'm facing is that SvelteKit won't feed the new array to the HTML to show only the elements from filtered_chats instead of all of the chats from chats. It checks for the original array only and leaves the rendered contents as they already are.
Script:
<script lang="ts">
import ChatSidebarElement from "$components/ChatSidebarElement.svelte";
export let chats : JSON;
var filtered_chats = [];
var searchval = "";
function search() {
// this pushes chat objects into "filtered_chats" if they match the search pattern
}
HTML:
<div class="mb-4">
<input type="search" class="form-control text-dark" bind:value={searchval} on:input={search} placeholder="Search chats">
</div>
<h3 class="text-light">Open Chats</h3>
{#if chats.length > 0}
<div class="chats">
{#if filtered_chats.length > 0}
<h1 class="text-light">{searchval}</h1>
{#each filtered_chats as chat}
<ChatSidebarElement chat={chat}></ChatSidebarElement>
{/each}
{:else}
{#each chats as chat}
<ChatSidebarElement chat={chat}></ChatSidebarElement>
{/each}
{/if}
</div>
{:else}
<li>
<div class="info">
<div>You don't have any open chats!</div>
</div>
</li>
{/if}
Svelte only updates the UI when a variable is reassigned. With that in mind, if you mutate an array instead of reassigning it with a new state, it will not update the UI. For example:
<script>
let items = ['1', '2', '3']
// Wrong! UI won't be updated
items.push('4')
// Right! UI gets updated
items = [...items, '4']
// Or you can also do this
items = items.concat('4')
</script>

Restcountries API - getting names of currencies dynamically into HTML through Javascript

I am new to Javascript and I've been learning how to import a country's attributes into an HTML element. Some of you might recognize this code, it's from a tutorial, which is now outdated. I've been searching around for an updated solution, but couldn't find any.
First I have the function to fetch the data:
const getCountryData = function (country) {
fetch(`https://restcountries.com/v3.1/name/${country}`)
.then(response => response.json())
.then(data => renderCountry(data[0]));
};
Then I call that function, supplying a country getCountryData('czechia') to infuse it into an element like this:
const renderCountry = function(data, className = '') {
const html = `
<article class="country ${className}">
<img class="country__img" src="${data.flags.svg}" />
<div class="country__data">
<h3 class="country__name">${data.name.common}</h3>
<h4 class="country__region">${data.region}</h4>
<p class="country__row">${(+data.population / 1000000).toFixed(1)} people</p>
<p class="country__row">${data.fifa}</p>
</div>
</article>
`
countriesContainer.insertAdjacentHTML
('beforeend', html);
countriesContainer.style.opacity = 1;
}
This works fine, but the issue is that at the end of the HTML, where I input {data.fifa} I want to have the name of the country's main currency instead. Unfortunately, the data is structured in a way, that in order to have the currency's name displayed, I first have to call it's short name, as shown below:
"currencies": {
"CZK": {
"name": "Czech koruna",
"symbol": "Kč"
}
},
If I call the {data.currencies} into the string, I'm just gonna get an empty object back. If I call it as {currencies.CZK.name}, it works, but the issue is that if I call Sweden, for example, it won't display anything, because then it'd need to be {currencies.SEK.name}. How do I get around this? How can I can call a currency's name without having to incorporate CZK, SEK, USD, EUR etc. into the variable?
Any help is appreciated.
You can transform that object into an array:
const currencyArray = Object.values(data.currencies)
console.log(currencyArray[0].name)
If the country has many currencies, just change the index from 0 to 1, 2, ...

Firebase Paginate

I made the code below referring to the pagination document of FIREBASE.
( https://firebase.google.com/docs/firestore/query-data/query-cursors#web-v8_3 )
I know that 'limit(3)' prints 3 documents, but I don't know how to use the 'next' and 'last' variables.
What I want to implement is to show three of my posts per page and move to the next page when the button is pressed.
Since I just started web development, everything is difficult. please help me
var first = db.collection('product').orderBy('date').limit(3);
var paginate = first.get().then((snapshot)=>{
snapshot.forEach((doc) => {
console.log(doc.id);
var summary = doc.data().content.slice(0,50);
var template = `<div class="product">
<div class="thumbnail" style="background-image: url('${doc.data().image}')"></div>
<div class="flex-grow-1 p-4">
<h5 class="title">${doc.data().title}</h5>
<p class="date">${doc.data().date.toDate()}</p>
<p class = "summary">${summary}</p>
</div>
</div>
<hr>`;
$('.container').append(template);
})
You can try this function:
let lastDocSnap = null
async function getNextPage(lastDoc) {
let ref = db.collection('product').orderBy('date')
// Check if there is previos snapshot available
if (lastDoc) ref = ref.startAfter(lastDoc)
const snapshot = await ref.limit(3).get()
// Set new last snapshot
lastDocSnapshot = snapshot.docs[snapshot.size - 1]
// return data
return snapshot.docs.map(d => d.data())
}
While calling this function, pass the lastDocSnap as a param:
getNextPage().then((docs) => {
docs.forEach((doc) => {
// doc itself is the data of document here
// Hence .data() is removed from original code
console.log(doc.id);
var summary = doc.content.slice(0,50);
var template = `<div class="product">
<div class="thumbnail" style="background-image: url('${doc.image}')"></div>
<div class="flex-grow-1 p-4">
<h5 class="title">${doc.title}</h5>
<p class="date">${doc.date.toDate()}</p>
<p class = "summary">${summary}</p>
</div>
</div>
<hr>`;
$('.container').append(template);
})
})
Call this function at page load (as lastDocSnap will be null, it'll fetch first 3 docs). Call the same function when user clicks 'next' but this time as we have lastDocSnap, the startAfter method will be added to the query. This essentially means the query will first order the document by date and then fetch 3 documents after the document you pass in startAfter

How do I output a specific nested JSON object in Angular?

I'm a rank newbie to Angular, and I'm attempting to port an old jQuery-based app to NG. The JSON I'm starting with (parsed from an XML file) looks like this:
{
"setUps":{
"cartImage":
{
"_cartIm":"addtocart.gif",
"_cartIm_o":"addtocart.gif"
}
,"_supportImsPath":"/"
},
"product":
{
"matrix":
{
"thumbnails":[
{
"_myLabel":"Antique Brass Distressed",
"_thumbImage":"3",
"_cartCode":"160"
},
{
"_myLabel":"Antique Brass Light",
"_thumbImage":"156",
"_cartCode":"156"
},
{
"_myLabel":"Old Iron",
"_thumbImage":"ap",
"_cartCode":"157"
},
{
"_myLabel":"Oil-Rubbed Bronze",
"_thumbImage":"ob",
"_cartCode":"3"
}
],
"_myLabel":"Finishes"
},
"_Title":"Flower Cabinet Knob",
"_itNum":"100407x"
}
}
What I need to do with this is output specific elements on my template - first and foremost, the matrix object with its associated thumbnails. In this example, there is only one matrix, but for many of the products there are multiples, each with their own thumbnail arrays.
This is the controller:
var XMLt = angular.module("XMLtest",[]);
XMLt.factory("Xfactory",function($http){
var factory = [];
factory.getXML = function(){
return $http.get(productXML);
}
return factory;
});
XMLt.controller("Xcontroller",function($scope,Xfactory) {
$scope.Xcontroller = [];
loadXML();
function loadXML() {
Xfactory.getXML().success(function (data) {
var x2js = new X2JS();
prodData = x2js.xml_str2json(data);
$scope.thisItem = prodData.contents;
$scope.matrices = [];
angular.forEach($scope.thisItem.matrix,function(value,key)
{
$scope.matrices.push(value,key);
});
});
}
});
And this is my view template:
<div ng-controller="Xcontroller">
<h2>Title: {{ thisItem.product._Title }}</h2>
<div ng-repeat="thisMatrix in thisItem.product" class="matrix">
{{ thisMatrix._myLabel }}
</div>
</div>
My problem is that this ng-repeat, not surprisingly, returns a div for every child element of the product that it finds, not just the matrix. So I wind up with a couple of empty divs (for _Title and _itNum) in addition to the matrix div.
I've seen quite a few examples of filtering by comparing value literals, but they don't seem to apply in this case. I also tried writing a custom filter:
$scope.isObjType = function(input) {
return angular.isObject(input);
};
<div ng-repeat="thisMatrix in thisItem.product | filter:isObjType(matrix)">
That seemed to have no effect, still returning the extraneous divs. I can't seem to wrap my head around how I'd limit the repeat to a specific object type. Am I thinking of this the completely wrong way? If so, I'd welcome any input.
Since you only have one matrix, you don't need the repeat for the matrix label. You only need it for the thumbnails.
<div ng-controller="Xcontroller">
<h2>Title: {{ thisItem.product._Title }}</h2>
<div class="matrix">
{{ thisItem.product.matrix._myLabel }}
<div ng-repeat="thisThumbnail in thisItem.product.matrix.thumbnails" class="thumbnail">
{{thisThumbnail._myLabel}}
</div>
</div>
</div>
If it were possible to have multiple matrixes, the object would need to be modified to be able to represent that (by wrapping the matrix object in an array.)
Update per comments:
If you have the possiblity of multiple matrixes, you will need to modify the object to ensure that it is consistent when there is 1 vs when there are 2+.
<div ng-controller="Xcontroller">
<h2>Title: {{ thisItem.product._Title }}</h2>
<div ng-repeat="thisMatrix in thisItem.product.matrix" class="matrix">
{{ thisMatrix._myLabel }}
<div ng-repeat="thisThumbnail in thisMatrix.thumbnails" class="thumbnail">
{{thisThumbnail._myLabel}}
</div>
</div>
</div>
and in controller:
XMLt.controller("Xcontroller",function($scope,Xfactory) {
$scope.Xcontroller = [];
loadXML();
function loadXML() {
Xfactory.getXML().success(function (data) {
var x2js = new X2JS();
prodData = x2js.xml_str2json(data);
// must always have an array of matrixes
if (!prodData.contents.product.matrix.slice) {
prodData.contents.product.matrix = [prodData.contents.product.matrix];
}
$scope.thisItem = prodData.contents;
$scope.matrices = [];
angular.forEach($scope.thisItem.matrix,function(value,key)
{
$scope.matrices.push(value,key);
});
});
}
});

Categories

Resources