I would like to expand on this question as I have a similar question:
I want to cy.get('#lead_name').type('foo') but it is covered by this element with opacity 0.9 while the form is loading:
<div class="blockUI blockOverlay" style="z-index: 1000; border: none; margin: 0px; padding: 0px; width: 665px; height: 100%; top: 0px; left: 0px; background-color: rgb(0, 0, 0); opacity: 0.9; cursor: wait; position: absolute;"></div>
When I start with the assertion
cy.get('#lead_name').should('be.visible)
it passes the assertion (maybe because of the opacity?) but when I then try to type in the field, I get the error message that the element is covered.
When I try to assert that the overlay is not there anymore and add
cy.get('.blockUI blockOverlay').should('not.exist')
Cypress also passes the assertion even though the element does exist and covers the other element and cy.get('#lead_name').type('foo') fails.
Is there any way to address this problem like
//This does not work it's just a sample to explain what I want to do
//test if the element I want to get is not covered
cy.get('#lead_name').should('not.be.covered')
//or test if the element is actionable
cy.get('#lead_name').should('be.actionable')
to make sure it waits until the form has loaded?
{edit} This is the error message I get from Cypress:
Timed out retrying after 4000ms: cy.type() failed because this element:
<input name="CrmLead[first_name]" id="CrmLead_first_name" type="text" maxlength="255">
is being covered by another element:
<div class="blockUI blockOverlay" style="z-index: 1000; border: none; margin: 0px; padding: 0px; width: 665px; height: 100%; top: 0px; left: 0px; background-color: rgb(0, 0, 0); opacity: 0.9; cursor: wait; position: absolute;"></div>
{edit 2} This is the code I use, condensed to the relevant parts:
it('should select new lead', () {
cy.visit(Cypress.env('lead_url'))
cy.get('#new_lead).click() //this opens the new form which takes some time to load
cy.get('#lead_name').type('foo')
cy.get('#lead_last_name').type('bar')
cy.get('.button').click()
}
{edit 3} The more I test possible solutions, the more I become convinced that the problem is not the overlay, but something in the Cypress code itself.
When I open the form manually it never takes more than a second for the loading spinner to disappear, usually just 0.1-0.2 seconds.
Yet when Cypress opens the form the form just doesn't load properly as the loading spinner stays there indefinitely.
The checking of the overlay should be done in two steps.
cy.get('.blockUI.blockOverlay')
.should('exist')
.then($overlay => $overlay.remove())
cy.get('.blockUI.blockOverlay').should('not.exist')
cy.get('#lead_name', {timeout: 10000})
.should(($el) => { // should will cause retry
return Cypress.dom.isFocusable($el) // instead of visible, more relevent to actionability
})
It's possible if you just do the second step, Cypress passes that command before the overlay is present.
It's the same principle as checjing a loading spinner, which has be tackled on SO before.
BTW you are selecting an element with two classed (according to the error message), so you need two . in the selector.
Perhaps that's the only change you need!
I added another check above that might help. Cypress.dom.isFocusable.
From the docs Is focusable
Cypress internally uses this method everywhere to figure out whether an element is hidden, mostly for actionability.
If you get really stuck, take a look at Gleb Bahmutov's walkthrough video here Debug the Element Visibility Problems in Cypress
One more idea - you can try removing the covering overlay in the test - added a line to the sample above.
You can use {force: true} with type(). This will igonre the overlapping of other element.
cy.get('#lead_name').type('foo', {force: true})
Or if you want to assert that the element has opacity 0.9 you can use:
cy.get('.blockUI blockOverlay')
.should('have.attr', 'style')
.and('include', 'opacity: 0.9')
Or, you can wait for the element to not have the opacity, in that case you can use:
cy.get('.blockUI blockOverlay', {timeout: 7000})
.should('have.attr', 'style')
.and('not.include', 'opacity: 0.9')
Related
I write in React. On the page I have a scroll up button.
When scrolling on some blocks, it is not visible due to the fact that the colors match.
How to make it so that when it touches certain blocks of the same color as the button, it turns white.
How to track the contact of a button with a specific block? What listeners to put?
Since the button is probably position: fixed or position: absolute, the easiest solution would be to give CSS property mix-blend-mode a try, instead of listeners, as there is no accurate way of telling the position w.r.t to background.
Check this
Try to give mix-blend-mode: difference; for the scroll to top button.
Although I think in this case you will have the color yellow for the button when it overlaps blue.
Although Sanjay's answer "works", you really don't have much control over the styles you get, unless that blend-mode:difference is the look you're going for.
For more fine grained control, you need to use the IntersectionObserver API.
There's really three steps to this process:
1. Get the required options for IntersectionObserver constructor
This means you need to find out the negative margins to set as the rootMargin key. This key is part of the configuration object of your IntersectionObserver instance.
In practice you would have to find the distance of your trigger element(in your case the floating action button) to the edges of the viewport. In the code snippet below, this is done using the getDistanceToEdge() function.
It's better to do it this way instead of statically setting your margins, since it dynamically infers the margins from the position set from your styles.
2. Tell the observer what you wish to when triggered
This is the callback which will trigger the new styles on your floating button. You need to check whether your elements are intersecting with the button using the entry.isIntersecting key, and then conditionally render the style.
3. Setup the observer with elements you wish to cause the trigger
The elements that scroll up causing your floating button to change color, should be passed to the observer, using the observer.observe() function.
The following code snippet really does all the setup you need. It won't trigger unless the element is actually underneath the button (the API internally checks vertical and horizontal overlaps).
For some reason I can't get it to work on iframes embedded in browsers. It could be something to do with the negative margins and multiple viewports.
It works perfectly on normal full page websites, as shown in this hosted link.
One little caveat is that consecutive elements that trigger the observer, will cause each other to cancel out, since the button enters the second area before fully leaving the first one. I'll leave it to someone else to figure out the specifics there.
// get the element that reacts to background elements
const fabElement = document.getElementById("fab");
// what do you want to do when it overlaps?
function reactToOverlap(entries) {
entries.forEach((entry) => {
if (entry.isIntersecting) fabElement.classList.add("triggered");
else fabElement.classList.remove("triggered");
});
}
const observerOptions = {
rootMargin: getDistanceToEdge(fabElement)
.map((val) => `-${val}px`)
.join(" "), // https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin for syntax
};
const observerInstance = new IntersectionObserver(
reactToOverlap,
observerOptions
);
// what elements do you want to trigger the change?
const elementsToTriggerFab = document.querySelectorAll(".triggeringContent");
elementsToTriggerFab.forEach((el) => observerInstance.observe(el));
/* Represents how far the element is from the edge of the browser, in all directions. */
function getDistanceToEdge(element) {
const elementBounds = element.getBoundingClientRect();
// top and left margins are identical to the pixel coordinates
const top = elementBounds.top;
const left = elementBounds.left;
// bottom and right need to be subtracted from the viewport bounds
const bottom = window.innerHeight - elementBounds.bottom;
const right = window.innerWidth - elementBounds.right;
return [top, right, bottom, left];
}
body {
font-size: 3rem;
background-color: white;
margin: 0;
padding: 0;
}
#fab {
background-color: blue;
color: white;
border-radius: 50%;
position: fixed;
padding: 1rem;
bottom: 50px;
right: 50px;
transition-property: background-color, color;
transition-duration: 0.5s
}
#fab.triggered {
background-color: white;
color: blue;
}
.normalContent {
color: green;
border: 10px solid green;
height: 100vh;
}
.triggeringContent {
height: 300px;
background-color: blue;
color: white;
}
.triggeringContent.avoidFab {
margin-right: 200px;
}
<div id="fab">Up</div>
<div class="normalContent">Other Content</div>
<div class="triggeringContent">Triggerring Content</div>
<div class="triggeringContent avoidFab">Triggerring Content</div>
<div class="normalContent">Problems coming up</div>
<div class="triggeringContent">Triggerring Content</div>
<div class="triggeringContent">Triggerring Content(Error)</div>
<div class="normalContent">Other Content</div>
<div class="triggeringContent avoidFab">Triggerring Content</div>
<script type="module" src="/main.js"></script>
Shoutout to cloned's comment for giving the idea for the solution.
This question was asked before, but the answer uses jQuery, here.
So, I am going to tweak the question to specifically ask for a native solution, to minimize dependencies.
Let's say hypothetically, you have a <div> and that <div> is in mid-transition of its opacity value and top value. How would I get the value of both of those properties mid-transition using native JavaScript?
It is very easy to port the jQuery script from the linked thread into its vanilla JavaScript equivalent and below is a sample. The output is printed on the right side (output#op element) once timer expires.
All that we are doing is the following:
Attach two event handlers to the element which triggers the transition (sometimes the triggering element can be different from the one that has animation). In the other thread, the element that is triggering the transition and the one that is being transitioned is the same. Here, I have put it on two different elements just for a different demo.
One event handler is for mouseover event and this creates a timer (using setTimeout) which gets the opacity and top value of the element that is being transitioned upon expiry of timer.
The other event handler is for mouseleave event to clear the timer when the user has hovered out before the specific point at which we need the opacity and top value to be obtained.
Getting the opacity and top value of the element that is being transitioned can be obtained by using the window.getComputedStyle method.
Unlike the demo in the other thread (which uses setInterval), here I have used setTimeout. The difference is that setInterval adds an interval and so the function is executed every x seconds whereas the function passed to setTimeout is executed only once after x seconds. You can use whichever fits your needs.
var wrap = document.querySelector('.wrapper'),
el = document.querySelector('.with-transition'),
op = document.querySelector('#op');
var tmr;
wrap.addEventListener('mouseenter', function() {
tmr = setTimeout(function() {
op.innerHTML = 'Opacity: ' + window.getComputedStyle(el).opacity +
', Top: ' + window.getComputedStyle(el).top;
}, 2500);
});
wrap.addEventListener('mouseleave', function() {
clearTimeout(tmr);
});
.wrapper {
position: relative;
height: 400px;
width: 400px;
background: yellowgreen;
}
.with-transition {
position: absolute;
top: 0px;
left: 100px;
width: 200px;
height: 100px;
background: yellow;
opacity: 0;
transition: all 5s linear;
}
.wrapper:hover .with-transition {
top: 300px;
opacity: 1;
}
output {
position: absolute;
top: 50px;
right: 50px;
}
<div class='wrapper'>
<div class='with-transition'></div>
</div>
<output id='op'></output>
The answer referenced in the duplicate question is easily modified to NOT use jquery. There is no black magic happening there.
The real question is why would you want to do this?
If You need control over a transition just impliment the partial transition with javascript, do what you need, then complete the transition.
I've two radio buttons with Drop down and I need to put the drop down
in parallel to the second radio button,when we add to the css code
the following its working but this is not a good solution since if I've
bigger page with other control this can override them either
#__box0 {
margin-top: 20px;
margin-left: 20px;
}
there is another option to do that with CSS?
http://jsbin.com/ziziqeyopu/edit?css,js,output
The Html is renders in the renderer method
This is SAPUI5
http://openui5.org/
code but for the question its not relevant since
renderer is related to pure html/css...
i've tried with the following which doesnt works.
.mylist-content>div:first-child {
margin-right:30px
margin-top:50px
}
.mylist-radiolist>DIV:last-child {
margin-left: 30px;
margin-top:100px;
}
If you still haven't figured it out, give this a try:
.mylist-content #__box0 {
position: relative;
top: 20px;
left: 20px;
}
What you see above should do the same thing as your first attempt, but not interfere with anything else on your page, by:
Adding extra application restrictions to the CSS rule, by having the .mylist-content scope restriction (even though this should not be necessary, in theory, because #__box0 is an ID and should be unique on the page).
Shifting the position of the dropdown without affecting any other elements - this is done with position: relative and the corresponding top and left offsets.
Without knowledge of SAP UI and/or your particular situation, I doubt someone will be able to give you a more appropriate answer.
I'm working on an ecommerce store product page and need to show an "in-stock" graphic and an "out-of-stock" graphic. The platform has some limitations but there's a setting to show an out of stock graphic but not an in stock one.
What I'd like to do is have the in stock graphic hardcoded into the page by default. Like this:
<div class="inStock"></div>
CSS below:
.inStock {
width: 143px;
height: 40px;
background: url('../product_images/uploaded_images/in-stock.jpg');
margin-left: 60%;
margin-top: 5%;
position: absolute;
}
When a product goes out of stock, the platform backend automatically adds a div that looks like this into the page:
<div class="CurrentlySoldOut">
<p>
<span lang="en">Sorry but this item is currently unavailable.</span>
</p>
</div>
When the class "CurrentlySoldOut" appears, which is generated from the platform automatically, I'd like to override the current hardcoded "in stock" graphic with the out of stock via the background atrribute. Something like this:
background: url('../product_images/uploaded_images/out-of-stock.jpg');.
In short, is there a way to override a CSS class based on the presence of another class. Sort of like "if "CurrentlySoldOut class is showing, then addClass to another div" (where I will control the graphic.)
If you only need this to happen when the page initially loads, you can just change the class if any element with matching selector .CurrentlySoldOut exists like this:
if ($('.CurrentlySoldOut').length > 0) {
$('.inStock').removeClass('inStock').addClass('outOfStock');
}
Then of course you need to add the style/image-url for the outOfStock class to your css.
If your page is being updated while the user is already viewing it, then it would be slightly more involved. You could listen for changes to the DOM, and then call the code above. Something like this works in Chrome and Firefox:
function updateInStockStatus() {
// same code as above
if ($('.CurrentlySoldOut').length > 0) {
$('.inStock').removeClass('inStock').addClass('outOfStock');
}
}
// listen for DOM updates, calling above function
$('body').bind('DOMSubtreeModified', function() {
updateInStockStatus();
});
Since you probably want to support IE though ;) you could use setInterval with the function above to check periodically that the status has changed:
window.setInterval(updateInStockStatus, 5000);
That would check every 5 seconds, since the delay is in ms.
Try this:
if($('.CurrentlySoldOut').length)//true when CurrentlySoldOut exists
$('.inStock').css("background", "url(../product_images/uploaded_images/out-of-stock.jpg");; //change bg image
You can add a class with the new background, since the class name seems to be updated already:
.inStock {
width: 143px;
height: 40px;
background: url('../product_images/uploaded_images/in-stock.jpg');
margin-left: 60%;
margin-top: 5%;
position: absolute;
}
.CurrentlySoldOut{
width: 143px;
height: 40px;
background: url('../product_images/uploaded_images/in-stock.jpg');
margin-left: 60%;
margin-top: 5%;
position: absolute;
}
Try this
if ($(".CurrentlySoldOut")[0])
$('.inStock').css("background", "url(../product_images/uploaded_images/out-of-stock.jpg");
In my backbone.js application, I'm trying to fade in the view element after it's been appended. However it doesn't work.
Live example here: http://metropolis.pagodabox.com
var itemRender = view.render().el;
$('#items-list').append(itemRender);
$(itemRender).addClass('show');
However if I add a small setTimeout function, it works.
var itemRender = view.render().el;
$('#items-list').append(itemRender);
setTimeout(function(){
$(itemRender).addClass('show');
},10);
Using fadeIn() also works but I prefer to use straight CSS for the transition as it's more efficient, and prefer not to use any setTimeout "hacks" to force it to work. Is there a callback I can use for append? Or any suggestions? The full code is below:
itemRender: function (item) {
var view = new app.ItemView({ model: item }),
itemName = item.get('name'),
itemRender = view.render().el;
$('#items-list').append(itemRender);
$(itemRender).addClass('show');
app.itemExists(itemName);
}
CSS/LESS:
#items-list li {
padding: 0 10px;
margin: 0 10px 10px;
border: 1px solid #black;
.border-radius(10px);
position: relative;
.opacity(0);
.transition(opacity)
}
#items-list li.show {.opacity(1)}
This "hack" you mention (or some variant of it) is occasionally necessary for web development, simply due to the nature of how browsers render pages.
(NOTE: This is all from memory, so while the overall idea is right please take any details with a small grain of salt.)
Let's say you do the following:
$('#someElement').css('backgroundColor', 'red');
$('#someElement').css('backgroundColor', 'blue');
You might expect to see the background color of #someElement flash red for a brief moment, then turn blue right? However, that won't happen, because browsers try to optimize rendering performance by only rendering the final state at the end of the JS execution. As a result, the red background will never even appear on the page; all you'll ever see is the blue.
Similarly here, the difference between:
append
set class
and:
append
wait 1ms for the JS execution to finish
set class
Is that the latter allows the element to enter the page and AFTER the JS is executed have its style change, while the former just applies the style change before the element gets shown.
So while in general window.setTimeout should be avoided, when you need to deal with these ... complications of browser rendeering, it's really the only way to go. Personally I like using the Underscore library's defer function:
var itemRender = view.render().el;
$('#items-list').append(itemRender);
_(function(){
$(itemRender).addClass('show');
}).defer();
It's the same darn thing, but because it's encapsulated in a library function it feels less dirty to me :-) (and if the "post-render" logic is more than a line or two I can factor it in to a Backbone View method and do _(this.postRender).defer() inside my render method).
You can use CSS animations
#keyframes show {
0% { opacity: 0; }
100% { opacity: 1; }
}
#items-list li {
padding: 0 10px;
margin: 0 10px 10px;
border: 1px solid #black;
.border-radius(10px);
position: relative;
}
#items-list li.show {
animation: show 1s;
}