How do I get the ElementHandle's class name when using Puppeteer? - puppeteer

I'm trying to get an ElementHandle's class name using Puppeteer... is it possible? Am I using the wrong approach? In this jsBin is part of my code, so you can understand what I am trying to achieve.
CriticalCssPlugin.prototype.load = function( page, src ) {
return page.goto( src, { waitUntil: 'networkidle2' } )
.then( () => {
return page
.$$( '*' )
.then( elements => {
return Promise.all( elements.map( element => {
return element.boundingBox()
} ) )
.then( positions => {
let visible = positions.filter( ( rect, index ) => {
if ( !rect ) {
return rect
}
rect.element = elements[ index ]
return this.isAnyPartOfElementInViewport( rect, page.viewport() )
} )
this.getClasses( visible )
} )
} )
} )
}
CriticalCssPlugin.prototype.getClasses = function( visibles ) {
Promise.all( visibles.map( visible => {
return visible.element.getProperty( '' )
} ) )
.then( classes => {
console.log(classes);
} )
}
CriticalCssPlugin.prototype.isAnyPartOfElementInViewport = function( rect, viewport ) {
const windowHeight = viewport.height
const windowWidth = viewport.width
const vertInView = ( rect.y <= windowHeight ) && ( ( rect.y + rect.height ) >= 0 )
const horInView = ( rect.x <= windowWidth ) && ( ( rect.x + rect.width ) >= 0 )
return ( vertInView && horInView )
}
https://jsbin.com/kuzejoluji/edit?js,output
Thank you :D

Going to drop this here since this page is currently first result searching for "elementhandle class name"
From the docs, you should just be able to the following
const el = await page.$('.myElement')
const className = await el.getProperty('className')
// alternatively,
// page.$('.myElement')
// .then(el => el.getProperty('className'))
// .then(className => ... )

jimmyjoy's answer is right but this may help others use the elementHandle
page.$(el) // This grabs the element (returns a elementHandle)
.then((el) => el.getProperty("className")) // Returns a jsHandle of that property
.then((cn) => cn.jsonValue()) // This converts the className jsHandle to a space delimitedstring
.then((classNameString) => classNameString.split(" ") // Splits into array
.then((x) => console.log(x)
Which would log an array of classes
Note: when i tried to do a .split on the end of jsonValue() it didn't work as i believe the promise isn't resolved at that point so cn.jsonValue().split(" ") wont work
References
List of properties on elements
Puppeteer docs for ElementHandle

I found a solution that helps in parts, but it was good enough to me. I've got the class name acessing ElementHandle._remoteObject.description.
Hope this helps someone.

you can get the element variable and use evaluate function like that:
const element = await page.$(".some-class"); // for ids you can write "#some-id"
const className = await page.evaluate(el => el.className, element);
console.log('className', className) // here you can get the class name

I use this function
// JS
export async function elementHasClass(el, className) {
const classNames = (
await (await el.getProperty('className')).jsonValue()
).split(/\s+/);
return classNames.includes(className);
}
// TS
export async function elementHasClass(
el: ElementHandle,
className: string,
): Promise<boolean> {
const classNames = (
await (await el.getProperty('className')).jsonValue<string>()
).split(/\s+/);
return classNames.includes(className);
}

This is what i did:
let posts = await page.$$(".postContainer .upvote");
for (let i = 0; i < posts.length; i++) {
// log class name
console.log(await (await posts[i].getProperty('className')).jsonValue());
// click upvotes on posts
await codes[i].click();
}

Related

How to set initial value on useRef<HTMLInputElement> - ReactJs + Typescript

I would like to set a number as initial value on useRef<HTMLInputElement>.
I don't need to use useState<number>() because the field is a simple counter.
Here is my typescript code:
const MyComponent = () => {
const productAmountRef = useRef<HTMLInputElement>();
const handleReduceClick = () => {
productAmountRef.current.value -= 1;
}
const handleAddClick = () => {
productAmountRef.current.value += 1;
}
return (
<>
<SomeWrapper>
<ReduceButton onClick={handleReduceClick}/>
<input disabled={true} ref={productAmountRef}/>
<AddButton onClick={handleAddClick}/>
</SomeWrapper>
</>
)
}
For obvious reasons, when the onClick function is triggered, the value is a NaN.
My doubt is, how can I set a Initial Value on useRef<HTMLInputElement>? As I said and as you saw, it need to be a number.
Is this possible?
Set the initial value using the defaultValue attribute:
<input disabled={true} ref={productAmountRef} defaultValue={3} />
Or use useState() and render the number without the use of an input:
const MyComponent = () => {
const [productAmount, setProductAmount] = useState(0);
const handleReduceClick = () => {
setProductAmount(val => val - 1);
}
const handleAddClick = () => {
setProductAmount(val => val + 1);
}
return (
<SomeWrapper>
<ReduceButton onClick={handleReduceClick}/>
<div>{productAmount}</div>
<AddButton onClick={handleAddClick}/>
</SomeWrapper>
)
}

How to wait to finish subscribe before moving to next index in for loop in Angular 6

I'm using Angular 6.
I have an array of links and a variable to store fetched information in same order as of array one by one.
Here is what I'm trying to do using for loop.
products: any;
processedItems: Array<any> = [];
private _processItem() {
for (let i = 0; i < this.products.length; i++) {
this.scraperService.scrapSingle(this.products[i].url).subscribe(
res => {
if (res.status.http_code === 200) {
const properties = this.scraperService.processSingleProduct(res.contents);
const p_item = {};
p_item['info'] = this.products[i];
p_item['properties'] = properties;
this.processedItems.push(p_item);
}
console.log(res);
}
);
}
console.log(this.products.length);
}
But how to wait for subscribe before moving to next index in the loop?
Just splice the p_item into your array at the required index given i.
For example instead of doing,
this.processedItems.push(p_item);
do this,
this.processedItems.splice(p_item, 0, i);
That solves your problem :)
Use promises instead of rx.js subscriptions via using toPromise method. You might need to map the res to json. res.map(item => item.json());
products: any;
processedItems: Array < any > =[];
private _processItem() {
this.products.array.forEach(async (element) => {
const res = await this.scraperService.scrapSingle(element.url).toPromise();
if (res.status.http_code === 200) {
const properties = this.scraperService.processSingleProduct(res.contents);
const p_item = {};
p_item['info'] = element
p_item['properties'] = properties;
this.processedItems.push(p_item);
}
console.log(res);
});
console.log(this.products.length);
}

Zoom on a room in the viewer

Now, I'm able to list the levels. I'm trying to zoom on a specific room of a given level.
I've the room dbId but when I do this :
v.fitToView(34969, v.model) (v => viewer / 34969 the dbId's room)
Camera is going to far :
I'm trying to do exactly the same camera movement than when I click on a sub item in modelStructureTool > Room
about color of a room
Here is my code with the correction :
var ulRoom = document.createElement('ul');
ulRoom.setAttribute('id','levelsList');
el.children.forEach(function(l){
var liRoom = document.createElement('li');
liRoom.setAttribute('class','room');
liRoom.setAttribute('style','cursor:pointer;color: #000; font-weigth: bold');
liRoom.onclick = function () {
console.log("l ====>", l);
viewer.fitToView( [l.dbId], viewer.model);
viewer.setThemingColor( l.dbId, viewer.model);
};
ulRoom.appendChild(liRoom);
liRoom.innerHTML = l.name;
});
viewer.fitToView is working nicely.
In my forEach, l is room :
The first argument must be an array, so your function call has to be changed to viewer.fitToView( [ 34969 ], viewer.model ). The 2nd argument model is optional, so it will become viewer.fitToView( [ 34969 ] ) in single model use case.
If you’re using the Viewer in multiple models case, you have to find the corresponding model from viewer.impl.modelQueue().getModels() for that room and the second argument of the Viewer3D#fitToView.
Hope it helps.
Edit:
The viewer API for changing element's color is viewer.setThemingColor( dbId, color ) and it only works for leaf nodes. After investigating the model you provided to Forge Helpdesk, I found the room which has dbId 34969 is not the leaf node of the Viewer instance tree. So, this's why the color didn't change while the API is event right.
function getLeafNodes( model, dbIds ) {
return new Promise( ( resolve, reject ) => {
try {
const instanceTree = model.getData().instanceTree
dbIds = dbIds || instanceTree.getRootId();
const dbIdArray = Array.isArray( dbIds ) ? dbIds : [dbIds]
let leafIds = [];
const getLeafNodesRec = ( id ) => {
let childCount = 0;
instanceTree.enumNodeChildren( id, ( childId ) => {
getLeafNodesRec( childId );
++childCount;
})
if( childCount == 0 ) {
leafIds.push( id );
}
}
for( let i = 0; i < dbIdArray.length; ++i ) {
getLeafNodesRec( dbIdArray[i] );
}
return resolve( leafIds );
} catch (ex) {
return reject(ex)
}
})
}
let color = new THREE.Vector4( 255/255, 0/255, 0/255, 1 );
getLeafNodes( viewer.model, [ 34969 ] )
.then( ( leafNodes ) => {
// Call setThemingColor for every leaf node.
for( let i = 0; i < leafNodes.length; i++ ) {
viewer.setThemingColor( leafNodes[i], color );
}
})
.catch( ( error ) => console.warn( error ) );
Here is the Viewer documentation: https://developer.autodesk.com/en/docs/viewer/v2/reference/javascript/viewer3d/

Wait for fetching data from Firebase before return

I have the following code:
UPDATE:
const showEvent = (eventLink) => {
let {events
} = database
let staffMembers = ""
let scenesList = ""
const staffMembersContainer = $('.staff-members')
const scenesContainer = $('.scenes')
const eventsGrid = $('#events-grid')
const eventHeader = $('#events-grid h2')
const eventKey = eventLink.getAttribute('data-key')
const {
name,
staff,
scenes
} = events[eventKey]
eventHeader.innerText = name
eventsGrid.classList.toggle("hidden")
Object.keys(staff).forEach(role => {
const staffMember = staff[role]
staffMembers += Staff(role, staffMember)
})
staffMembersContainer.innerHTML = staffMembers
Object.keys(scenes).forEach(scene => {
scenesList += Scene(scenes[scene])
})
scenesContainer.innerHTML = scenesList
}
const Staff = (role, staffMember) => {
const {
name
} = database.profiles[staffMember[0]]
return `
<li>
<p>${role}:</p>
<p>${name}</p>
</li>
`
}
const Scene = (id) => {
let promises = []
const {
name,
concerts
} = database.scenes[id]
let concertList = ""
concerts.forEach(concert => {
promises.push(
Concert(concert).then(bandName => {
concertList += `<li><p>${bandName}</p></li>`
})
)
})
return Promise.all(promises).then(() => {
return `
<li class="scene">
<p>Scene ${name}:</p>
<ul>
${concertList}
</ul>
</li>
`
})
}
const Concert = (id) => {
const bandId = database.concerts[id].band
return firebase.database().ref(`/bands/${bandId}/name`).once('value').then(snap => {
return snap.val()
})
}
So I would like to generate two lists, one containing the Staff members, the other one is containing lists of scenes, where scenes themselves are lists of Band names. I only could get plain HTML elements until I found out, that I should probably wait until the data is fetched from Firebase. As this is my first try with promises, the code is a bit messy, and probably is broken on several points.
If someone would have the time to help, I would extremely appreciate it!
Thanks

Autodesk Forge setNodeOff turns all nodes off

When I pass an array of dbIds to be turned off the viewer is turning every node off in my model.
Autodesk.Viewing.Viewer3D.prototype.turnOff = function(dbIds) {
var node;
$(dbIds)
.each(function(index, item) {
node = viewer.model.getData().instanceTree.nodeAccess.nodes[item];
viewer.impl.visibilityManager.setNodeOff(node, true);
});
}
If you pass the id of a parent, it will turn off all its children, which is probably what happens in your case. Turning nodes off definitely works fine, you can take a look at my demo at https://forge-rcdb.autodesk.io.
Select a row in the database view or a segment in the pie chart:
What you need to do is to get the leaf node ids, only leaf nodes are represented by geometry in the viewer.
Here is some ES6 code sample, extracted from there:
static getLeafNodes (model, dbIds) {
return new Promise((resolve, reject)=>{
try {
const instanceTree = model.getData().instanceTree
dbIds = dbIds || instanceTree.getRootId()
const dbIdArray = Array.isArray(dbIds) ? dbIds : [dbIds]
let leafIds = []
const getLeafNodesRec = (id) => {
var childCount = 0;
instanceTree.enumNodeChildren(id, (childId) => {
getLeafNodesRec(childId)
++childCount
})
if (childCount == 0) {
leafIds.push(id)
}
}
for (var i = 0; i < dbIdArray.length; ++i) {
getLeafNodesRec(dbIdArray[i])
}
return resolve(leafIds)
} catch(ex){
return reject(ex)
}
})
}
static async isolateFull (viewer, dbIds = [], model = null) {
try {
model = model || viewer.activeModel || viewer.model
viewer.isolate(dbIds)
const targetIds = Array.isArray(dbIds) ? dbIds : [dbIds]
const targetLeafIds = await ViewerToolkit.getLeafNodes(
model, targetIds)
const leafIds = await ViewerToolkit.getLeafNodes (model)
const leafTasks = leafIds.map((dbId) => {
return new Promise((resolveLeaf) => {
const show = !targetLeafIds.length ||
targetLeafIds.indexOf(dbId) > -1
viewer.impl.visibilityManager.setNodeOff(
dbId, !show)
resolveLeaf()
})
})
return Promise.all(leafTasks)
} catch (ex) {
return Promise.reject(ex)
}
}