Backbone.js应用程序完全在JS中呈现

时间:2015-08-10 02:00:11

标签: javascript backbone.js single-page-application

我试图在Backbone.js中构建一个新项目,来自开发Java,PHP,Rails,Perl等Web应用程序的后台。我已经完成了相当多的JS工作,包括JQuery和一些Node,但是我在使用Backbone构建整个应用程序的最佳实践时遇到了一些麻烦。看起来教程中的内容与我看到的实际应用程序的实现方式有所不同。

我一直在读Addy Osmani的Backbone Fundamentals。他的示例让您创建一个静态HTML文件,您可以在其中放置应用程序的框架,然后由JS进行修改。一个示例代码段:



  <section id="todoapp">
    <header id="header">
      <h1>todos</h1>
      <input id="new-todo" placeholder="What needs to be done?" autofocus>
    </header>
    <section id="main">
      <input id="toggle-all" type="checkbox">
      <label for="toggle-all">Mark all as complete</label>
      <ul id="todo-list"></ul>
    </section>
    <footer id="footer"></footer>
  </section>
&#13;
&#13;
&#13;

这一切都很好,看起来像是一个不错的方法。但我在野外看到了几个应用程序,其中静态HTML文件只是少数样式表和脚本标记。然后看起来整个应用程序由JS生成。这对于具有大量动态内容的不同视图的应用程序似乎是有益的。

我真的很想了解这些应用是如何构建的,但我一直无法找到涵盖它的文档或教程。我想我可以一起破解某些东西,但我真的很了解这样做的正确方法,最佳实践等。

有人能指点我学习一些文档吗?

1 个答案:

答案 0 :(得分:1)

很好的问题但不幸的是开放性很强。有many such tutorials around,但我更愿意回答一下您需要做些什么的高级概述。

你的问题所指的区别是其中 HTML的呈现:所有在服务器端,所有在客户端,或两者的混合。

另一个(相关)问题是发生 路由

最接近“所有客户端”的方法是,其中有一个服务器端面向用户的URL端点,它返回一个小的HTML响应,它本质上是客户端的引导程序,如下例所示: / p>

  

GET /

<html>
<head>
    <title>Loading</title>
    <script data-main="/client/main" src="/client/libs/require.js"></script>
</head>
<body>
</body>
</html>

这使用AMD模块加载器require.js(还有很多其他选择,但请耐心等待)。反过来,require.js会自动加载data-main中指定的脚本,在这种情况下,/client/main.js(约定不包含.js)。

  

获取/client/main.js

require.config({
    baseUrl: '/client',       // this tells require to load things relative to this "base" path
    paths: {
         underscore: 'libs/underscore',
         jquery: 'libs/jquery',
         backbone: 'libs/backbone',
         text: 'libs/require-plugins/text',      // this is a "plugin" for require.js that allows you to load textfiles instead of scripts when you precede the path with 'text!'
         // etc for other libs
    },
    shim: {
         underscore: { exports: '_' },
         jquery: { exports: '$' },
         backbone: { deps: ['underscore', 'jquery'], exports: 'Backbone' }
    }
});

require(['jquery', 'application'], function($, Application) {

    var $rootDiv = $("<div>", {id: "app-root", class: ""});
    $('body').prepend($rootDiv);
    var app = new Application({ rootEl: $rootDiv });
    app.start();

});

我不想特意深入研究require.js,所以我只是注意它看起来像它做的那样:它以异步方式加载JavaScript(所以你说< em>此脚本依赖于那个脚本等)。第一个块只是require.js的配置,并且有点不合适,但我想让它变得现实。

第二块更有趣。这就是它所说的:

  1. require以下依赖项:jqueryapplication
  2. 加载后(从HTTP GET加载或从加载缓存中加载),将它们分别归为$Application
  3. 创建一个新的div并将其添加到body
  4. 创建新的Application,将rootEl指定为div已创建的
  5. 在应用程序实例上调用start
  6. 这就是DOM在这一点上的样子:

    <html>
    <head>
        <title>Loading</title>
        <script data-main="/client/main" src="/client/libs/require.js"</script>
        <script data-requiremodule="main" src="/client/main.js"></script>
        <script data-requiremodule="jquery" src="/client/libs/jquery.js"></script>
         <!-- ... etc ... -->
         <script data-requiremodule="application" src="/client/application.js"></script>
    </head>
    <body>
         <div id="application-root"></div>
    </body>
    </html>
    

    现在关于最后一个依赖项,application.js

      

    获取/client/application.js

    define(['underscore', 'jquery', 'backbone', 'text!templates.html'], function(_, $, Backbone, Templates) {
    
        var getStartOptions = function(options) {
            options = options || {};
            _(options).defaults({
                rootEl: $('body'),
                initialRoute: '/'
            });
    
            return options;
        };
    
        return Backbone.View.extend({
            initialize: function(options) {
                this.state = new Backbone.Model(getStartOptions(options));
    
                this.listenToOnce(this.state, 'change:started', function() {
                    this.state.set(getStartOptions(options));
                }, this);
    
                this.listenTo(this.state, 'change:rootEl', this.onChangeRootEl, this);
            },
            onChangeRootEl: function(val) {
                this.setElement(val);
                if (!this.state.previous('rootEl')) {
                    var $templates = $('<div>', {id: 'app-templates'});
                    $templates.html(Templates);
                    $('body').append($templates);
                }
                this.render();
            },
            template: _.template($('#app-templates #app-layout-template').html()),
            render: function() {
                this.$el.html(this.template());
                return this;
            },
            start: function() {
                this.state.set('started', true);
            }
        });
    
    });
    

    define函数就像require(它是require.js的一部分),但它本身并不“运行” - require。所以,无论你define,你必须require在其他地方跑。

    return函数参数中的define值是您在require时获得的值。

    在这种情况下,getStartOptions 私有,但返回的View是之前需要['application']的值。

    大致情况如下:

    1. 从以前开始,我们实例化此处定义的View(我们称之为Application
    2. 我们致电start
    3. startstarted设置为true
    4. started为真时,视图(通过listenToOnce调用)会设置初始选项。
    5. 我们第一次设置rootEl时,我们会将模板注入DOM
    6. 当选项更改时,我们会相应地更新视图,然后重新渲染。
    7. 毕竟,结果将是:

      <body>
          <div id="app-root">
              ... content of the template with id "app-layout-template" ...
          </div>
      </body>
      

      布局模板只需要采用这种形式。

        

      获取/client/templates.html

      <script type="text/whatever-you-want" id="app-layout-template">
      ...
      </script>
      
      <script type="text/x-underscore-template" id="example-of-app-layout-template">
          <header><h2><%= appTitle %></h2></header>
          <section><%- appContent %></section>
          <footer><p><%= appFooterMessage %></footer>
      </script>
      

      Here is some information about underscore templates.还有很多其他选择。