0

I’m trying to install this custom jQuery scrollbar: http://manos.malihu.gr/jquery-custom-content-scroller/ onto a modal in my Facebook Canvas app, but I can’t seem to apply it to the correct div.

My app/html page has an “Invite” button that opens up a modal which contains a multi-friend selector. The modal/multi-friend selector contains a div that is created by Javascript and then populated with a list of the user’s friends. The user can then choose people to invite from this list.

This is the Javascript that creates the friends list div in the modal (mfs is the id of a parent div in the modal, this parent div will contain the mfsForm div after it (mfsForm) is created).

//Code to login to Facebook’s API goes here…

// Getting the list of friends for the logged in user with the Graph API
            FB.api('/me/invitable_friends?limit=48', function(response) {
                var container = document.getElementById('mfs');
                var mfsForm = document.createElement('form');
                mfsForm.id = 'mfsForm';

//This area contains code which goes through each friend and creates an image and name for each one. This populates the mfsForm.

mfsForm is the id of the div that holds the list of friends. I’m trying to apply the scroll bar to this div. To do this I need to apply the class “ content mCustomScrollbar” to it.

I’m sure that I have the correct scripts included on the page because I’ve tested the scroll bar plugin by applying the same class (“ content mCustomScrollbar”) to the div mfs and it appears along the right hand side of that div perfectly, but I want to apply it to mfsForm instead.

Nothing I’ve tried so far has worked, the browser’s default gray scrollbar is the only one that appears on the mfsForm div. I think the problem might be due to the div mfsForm being created in Javascript.

I’ve tried the following methods to apply the class to mfsForm:

Applying the class to mfsForm in the external Javascript file right after it is created:

//Code to login to Facebook’s API goes here…

// Getting the list of friends for the logged in user with the Graph API
            FB.api('/me/invitable_friends?limit=48', function(response) {
                var container = document.getElementById('mfs');
                var mfsForm = document.createElement('form');
                mfsForm.id = 'mfsForm';

var attempt = document.getElementById("mfsForm");
attempt.className = " content mCustomScrollbar";

//This area contains code which goes through each friend and creates an image and name for each one. This populates the mfsForm.

Applying the class to mfsForm in the html page on document.ready:

<script>
$(document).ready(function(){
var attempt = document.getElementById("mfsForm");
attempt.className = " content mCustomScrollbar";
});
</script>

Applying the class to mfsForm in the html page on click of the button that opens the Invite modal:

//this code is in an external Javascript file:
function applyScroll(){
var attempt = document.getElementById("mfsForm");
attempt.className = " content mCustomScrollbar";
 }

//this is the onClick event on the html page:
<a class="invitebutton" href="#openModal" onclick="applyScroll();"></a>

I thought the error might be caused by mfsForm not existing when the class is applied to it, so I thought applying it only after the document is loaded, or only when the invite button is clicked, would solve this problem as mfsForm would be created by then, but this hasn’t solved the problem. I get the following error in the console:

TypeError: attempt is null

Can anyone see what I’m doing wrong? I’d really appreciate any advice on how to solve this. Thank you in advance!

UPDATE

Current HTML code:

<!doctype html>
<head>
<meta charset="UTF-8">
<title>Facebook App.</title>

<!-- Loading jquery 2.1.3 -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>

<!-- This .js file contains the mfsForm creation code -->
<script type="text/javascript" src="scripts/fb.js"></script>

<!-- This .css file contains the custom scrollbar's default styles -->
<link rel="stylesheet" type="text/css" href='https://cdn.jsdelivr.net/jquery.mcustomscrollbar/3.0.6/jquery.mCustomScrollbar.min.css'>

<!-- This .css file contains the styles for .mfsForm and .content -->
<link rel="stylesheet" type="text/css" href="styles/scrolltestcss.css">

<!-- This is the custom scrollbar's .js file-->
<script src='https://cdn.jsdelivr.net/jquery.mcustomscrollbar/3.0.6/jquery.mCustomScrollbar.concat.min.js'></script>

</head>
<body >

<div>
<a class="invitebutton" href="#openModal"></a>
</div>

    <!--Modal-->
<div id="openModal" class="modalDialog">
<div id="mfs">
<div id="wrapper">

</div>
</div>
</div>

</body>
</html>

Current fb.js file:

window.fbAsyncInit = function() {
    FB.init({
        appId: 'myappId',
        xfbml: true,
        version: 'v2.3'
    });

    function onLogin(response) {
        if (response.status == 'connected') {
            FB.api('/me?fields=first_name', function(data) {
                var welcomeBlock = document.getElementById('fb-welcome');       
                welcomeBlock.innerHTML = 'Hello, ' + data.first_name + '!';
            });
            var jQuery = document.createElement('script');
            jQuery.src = 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js';
            document.head.appendChild(jQuery);

            // First get the list of friends for this user with the Graph API
            FB.api('/me/invitable_friends?limit=48', function(response) {
                var container = document.getElementById('mfs');
                var mfsForm = document.createElement('form');
                mfsForm.id = 'mfsForm';
                mfsForm.className = " content mCustomScrollbar mfsForm";

                // Iterate through the array of friends object and create a checkbox for each one.
                for (var i = 0; i < response.data.length; i++) { 

                    var friendItem = document.createElement('div');  
                    friendItem.id = 'friend_' + response.data[i].id;
                    friendItem.style.cssText="width:100px; height:100px; padding:7px; color:#FFF;"
                    friendItem.style.cssFloat="left";
                    friendItem.innerHTML = '<input type="checkbox" name="friends" value="' + response.data[i].id + '" />';

                    var img = document.createElement('img');
                    img.src = response.data[i].picture.data.url;
                    img.style.cssText = 'width: 70px;height: 70px;'
                    friendItem.appendChild(img);

                    var labelName = document.createElement('label');
                    labelName.style.cssText = 'font-size: 14px;'
                    labelName.innerHTML = response.data[i].name;
                    friendItem.appendChild(labelName);

                    mfsForm.appendChild(friendItem);
                }
                container.appendChild(mfsForm);

                // Create a button to send the Request(s)
                var sendButton = document.createElement('div');
                sendButton.id = 'sendButton';
                sendButton.onclick = sendRequest;
                container.appendChild(sendButton);

                $("#filter").keyup(function(){

        // Retrieve the input field text and reset the count to zero
        var filter = $(this).val()//, count = 0;

        // Loop through the comment list
        $("#mfsForm div").each(function(){

            // If the list item does not contain the text phrase fade it out
            if ($(this).text().search(new RegExp(filter, "i")) < 0) {
                $(this).fadeOut("fast");

            // Show the list item if the phrase matches and increase the count by 1
            } else {
                $(this).show();                
            }
        });


    })
            });
        }
    }

    function sendRequest() {
        // Get the list of selected friends
        var sendUIDs = '';
        var mfsForm = document.getElementById('mfsForm');
        for (var i = 0; i < mfsForm.friends.length; i++) {
            if (mfsForm.friends[i].checked) {
                sendUIDs += mfsForm.friends[i].value + ',';
            }
        }

        // Use FB.ui to send the Request(s)
        FB.ui({
            method: 'apprequests',
            to: sendUIDs,
            title: 'My Great Invite',
            message: 'Add this app!',
        }, callback);
    }

    function callback(response) {
        console.log(response);
    }

    FB.getLoginStatus(function(response) {
        if (response.status == 'connected') {
            onLogin(response);
        } else {
            // Otherwise, show Login dialog first.
            FB.login(function(response) {
                onLogin(response);
            }, {
                scope: 'user_friends, email'
            });
        }
    });
  };

(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 = "//connect.facebook.net/en_US/sdk.js";
    fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));

Current scrolltestcss.css:

.mfsForm {
    padding-left: 90px;
    position: absolute;
    left: 100px;
    top: 250px;
    font-family:"Bubblegum Sans";
    font-size: 22px;
    text-decoration: none;
    color: #FFF;
    text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
    margin: auto;
    width: 400px;
    height: 200px;
    overflow: auto;
     border: 2px solid red;
}

.content { border: 2px solid green;
}
Emily
  • 1,151
  • 4
  • 21
  • 42
  • 1
    You don't seem to have inserted the `form` element (`var msForm`) into the DOM after you created it. – Kenney Nov 13 '15 at 15:10
  • Yeah it has to be in the DOM structure before it can be accessed via an id. So you could use `container.appendChild(mfsForm);`. Then you can access `mfsForm` by its id. [Simple Demo](https://jsfiddle.net/PalinDrome555/b221my83/) – PalinDrome555 Nov 13 '15 at 15:25
  • Hi @Kenney and @PalinDrome555 thank you for your comments. Unfortunately I already have the ``container.appendChild(mfsForm);`` included further down the Javascript code. I've created a jsfiddle to show you the full Javascript function, as well as the modal in HTML and a CSS style for the mfsForm. Please take a look and let me know what you think: http://jsfiddle.net/6pp11nsw/ Thanks again! – Emily Nov 13 '15 at 20:49
  • @PalinDrome555 and Kenney the inclusion is on line 39 of the fiddle. – Emily Nov 13 '15 at 20:52
  • I've just realised I haven't included the styles for the other elements of the modal, so it looks very unfinished, but the Javascript and HTML code is all there. Thanks again. – Emily Nov 13 '15 at 20:54

1 Answers1

1

I updated the fiddle to provide a working copy of the issue. It mock's the FB api and fixes reference errors ($ is not defined until jquery is loaded, which is after the onLogin event, which is called after the document is ready).

It shows the form with a red border, defined in a #mfsForm CSS selector, showing it is created using the callback. It has both it's id and class attributes set correctly by the JS, only the .custom, setting the border to green, seems to have no effect.

In this fiddle I changed #mfsForm to .mfsForm, and now the border is green.

So, yes, to what you asked in the code:

Could this be causing a conflict with " content mCustomScrollbar"? */
#mfsForm {

There is a complex rule set governing the priority of css selectors. In this case, #id is more specific than .class, as #id is (supposed to be) unique and can only match one element, and a class is a collection of elements.

If we change the order of the css rules, the border becomes red. So, it will matter whether your css is loaded before or after the css defining the .content and .mCustomScrollbar.

If you're concerned that the wider .mfsForm matches more elements than the one you just added, you can refer to it using #mfs > .mfsForm. This changes things again, as we see here: even though .custom is listed second, #mfsForm > .mfsForm takes precedence because it is more specific.

When using third party libraries, often you'll have to check the specific CSS selector used to set a style, and make sure you either use a more specific one, load it after the third party css, or hope that the library doesn't use !important and use that.

In this particular case however, the styling of #mfsForm was overriding the style in the css defining .custom and/or .mCustomScrollBar, because it is more specific.

UPDATE

Here's a fiddle that takes a slightly different approach.

First, all <script> tags loading jQuery and mCustomScrollbar are removed. Since jQuery is loaded dynamically by injecting a <script> tag, the required dependency jquery.mcustomscrollbar won't work.

Also, we cannot append a script tag loading the jquery.mcustomscrollbar until jQuery is loaded. Therefore I've added a little function based off of this:

function loadScript(url, cb) {
    var done = false;
    var tmp = document.createElement('script');
    tmp.src = url;
    tmp.onload = tmp.onreadystatechange = function () {
        console.log("onload/readystatechange called");
        if (!done || (!this.readyState || this.readyState === "loaded" || this.readyState === "complete")) {
            done = true;
            tmp.onload = tmp.onreadystatechange = null;
            if (cb) cb(tmp);
        }
    };
    document.head.insertBefore(tmp, document.head.firstChild);
}

which will load our script from url and call our callback cb when it's done.

Then, the flow is as follows:

loadScript('http.....jquery.min.js', function() {

    invitableFriends(); // do the FB call and render the form

    loadScript('http.....mcustomscrollbar.concat.js', function() {
        $('.mcustomScrollBar').mCustomScrollbar();
    }
})

The problem is, that the scrollbar plugin only automatically adds scrollbars to elements already existing at page load in an onload handler. Since the element to apply the scrollbar to is added dynamically, we have to explicitly enable the scrollbar on it, as is done in the callback loading the plugin.

The above solution loads everything dynamically (except the mcustomscrollbar CSS, but that can be changed).

Alternative

Loading everything 'statically' as in the updated code in your question will work too, but you'll have to enable the scrollbar explicitly:

...
container.appendChild(mfsForm);   
$(mfsForm).mCustomScrollbar();    // add this line
Community
  • 1
  • 1
Kenney
  • 9,003
  • 15
  • 21
  • Hi @Kenney thanks very much for your answer, and for the link to the info about CSS specificity. I’ve been trying to get the custom scrollbar to work with your jsfiddle. This is a fiddle with my edits: http://jsfiddle.net/waf11s6u/ – Emily Nov 15 '15 at 07:13
  • I’ve added some text into mfsForm to make a scrollbar appear, and I’ve added the necessary cdn files for the custom scrollbar to work at the top of the html. The plugin’s website: http://manos.malihu.gr/jquery-custom-content-scroller/ says that to initialize the scrollbar you can just add this to the div in HTML: ``
    `` This applies the scrollbar class, and specifies a theme named “dark” which is included in the plugin’s files.
    – Emily Nov 15 '15 at 07:13
  • To apply the default theme you can just apply the class “mCustomScrollbar” on its own without “data-mcs-theme="dark"”. If I understand your example correctly, you have added the class “mCustomScrollbar” with this line of code: ``mfsForm.classList.add("mCustomScrollbar");`` Now that I’ve inserted content that creates a scrollbar on the mfsForm, and included the plugin files in the HTML, shouldn’t the plugin’s default scrollbar be appearing instead of the browser’s default scrollbar? Thanks again for your help with this! – Emily Nov 15 '15 at 07:14
  • Hi, you're welcome! I've updated your new [fiddle](http://jsfiddle.net/waf11s6u/1/); it works now. You added 3 ` – Kenney Nov 15 '15 at 15:43
  • Hi @Kenney, thanks very much for for the new fiddle, you’ve definitely solved the problem with loading classes so I’ve marked your answer as correct. I’ve tried implementing your answer on my page but unfortunately I haven’t been able to get the custom scrollbar to appear. I’ve tried rearranging the scripts between the head tags and I’ve removed any html code, css code and other javascript files that relate to other parts of the page (in case some of those were causing a conflict), but the custom scrollbar still isn’t appearing. – Emily Nov 17 '15 at 08:35
  • I’ve added an update at the bottom of my question. It has my current stripped-down html code, css code, and the javascript file which contains the code that creates ``mfsForm``. I was wondering if the problem might be with the way the jquery is loaded by jsfiddle, maybe I’m loading it incorrectly in my html? I’d really appreciate it if you could take a look at my update to see if you can see something obvious that I’m doing wrong. Thanks so much again! – Emily Nov 17 '15 at 08:36
  • Hi Emily, here's another [updated fiddle](http://jsfiddle.net/6pp11nsw/7/) based off of my earlier one. It turns out the loading of jQuery is quite sensitive! Only with the `onDomReady` does it work. You can see in the browser developer console (F12) that the form gets another class `_mCS_1`, and in the form there's another `
    `: both of these changes are made by the mCustomScrollBar JS file, which scans for elements with class `mCustomScrollbar` and changes the DOM to show the scrollbars. I'll add another fiddle to the post momentarily!
    – Kenney Nov 17 '15 at 14:42
  • Hi @Kenney, thank you for the update to your answer, I tried integrating the dynamic method you showed but I got the error "TypeError: $(...).mCustomScrollbar is not a function" in the console. I then retried the code I had in my last update (as I had trouble getting the friend names & images to appear using the dynamic method) and added the line ``$(mfsForm).mCustomScrollbar();`` right after ``container.appendChild(mfsForm);``, but I got the same TypeError in the console. – Emily Nov 18 '15 at 08:51
  • I'm confused because the line ``container.appendChild(mfsForm);`` references "mfsForm", but it looks like the console is saying "mfsForm" doesn't exist when ``$(mfsForm).mCustomScrollbar();`` is executed. I was wondering is there something else I should be adding along with ``$(mfsForm).mCustomScrollbar();``. Thanks again for your help with this! – Emily Nov 18 '15 at 08:51
  • What is it saying exactly? After you do, `container.appendChild(mfsForm)`, do a `console.log(mfsForm)`, it should print ``. It might be that jQuery is not loaded properly, or is loaded twice (your current code declares a script tag with jquery in the HTML, but you also load it with the `var jQuery`, which isn't necessary). If you update the question with the exact code you have now I'll have another look. – Kenney Nov 18 '15 at 17:57
  • Hi @Kenney you're right it was due to the jquery being loaded twice, I removed it from the javascript file and it now works perfectly. Thanks so much again! – Emily Nov 20 '15 at 06:31