Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Exclude HTML from script tag

I'm trying to learn Handlebars.js and thought of a way to get use of it in a site im making. It's a one page site which will have two containers with three divs in each which will contain embedded Soundcloud players through their API.

When including the divs which contain the API requests in the script tag handled by Handlebars, the site behave very unreliable and shows only some of the six players. It's not really consistent but can show different players all the time. The problem seems to be in the Soundcloud javascript SDK but I don't feel to familiar to dig around in there too much.

Therefore I thought of some way to exclude the player divs (see code) so that they're loading instantly and not being Handled as javascript, but still show up beneath the artist - title in the placeholder div (which is set to contain the result of the Handlebar script).

The problem is that I can't come up with a nice way of doing this, are there any easy function (with Handlebars helpers maybe) that will help me do what I want?

<div id="placeholder"></div>
<script id="player-template" type="text/x-handlebars-template">
            <div id="container1">
                    Artist1 - {{title1}}
                    <div id="player1"></div>
                    Artist2 - {{title2}}
                    <div id="player2"></div>
                    Artist3 - {{title3}}
                    <div id="player3"></div>
           </div>
           <div id="container2">
                    Artist4 - {{title4}}
                    <div id="player4"></div>
                    Artist5 - {{title5}}
                    <div id="player5"></div>
                    Artist6 - {{title6}}
                    <div id="player6"></div>
            </div>
    </script>
    <script src="js/handlebars_title_script.js"></script>

One solution is of course to make one Handlebar template for each Artist - Title div and set the placeholder of each template to a div containing only Artist1 - {{title1}} but that really destroys the point of using Handlebars to minimize my HTML coding.

Anyone got any tip for me how to solve this?

Edit 1:

I found another solution by changing in my javascript (which I didn't post at first so obviously you couldn't help me with that).

$(document).ready(function() {
    var hey = "heya";
    SC.get("/users/artist/tracks", {limit: 1}, function(tracks){
    var title_data1 = tracks[0].title;
    hey = tracks[0].title;
    alert(title_data1);
    alert(hey)
    });


//Data that will replace the handlebars expressions in our template
var playerData = {
    title1 : hey,
};

document.getElementById( 'player-placeholder' ).innerHTML = playerTemplate( playerData );

});

Sorry for bad intendetion. The only problem with this code is that title1 (in the variable playerData which is the Handlebars context) gets the first value of the variable hey ("heya"). When it's alerted it pops up the real title, how can I make title1 use this value instead without nesting the variable in more javascript (since that's what causes the before mentioned error with players showing up weird)?

like image 314
user3880485 Avatar asked Nov 01 '22 18:11

user3880485


1 Answers

Note: throughout the comments this answer has changed drastically. Please view the earlier revisions if you would like to see the evolution of this answer.

After getting a hold of your JsFiddle example I was able to get it working in a way I think you wanted.

Working Demo

HTML:

<body>
    <div id="wrapper">
        <div id="player-placeholder"><!-- rendered template goes here --></div>

        <!-- handlebars template: -->
        <script id="player-template" type="text/x-handlebars-template">
            {{#each tracks}}
                <div class="track">
                    <header class="header">
                        <span class="artist">{{user.username}}</span> - <span class="title">{{title}}</span>
                    </header>
                    <section class="player" data-uri="{{permalink_url}}">
                    </section>
                </div>
            {{/each}}
        </script>
    </div>
</body>

JavaScript:

$(document).ready(function() {

  /*
    get your template string See:

    http://api.jquery.com/id-selector/
    http://api.jquery.com/html/
  */
  var source = $('#player-template').html();

  // compile the template into a handlebars function
  var template = Handlebars.compile(source);

  // initialize sound cloud api
  SC.initialize({
    client_id: '90fb9e15c1e26f39b63f57015ab8da0d'
  });

  /*
    This function will be called once the HTTP transaction
    started by SC.get(...) completes. Note, there's nothing
    wrong with doing this as an anonymous function, I'm
    simply assigning it to a variable to show that this
    is a distinct function that's called later
  */
  var callback = function(tracksResponse){
    /*
       once a response has been received, we'll use the response
       to generate a new context to pass to the template function.
       Note, you can use the template function in here because its
       within a closure. See:
       https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures
    */
    var context = { tracks: tracksResponse };
    var html = template(context);

    /*
      assign the rendered html to your placeholder on the page
      see: http://api.jquery.com/html/
    */
    $('#player-placeholder').html(html);

    /*
      Now that the html is rendered and on the page, its time to
      setup the sound cloud players. Note the css classes I assigned
      to the track/player. This line selects all of the player's and
      runs the function over each. See:

      http://api.jquery.com/class-selector/
      http://api.jquery.com/each/
    */
    $('.track .player').each(function(index, e){
      var $this = $(this); // jQuery reference to the current object in 'each loop'

      /*
        I assigned the permalink_url of each track to an attribute called 'data-uri'
        This line gets the value of that attribute. See:

        http://api.jquery.com/data/#data2
      */
      var permalink = $this.data('uri'); 
      var urlParameters = '/&maxheight=100&maxwidth=300&format=json&sharing=false';

      /*
        finally we call the sound cloud oEmbed function feeding it the url
        stored in the element, as well as the actual element.

        (see the second argument of the each function: http://api.jquery.com/each/)
      */
      SC.oEmbed(permalink + urlParameters, e);
    });
  };

  // get tracks for your artist
  // Note the "limit" in the object controls the number of items returned
  // by sound cloud
  SC.get("/users/theshins/tracks", {limit: 5}, callback);
});

What went wrong?

JavaScript is a single-threaded, asynchronous, event-driven language. That giant mouth-full means JavaScript doesn't really have a notion of threading (I'm intentionally ignoring WebWorkers). To work around that limitation, almost all IO in JavaScript is non-blocking (asynchronous).

Whenever an asynchronous IO transaction begins it immediately returns to the caller and code execution continues. Almost all IO transactions take a 'callback' or have an event that will be called when the IO transaction completes. That means the basic pattern for all IO operations follows something like this:

  • Create a callback function
  • Call IO operation, passing it the arguments it requires to complete, plus the callback
  • Execution returns immediately
  • Sometime in the future, the callback function is called

In your original example $(document).ready(function() { ... }) queues an anonymous function to fire when the document.onReady event is raised. Your original example, however, had two callbacks assigned. This isn't a problem, an in fact .ready(...) is designed to accept and queue many callbacks. However, where you went wrong is you had two separate blocks of code that called SC.get(...).

Technically if done right this wouldn't be a problem, but your first on ready callback's purpose was tasked to setup the page's HTML while your second callback tried to initialize the player controls based on html on the page. Remember these events and IO operations are asynchronous, they'll fire in whatever order. Essentially this became a timing issue, you were attempting to initialize controls on the page, and generate HTML to display on the page at the same time.

How it was fixed

To fix the timing issue you need to synchronize when you get your info, when you build your template HTML, and when you initialize your controls. There's a lot of ways to do this and many frameworks support the idea of promises to help gain control over the order that asynchronous events are fired, and their callbacks called.

I took the simple route and combined all of your SC.get calls in to one, then within it's callback I render the handlebars template and initialize the SoundCloud players.

like image 53
klyd Avatar answered Nov 12 '22 12:11

klyd