I am working on a polymer2 shadow dom template project need to select children elements from parent elements. I found this article introduces a way to select child shadow dom elements that like this:
// No fun.
document.querySelector('x-tabs').shadowRoot
.querySelector('x-panel').shadowRoot
.querySelector('#foo');
// Fun.
document.querySelector('x-tabs::shadow x-panel::shadow #foo');
However, when I tried in my polymer2 project, like this:
//First: works!!
document.querySelector('container')
.shadowRoot.querySelector('app-grid')
.shadowRoot.querySelector('#apps');
//Second: Doesn't work!// got null
document.querySelector('container::shadow app-grid::shadow #apps')
// Thrird: document.querySelector('* /deep/ #apps') // Doesn't work, got null
I really need the second way or the third, which to put selectors in (), but both couldn't work. Does anyone know why the second one doesn't work? Thank you so much!
::shadow and /deep/ has never(?) worked in Firefox, and is depraved in Chrome 63 and later.
Source
Eric Biedelman has written a nice querySelector method for finding all custom elements on a page using shadow DOM. I wouldn't use it myself, but I have implemented it so I can "querySelect" custom elements in the console. Here is his modified code:
// EXAMPLES
// findCustomElement('app-grid') // Returns app-grid element
// findCustomElements('dom-if') // Returns an array of dom-if elements (if there are several ones)
// findCustomElement('app-grid').props // Returns properties of the app-grid element
function findCustomElement(customElementName) {
const allCustomElements = [];
customElementName = (customElementName) ? customElementName.toLowerCase() : customElementName;
function isCustomElement(el) {
const isAttr = el.getAttribute('is');
// Check for <super-button> and <button is="super-button">.
return el.localName.includes('-') || isAttr && isAttr.includes('-');
}
function findAllCustomElements(nodes) {
for (let i = 0, el; el = nodes[i]; ++i) {
if (isCustomElement(el)) {
el.props = el.__data__ || el.__data || "Doesn't have any properties";
if (customElementName && customElementName === el.tagName.toLowerCase()) {
allCustomElements.push(el);
} else if (!customElementName) {
allCustomElements.push(el);
}
}
// If the element has shadow DOM, dig deeper.
if (el.shadowRoot) {
findAllCustomElements(el.shadowRoot.querySelectorAll('*'));
}
}
}
findAllCustomElements(document.querySelectorAll('*'));
if (allCustomElements.length < 2) {
return allCustomElements[0] || customElementName + " not found";
} else if (customElementName) {
allCustomElements.props = "Several elements found of type " + customElementName;
}
return allCustomElements;
}
Remove the if (isCustomElement(el)) { statement, and you can querySelect whatever element and get an array of it if several of them exists. You can change findAllCustomElements to implement a smarter querySelect using the recursive loop on shadowDoom as base. Again, I wouldn't use this myself – and instead pass on variables from parent element(s) to children where the children have observers that activates specific behaviors – but I wanted to give you a general implementation of a fallback if nothing else works.
The problem with your question is that you don't give any specifics about WHY you want to select the children in the first place.
Related
I'm loading a radio-button-group in polymer with a set of 4 radio buttons as answer options to a question. I have 4 questions. Now if I select an option in the first question and select next(go to the next question), the second question has its option already selected(same as the first question). Why is this happening?
<paper-radio-group>
<template is="dom-repeat" items=[[_calculateQuestionOptions(index)]]>
<paper-radio-button name="{{_calculateId(index)}}">
[[item.questionOption]]
</paper-radio-button><br>
</template>
</paper-radio-group>
CODE for next and prev operations
next() {
if (this.index < this.repos.length-1) {
this.index +=1;
}
}
prev() {
if (this.index>0) {
this.index -= 1;
}
}
To solve this issue. I wanted to add all the answers to an array and clear the selection as soon as the next button is click. Somthing like this.
next() {
var questionId = this.repos[this.index].questionId;
if (Polymer.dom(this.root).querySelector("paper-radio-group")) {
this.answers[questionId] = Polymer.dom(this.root).querySelector("paper-radio-group").selected;
Polymer.dom(this.root).querySelector("paper-radio-group").selected = null;
}
if (this.index < this.repos.length-1) {
this.index +=1;
}
questionId = this.repos[this.index].questionId;
if (this.answers[questionId] !== null) {
Polymer.dom(this.root).querySelector("paper-radio-group").selected = this.answers[questionId];
}
}
Is this the only way or is there a better way to do this.
Why is this happening?
It happens because dom-repeat reuse old elements when it update (for better performance, re-create DOM is slow). Its means only bound data in that scope will be updated.
Is this the only way or is there a better way to do this.
I think your way is already fine but you still can reduce some code by using advantage of Polymer like:
Static node map here
Complex observers here
My example see here
Thanks.
I need some tree structure in Aurelia. I got some link for that. It's working fine. But My requirement is like accordion with tree view. Means when I clicked on closed parent all opened parents should close and clicked one should open same as bootstrap accordion. same thing should happen When ever I clicked on child parent element with in parent repeat.
Below is my image for tree structure.
Gist run Link: Gist
The above gist is just tree structure with open and collapse. From that when I clicked on closed tree node , that should open and remaining tree nodes should be closed.
In the above gist "node-model.js" is having events for open and close. So when ever I clicked on icon the clicked event inside this variable will get only clicked node. How can I get other node in that method to hide.
Answer:
Inside your tree-view.js, add the following code (3 methods):
attached() {
window.addEventListener('goCollapseAll', (e) => {
this.closeOtherBranches(e.detail);
}, false);
}
closeOtherBranches(exceptNode) {
// traverse node tree to find current one
var found = null;
for(var i = 0; i < this.nodes.length; i++){
if (this.subSearch(this.nodes[i], exceptNode)) {
found = i;
}
}
if (found !== null) {
for(var i = 0; i < this.nodes.length; i++){
if ((i != found) && (this.nodes[i].expanded)) {
this.nodes[i].toggleNode();
}
}
}
}
subSearch(node, findNode) {
// recursive search of tree for findNode
var match = null;
if (node === findNode) {
match = node;
} else {
for(var i = 0; i < node.children.length; i++){
if (node.children[i] === findNode) {
match = node;
} else {
match = this.subSearch(node.children[i], findNode);
}
}
}
return match;
}
Then, inside your node-model.js, add the following lines at the beginning of toggleNode():
// close other node branches
if (!this.expanded) {
var event = new CustomEvent('goCollapseAll', { 'detail': this });
window.dispatchEvent(event);
}
Explanation:
When a node is expanded, it publishes a custom event to trigger the recursive search to close all nodes that are part of a different branch. It's not the prettiest solution and I think there might be a cleaner way if you adopt a different structure for the tree, but this solution definitely works well and accomplishes your purpose.
GistRun:
I've updated your GistRun to demonstrate the functionality. You can see it working here:
https://gist.run/?id=828c3c79bff0dfbaffec3252ed376c8c
Hi I'm running into a little problem with DomCrawler. I'm scraping a page and it has a div with a class of .icon3d. I want to go through the page and for every div with that class I will add an "3D" item to an array, and every div without it I will add a "2D" item. Here is the code I have so far.
for ($i=0; $i < 10; $i++) {
$divs = $crawler->filter('div.icon3d');
if(count($divs)){
$type[] = '3D';
}else{
$type[] = '2D';
}
}
Check The DomCrawler Component documentation first. filter method returns filtered list of nodes, so by calling ->filter('div.icon3d') returned value will be list of all div elements which have icon3d class.
First you need to find all div elements, loop through them and add either 3D or 2D the to array depending on icon3d css class existance.
$divs = $crawler->filter('div');
foreach ($divs as $node) {
$type[] = (false !== strpos($node->getAttribute('class'), 'icon3d')) ? '3D' : '2D';
}
UPDATE
$crawler->filter('a')->each(function(Crawler $a) {
$div = $a->filter('div');
// Div exists
if ($div->count()) {
}
});
To get crawler node class use
$div->getNode(0)->getAttribute('class')
I figured it out. Here's the code I used. I'm sure there is a cleaner way.
$divs = $crawler->filter('#schedule li a')->each(function($node){
if ($node->children()->last()->attr('class') == 'icon3d') {
return '3D';
}else{
return '2D';
}
});
I have written a custom component to create a html button.
custom component is defined as follows
dojo.provide("ovn.form.OvnButton") ;
require([ "dojo/_base/declare",
"dojo/dom-construct",
"dojo/parser",
"dojo/ready",
"dijit/_WidgetBase"],
function (declare, domConstruct, parser, ready, _WidgetBase){
return declare ("ovn.form.OvnButton",[_WidgetBase],{
label: "unknown",
constructor : function(args){
this.id = args.id;
args.props.forEach(function(prop) {
if(prop.name == 'label'){
this.label = prop.value;
alert("found label " + this.label);
}
});
alert("from constructor " + this.label);
},
postMixInProperties : function(){
},
buildRendering : function(){
alert("from renderer label is " + this.label);
this.domNode = domConstruct.create("button", { innerHTML: this.label }); //domConstruct.toDom('<button>' + this.label + '</button>');
},
_getLabelAttr: function(){
return this.label;
},
_setLabelAttr : function(label){
alert("from set input is " + label)
this.label = label;
},
postCreate : function(){
alert("Post create label is " + this.label);
},
startUP : function(){
}
});
});
This is how I am instantiating the component
var button = new ovn.form.OvnButton({
id:'run',
props:[{"name":"label","value":"Run"},{"name":"class","value":"btn"}]
});
In the constructor of the custom component, I am iterating through the array passed and assigning to the instance variable called 'label'. To my surprise when we print the instance variable in buildRendering function, it is still printing the default instead of the assigned value.
can somebody give some light on why this is so.
FYI:
I am getting the following sequence of messages on the console
1.found label Run
2. from constructor unknown
3. from renderer label is unknown
4. from set input is unknown
5. Post create label is unknown
This happens because inside the little forEach function, this actually points to something completely different than your OvnButton object.
Javascript's this keyword is quite strange in this regard (it doesn't have anything to do with Dojo, actually). You can read more about how it works here: http://howtonode.org/what-is-this . It's a quite fundamental concept of Javascript, different from other languages, so it's well worth your time to get familiar with.
But there are various different ways you can quickly solve it, so here are a few!
Use a regular for loop instead of forEach and callback
The easiest is probably to use a regular for loop instead of forEach with a callback.
....
// args.props.forEach(function(prop) {
for(var i = 0, l = args.props.length; i < l; i++) {
var prop = args.props[i];
if(prop.name == 'label'){
this.label = prop.value;
alert("found label " + this.label);
}
}//); <-- no longer need the closing parenthesis
The takeaway here is that Javascript's this magic only happens for function calls, so in this case, when we just use a for loop, this continues to point to the right thing.
... or use forEach's second thisArg argument
But perhaps you really want to use forEach. It actually has a second argument, often called thisArg. It tells forEach to make sure this points to something of your choice inside the callback function. So you would do something like this:
....
args.props.forEach(function(prop) {
if(prop.name == 'label'){
this.label = prop.value;
alert("found label " + this.label);
}
}, this); // <--- notice we give forEach two arguments now,
// the callback function _and_ a "thisArg" value
I'm not completely sure the above works in all browsers though, so here's another way to solve your issue:
... or use a temporary "self" variable
We will make a temporary variable equal to this. People often call such a variable self, but you can name it anything you want. This is important: it's only the this keyword that Javascript will treat differently inside the callback function:
....
var self = this; //<--- we basically give `this` an alternative
// name to use inside the callback.
args.props.forEach(function(prop) {
if(prop.name == 'label'){
self.label = prop.value; //<--- replaced `this` with `self`
alert("found label " + self.label); //<--- here as well
}
});
... or use hitch() from dojo/_base/lang
Some people don't like the self solution, perhaps because they like to consistently use this to refer to the owning object. Many frameworks therefore have a "bind" function, that makes sure a function is always called in a particular scope. In dojo's case, the function is called hitch. Here's how you can use it:
require([....., "dojo/_base/lang"], function(....., DojoLang) {
....
args.props.forEach(DojoLang.hitch(this, function(prop) {
if(prop.name == 'label'){
this.label = prop.value;
alert("found label " + this.label);
}
}));
... or use Javascript's own bind()
Dojo and pretty much every other framework out there has a hitch() function. Because it's such a commonly used concept in Javascript, the new Javascript standard actually introduces it's own variant, Function.prototype.bind(). You can use it like this:
....
args.props.forEach(function(prop) {
if(prop.name == 'label'){
this.label = prop.value;
alert("found label " + this.label);
}
}.bind(this));
That was a very long answer for a pretty small thing, I hope it makes some sense!
I currently have a large number of circumstances where I need to verify that a page (along with all of its elements) are displaying correctly. The isDisplayed() method of WebElement appears to be a logical way to do this, however I would like to understand precisely what this method is doing to determine whether or not an element "is displayed". The javadoc does not shed any light on the inner workings of the method and other information on the web appears to be sparse at best.
If anyone could provide a detailed description of how this method works, I would be very grateful.
I would trust in Selenium to work out if an element is displayed or not. If it doesn't work you can raise a bug and/or fix any issues you see and provide a patch.
This is what the method does (Taken from the current Selenium source code):
/**
* Determines whether an element is what a user would call "shown". This means
* that the element is shown in the viewport of the browser, and only has
* height and width greater than 0px, and that its visibility is not "hidden"
* and its display property is not "none".
* Options and Optgroup elements are treated as special cases: they are
* considered shown iff they have a enclosing select element that is shown.
*
* #param {!Element} elem The element to consider.
* #param {boolean=} opt_ignoreOpacity Whether to ignore the element's opacity
* when determining whether it is shown; defaults to false.
* #return {boolean} Whether or not the element is visible.
*/
bot.dom.isShown = function(elem, opt_ignoreOpacity) {
if (!bot.dom.isElement(elem)) {
throw new Error('Argument to isShown must be of type Element');
}
// Option or optgroup is shown iff enclosing select is shown (ignoring the
// select's opacity).
if (bot.dom.isElement(elem, goog.dom.TagName.OPTION) ||
bot.dom.isElement(elem, goog.dom.TagName.OPTGROUP)) {
var select = /**#type {Element}*/ (goog.dom.getAncestor(elem, function(e) {
return bot.dom.isElement(e, goog.dom.TagName.SELECT);
}));
return !!select && bot.dom.isShown(select, /*ignoreOpacity=*/true);
}
// Image map elements are shown if image that uses it is shown, and
// the area of the element is positive.
var imageMap = bot.dom.maybeFindImageMap_(elem);
if (imageMap) {
return !!imageMap.image &&
imageMap.rect.width > 0 && imageMap.rect.height > 0 &&
bot.dom.isShown(imageMap.image, opt_ignoreOpacity);
}
// Any hidden input is not shown.
if (bot.dom.isElement(elem, goog.dom.TagName.INPUT) &&
elem.type.toLowerCase() == 'hidden') {
return false;
}
// Any NOSCRIPT element is not shown.
if (bot.dom.isElement(elem, goog.dom.TagName.NOSCRIPT)) {
return false;
}
// Any element with hidden visibility is not shown.
if (bot.dom.getEffectiveStyle(elem, 'visibility') == 'hidden') {
return false;
}
// Any element with a display style equal to 'none' or that has an ancestor
// with display style equal to 'none' is not shown.
function displayed(e) {
if (bot.dom.getEffectiveStyle(e, 'display') == 'none') {
return false;
}
var parent = bot.dom.getParentElement(e);
return !parent || displayed(parent);
}
if (!displayed(elem)) {
return false;
}
// Any transparent element is not shown.
if (!opt_ignoreOpacity && bot.dom.getOpacity(elem) == 0) {
return false;
}
// Any element with the hidden attribute or has an ancestor with the hidden
// attribute is not shown
function isHidden(e) {
//IE does not support hidden attribute yet
if (goog.userAgent.IE) {
return true;
}
if (e.hasAttribute) {
if (e.hasAttribute('hidden')){
return false;
}
} else {
return true;
}
var parent = bot.dom.getParentElement(e);
return !parent || isHidden(parent);
}
if (!isHidden(elem)) {
return false;
}
// Any element without positive size dimensions is not shown.
function positiveSize(e) {
var rect = bot.dom.getClientRect(e);
if (rect.height > 0 && rect.width > 0) {
return true;
}
// A vertical or horizontal SVG Path element will report zero width or
// height but is "shown" if it has a positive stroke-width.
if (bot.dom.isElement(e, 'PATH') && (rect.height > 0 || rect.width > 0)) {
var strokeWidth = bot.dom.getEffectiveStyle(e, 'stroke-width');
return !!strokeWidth && (parseInt(strokeWidth, 10) > 0);
}
// Zero-sized elements should still be considered to have positive size
// if they have a child element or text node with positive size, unless
// the element has an 'overflow' style of 'hidden'.
return bot.dom.getEffectiveStyle(e, 'overflow') != 'hidden' &&
goog.array.some(e.childNodes, function(n) {
return n.nodeType == goog.dom.NodeType.TEXT ||
(bot.dom.isElement(n) && positiveSize(n));
});
}
if (!positiveSize(elem)) {
return false;
}
// Elements that are hidden by overflow are not shown.
if (bot.dom.getOverflowState(elem) == bot.dom.OverflowState.HIDDEN) {
return false;
}
Not sure it really needs any more explanation, the comments are quite clear. Let me know if you want any more info added.
WebDriver has its own W3C specification.
The section about determining visibility is what you are after.
I would warn that saying something "is displayed" is such a broad term, and thus there are many scenarios to it. Therefore, there may well be situations that WebDriver does not account for.
So it's important, vital in fact, to remember that something being "displayed" or "visible" has many meanings. (In the same way a page being fully loaded, also has many meanings.)
Also remember Selenium is entirely open source. There is nothing stopping you from getting a fresh checkout of the repository and inspecting it locally.
As per the documentation isDisplayed() method determines whether the WebElement is displayed or not and returns a boolean whether or not the element is displayed or not. This method avoids the problem of having to parse an element's style attribute.
This implementation is inline with the specification with in the WebDriver Level 2 W3C Working Draft which mentions:
Although WebDriver does not define a primitive to ascertain the
visibility of an
element in the
viewport,
we acknowledge that it is an important feature for many users. Here we
include a recommended approach which will give a simplified
approximation of an element’s visibility, but please note that it
relies only on tree-traversal, and only covers a subset of visibility
checks.
The visibility of an element is guided by what is perceptually visible
to the human eye. In this context, an element’s displayedness does not
relate to the
visibility or
display style
properties.
The approach recommended to implementors to ascertain an element’s
visibility was originally developed by the Selenium project, and is
based on crude approximations about an element's nature and
relationship in the tree. An element is in general to be considered
visible if any part of it is drawn on the canvas within the boundaries
of the viewport.
The element displayed algorithm is a boolean state where true
signifies that the element is displayed and false signifies that the
element is not displayed. To compute the state on element, invoke the
Call(bot.dom.isShown, null, element). If doing so does not produce an
error, return the return value from this function call. Otherwise
return an error with error code unknown error.
This function is typically exposed to GET requests with a URI Template of:
/session/{session id}/element/{element id}/displayed