Twttr.widgets.createTweet() callback fires too early


#1

I have an app that opens embedded tweets in a popup modal dialog. The problem I’ve encountered: in order to center the dialog vertically, I need to get its dimensions AFTER the embedded tweet is rendered.

The callback function provided by twttr.widgets.createTweet() is supposed to fire after the tweet is fully rendered, but I’ve found that it’s firing too early – before the iframe element’s width and height have been set, so the popup dialog is not correctly centered. However, if I set a 250ms timeout, and THEN get the iframe’s size, it’s set correctly and the centering routine is happy.

A quarter of a second timeout isn’t unbearable, but it would be much better if the completion callback could happen after all rendering is finished instead of in a halfway state. Timeouts are fragile.


#2

Update: if I set a breakpoint at the entrance to the completion callback and inspect the iframe, this is what it looks like:

And the body of the iframe is empty.


#3

Hi Charles,

The currently implementation is working as designed, which is to return to you the new embedded element once it’s available (likewise remote Tweet Button iframes may not have loaded when the iframe is returned to the client.) It doesn’t guarantee content, but gives you a reference to work with.

However, we actually have a change in widgets-js coming very soon which should fix this, and fire a render event much closer to the actual moment of painting. I’ll follow up when that’s available.

Ben


#4

Great, thanks. I’ll watch for that.


#5

OK, the documentation for this isn’t written up yet, but give this a try:

twttr.events.bind(‘rendered’, function (element) {
// do stuff with a rendered widget here
});
twttr.widgets.createTweet(‘20’, someContainer);

Any callback to the createTweet function will execute when the frame is in the DOM, while the rendered event will fire after the widget has been sized and should be rendered.

The event is global (it’s fired once for any widget on the page) but if you use the createTweet callback to record the resultant element ID, you can map the rendered event handler back to it. Or you could add the callback to the element object itself:

twttr.events.bind(‘rendered’, function (element) {
if (!element.myAfterRenderCallback) return;
element.myAfterRenderCallback();
})
twttr.widgets.createTweet(‘20’, someContainer, function (element) {
if (!element) return;
element.myAfterRenderCallback = function () {
someContainer.style.top = (window.height - this.offsetHeight) / 2;
}
}

Some day I’d like to start returning more abstracted objects in these callbacks that would expose the Promises for ready/rendered state per widget (so you could do function (widget) { widget.rendered.then(function () { … }); directly. Or possibly augment the HTMLElement objects with such additions. That’s a little way off though.

Anyway, for now, let us know how you get on with the new event. Thanks!

Ben


#6

This looks like a great solution, but for some reason the ‘rendered’ event isn’t firing for me. I’ve set breakpoints and checked things at various stages; the createTweet() callback does correctly attach the rendered event handler to the iframe, but if I set a breakpoint at the entrance to the event handler it never gets there.

I inspected the twttr.events object and it looks like the event handler is properly installed in the array of handler functions, but as far as I can tell, it isn’t being called.


#7

Hi Ben,

In my testing, the ‘rendered’ event is fired when some initial blockquote’s are rendered at page load (just by loading /widgets.js) , but it is not when using twttr.widgets.createTweet() for loading dynamically more tweets later on.

i tried to reattach the event before calling createTweet (just in case twice is better than once ;-D ) with no effect.

JY


#8

Thanks for the feedback guys. Looks like we’ve got a gap between the new event implementation and the create functions. We’ll try and get that fixed up soon.


#9

Did a little more experimenting and I can see that the ‘rendered’ events are firing for elements created with twttr.widgets.load(), but the lack of a callback parameter for .load() makes using it in this case a little problematic.

I did, however, set a breakpoint at the entrance to the ‘rendered’ event, just to see whether the iframe’s dimensions were all set at that point, and unfortunately … no. Here’s what the iframe of an embedded tweet looks like at the entrance to the rendered handler:

Notice: no height is set for the iframe and the visibility is still set to hidden. If I proceed from that breakpoint and then look at the iframe again, it’s finished rendering and has a height attribute:

Anyway, thought this might help with debugging.


#10

Hi,

Any update on this front ?

Thx!
JY


#11

same problem :frowning:


#12

Any news?


#13

Hi all,

I’m very sorry for the delay over this, the fix got mislaid over the Christmas break and I failed to pick it up when I got back at the start of the month. Thank you, James, for bringing it back to my attention. A release finally went out this afternoon that fixes triggering rendered events from create functions. Also, a bonus enhancement that the create methods now all return Promise-like objects for chaining callbacks (in addition to the callback parameter form.) These same Promises now trigger the rendered event, so you can listen or chain to the same point in the pipeline depending on your needs.

twttr.events.bind(‘rendered’, function (element) {
// element.tagName === ‘IFRAME’
});

twttr.widgets.createTweet(‘20’, document.body).then(function (element) {
// element.tagName === ‘IFRAME’
});

I say Promise-like, because we’re internally using a polyfill derived from Alex Russell’s work on the ECMAScript Promises spec (a version of which is now native in Chrome and coming soon to Firefox.) We will, at some point, synchronize with the spec and return native Promises where available.

Let me know how you get on.

Ben


#14

Still not getting a fully rendered iframe either from the rendered event or the callback (whether passed into createTweet, or with then). The iframe at that point still has visibility: hidden and position: absolute.


#15

The new Promise interface is awesome, but I can confirm that when the callback is invoked, the iframe is still not completely finished rendering. Setting a breakpoint at the entrance to the callback, I still see this iframe code:

Notice: still no height attribute, and visibility:hidden.


#16

Interestingly, if I use console.log() to write the iframe code to the log instead of setting a breakpoint and inspecting it manually, the iframe code is fully rendered, visible, and has a height attribute. Using setTimeout to wait a little bit also finds the iframe fully rendered.

Seems to be some kind of timing-related issue, causing the rendered callback to be invoked just a teensy bit too early. Is the callback being fired from a ready() event on the iframe?


#17

Thanks for the note about setTimeout as a workaround. Oh how I love the opacity of browser rendering.

I’ll try to find some time this week to further iterate on the triggering—obviously a fully updated DOM object is what we want to provide to you after render.

I think, since it’s a flakiness issue and those inevitably take longer to fix that we’d ever like them to, it would be best for you all to proceed with putting timeouts in place where you need them, since getting this to be reliable (along with juggling various other pieces of work we have going on this week and next) might not make for an immediate fix. Sorry that’s a little ugly.

If you find any other quirks please let me know.

Ben


#18