骨干项目组织 [英] Backbone project organization

查看:17
本文介绍了骨干项目组织的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在努力想出一种干净、可靠的方式来组织我的 Backbone 应用程序.我正在使用 Requirejs、Handlebars 和 Requirejs Text 插件来动态加载 HTML 视图.为简化起见,我们假设该站点具有以下页面:

I'm struggling a bit with coming up with a clean, solid way to organize my Backbone application. I'm using Requirejs, Handlebars, and the Requirejs Text plugin to dynamically load HTML views. To simplify things, let's just say the site has the following pages:

首页:显示一系列产品

关于:静态页面

帐户: 包含帐户信息.购买的产品,允许进行各种更新.很多功能.有标签可以导航到不同的部分.

Account: contains account information. products purchased, allows for various updates. Lots of functionality. Has tabs to navigate to different sections.

所以我要使用 SPA 将新页面加载到 div ('.backbone-view') 中.我是否应该有一个带有 el: $('.backbone-view') 的通用 AppView,它会在路由更改时调用,然后加载适当的模板?或者我应该为每个页面(homeView、aboutView、accountView)都设置一个视图,并将它们的 el 设置为主干视图?

So I'm going for an SPA that loads new pages into a div ('.backbone-view'). Should I have a general AppView with an el: $('.backbone-view') that is called when the route changes and then loads the appropriate template? Or should I have a view for every page (homeView, aboutView, accountView), all with their el set to backbone-view?

除此之外……除了产品之外,我还需要其他任何东西的模型吗?对于静态 about 页面,我只加载 html 模板,仅此而已.但是对于产品,我需要调用 products 集合,它呈现每个产品视图,每个视图都与一个产品模型相关联.那很好……但是我在哪里初始化这些产品结构?当我路由到主页时,我在那里做吗?我有这个伪代码:

Beyond that...do I need a model for anything except Products? For the static about page, I just load in the html template and that's it. But for products, I need to call the products collection, which renders each product view, each of those being associated with a product model. That's fine...but where do I initialize these product constructs? When I route to the home page, do I do it there? I have this pseudo-code:

  routes: {
        '': 'home',
        'about': 'about',
        'my-account': 'myAccount',
        '*default': 'home'
    },

    'home': function() {
        // Grab template for home page

        // Load up products

        // Replace $('.backbone-view') with home page template populated with products
    },

    'about': function() {
        // Grab about template and replace $('.backbone-view') with its contents
    },

    'myAccount': function() {
        MIND EXPLOSION
    }

我认为一个大问题是我不清楚视图的用途……它们可以简单地用于页面转换,还是应该始终附加一个模型?如果是前者,我至少需要一个 AppView 然后每个页面的视图,对吗?我不知道我将在哪里委派每个步骤......所以感谢任何帮助.

I think a big issue is that I'm not clear on the purpose of Views...can they be used simply for page transitions, or should they always have a model attached to them? If the former, I would at least need an AppView and then Views for each page, right? I'm lost as to where I would delegate each step...so any help is appreciated.

感谢您的帮助!

推荐答案

以下是在处理大型主干应用程序后的一些提示.这不是详尽的或最终的..

Here are a few tips after working on very large backbone apps. It's not exhaustive or final..

分成两个目录

  1. 服务器目录,例如 server/
  2. 可公开访问的目录,例如www/

此外,当您运行构建任务时,它会将应用程序构建到 build/dist/ 目录中的可分发版本中.可能使用 Gulp 或 Grunt.

Also when you run your build task it would build the app into a distributable version into a build/ or dist/ directory. Probably using Gulp or Grunt.

扩展主干

您的整个应用程序将包括:

Your entire app will consist of:

  • 观看次数子视图
  • 路由器和子路由器
  • 模型
  • 集合

您应该扩展 Backbone 类,即使它们一开始是空的.最有用的两个扩展是:

You should extend the Backbone classes even if they are empty at first. The most useful two extensions are:

  • 子视图(视图可以有一个带有更多视图的 views 对象/函数,当您删除父视图时,这些视图会被清除).称为 modelcollection 的模型和集合会自动传递给子视图.
  • 子路由器(模块文件夹中有每个模块的路由逻辑很好)
  • Sub views (a view can have a views object/function with more views, which get cleaned up when you remove the parent view). Models and collections called model or collection get automatically passed down to sub views.
  • Sub routers (it's nice to have the routing logic for each module inside the module folder)

使用 Pod 架构

就像围绕自包含模块组织您的应用程序一样:

As in organise your app around self-contained modules e.g.:

www/app/modules/home/router.js <-- 子路由器,调用modules.js中的方法
www/app/modules/home/module.js <-- 准备端点 - 改变布局,初始化视图 &模型等
www/app/modules/home/views/... 所有视图(也可以有子文件夹)
www/app/modules/home/templates/
www/app/modules/home/models/
www/app/modules/home/collections

www/app/modules/home/router.js <-- sub router, calls methods in modules.js
www/app/modules/home/module.js <-- prepares endpoints - changing layout, initializing views & models etc
www/app/modules/home/views/... all the views (can have subfolders too)
www/app/modules/home/templates/
www/app/modules/home/models/
www/app/modules/home/collections

开始在视图和子视图方面查看您的应用

一个页面不仅仅包含一个视图.它可能会有一个特殊的布局"视图,里面有很多视图——一个将页面分成两半,一个分页,每个页码内部有更多的视图,一个视图内部有很多子视图对于每个表单元素和消息等

A page doesn't consist of just one view. It would have perhaps a special "layout" view and inside that would be many views - one which splits the page in half, one which has pagination with more views inside for each page number, a view for a form with lots of sub views inside for each form element and message etc etc

您可以开始将视图视为 DOM 树的阴影并进行逻辑划分 - 您认为可以在页面上重复使用的任何内容都将其作为一个包(如果需要,它是自己的视图和模型/集合).

You can start thinking of views as shadowing the DOM tree and divide logically - anything which you think is re-useable on your page make it a package (it's own views and models/collections if it needs them).

模型适用于任何数据和对数据执行的任何逻辑,如果视图显示来自服务器/api/数据库的任何内容,它通常会传递给视图,该视图会将所有或部分模型属性传递给模板.

Models are for any data and any logic performed on data, if a view was showing anything from the server/api/database it would typically be passed to the view which would pass all or some of the model attributes to the template.

如果显示信息的项目在列表中,那么集合将管理每个项目的每个模型.

If that item displaying information was in a list, then a collection would manage each model for each item.

与模特交流

如果您发现自己想要从一个视图向另一个视图传达某些信息,请使用共享模型.视图应该尽可能解耦(它不需要知道它的父视图).

If you find yourself wanting to communicate something from a view to another view, use a shared model. A view should be as decoupled as possible (it shouldn't need to be aware of it's parent).

拥有应用状态

创建一个名为 AppState 的模型,以使用触发器和侦听在整个应用中进行广泛的通信.

Create a model called AppState to broadly communicate across the app using triggers and listens.

有一个包文件夹(可选)

每当您在应用中遇到您认为可以重用的内容时,即使在未来的其他应用中,也请创建一个包.这些通常托管在他们自己的 git 存储库上,您可以使用 package.json 或命令行将它们拉入项目.

Whenever you come across stuff in your app which you think could be re-useable, even in other future apps, create a package. These would typically be hosted on their own git repos and you could pull them into projects using package.json or the command line.

有一个文件夹,您可以在其中扩展应用间内容

为多个应用程序使用的模块提供一个扩展文件夹 - 例如您的主干扩展可以放在这里.或者,如果您为表单创建了一个包,但想要专门为此应用做一些事情,请在此处扩展它.

Have an extensions folder for modules which are consumed by multiple apps - e.g. your backbone extensions could go here. Or, if you created a package for forms but want to do something specifically for this app, then extend it here.

例如www/app/extensions/view.js
www/app/extensions/model.js
www/app/extensions/collection.js
www/app/extensions/buttons/link.js//从buttons"包中扩展链接视图.

e.g. www/app/extensions/view.js
www/app/extensions/model.js
www/app/extensions/collection.js
www/app/extensions/buttons/link.js // Extending the link view from a "buttons" package.

资产

我在公共 www/ 文件夹中有一个 app/ 文件夹的原因是,我也可以在那里有一个用于字体和图像等的资产文件夹:

The reason why I would have an app/ folder in the public www/ folder is so that I could also have an assets folder in there for fonts and images etc:

www/assets/css
www/assets/images

注意:也许您想尝试将资产保存在模块文件夹中(与 pod 架构内联).我以前没有这样做过,但值得考虑.

Note: Maybe you want to try and keep assets in the module folders (inline with pod architecture). I haven't done this before but it's worth considering.

index.html

通常,如果您使用 CommonJS 或 AMD,您的 index.html 将只是样板文件,没有实际的 DOM 元素,您将在那里调用一个入口 js 文件.由于 CommonJS 必须编译,这将类似于 <script src="/app.js"></script> 但对于 AMD,它更像是:

Typically if you are using CommonJS or AMD your index.html would just be boilerplate with no actual DOM elements and you would have one call in there to an entry js file. Since CommonJS has to compile this would just be something like <script src="/app.js"></script> but for AMD it would be more like:

<!--IF NOT BUILD-->
<script data-main="/app/config" src="/packages/require.js"></script>
<!--ELSE
<script src="/app.js"></script>
-->

因此,在开发(非构建)中运行时,RequireJS 将加载 app/config.js,但在构建中,整个应用程序将在 app.js 中.有各种 Grunt/Gulp 构建任务可以为你做类似上面的事情(显然条件语法只是组成).

So when running in dev (non-build) RequireJS will load up app/config.js but in build the whole app will be in app.js. There are various Grunt/Gulp build tasks which will do something like the above for you (obviously that conditional syntax is just made up).

布局

我会创建一个 extensions/layout.js 来扩展 extensions/view.js 并且这将是一个简单的扩展,可以像普通的一样有子视图(例如标题和页脚),还有一个特殊的子视图,我可以将任何视图附加到(对于主体子视图)例如类似 setContentView(view) 的方法.

I would create a extensions/layout.js which extends extensions/view.js and it would be a simple extension that could have sub views like normal (e.g. header and footer), but also a special subview which I could attach any view to (for the body subview) e.g. a method like setContentView(view).

我可能会创建一个名为 layouts 的模块,并在其中有一个目录 modules/layout/default,其中有一个包含页眉和页脚子视图的视图.然后到达索引路线将是这样的:

I would maybe create a module called layouts and in there have a directory modules/layout/default which has a view that has a header and footer subviews. Then reaching the index route would flow something like this:

app/router.js =>app/modules/home/router.js =>app/modules/home/module.js@index =>setContentView(从app/modules/home/views/index.js查看)"

路由

我会有一个应用路由器位于例如www/app/router.js 可能有一些特殊的路由,但主要只是使用指向子路由器的对象进行子路由:

I would have a app router located at e.g. www/app/router.js which could have some special routes but would largely just subroute with an object that pointed at sub routers:

subRouters: {
    'store-locator': StoreLocatorRouter,
    myaccount: MyAccountRouter,
    sitemap: SitemapRouter
}

我会通过扩展普通的 Backbone 路由器来实现这一点(注意在你的扩展中你需要在 initialize 中调用 initSubRouters) -

I would make this possible by extending the normal Backbone router with something like (note in your extension you need to call initSubRouters in initialize) -

define([
    'underscore',
    'backbone'
],
function(_, Backbone) {

    'use strict';

    /**
     * Extended Backbone Boilerplate Router
     * @class extensions/router
     * @extends backbone/view
     */
    var Router = Backbone.Router.extend(
        /** @lends extensions/router.prototype */
        {

        /**
         * Holds reference to sub-routers
         * @type {Object}
         */
        subRouters: {},

        /**
         * Adds sub-routing
         * based on https://gist.github.com/1235317
         * @param {String} prefix The string to be prefixed to the route values
         */
        constructor: function(options) {
            if (!options) {
                options = {};
            }

            var routes = {}, prefix = options.prefix;

            if (prefix) {
                // Ensure prefixes have exactly one trailing slash
                prefix.replace(//*$/, '/');
            } else {
                // Prefix is optional, set to empty string if not passed
                prefix = '';
            }

            if (prefix) {
                // Every route needs to be prefixed
                _.each(this.routes, function(callback, path) {
                    if (path) {
                        routes[prefix + '/' + path] = callback;
                    } else {
                        // If the path is "" just set to prefix, this is to comply
                        // with how Backbone expects base paths to look gallery vs gallery/
                        routes[prefix + '(/)'] = callback;
                    }
                });

                // Must override with prefixed routes
                this.routes = routes;
            }

            // .navigate needs subrouter prefix
            this.prefix = prefix;

            // Required to have Backbone set up routes
            Backbone.Router.prototype.constructor.apply(this, arguments);
        },

        /**
         * Sets up 'beforeRoute' event.
         */
        initialize: function() {
            // This is a round about way of adding a beforeRoute event and must
            // happen before any other routes are added.
            Backbone.history.route({
                test: this.beforeRoute
            }, function() {});
        },

        /**
         * Called before routes.
         * @return {Boolean} false This ensures the 'route' is disabled.
         */
        beforeRoute: function() {
            Backbone.history.trigger('beforeRoute');
            return false;
        },

        /**
         * Adds prefix to navigation routes
         * @param  {String} route   Non-prefixed route
         * @param  {Object} options Passed through to Backbone.router.navigate
         */
        navigate: function(route, options) {
            if (route.substr(0, 1) !== '/' && route.indexOf(this.prefix.substr(0,
                this.prefix.length - 1)) !== 0) {
                route = this.prefix + route;
            }
            Backbone.Router.prototype.navigate.call(this, route, options);
        },

        /**
         * Initializes sub-routers defined in `this.subRouters`
         */
        initSubRouters: function() {
            _.each(this.subRouters, function(Router, name) {
                this[name] = new Router({
                    prefix: name
                });
            }, this);
        }

    });

    return Router;
});

这篇关于骨干项目组织的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆