【问题标题】:Exclude HTML from script tag从脚本标签中排除 HTML
【发布时间】:2014-08-12 18:21:44
【问题描述】:

我正在尝试学习 Handlebars.js,并想出了一种在我正在制作的网站中使用它的方法。这是一个单页网站,其中包含两个容器,每个容器中包含三个 div,每个容器将通过其 API 包含嵌入式 Soundcloud 播放器。

当在 Handlebars 处理的脚本标记中包含包含 API 请求的 div 时,该站点的行为非常不可靠,并且仅显示六个播放器中的一些播放器。它不是真正一致的,但可以一直显示不同的玩家。问题似乎出在 Soundcloud javascript SDK 中,但我不觉得熟悉在那里挖掘太多。

因此我想了一些方法来排除播放器 div(请参阅代码),以便它们立即加载而不是作为 javascript 处理,但仍显示在艺术家下方 - 占位符 div 中的标题(设置为包含 Handlebar 脚本的结果)。

问题是我想不出一个好的方法来做这件事,有没有什么简单的功能(可能有 Handlebars 助手)可以帮助我做我想做的事?

<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>

一种解决方案当然是为每个 Artist - Title div 制作一个 Handlebar 模板,并将每个模板的占位符设置为仅包含 Artist1 - {{title1}} 的 div,但这确实破坏了使用 Handlebars 来最小化我的HTML 编码。

有人知道如何解决这个问题吗?

编辑 1:

我通过更改我的 javascript 找到了另一个解决方案(我一开始没有发布,所以很明显你无法帮助我)。

$(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 );

});

对不起,不好的打算。此代码的唯一问题是 title1(在变量 playerData 中,即 Handlebars 上下文中)获取变量 hey(“heya”)的第一个值。当它被警告时它会弹出真实的标题,我怎样才能让 title1 使用这个值而不是在更多的 javascript 中嵌套变量(因为这就是导致前面提到的玩家出现奇怪的错误的原因)?

【问题讨论】:

    标签: javascript html handlebars.js


    【解决方案1】:

    注意: 在整个 cmets 中,这个答案发生了巨大变化。如果您想了解此答案的演变,请查看较早的修订版。

    在掌握了你的 JsFiddle 示例后,我能够让它以我认为你想要的方式工作。

    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);
    });
    

    出了什么问题?

    JavaScript 是一种单线程、异步、事件驱动的语言。那个大嘴巴意味着 JavaScript 并没有真正的线程概念(我故意忽略了 WebWorkers)。为了解决这个限制,JavaScript 中几乎所有的 IO 都是非阻塞(异步)的。

    每当异步 IO 事务开始时,它都会立即返回给调用者并继续执行代码。几乎所有的 IO 事务都需要一个“回调”,或者有一个在 IO 事务完成时将被调用的事件。这意味着所有 IO 操作的基本模式如下:

    • 创建回调函数
    • 调用 IO 操作,向其传递完成所需的参数以及回调
    • 立即执行返回
    • 将来某个时候,回调函数会被调用

    在您的原始示例中,$(document).ready(function() { ... }) 将一个匿名函数排队,以便在引发 document.onReady 事件时触发。但是,您的原始示例分配了两个回调。这不是问题,事实上.ready(...) 旨在接受和排队许多回调。但是,您出错的地方是您有两个单独的代码块,分别称为 SC.get(...)

    从技术上讲,如果操作正确,这不会有问题,但是您的第一个就绪回调的目的是设置页面的 HTML,而您的第二个回调尝试基于页面上的 html 初始化播放器控件。请记住,这些事件和 IO 操作是异步的,它们会以任何顺序触发。本质上,这变成了一个时间问题,您试图初始化页面上的控件,同时生成 HTML 以显示在页面上。

    它是如何解决的

    要解决时间问题,您需要在获取信息、构建模板 HTML 以及初始化控件时进行同步。有很多方法可以做到这一点,许多框架都支持promises 的想法,以帮助控制触发异步事件的顺序以及调用它们的回调。

    我采用了简单的方法并将您的所有 SC.get 调用合并为一个,然后在它的回调中渲染把手模板并初始化 SoundCloud 播放器。

    【讨论】:

    • 感谢您的帮助。我很难理解它(newb,我知道),但它帮助我想到了一个替代解决方案。如果你有时间,请检查我的编辑!
    • 没关系,在我看来,您似乎已经了解了 JavaScript 的异步/事件驱动性质的工作原理。学习语言时,这可能是一个很大的陷阱。我在答案中添加了另一部分。
    • 再次感谢。嗯,好的,我会检查的。实际上,您在此处的代码几乎与我进行更改之前的 js 代码相同。虽然这段代码在 HTML 中的正确位置生成了正确的 Handlebar 数据,但将变量数据放入另一个函数的事情让玩家显得很奇怪。你能看到任何其他方式将tracks[0].title声明到一个可以在变量数据中正确读取的变量吗?
    • 你为什么不在 plunkr 或 jsfiddle 上放一个例子,我去看看。
    • 我现在看到了,你注册了两个 $(document).ready() 回调。这没有什么问题,他们会按顺序开火。但是,其中一个准备好的回调处理异步获取数据,另一个处理为该数据设置 DOM/播放器。这两个事件需要以某种方式联系在一起。今晚晚些时候我会试着为你写一个例子。