Event for opened fires too soon in <paper-dialog> - polymer

I'm using <paper-dialog> to display an SVG image I'll construct programatically. I need to know the size of the rendered container before I begin. I am waiting for the opened property to change to true however that is apparently too soon as .clientWidth is 0 when it fires however later .clientWidth does provide the correct value.
<paper-dialog class="dialog" opened={{modalOpen}} modal>
<svg width="100%", height="100%" />
</paper-dialog>
How can I wait for the SVG clientWidth and clientHeight to be computed?

Sometimes JS events are fired before the DOM has time to finish processing, which appears to be the case here.
To get around this (or at least to provide additional clues on what's happening), try putting your code (for the clientWidth property) within a setTimeout() function and give it a zero (0) millisecond timeout. This will simply move your code to the end of the JS execution stack, which should process only after the DOM has finished updating... and subsequently be when the DOM element's size and positioning properties are available.
Here's an example...
modalOpen() {
setTimeout( () => {
/* Your code here... for example... */
const paperDialog = document.getElementsByTagName("paper-dialog")[0];
console.log("paperDialog width = ", paperDialog.clientWidth);
}, 0); /* 0 milliseconds = Execute immediately after everything else processes. */
}
WARNING: Using setTimeout() is typically frowned upon since it doesn't fully adhere to asynchronous development and can produce unexpected results (such as firing too soon or not firing as soon as possible). While this approach is quick and can work fine some of the times, triggering and handling events are generally the best way to solve these timing issues.

Polymer's <paper-dialog> implements iron-resizable-behavior so we can listen for the iron-resize event:
disconnectedCallback() {
super.disconnectedCallback();
this.removeEventListener('iron-resize', this.onIronResize);
}

Related

puppeteer element.click() not working and not throwing an error

I have a situation where a button, on a form, that is animated into view, if the element.click() happens while the animation is in progress, it doesn't work.
element.click() doesn't throw an error, doesn't return a failed status (it returns undefined) it just silently doesn't work.
I have tried ensuring the element being clicked is not disabled, and is displayed (visible) but even though both those tests succeed, the click fails.
If I wait 0.4s before clicking, it works because the animation has finished.
I don't want to have to add delays (which are unreliable, and a bodge to be frank), if I can detect when a click worked, and if not automatically retry.
Is there a generic way to detect if a click() has actually been actioned so I can use a retry loop until it does?
I have determined what is happening, and why I don't get an error, and how to work around the issue.
The main issue is with the way element.click() works. Using DEBUG="puppeteer:*" I was able to see what is going on internally. What element.click() actually does is:-
const box = element.boundingBox();
const x = box.x + (box.width/2);
const y = box.y + (box.height/2);
page.mouse.move(x,y);
page.mouse.down();
sleep(delay);
page.mouse.up();
The problem is that because the view (div) is animating the element's boundingBox() is changing, and between the time of asking for the box position, and completing the click() the element has moved or is not clickable.
An error isn't thrown (promise rejected) because its just a mouse click on a point in the viewport, and not linked to any element. The mouse event is sent, just that nothing responds to it.
One workaround is to add a sufficient delay to allow the animation to finish. Another is to disable animations during tests.
The solution for me was to wait for the position of the element to settle at its destination position, that is I spin on querying the boundingBox() and wait for the x,y to report the elements previously determined position.
In my case, this is as simple as adding at 10,10 to my test script just before the click, or specifically
test-id "form1.button3" at 10,10 click
And in action it works as follows, in this case, the view is being animated back in from the left.
00.571 [selector.test,61] at 8,410
test-id "main.add" info tag button displayed at -84,410 size 116,33 enabled not selected check "Add"
test-id "main.add" info tag button displayed at -11,410 size 116,33 enabled not selected check "Add"
test-id "main.add" info tag button displayed at 8,410 size 116,33 enabled not selected check "Add"
00.947 [selector.test,61] click
It wouldn't work for an element that was continually moving or for an element that is covered by something else. For those cases, try page.evaluate(el => el.click(), element).
Generic click with timeout function inspired by Andrea's answer. This one returns as soon as the element is clickable, so won't slow down tests.
click: async function (page, selector, timeout = 30000) {
await page.waitForSelector(selector, { visible: true, timeout })
let error;
while (timeout > 0) {
try {
await page.click(selector);
return;
} catch (e) {
await page.waitFor(100);
timeout -= 100;
error = e;
}
}
throw error;
}
The page.click() returns a promise, make sure to handle it as such, but also note that you may have issues if you are not referencing it as an xpath. That's what I had to do in order to get it working. I've tried using querySelectors and interacting with the objects that way, but I ran into issues.
page.evaluate(()=>{
await Promise.all([
page.click("a[id=tab-default-2__item]"),
//The page.waitFor is set to 15000 for my personal use.
//Feel free to play around with this.
page.waitFor(15000)
]);
});
I hope this helps.
i use a helper function to handle click
click: async function (page, selector) {
//selector must to exists
await page.waitForSelector(selector, {visible: true, timeout: 30000})
//give time to extra rendering time
await page.waitFor(500)
try {
await page.click(selector)
} catch (error) {
console.log("error clicking " + selector + " : " + error ;
}
}
using page.waitFor(500) is a VERY BAD PRACTICE in a THEORICAL WORLD, but it remove a lot of false positive in the practical with complex interfaces.
i prefer to wait 500ms more than obtain a false positive.
I've just had a very similar problem: I had Puppeteer script that used to work and now suddenly click stopped working. The culprit turned out to be zoom level. It started working again once I switched zoom to 100%. Apparently, Puppeteer does not adjust click coordinates to zoom level.
For me below code did the trick
const element = await page.$('[name="submit"]')
await this.page.evaluate(ele => ele.click(), element);

Stub Element Is Not Effective In The Ready Function

With Polymer 1.* and WCT, when testing my element <sp-veteran></sp-veteran> I am not able to stub out the methods ._getSpComboBox() and ._getItems() in the ready function. I get Error thrown outside of test function: this._getSpComboBox(...)._getItems is not a function.
Since it is in the ready function, I need to use the WCT api stub instead of sinon.stub since the later requires me to grab the element which I can not do before fixture().
Any suggestions?
original code:
_getSpComboBox: function() {
return Polymer.dom(this.$.veteran.root).querySelector('sp-combo-box');
},
ready: function() {
if (this.editMode) {
this._getSpComboBox()._getItems();
}
this.$.veteranNoAjax.read();
this._setStyle();
}
test:
<test-fixture id="sp-veteran">
<template>
<h2>edit veteran in edit mode</h2>
<sp-app>
<sp-toast></sp-toast>
<sp-veteran edit-mode></sp-veteran>
</sp-app>
</template>
</test-fixture>
before(() => {
replace('sp-app').with('fake-sp-app');
stub('sp-ajax', {read: ()=> entitiesMock});
const _getItems = ()=> entitiesMock;
stub('sp-veteran', {_getSpComboBox: ()=> _getItems});
Unfortunately testing ready in Polymer1 is kind of a pain, or at least I haven't found an easy way that doesn't have odd side-effects. Calling the ready method after you've attached your stubs/spies is always an option but as I mentioned it can cause some odd issues. This was alleviated in Polymer2 as ready is called by the first call of connectedCallback for your element, so you can create the element then bind your spies and manually add to trigger it, just don't forget to remove after.
In the case of DOM manipulation in a Polymer element, you should be using the attached lifecycle instead, this will solve your issue as I mentioned above for testing, but it also saves you a weird potential usage case in the future. Since ready only runs once for an instance of an element, any logic in your ready statement won't get re-run if that element is re-used later, instead if you put the logic in your attached lifecycle if that element is removed from the DOM then added again later in another location it will rerun it's logic to fetch it's new children.

How to use Polymer lifecycle hooks

I have a component that contains video. My component is nested in a dom-if and can disappear. When this happens the video (and it sound) keep playing.
Is there a way in which my component can detect that is has disappeared from the DOM? I have tried to use the 'detached' callback as described here: https://www.polymer-project.org/1.0/docs/devguide/registering-elements
Polymer({
is: 'my-component-with-video',
properties: {
// some properties
},
detached: function() {
console.log('Component detached');
// more code to stop video
},
but when my element is removed by the dom-if nothing happens, I don't see the console.log message. What am I doing wrong?
There are two scenarios possible here:
You want your element to be discarded and recreated fresh when the condition changes.
You want to keep it in the dom but freeze it.
In the first case you need to add the restamp attribute to the dom-if to make sure the template DOM is destroyed, not hidden. By default the dom-if stamps the template at first initialization and then hides it from the view if the condition becomes falsy.
In the second case, the suggestion given by Intervalia will not work, because the dom-if in "hide" mode does not detach anything from the DOM. Setting restamp attribute will make the detached callback run but then no point in pausing anything since the element will be discarded.
If you want to keep it in the DOM and freeze it's state you need to listen to dom-change event on the dom-if and run the .pause() accordingly.
No need for any workaround other than simply using your dom-if and rather than
<dom-if if="[[condietionBoolean]]">
<your-video-element id="giveanId"></your-video-element>
</dom-if>
write the if statement like below and so each time your condition changes, you check and make sure the video is paused if when you like. See below.
...
<dom-if if="[[_shouldShowVideo(conditionBoolean)]]">
<your-video-element id="giveanId"></your-video-element>
</dom-if>
...
Polymer({
is: 'my-component-with-video',
properties: {
conditionBoolean : {
type: Boolean,
},
},
_shouldShowVideo: function(conditionBoolean ) {
if (!conditionBoolean) this.$$(#yourVideoElementId).pause();
return conditionBoolean ;
}
});
In your detached function you need to get the Video Element and call .pause() on it.
You will probably also need to call .pause() when your condition changes that would cause the dom-if to remove the player.

AngularJS: How to block execution until function returns?

I'm trying to display a google map on click with angular JS. But my function seems to be toggling the visibility based on a function call (via ng-show). This means that my trigger of the 'resize' event executes before the map div is actually visible so it doesn't work correctly.
$scope.mapVisible = false;
$scope.toggleMap = function() {
$scope.myMap.panTo($scope.myMarkers[0].getPosition());
$scope.mapVisible = !$scope.mapVisible;
// this executes too soon. How to block until the div is really visible?
google.maps.event.trigger($scope.myMap, 'resize');
}
<div ng-show="mapVisible">
<!-- map here -->
</div>
So how can I block triggering the 'resize' event on my map until the div is truly visible?
$scope.$watch("mapVisible", function (val) {
if (val) {
google.maps.event.trigger($scope.myMap, "resize");
}
});
This would make sure the map is visible before triggering the event.
Angularjs handles two-way binding by doing dirty-checking. It basically means that the value being watched is compared to the one in previous cycle. Each cycle ($digest) starts to run when something happens which could possibly change any value in the scope. If there is a change in the value, it would be reflected after the cycle has completed.
Back to your problem, when you toggle the mapVisible property, the view does not update immediately. It waits for the cycle to end before redrawing the view. But you fire the resize event so early, when the map is still invisible, therefore rendering invalid.
$watch does indeed watch the property and the changes will be reflected in the next cycle of the change, which means the view would be updated by the time watcher function has been invoked. Putting the resize function here would hence solve your issue.
I figured the easiest case is to use the $timeout service:
scope.$on "map:ui:shown", (event, args)->
$timeout ->
#use a delay because most of the time, the resizing should occur immediately after an angular cycle
#like when an ng-show has been set to the True condition
googleMaps.event.trigger(map, 'resize')
map.fitBounds scope.bounds if scope.bounds

mootools - using addEvent to element not working properly?

bangin' my head against this and it's starting to hurt.
I'm having trouble with adding an event to an element.
I'm able to add the event, and then call it immediately with element.fireEvent('click'), but once the element is attached to the DOM, it does not react to the click.
example code:
var el = new Element('strong').setStyle('cursor','pointer');
el.addEvent('click',function () { alert('hi!'); });
el.replaces(old_element); // you can assume old_element exists
el.fireEvent('click'); // alert fires
however, once I attach this to the DOM, the element is not reactive to the click. styles stick (cursor is pointer when I mouseover), but no event fires. tried mouseover as well, to no avail.
any clues here? am I missing something basic? I am doing this all over the place, but in this one instance it doesn't work.
EDIT----------------
ok here's some more code. unfortunately I can't expose the real code, as it's for a project that is still under tight wraps.
basically, the nodes all get picked up as "replaceable", then the json found in the rel="" attribute sets the stage for what it should be replaced by. In this particular instance, the replaced element is a user name that should pop up some info when clicked.
again, if I fire the event directly after attaching it, all is good, but the element does not react to the click once it's attached.
HTML-----------
<p>Example: <span class='_mootpl_' rel="{'text':'foo','tag':'strong','event':'click','action':'MyAction','params':{'var1': 'val1','var2': 'val2'}}"></span></p>
JAVASCRIPT-----
assumptions:
1. below two functions are part of a larger class
2. ROOTELEMENT is set at initialize()
3. MyAction is defined before any parsing takes place (and is properly handled on the .fireEvent() test)
parseTemplate: function() {
this.ROOTELEMENT.getElements('span._mootpl_').each(function(el) {
var _c = JSON.decode(el.get('rel'));
var new_el = this.get_replace_element(_c); // sets up the base element
if (_c.hasOwnProperty('event')) {
new_el = this.attach_event(new_el, _c);
}
});
},
attach_event: function(el, _c) {
el.store(_c.event+'-action',_c.action);
el.store('params',_c.params);
el.addEvent(_c.event, function() {
eval(this.retrieve('click-action') + '(this);');
}).setStyle('cursor','pointer');
return el;
},
Works just fine. Test case: http://jsfiddle.net/2GX66/
debugging this is not easy when you lack content / DOM.
first - do you use event delegation or have event handlers on a parent / the parent element that do event.stop()?
if so, replace with event.preventDefault()
second thing to do. do not replace an element but put it somewhere else in the DOM - like document.body's first node and see if it works there.
if it does work elsewhere, see #1
though I realsie you said 'example code', you should write this as:
new Element('strong', {
styles: {
cursor: "pointer"
},
events: {
click: function(event) {
console.log("hi");
}
}
}).replaces(old_element);
no point in doing 3 separate statements and saving a reference if you are not going to reuse it. you really ought to show the ACTUAL code if you need advice, though. in this snippet you don't even set content text so the element won't show if it's inline. could it be a styling issue, what is the display on the element, inline? inline-block?
can you assign it a class that changes it on a :hover pseudo and see it do it? mind you, you say the cursor sticks which means you can mouseover it - hence css works. this also eliminates the possibility of having any element shims above it / transparent els that can prevent the event from bubbling.
finally. assign it an id in the making. assign the event to a parent element via:
parentEl.addEvent("click:relay(strong#idhere)", fn);
and see if it works that way (you need Element.delegate from mootools-more)
good luck, gotta love the weird problems - makes our job worth doing. it wouldn't be the worst thing to post a url or JSFIDDLE too...