Loop Object and Array when Array is inside of an Object - arrays

I am using a query to receive a JSON response. I would like to loop each object (VGF, SSR, BCV, etc..) and output them to premade divs, then the arrays within those objects will loop and create divs within that matching object container.
This is a shortened down version of what I have, and it works mostly. (hopefully, I haven't screwed it up here).
The problem is I have to repeat the searchresult function by copying and pasting the entire function for each object (VGF, SSR, BCV, etc). I would really like to learn how to loop this and not have the same code pasted more than a dozen times.
If I have messed up or left something out of this question, please let me know and I will take care of it.
Here is my ajax request and javascript. I know my problem lies within this loop. I have tried to do a loop inside of a loop, etc. But, when I do that I get no results at all. I am baffled and ready to learn.
$(function getData() {
$("#searchbtn").click(function () {
$.ajax({
url: "action.php",
type: "POST",
data: {},
dataType: "json",
success: function (response) {
console.log(response);
searchresult(response);
}
});
});
});
let searchresult = function(response) {
let container = document.getElementById('VGFresults');
let output = "";
for (let j = 0; j < response.length; j++) {
if (response[j].rcode == "VGF") {
output +=
`<div id="person${response[j].code}">
<p>${response[j].firstname} ${response[j].lastname}</p>
</div>`
}
$(container).html(output);
}
};
Here is my response (Same layout as I am currently receiving but shortened the objects in the arrays).
response =
{"VGF":
[{"code":"TU","rcode":"VGF","firstname":"Tom","lastname":"Riddle"},
{"code":"AZ","rcode":"VGF","firstname":"Harry","lastname":"Potter"},
{"code":"FR","rcode":"VGF","firstname":"Hermoine","lastname":"Granger"}],
"SSR":
[{"code":"HG","rcode":"SSR","firstname":"Walt","lastname":"Disney"},
{"code":"TR","rcode":"SSR","firstname":"H.R.","lastname":"Pickins"},
{"code":"ED","rcode":"SSR","firstname":"Tom","lastname":"Ford"}],
"BCV":
[{"code":"YH","rcode":"BCV","firstname":"Tom","lastname":"Clancy"},
{"code":"RS","rcode":"BCV","firstname":"Robin","lastname":"Williams"},
{"code":"AB","rcode":"BCV","firstname":"Brett","lastname":"Favre"}]}
Here is the HTML that the searchresult function is working with. Currently, it works fine.
To clarify, I would like each object to insert its arrays within the corresponding div. Example:
SSR arrays will go into <div id="SSRresults">
BCV arrays will go into <div id="BCVresults">
From there, each array will create a div within that *results div for each array.
<div id="VGFresults">
<div id="VGFheader">This is the VGF Header</div>
<div id="VGFresults">The Javascript Creates Divs for each array here.</div>
</div>
<div id="SSRresults">
<div id="SSRheader">This is the SSR Header</div>
<div id="SSRresults">The Javascript Creates Divs for each array here.</div>
</div>
<div id="BCVresults">
<div id="BCVheader">This is the BCV Header</div>
<div id="BCVresults">The Javascript Creates Divs for each array here.</div>
</div>
Thanks, any help is much appreciated.

I would do like this:
I declare the response as variable (but sure it will work with your ajax response.
var response =
{"VGF":
[{"code":"TU","rcode":"VGF","firstname":"Tom","lastname":"Riddle"},
{"code":"AZ","rcode":"VGF","firstname":"Harry","lastname":"Potter"},
{"code":"FR","rcode":"VGF","firstname":"Hermoine","lastname":"Granger"}],
"SSR":
[{"code":"HG","rcode":"SSR","firstname":"Walt","lastname":"Disney"},
{"code":"TR","rcode":"SSR","firstname":"H.R.","lastname":"Pickins"},
{"code":"ED","rcode":"SSR","firstname":"Tom","lastname":"Ford"}],
"BCV":
[{"code":"YH","rcode":"BCV","firstname":"Tom","lastname":"Clancy"},
{"code":"RS","rcode":"BCV","firstname":"Robin","lastname":"Williams"},
{"code":"AB","rcode":"BCV","firstname":"Brett","lastname":"Favre"}]}
let searchresult = function(response) {
// let container = document.getElementById('VGFresults');
let output = "";
for (var key in response) {
// skip loop if the property is from prototype
if (!response.hasOwnProperty(key)) continue;
var obj = response[key];
let container = document.getElementById(key+'results');
for (var prop in obj) {
// skip loop if the property is from prototype
if (!obj.hasOwnProperty(prop)) continue;
// your code
//alert(prop + " = " + obj[prop]);
console.log(obj[prop])
output += "<div id="+prop+"><p>"+obj[prop].firstname+" "+ obj[prop].lastname+"</p></div>"
}
}
container.innerText = output;
console.log(output);
};
<div id="VGFresults"></div>
each property VGF, SSR, BCV and so on can be handled now.
EDIT: based on users request, I guess you can edit the selector like this:
let container = document.getElementById(key+'results');

Related

Creating new HTML images using a For loop through an array of image URLs

I have an array of 40 different image URLs being returned from an AJAX request. I'm trying to create a new HTML image element for each URL in the array using a For loop, as seen in the below code. For some reason, it's only displaying the image at the first URL and that's it. Any idea why the other 39 aren't showing up?
$(document).ready(function() {
$.ajax({
type: 'GET',
dataType: 'json',
url: 'https://****/images',
success: function(data) {
console.log(data);
let container = document.getElementById('feed');
let image = document.createElement("img");
for (let i = 0; i < data.length; i++) {
image.setAttribute('src', data[i]);
container.appendChild(image);
}
}
});
});
<body>
<div id="feed">
</div>
</body>
Try to create the element inside the loop.
for (let i = 0; i < data.length; i++) {
let image = document.createElement("img");
image.setAttribute('src', data[i]);
container.appendChild(image);
}
The way to create images is with new Image() and when appending multiple nodes to the DOM at once, it's better to first append the image nodes into a document fragment, and only when all the images have been appended to the fragment, then append the fragment itself into the Document (prevents redundant repaints)
// dummy data
const data = ['http://placekitten.com/100/100',
'http://placekitten.com/100/150',
'http://placekitten.com/100/180',
'http://placekitten.com/100/200']
// create a dumpster-node for the images to reside in
const fragment = document.createDocumentFragment();
// iterate the data and create <img> elements
data.forEach(url => {
let image = new Image()
image.src = url;
fragment.appendChild(image);
})
// dump the fragment into the DOM all the once (FTW)
document.body.appendChild(fragment);
I've used Array forEach iterator in my example, because it's easier in my opinion, but you can use a for loop (or for..of loop)

AngularJs Auto Complete Search

So this works with static data, but when I push data with a $http this autocomplete does not work. The data pushes to the empty array of airport_list but something is happening when I try to use airport_list in for the autocomplete. Not sure what is is. I can only find answers which pertain to static data.
This is updated per everyones help.
Here is the controller
app.controller('selectCtrl', function($scope, $http) {
$scope.airport_list = null;
$http({
url: 'someUrl.com',
method: 'GET'
})
.then((response) => {
angular.forEach(response.data.airports, function(value, key) {
$scope.airport_list = response.data.airports;
})
$scope.airports = $scope.airport_list;
});
$scope.selectAirport = function(string) {
$scope.airport = string;
$scope.hidelist = true;
};
})
Here is the template
<div class="control">
<div>
<input
type="text"
name="airport"
id="airport"
ng-model="airport"
ng-change="searchFor(airport)"
placeholder="From..."
/>
<div class="airport-container-dropdown" ng-hide="hidelist">
<div
class="airport-list"
ng-repeat="airport in airports"
ng-click="selectAirport(airport)"
>
{{ airport.name }}
</div>
</div>
</div>
</div>
I really would like to do this without using bootstrap typeahead.
Thank you for looking at this.
I have made changes as recommended by below answers and the $http request is feeding into the autocomplete as a whole list but searching by name does not work and clicking on name sets [object, object]
this would be the code which is specific to that functionality.
$scope.searchFor = function(string) {
$scope.hidelist = false;
const output = [];
angular.forEach($scope.airport_list, function(airport) {
if (airport[0].toLowerCase().indexOf(string.toLowerCase(airport)) >=
0) {
output.push(airport);
}
});
$scope.airports = output;
};
$scope.selectAirport = function(string) {
$scope.airport = string;
$scope.hidelist = true;
};
Try this:
$scope.airport_list = response.data.airports;
What I am seeing is that you have an array: $scope.airport_list = [];
When you make your http request, you push what I would understand to be an array of airports into that array. So you end up with your airport array from the backend at the first position of $scope.airport_list, vs. $scope.airport_list being the actual list.
For your search method, you should change the following:
In your HTML:
ng-change="searchFor(airport.name)"
In your JS:
I've renamed your function and changed the input variable to be more clear. You were passing in a full airport, but treating it as a string. You need to compare your provided airport name to that of the airports in the array. So you iterate over the array, and compare each element's name property to what you pass in.
$scope.searchFor = function(airportName) {
$scope.hidelist = false;
const output = [];
angular.forEach($scope.airport_list, function(airport) {
if (airport.name.toLowerCase() === airportName) {
output.push(airport);
}
});
$scope.airports = output;
console.log($scope.airports);
};
I have provided minimal changes to your code to implement this, however I suggest you look at this SO post to filter drop down data more appropriately.
Angularjs Filter data with dropdown
If you want to simply filter out what is displayed in the UI, you can try this in your HTML template. It will provide a text field where you supply a partial of the airport name. If at least one character is entered in that box, the list will display on the page, with the appropriate filtering applied. This will avoid having to call functions on change, having a separate array, etc.
<input type="text" name="airport" id="airport" ng-model="airportSearch.name" placeholder="From..." />
<div class="airport-container-dropdown" ng-hide="!airportSearch.name">
<div class="airport-list"
ng-repeat="airport in airport_list | filter:airportSearch"
ng-click="selectAirport(airport)">
{{ airport.name }}
</div>
</div>

Changing the template data not refreshing the elements

I have searched and tried suggestions mentioned in various posts but no luck so far.
Here is my issue.
I have created a custom element <month-view id="month-view-element"></month-view> in my mainpage.html. Inside mainpage.html when this page is initially loaded i created a empty json object for all the 30days of a month and print a placeholder type cards in UI. Using the code below.
var json = [];
for(var x = 0; x < total; x++) {
json.push({'hours': 0, 'day': x+1, 'year': year});
}
monthView.month = json; //Doing this line. Prints out the desired empty cards for me in the UI.
created a month-view.html something like below:
<dom-module id='month-view'>
<template>
<template is="dom-repeat" items= "{{month}}">
<paper-card class="day-paper-card" heading={{item.day}}>
<div class="card-content work">{{item.work}}</div>
<div class="card-actions containerDay layout horizontal">
<div style="display:inline-block" class="icon">
<paper-icon-button icon="icons:done" data-hours = "8" data-day$="{{item.day}}" data-month$={{item.month}} data-year$={{item.year}} on-click="updateWorkHours"></paper-icon-button>
<paper-tooltip>Full day</paper-tooltip>
</div>
</div>
</paper-card>
</template>
</template>
<script>
Polymer({
is: "month-view",
updateWorkHours: function (e, detail) {
console.log(e);
this.fire('updateWorkHour', {day: e.target.dataHost.dataset.day,
month: e.target.dataHost.dataset.month,
year: e.target.dataHost.dataset.year,
hours: e.target.dataHost.dataset.work
});
}
});
</script>
</dom-module>
There is another file script.js which contains the function document.addEventListener('updateWorkHour', function (e) { // doStuff });. I use this function to make a call to a google client API. I created a client request and then do request.execute(handleCallback);
Once this call is passed i landed in handleCallback function. In this function i do some processing of the response data and save parts of data into json variable available in the file already. And once all processing is done i did something like below.
monthView.month = json;
But this above line is not refreshing my UI with the latest data. Is there anything I am missing? Any suggestions or anything i am doing incorrectly.
You need to use 'set' or 'notifyPath' while changing Polymer Object or Arrays in javascript for the databinding/obserers to work. You can read more about it in https://www.polymer-project.org/1.0/docs/devguide/data-binding.html#path-binding
In your case try below code
monthView.set('month',json);
Updated suggestions:
Wrap your script on main page with. This is required for non-chrome browsers.
addEventListener('WebComponentsReady', function() {})
This could be scoping issue. Try executing 'document.querySelector('#month-view-element');' inside your callback addWorkHoursCallBack. Also, Use .notifyPath instead of .set.

Clicking an element inside repeater when another element equals specific input with protractor and jasmine

My HTML structure is this:
<li ng-repeat="m in members">
<div class="col-name">{{m.name}}</div>
<div class="col-trash">
<div class="trash-button"></div>
</div>
</li>
What I want to be able to do is using protractor, click on the trash when m.name equals a specific value.
I've tried things like:
element.all(by.repeater('m in members')).count().then(function (number) {
for (var i = 0 ; i < number ; i++) {
var result = element.all(by.repeater('m in members').row(i));
result.get(0).element(by.binding('m.name')).getAttribute('value').then(function (name) {
if (name == 'John') {
result.get(0).element(by.className('trash-button')).click();
};
});
};
});
This seems like it should work, however, it seems like my function does not even run this.
I've also looked into promises and filters though have not been successful with those either. Keep getting errors.
var array = element.all(by.repeater('m in members'));
array.filter(function (guy) {
guy.getText().then(function (text) {
return text == 'John';
})
}).then(function (selected) {
selected.element(by.className('trash-button')).click()
}
All I would like to do is click on the corresponding trash when looking for a specific member list!
Any help would be much appreciated.
EDIT: suggested I use xpath once I find the correct element, which is fine, the problem is I cannot get the filter function's promise to let me use element(by.xpath...)
If I try:
var array = element.all(by.repeater('m in members'));
array.filter(function (guy) {
guy.getText().then(function (text) {
return text == 'John';
})
}).then(function (selected) {
selected.element(by.xpath('following-sibling::div/div')).click()
}
I get the error:
Failed: Object has no method 'element'
Figured it out. Following the Protractor filter guideline in the API reference and using alecxe's xpath recommendation, this code will click on any element you find after filtering it.
element.all(by.className('col-name')).filter(function (member, index) {
return member.getText().then(function (text) {
return member === 'John';
});
}).then( function (elements) {
elements[0].element(by.xpath('following-sibling::div/div/')).click();
});
You can change the xpath to select different stuff incase your HTML does not look like mine.
It looks like you can avoid searching by repeater, and use by.binding directly. Once you found the value you are interested in, get the following sibling and click it:
element.all(by.binding('m.name')).then(function(elements) {
elements.filter(function(guy) {
guy.getText().then(function (text) {
return text == 'John';
})
}).then(function (m) {
m.element(by.xpath('following-sibling::div/div')).click();
});
});
Or, a pure xpath approach could be:
element(by.xpath('//li/div[#class="col-name" and .="John"]/following-sibling::div/div')).click();

How do I change AngularJS ng-src when API returns null value?

In working with the API from themoviedb.com, I'm having the user type into an input field, sending the API request on every keyup. In testing this, sometimes the movie poster would be "null" instead of the intended poster_path. I prefer to default to a placeholder image to indicate that a poster was not found with the API request.
So because the entire poster_path url is not offered by the API, and since I'm using an AngularJS ng-repeat, I have to structure the image tag like so (using dummy data to save on space):
<img ng-src="{{'http://example.com/'+movie.poster_path}}" alt="">
But then the console gives me an error due to a bad request since a full image path is not returned. I tried using the OR prompt:
{{'http://example.com/'+movie.poster_path || 'http://example.com/missing.jpg'}}
But that doesn't work in this case. So now with the javascript. I can't seem to get the image source by using getElementsByTagName or getElementByClass, and using getElementById seems to only grab the first repeat and nothing else, which I figured would be the case. But even then I can't seem to replace the image source. Here is the code structure I attempted:
<input type="text" id="search">
<section ng-controller="movieSearch">
<article ng-repeat="movie in movies">
<img id="myImage" src="{{'http://example.com/'+movie.poster_path}}" alt="">
</article>
</section>
<script>
function movieSearch($scope, $http){
var string,
replaced,
imgSrc,
ext,
missing;
$(document).on('keyup', function(){
string = document.getElementById('search').value.toLowerCase();
replaced = string.replace(/\s+/g, '+');
$http.jsonp('http://example.com/query='+replaced+'&callback=JSON_CALLBACK').success(function(data) {
console.dir(data.results);
$scope.movies = data.results;
});
imgSrc = document.getElementById('myImage').src;
ext = imgSrc.split('.').pop();
missing='http://example.com/missing.jpg';
if(ext !== 'jpg'){
imgSrc = missing;
}
});
}
</script>
Any ideas with what I'm doing wrong, or if what I'm attempting can even be done at all?
The first problem I can see is that while you are setting the movies in a async callback, you are looking for the image source synchronously here:
$http.jsonp('http://domain.com/query='+replaced+'&callback=JSON_CALLBACK').success(function(data) {
console.dir(data.results);
$scope.movies = data.results;
});
// This code will be executed before `movies` is populated
imgSrc = document.getElementById('myImage').src;
ext = img.split('.').pop();
However, moving the code merely into the callback will not solve the issue:
// THIS WILL NOT FIX THE PROBLEM
$http.jsonp('http://domain.com/query='+replaced+'&callback=JSON_CALLBACK').success(function(data) {
console.dir(data.results);
$scope.movies = data.results;
// This will not solve the issue
imgSrc = document.getElementById('myImage').src;
ext = img.split('.').pop();
// ...
});
This is because the src fields will only be populated in the next digest loop.
In your case, you should prune the results as soon as you receive them from the JSONP callback:
function movieSearch($scope, $http, $timeout){
var string,
replaced,
imgSrc,
ext,
missing;
$(document).on('keyup', function(){
string = document.getElementById('search').value.toLowerCase();
replaced = string.replace(/\s+/g, '+');
$http.jsonp('http://domain.com/query='+replaced+'&callback=JSON_CALLBACK').success(function(data) {
console.dir(data.results);
$scope.movies = data.results;
$scope.movies.forEach(function (movie) {
var ext = movie.poster_path && movie.poster_path.split('.').pop();
// Assuming that the extension cannot be
// anything other than a jpg
if (ext !== 'jpg') {
movie.poster_path = 'missing.jpg';
}
});
});
});
}
Here, you modify only the model behind you view and do not do any post-hoc DOM analysis to figure out failures.
Sidenote: You could have used the ternary operator to solve the problem in the view, but this is not recommended:
<!-- NOT RECOMMENDED -->
{{movie.poster_path && ('http://domain.com/'+movie.poster_path) || 'http://domain.com/missing.jpg'}}
First, I defined a filter like this:
In CoffeeScript:
app.filter 'cond', () ->
(default_value, condition, value) ->
if condition then value else default_value
Or in JavaScript:
app.filter('cond', function() {
return function(default_value, condition, value) {
if (condition) {
return value;
} else {
return default_value;
}
};
});
Then, you can use it like this:
{{'http://domain.com/missing.jpg' |cond:movie.poster_path:('http://domain.com/'+movie.poster_path)}}

Resources