0
votes

Update 1

I've traced the issue to the html5Info function used by FB.XFBML.parse(). It has a conditional. This is the boolean statement that differs on the two elements: element.getAttribute('fb-xfbml-state') becomes "rendered" (truthy) or null (falsy) depending on what element handled.

So the question narrows down to...

Why has my ember-component's rendered HTML not got its 'fb-xfbml-state' set to "rendered" while the static HTML in a template have?

Update 2

Dirtyfixxed my issue by making my facebook component contain a .setAttribute call like this.

/* global FB */
import Ember from 'ember';

export default Ember.Component.extend({
    tagName: 'div',
    classNames: 'fb-share-button',
    attributeBindings: [
        'data-href',
        'data-layout',
        'data-action',
        'data-show-faces',
        'data-share'
    ],

    onDidInsertElement: function() {
        Ember.run.debounce(this, function() {
            console.log(document)
            _.forEach(document.getElementsByClassName('fb-share-button'), function(element) {
                element.setAttribute('fb-xfbml-state', 'rendered');
            });
            FB.XFBML.parse();
        }, 250);
    }.on('didInsertElement'),
});

Update 3

It seems that the component generates child nodes, as in document.getElementById('myComponentElementInTheDOM').childNodes and these are what differs between the component generated HTML and the plain HTML. The FB-SDK has a conditional IF statement that yields true for the plain HTML that has no childNodes, while false for the component generated HTML that has child nodes, resulting in considering only the plain HTML as a target to render.

Issue...

I have a situation where I check the DOM using chrome inspector, and I find two equal html snippets:

<div id="ember403" class="ember-view fb-share-button" data-href="https://mypage.com" data-layout="button_count" data-action="like" data-show-faces="false" data-share="true"></div>
<div id="ember407" class="ember-view fb-share-button" data-href="https://mypage.com" data-layout="button_count" data-action="like" data-show-faces="false" data-share="true"></div>

One of these, were put as plain HTML inside the routes handlebars file, the other were rendered using a Ember component I made. Only one will be found when I call FB.XFBML.parse() manually after page has loaded.

What is the difference!?

I've used the debug.js version of the facebook SDK, and when I call FB.XFBML.parse() it says:

XFBML Parsing Finish 2, 1 tags found

// Debug version: connect.facebook.net/en_US/all/debug.js
// Normal version: connect.facebook.net/en_US/sdk.js

But...

If I now first use chrome inspector to modify the DOM in some supertrivial way, like adding a new blank line. I can after that call FB.XFBML.parse() again and suddenly find both tags.

I don't understand...

What can I do to make the HTML generated by my ember component be found by FB.XFMBL.parse() just as the HTML generated by plain HTML from the routes handlebars file?

My route template generating the same HTML

{{social-facebook data-href="https://mypage.com" data-layout="button_count" data-action="like" data-show-faces="false" data-share="true"}}

<div id="ember407" class="ember-view fb-share-button" data-href="https://mypage.com" data-layout="button_count" data-action="like" data-show-faces="false" data-share="true"></div>

The above code generates the following in as shown in chrome inspector:

Identical HTML output - Yet the FB.XFBML.parse() command seem to make a difference on them, no matter what order I put them in also

My component

/* global FB */
import Ember from 'ember';

export default Ember.Component.extend({
    tagName: 'div',
    classNames: 'fb-share-button',
    attributeBindings: [
        'data-href',
        'data-layout',
        'data-action',
        'data-show-faces',
        'data-share'
    ],

    onDidInsertElement: function() {
        Ember.run.schedule('afterRender', FB.XFBML.parse);
    }.on('didInsertElement'),
});

My components template

{{yield}}

My initializer

/* global FB */
import ENV from '../config/environment';

export function initialize(container, application) {
    var debug = false;

    // Wait for Facebook to load before allowing the application
    // to fully boot. This prevents `ReferenceError: FB is not defined`
    application.deferReadiness();

    var fbAsyncInit = function() {
        console.log("SOCIAL DEBUG: fbAsyncInit invoked.");
        initFacebook(window.FB);
        application.advanceReadiness();
    };

    loadFacebookSDK(debug);
    window.fbAsyncInit = fbAsyncInit;
}

function initFacebook(FB) {
    console.log("SOCIAL DEBUG: FB.init invoked.", ENV.fbAppId, ENV.fbSDKVersion);
    FB.init({
        appId      : ENV.fbAppId,
        xfbml      : true,
        version    : ENV.fbSDKVersion
    });
}

function loadFacebookSDK(debug) {
    (function(d, s, id){
        console.log("SOCIAL DEBUG: Load facebook SDK code", d, s, id);
        var js, fjs = d.getElementsByTagName(s)[0];
        if (d.getElementById(id)) {return;}
        js = d.createElement(s); js.id = id;
        js.src = debug ? "//connect.facebook.net/en_US/all/debug.js" : "//connect.facebook.net/sv_SE/sdk.js";
        fjs.parentNode.insertBefore(js, fjs);
    }(document, 'script', 'facebook-jssdk'));
}

export default {
  name: 'social-facebook',
  initialize: initialize
};
1

1 Answers

2
votes

This component modification will do the trick

This component code can be used to solve your issue arising from hidden childNodes created by a component to handle databindings. This solution is as close to perfect as I can imagine since I cant adjust how ember component nor the facebook sdk works.

/* global FB */
import Ember from 'ember';

export default Ember.Component.extend({
    tagName: 'div',
    classNames: 'fb-share-button',
    attributeBindings: [
        'data-href',
        'data-layout',
        'data-action',
        'data-show-faces',
        'data-share'
    ],

    onDidInsertElement: function() {
        // This is needed to make the FB SDK accept the component rendered HTML even though child nodes due to databinding exists
        // To understand more about this, follow the code inside connect.facebook.net/en_US/all/debug.js
        // 1: Read up on the function FB.XFBML.parse, found by searching for: 'function parse(/*DOMElement*/ dom'
        // 2: Read up on the function html5Info, whos returnvalue will be affected, found by searching for: 'function html5Info('
        // 3: Notice the conditional inside html5Info containing the booleans below:
        //        element.childNodes.length === 0           --- supposed to evaluate to true, but isn't due to childNodes used for databinding
        //        element.getAttribute('fb-xfbml-state')    --- used to make sure that the conditional becomes true for the component
        document.getElementById(this.get('elementId')).setAttribute('fb-xfbml-state', 'rendered');

        // This makes FB-SDK render the HTML-stubs inserted by the ember components
        Ember.run.debounce(this, FB.XFBML.parse, 100);
    }.on('didInsertElement'),
});

For reference, here is a related initializer I use

/* global FB */
import ENV from '../config/environment';

export function initialize(container, application) {
    var debug = true;

    // Wait for Facebook to load before allowing the application
    // to fully boot. This prevents `ReferenceError: FB is not defined`
    application.deferReadiness();

    var fbAsyncInit = function() {
        initFacebook(window.FB);
        application.advanceReadiness();
    };

    loadFacebookSDK(debug);

    window.fbAsyncInit = fbAsyncInit;
}

function initFacebook(FB) {
    FB.init({
        appId      : ENV.fbAppId,
        xfbml      : true,
        version    : ENV.fbSDKVersion
    });
}

function loadFacebookSDK(debug) {
    (function(d, s, id){
        var js, fjs = d.getElementsByTagName(s)[0];
        if (d.getElementById(id)) {return;}
        js = d.createElement(s); js.id = id;
        js.src = debug ? "//connect.facebook.net/en_US/all/debug.js" : "//connect.facebook.net/sv_SE/sdk.js";
        fjs.parentNode.insertBefore(js, fjs);
    }(document, 'script', 'facebook-jssdk'));
}

export default {
  name: 'social-facebook',
  initialize: initialize
};

And an example on how to use the component

{{social-facebook data-href="https://yourwebpage.com" data-layout="button_count" data-action="like" data-show-faces="false" data-share="true"}}