Running nested for loops with Puppeteer page.$$eval(...) - puppeteer

I have an html document with two levels of repeating html elements. The first level is a variable number of test questions (1 -> n), and then within each test question, there are a variable number of possible answers (2 -> n).
Using Puppeteer's page.$$eval(...) function, I need to iterate through these two levels and capture the data associated with each level.
I am able to capture the first level data (test questions), but am unable to figure out how to iterate through and capture the nested inner level (possible answers).
Here's what I have so far...
Sample HTML:
<html>
<body>
<div id="8888" class="course-wrapper">
<div class="question">
<div class="q-label">Question 1</div>
<div class="q-question">Is this question #2?</div>
<div class="q-choices">
<div class="choice">
<div class="num">1.</div>
<div class="answer">yes</div>
<div class="answer-check">incorrect</div>
</div>
<div class="choice">
<div class="num">2.</div>
<div class="answer">no</div>
<div class="answer-check">correct</div>
</div>
<div class="choice">
<div class="num">3.</div>
<div class="answer">perhaps</div>
<div class="answer-check">incorrect</div>
</div>
<div class="choice">
<div class="num">4.</div>
<div class="answer">all of the above</div>
<div class="answer-check">incorrect</div>
</div>
</div>
</div>
<div class="question">
<div class="q-label">Question 2</div>
<div class="q-question">How far is it to Tipperary?</div>
<div class="q-choices">
<div class="choice">
<div class="num">1.</div>
<div class="answer">a long way</div>
<div class="answer-check">correct</div>
</div>
<div class="choice">
<div class="num">2.</div>
<div class="answer">not so far</div>
<div class="answer-check">incorrect</div>
</div>
<div class="choice">
<div class="num">3.</div>
<div class="answer">a very long way</div>
<div class="answer-check">incorrect</div>
</div>
<div class="choice">
<div class="num">4.</div>
<div class="answer">right next door</div>
<div class="answer-check">incorrect</div>
</div>
<div class="choice">
<div class="num">5.</div>
<div class="answer">just over the hill</div>
<div class="answer-check">incorrect</div>
</div>
</div>
</div>
<div class="question">
<div class="q-label">Question 3</div>
<div class="q-question">Is this question #3?</div>
<div class="q-choices">
<div class="choice">
<div class="num">1.</div>
<div class="answer">yes</div>
<div class="answer-check">correct</div>
</div>
<div class="choice">
<div class="num">2.</div>
<div class="answer">no</div>
<div class="answer-check">incorrect</div>
</div>
<div class="choice">
<div class="num">3.</div>
<div class="answer">perhaps</div>
<div class="answer-check">incorrect</div>
</div>
<div class="choice">
<div class="num">4.</div>
<div class="answer">all of the above</div>
<div class="answer-check">incorrect</div>
</div>
<div class="choice">
<div class="num">5.</div>
<div class="answer">i don't know</div>
<div class="answer-check">incorrect</div>
</div>
<div class="choice">
<div class="num">6.</div>
<div class="answer">none of your business</div>
<div class="answer-check">incorrect</div>
</div>
</div>
</div>
</div>
</body>
</html>
Target JSON:
[
{
"courseUNID": "8888",
"qCount": 3,
"qArray": [
{
"testQLabel": "Question 1",
"testQ": "Is this question 2?",
"testQAnswerChoiceCount": 4,
"testQPossibleAnswers": [
{
"Num": "1.",
"Answer": "yes",
"AnswerCheck": "incorrect"
},
{
"Num": "2.",
"Answer": "no",
"AnswerCheck": "correct"
},
{
"Num": "3.",
"Answer": "perhaps",
"AnswerCheck": "incorrect"
},
{
"Num": "4.",
"Answer": "all of the above",
"AnswerCheck": "incorrect"
}
]
},
{
"testQLabel": "Question 2",
"testQ": "How far is it to Tipperary?",
"testQAnswerChoiceCount": 5,
"testQPossibleAnswers": [
{
"Num": "1.",
"Answer": "a long way",
"AnswerCheck": "correct"
},
{
"Num": "2.",
"Answer": "not so far",
"AnswerCheck": "incorrect"
},
{
"Num": "3.",
"Answer": "a very long way",
"AnswerCheck": "incorrect"
},
{
"Num": "4.",
"Answer": "right next door",
"AnswerCheck": "incorrect"
},
{
"Num": "5.",
"Answer": "just over the hill",
"AnswerCheck": "incorrect"
}
]
},
{
"testQLabel": "Question 3",
"testQ": "Is this question 2?",
"testQAnswerChoiceCount": 6,
"testQPossibleAnswers": [
{
"Num": "1.",
"Answer": "yes",
"AnswerCheck": "incorrect"
},
{
"Num": "2.",
"Answer": "no",
"AnswerCheck": "correct"
},
{
"Num": "3.",
"Answer": "perhaps",
"AnswerCheck": "incorrect"
},
{
"Num": "4.",
"Answer": "all of the above",
"AnswerCheck": "incorrect"
},
{
"Num": "5.",
"Answer": "i don't know",
"AnswerCheck": "incorrect"
},
{
"Num": "6.",
"Answer": "none of your business",
"AnswerCheck": "incorrect"
}
]
}
]
}
]
Code (need help with the nested page.$$eval(...) function):
const scraped_post_test = async (page) => {
const courseUNID = await page.$eval("#8888", element => element.getAttribute("id"));
const qCount = await page.$$eval("#8888 > div.question > div.q-label", elements => {
return elements.length
});
const qLabel = await page.$$eval("#8888 > div.question > div.q-label", elements => {
return elements.map(element => element.textContent)
});
const qQuestion = await page.$$eval("#8888 > div.question > div.q-question", elements => {
return elements.map(element => element.textContent)
});
const qPossibleAnswersArray = await page.$$eval("#8888 > div.question > div.q-choices", elements => {
/*** nested iteration here? ***/
const answersArray = [
{
"Num": "1.",
"Answer": "a long way",
"AnswerCheck": "correct"
},
{
"Num": "2.",
"Answer": "something else",
"AnswerCheck": "incorrect"
},
...
]
return answersArray
});
let qArray = [];
for (let i = 0; i < qCount; i++) {
let testQObj = {};
testQObj.testQLabel = qLabel[i];
testQObj.testQ = qQuestion[i];
testQObj.testQAnswerChoiceCount = qPossibleAnswersArray.length;
testQObj.testQPossibleAnswers = qPossibleAnswersArray;
await qArray.push(testQObj)
}
return {
courseUNID,
qCount,
qArray
}
}
module.exports = { scraped_post_test }

I can get you started:
page.$$eval('.course-wrapper', divs => divs.map(div => {
let questions = [...div.querySelectorAll('.question')]
return {
courseUNID: div.id,
qCount: questions.length,
qArray: questions.map(q => {
let [label, question, choices] = [...q.querySelectorAll('.q-label', '.q-question', '.q-choices')]
return {
testQLabel: label.innerText,
...
}
})
}
}))

Related

AngularJS nested objects in the same bootstrap row

i have a JSON object
{
"products": [
{
"devices": [
{
"label": "P1D1"
},
{
"label": "P1D2"
}
]
},
{
"devices": [
{
"label": "P2D1"
},
{
"label": "P2D2"
}
]
}
]
}
and i want to have in HTML something like this
<div class="row">
<div class="col-3">
P1D1
</div>
<div class="col-3">
P1D2
</div>
<div class="col-3">
P2D1
</div>
<div class="col-3">
P2D2
</div>
</div>
AngularJS is the language i am using, but i don't seem the find the right syntax
<div class="row">
<div ng-repeat="product in obj.products">
<div class="col-3" ng-repeat="device in product.devices">
{{device.label}}
</div>
</div>
</div>
How can i achieve all the subitems to be displayed in a bootstrap column next to eachother?
I think you can't do it directly using only HTML. Alternatively you can use some manipulation in Javascript to flatten your data.
JAVASCRIPT
$scope.getItems = function(){
return $scope.obj.products.flatMap(function(element){
return element.devices;
})
}
HTML
<div class="col-3" ng-repeat="device in getItems()">
{{device.label}}
</div>
Check my example:
https://codepen.io/avgustint/pen/XWYyoyd?editors=1111

Nested Children Json in Angular HTML

I have a JSON with nested children. RowElements can have n colElements and colElements can have n RowElements.
JSON
"container": {
"id": "37ec0632-3391-4ef5-a9cb-25db2216fa75",
"rowElements": [
{
"id": "80e1dae7-b497-48d3-80ad-b94d36ada642",
"colElements": [
{
"id": "c662efe9-615c-4de5-97d3-2b02a5bce831",
"rowElements": [
{
"id": "b31960fd-c082-4d3b-88fc-c05f0cf837aa",
"colElements": []
},
{
"id": "7552306a-ca61-4d80-bb4e-d6c8d15c2c78",
"colElements": []
},
{
"id": "f669896d-132b-468e-8dcf-f2b59d509a76",
"colElements": []
}
]
}
]
}
]
}
How can i iterate over the children dynamically in the template like:
<div class="row">
<div class="col">
<div class="row">
<div class="col">
</div>
<div class="col">
</div>
<div class="col">
</div>
...
</div>
...
</div>
...
</div>
You can use nested *ngFor to iterate the JSON and display it dynamically.
I also created a stackblitz for you to check my solution.
HTML - I'm display the id here just to check the JSON.
<div class="row" *ngFor="let firstRow of arr.container.rowElements">
{{ firstRow.id }}
<div class="col" *ngFor="let firstCol of firstRow.colElements">
{{ firstCol.id }}
<div class="row" *ngFor="let secondRow of firstCol.rowElements">
{{ secondRow.id }}
<div class="col" *ngFor="let secondCol of secondRow.colElements"></div>
</div>
</div>
</div>
AppComponent.ts - I added a variable to hold your sample JSON
export class AppComponent {
arr = {
container: {
id: '37ec0632-3391-4ef5-a9cb-25db2216fa75',
rowElements: [{
id: '80e1dae7-b497-48d3-80ad-b94d36ada642',
colElements: [{
id: 'c662efe9-615c-4de5-97d3-2b02a5bce831',
rowElements: [{
id: 'b31960fd-c082-4d3b-88fc-c05f0cf837aa',
colElements: []
},
{
id: '7552306a-ca61-4d80-bb4e-d6c8d15c2c78',
colElements: []
},
{
id: 'f669896d-132b-468e-8dcf-f2b59d509a76',
colElements: []
},
],
}, ],
}, ],
},
};
constructor() {
console.log(this.arr.container);
}
}

Looping twig DIV and list with my JSON array

Hey Guys I have the following code which loops the <section> everytime I use it within the JSON. This works fine.
However, I am having problems with the nested loop {% for list in lists %} which handles the li elements. It comes out blank and seems to only loop twice when I inspect the element?
{# Question 1 #}
{% for question in questions %}
<section>
<div class="container question" id="question-one">
<div class="row row-eq-height">
<div class="gradient"></div>
<div class="col-md-1 green-box">
<div class="number"><span>1</span></div>
</div>
<div class="col-md-10 dark-grey-box text-center">
<div class="content-wrapper">
{{ question.text|markdown }}
{# SLIDER #}
<div class="row">
<div class="col-md-8 offset-md-2">
<div class="slider-container">
<ul class="list-inline justify-content-center range-labels">
{% for list in lists %}
<li class="list-inline-item"><img src="{{ list.img }}"><span>{{ list.label }}</span></li>
{% endfor %}
</ul>
<div class="range-wrapper">
<img src="../resources/images-assets/images/place-holder-slider.png">
</div>
</div>
</div>
</div>
<div class="cta-wrapper">
<button id="question-one-submit" onclick="buttonClick()">DONATE £1</button>
</div>
</div>
</div>
<div class="col-sm-1 dark-grey-box"></div>
</div>
</div>
</section>
{% endfor %}
In my JSON file I have laid everything out as follows:
"questions": [
{
"text": "##How confident are you in achieving your marketing goals this year?",
"lists": [
{ "img": "..\/resources\/images-assets\/images\/sad.svg", "label": "Dejected" },
{ "img": "..\/resources\/images-assets\/images\/sad.svg", "label": "Dejected" },
{ "img": "..\/resources\/images-assets\/images\/sad.svg", "label": "Dejected" },
{ "img": "..\/resources\/images-assets\/images\/sad.svg", "label": "Dejected" },
{ "img": "..\/resources\/images-assets\/images\/sad.svg", "label": "Dejected" }
]
}
],
I want the li to appear 5 times with the relevant image and label as I have added in the JSON file.
Can anyone point me in the right direction?
I managed to get this working correctly. Here is the markup:
{% for question in questions %}
<section>
<div class="container question" id="question-{{ loop.index }}">
<div class="row row-eq-height">
<div class="gradient"></div>
<div class="col-md-1 green-box">
<div class="number"><span>
{{ loop.index }}
</span></div>
</div>
<div class="col-md-10 dark-grey-box text-center">
<div class="content-wrapper">
{{ question.text|markdown }}
{# SLIDER #}
<div class="row">
<div class="col-md-8 offset-md-2">
<div class="slider-container">
<ul class="list-inline justify-content-center range-labels">
{% for option in question.options %}
<li class="list-inline-item"><img src="{{ option.img }}"><span>{{ option.label }}</span></li>
{% endfor %}
</ul>
<div class="range-wrapper">
<img src="images/place-holder-slider.png">
</div>
</div>
</div>
</div>
<div class="cta-wrapper">
<button id="button-{{ loop.index }}" onclick="buttonClick()">{{ question.button }}</button>
</div>
</div>
</div>
<div class="col-sm-1 dark-grey-box"></div>
</div>
</div>
</section>
{% endfor %}
The JSON file I am using to accompany this is as follows:
"questions": [
{
"text": "##How confident are you in achieving your marketing goals this year?",
"options": [
{ "img": "images/sad.svg", "label": "Dejected" },
{ "img": "images/thinking-2.svg", "label": "Doubtful" },
{ "img": "images/like.svg", "label": "Hopeful" },
{ "img": "images/check.svg", "label": "Upbeat" },
{ "img": "images/goal.svg", "label": "Surefire" }
],
"button": "DONATE £1"
},
{
"text": "##What are the greatest challenges you face?",
"options": [
{ "img": "images/money-bag.svg", "label": "Budget" },
{ "img": "images/team.svg", "label": "Resources" },
{ "img": "images/bar-chart.svg", "label": "ROI" },
{ "img": "images/timer.svg", "label": "Short-termis" },
{ "img": "images/martech.svg", "label": "Martech" }
],
"button": "DONATE £1"
},
{
"text": "##Where could external agencies add the most value?",
"options": [
{ "img": "images/networking.svg", "label": "ABM" },
{ "img": "images/increasing-stocks-graphic-of-bars.svg", "label": "Demand" },
{ "img": "images/options.svg", "label": "Strategy" },
{ "img": "images/full-items-inside-a-shopping-bag.svg", "label": "Sales Enablement" },
{ "img": "images/content.svg", "label": "Content" }
],
"button": "DONATE £1"
},
{
"text": "##Would you be interested in a further conversation?",
"options": [
{ "img": "images/thumb-down.svg", "label": "Don't contact me again" },
{ "img": "images/maybe.svg", "label": "Unlikely this year" },
{ "img": "images/info.svg", "label": "Need to know more" },
{ "img": "images/calendar.svg", "label": "Get a date in the diary" },
{ "img": "images/boy-broad-smile.svg", "label": "Call me now" }
],
"button": "DONATE £1"
}
],

Collapse/Expand nested div in Angular 6

I am developing a schedule like structure using div. And my design looks like this.
What i want is when loading, only 1st level should be displayed (all venues) and when clicking on venue its immediate child should be displayed and so on. Is there any way to do so. my html is:
<div class="div-table">
<div class="div-table-row">
<div class="div-header-col" style="visibility: hidden;">A</div>
<div *ngFor="let date of dates" class="div-date-col">{{date | date:'d E'}}</div>
</div>
<!-- level1 -->
<div *ngFor="let venue of venues" class="level1" style="color: red">
<div class="div-table-row-level1" >
<div class="div-header-col">{{venue.name}}</div>
<div *ngFor="let x of dates" class="div-event-level1-col"></div>
</div>
<!-- level2 -->
<div *ngFor="let category of venue.categories" class="level2" style="color: blue">
<div class="div-table-row-level2">
<div class="div-header-col" style="padding-left: 10px">{{category.name}}</div>
<div *ngFor="let x of dates" class="div-event-level2-col"></div>
</div>
<!-- level3 -->
<div *ngFor="let asset of category.assets" class="level3" style="color: green">
<div class="div-table-row-level3">
<div class="div-header-col" style="padding-left: 20px">{{asset.name}}</div>
<div *ngFor="let x of dates" class="div-event-level3-col assest-hover" "></div>
</div>
</div>
</div>
</div>
</div>
My data for the table(div) is :
[
{
"id":1,
"name":"venue1",
"categories":[
{
"id":1,
"name":"cat1",
"assets":[
{
"id":1,
"name":"assest1"
},
{
"id":2,
"name":"assest2"
}
]
},
{
"id":2,
"name":"cat2",
"assets":[
{
"id":3,
"name":"assest3"
},
{
"id":4,
"name":"assest4"
}
]
}
]
},
{
"id":2,
"name":"venue2",
"categories":[
{
"id":3,
"name":"cat3",
"assets":[
{
"id":5,
"name":"assest5"
},
{
"id":6,
"name":"assest6"
}
]
},
{
"id":4,
"name":"cat4",
"assets":[
{
"id":7,
"name":"assest7"
},
{
"id":8,
"name":"assest8"
}
]
}
]
},{
"id":3,
"name":"venue3",
"categories":[
{
"id":5,
"name":"cat5",
"assets":[
{
"id":9,
"name":"assest9"
},
{
"id":10,
"name":"assest10"
}
]
},
{
"id":6,
"name":"cat6",
"assets":[
{
"id":11,
"name":"assest11"
},
{
"id":12,
"name":"assest12"
}
]
}
]
}
]
If you have entire data loaded together then you have good news that it can be implemented in a very simple way -
You can play around Element Reference. Nothing needs to be managed from ts file.
in html
<div *ngFor="let venue of venues" class="level1" style="color: red"
(click)="ele.class = ele.class == 'showChildren' ? '' : 'showChildren'"
[ngClass]="{ hideChildren : ele.class !== 'showChildren' }">
Repeat the same for all parent div
in CSS
.hideChildren>div{
display: none;
}
Here is the working copy- https://stackblitz.com/edit/angular-n43ihd
There are some more way to handle if the data is being fetched in Asynchronous fashion. Then these logic will move to ts file.
You can try to hide component's on-load, and set them to visible onClick
$scope.$on('$viewContentLoaded', function() {
// Hide all unwanted div's here
});

Multidimensional array with jquery templates

I have the following javascript object
var arr = [
[
{ "id": 1, "name": "one" },
{ "id": 2, "name": "two" },
{ "id": 3, "name": "three" }
],
[
{ "id": 4, "name": "four" },
{ "id": 5, "name": "five" },
{ "id": 6, "name": "six" }
],
]
I'm trying to use jquery templates to create the following HTML
<div class="row">
<div class="cell">
<span>1</span> : <span>one</span>
</div>
<div class="cell">
<span>2</span> : <span>two</span>
</div>
<div class="cell">
<span>3</span> : <span>three</span>
</div>
</div>
<div class="row">
<div class="cell">
<span>4</span> : <span>four</span>
</div>
<div class="cell">
<span>5</span> : <span>five</span>
</div>
<div class="cell">
<span>6</span> : <span>six</span>
</div>
</div>
I am using the following templates with no luck :(
<script id="rowTemplate" type="text/x-jQuery-tmpl">
<div class="row">
{{tmpl "#cellTemplate"}}
</div>
</script>
<script id="cellTemplate" type="text/x-jQuery-tmpl">
<div class="cell">
<span>${id}</span> : <span>${name}</span>
</div>
</script>
The line that calls the template is the following:
$("#rowTemplate").tmpl(arr).replaceAll("#somediv");
I am getting only one row with one cell with no data...
<div class="row">
<div class="cell">
<span></span> : <span></span>
</div>
</div>
What am I doing wrong?
I think the problem is with usage of replaceAll and the missing parameter to tmpl in the template.
Try this(used replaceWith for #someDiv and passed $data as tmpl parameter for child template):
<script type="text/javascript">
var arr =
[
[
{
"id": 1,
"name": "one"
},
{
"id": 2,
"name": "two"
},
{
"id": 3,
"name": "three"
}
],
[
{
"id": 4,
"name": "four"
},
{
"id": 5,
"name": "five"
},
{
"id": 6,
"name": "six"
}
]
];
$(function(){
$("#somediv").replaceWith($("#rowTemplate").tmpl(arr));
});
</script>
<script id="rowTemplate" type="text/x-jQuery-tmpl">
<div class="row">
{{tmpl($data) "#cellTemplate"}}
</div>
</script>
<script id="cellTemplate" type="text/x-jQuery-tmpl">
<div class = "cell"><span>${id}</span>:<span>${name}</span></div>
</script>
<div id="somediv"></div>