Backbone project organization

前端 未结 1 1713
孤城傲影
孤城傲影 2021-01-15 02:36

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 dynamic

1条回答
  •  挽巷
    挽巷 (楼主)
    2021-01-15 03:38

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

    Divide into two directories

    1. The server directory e.g server/
    2. The publicly accessible directory e.g. www/

    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.

    Extend Backbone

    Your entire app will consist of:

    • Views & sub views
    • Routers & sub routers
    • Models
    • Collections

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

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

    Use pod architecture

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

    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

    Start seeing your app in terms of views and sub views

    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

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

    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.

    Do communication with models

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

    Have an app state

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

    Have a packages folder (optional)

    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 a folder where you extend inter-app stuff

    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.

    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.

    assets

    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

    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

    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 but for AMD it would be more like:

    
    
    
    

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

    Layouts

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

    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(view from app/modules/home/views/index.js)"

    Routing

    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
    }
    

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

    project layout

    app layout

    0 讨论(0)
提交回复
热议问题