4

私はBackboneとBackbone.Marionetteにかなり慣れていません。ページング(最初、前、次、最後のページ)、クイック検索(キーが押されるたびにトリガーされる)、ページに表示されるアイテムの数の選択(5 、10、すべて、...)

何かが機能するようになったので、それを改善し、これらの機能を一種の再利用可能なコンポーネントとして作成しようとしましたが、正確な方法がわかりません。すでに行った作業を改善する方法がわかりません。

たとえば、すべてを書き直すことなく、データグリッドによって管理されるコレクション/モデルを変更できるようにしたいと考えています。これは私がそれを行う方法に自信がないところです、そしてそれはおそらく知識の欠如によるものです。したがって、さらに進んでいくためのあなたの意見やアドバイスは本当に感謝され、歓迎されます。

// JST and HAML Assets is used for the templating pre-compilation
Backbone.Marionette.Renderer.render = function(template, data) {
  if (!JST[template]) {
    throw "Template '" + template + "' not found!";
  }
  return JST[template](data);
};

window.MyApp = new Backbone.Marionette.Application();

MyApp.addRegions({
  content: ".content-box"
});

MyApp.Datagrid = (function() {
  var Datagrid, ItemPerPageView, Layout, PagerView, QuickSearchView, Theme, ThemeView, Themes, ThemesView;

  Datagrid = {};

  Layout = Backbone.Marionette.Layout.extend({
    template: "layouts/grid",
    regions: {
      grid: "#grid",
      quickSearch: "#quickSearch",
      itemPerPage: "#itemPerPage",
      pager: ".pager"
    }
  });

  Theme = Backbone.Model.extend();

  Themes = Backbone.ExtendedCollection.paginatedCollection.extend({
    url: "/themes",
    model: Theme,

    initialize: function() {
      var _this = this;

      MyApp.vent.on("quickSearch:term", function(term) {
        _this.quickSearch(term);
      });

      MyApp.vent.on("itemPerPage:count", function(count) {
        _this.perPage(count);
      });

      MyApp.vent.on("pager:previous", function() {
        _this.previous();
      });

      MyApp.vent.on("pager:next", function() {
        _this.next();
      });

      MyApp.vent.on("pager:first", function() {
        _this.first();
      });

      MyApp.vent.on("pager:last", function() {
        _this.last();
      });
    }
  });

  ThemeView = Backbone.Marionette.ItemView.extend({
    tagName: "tr",
    template: "theme",
    model: Theme,

    events: {
      "click span": "edit",
      "blur input": "save"
    },

    edit: function(event) {
      var id, span;
      id = this.model.get("id");
      span = $("span", this.el).hide();
      $("input", this.el).show().focus().val(span.text());
    },

    save: function(event) {
      var id, input, span;
      id = this.model.get("id");
      span = $("span", this.el).show();
      input = $("input", this.el).hide();
      if (this.model.get("name") !== input.val()) {
        this.model.set("name", input.val());
        this.model.save();
      }
      span.text(this.model.get("name"));
    }
  });

  ThemesView = Backbone.Marionette.CompositeView.extend({
    template: "index",
    model: Theme,
    itemView: ThemeView,
    collection: Themes,
    itemViewContainer: "#themes",

    serializeData: function() {
      return this.data;
    }
  });

  QuickSearchView = Backbone.Marionette.View.extend({
    el: "#quickSearch",

    events: {
      "keyup input": "search"
    },

    search: function(event) {
      var searchTerm;
      searchTerm = this.$("input").val().trim();
      MyApp.vent.trigger("quickSearch:term", searchTerm);
    }
  });

  ItemPerPageView = Backbone.Marionette.View.extend({
    el: "#itemPerPage",

    events: {
      "change select": "count"
    },

    count: function(event) {
      var count;
      count = this.$("select").val();
      MyApp.vent.trigger("itemPerPage:count", count);
    }
  });

  PagerView = Backbone.Marionette.View.extend({
    el: ".pager",

    events: {
      "click #next": "next",
      "click #previous": "previous",
      "click #first": "first",
      "click #last": "last"
    },

    first: function(event) {
      MyApp.vent.trigger("pager:first");
    },

    last: function(event) {
      MyApp.vent.trigger("pager:last");
    },

    next: function(event) {
      MyApp.vent.trigger("pager:next");
    },

    previous: function(event) {
      MyApp.vent.trigger("pager:previous");
    }
  });

  Datagrid.initializeLayout = function() {
    var collection;

    Datagrid.layout = new Layout();

    Datagrid.layout.on("show", function() {
      MyApp.vent.trigger("layout:rendered");
    });

    MyApp.content.show(Datagrid.layout);

    collection = new Themes();
    collection.fetch();

    collection.on("reset", function() {
      return Datagrid.layout.grid.show(new ThemesView({
        collection: collection
      }));
    });
  };

  MyApp.vent.on("layout:rendered", function() {
    var itemPerPageView, pagerView, quickSearchView;

    quickSearchView = new QuickSearchView();
    Datagrid.layout.quickSearch.attachView(quickSearchView);

    itemPerPageView = new ItemPerPageView();
    Datagrid.layout.itemPerPage.attachView(itemPerPageView);

    pagerView = new PagerView();
    Datagrid.layout.pager.attachView(pagerView);
  });

  return Datagrid;
})();

MyApp.addInitializer(function() {
  MyApp.Datagrid.initializeLayout();
});

$(document).ready(function() {
  return MyApp.start();
});

編集1:

与えられた答えと私自身の考えに基づいて、私は解決策の最初の草案を書きました。実際の再利用可能なコンポーネントを作成することに成功しませんでしたが、コードを統合するソリューションがあります。一部の部分はリファクタリングして改善する必要があります。後のリファクタリングで解決したいいくつかの落とし穴もあります。

コンテキストを追加するために、アプリケーションはRailsをバックエンドとして使用して作成されています。だから私のjavascriptフォルダ構造があります

assets
|--javascripts
   |--application.js
   |--admin
      |--admin.js
      |--admin.layout.js
      |--subthemes
         |--admin.subtheme.controller.js
         |--admin.subtheme.view.js
      |--themes
         |--admin.theme.controller.js
         |--admin.theme.view.js
|--templates
   |--admin
      |--subthemes
         |--index.hamlc
         |--subtheme.hamlc
      |--themes
         |--index.hamlc
         |--theme.hamlc
   |--layouts
      |--grid.hamlc

まず、application.jsが起動します。Rails 3.2のアセットパイプラインは、期待どおりに依存関係を準備します。

//= require underscore
//= require backbone
//= require backbone.marionette
//= require_tree ./lib/backbone
//= require hamlcoffee
//= require i18n
//= require i18n/translations
//= require_tree ../templates/
//= require_tree ./admin
//= require_tree ./admin/theme
//= require_tree ./admin/subtheme

I18n.defaultLocale = "en";


Backbone.Marionette.Renderer.render = function(template, data) {
  if (!JST[template]) {
    throw "Template '" + template + "' not found!";
  }
  return JST[template](data);
};

$(document).ready(function() {
  return MyApp.start();
});

これで、管理部分を準備して開始できます。

var AdminRouter, TempView;

// Create the application for admin part
MyApp.Admin = new Backbone.Marionette.Application();

// Define a router to handle the grid collection type change
AdminRouter = Backbone.Marionette.AppRouter.extend({
  initialize: function() {
    var _this = this;

    // Route quite generic to easily change the data in the grid
    this.route(/^admin\/(.*?)$/, "changeCollection");

    // Manage event to handle the navigation on client side
    MyApp.Admin.vent.on("admin:navigate", function(link) {
      _this.navigate(link, {
        trigger: true
      });
    });
  },

  // Trigger an event to change the collection if one exist for the URL
  changeCollection: function(collectionName) {
    MyApp.Admin.vent.trigger("grid:collection:change", collectionName);
  }
});

// Side menu that allows changing the collection in the data grid
SideMenuView = Backbone.Marionette.View.extend({
  el: ".side-menu",

  events: {
    "click a": "handleClick"
  },

  // Prevent the normal behavior on the link click
  handleClick: function(event) {
    event.preventDefault();
    MyApp.Admin.vent.trigger("admin:navigate", $(event.target).attr("href"));
  }
});

// Add the initializer to the main application to prepare the admin part (grid)
MyApp.addInitializer(function() {
  new SideMenuView();
  new AdminRouter();
  Backbone.history.start({
    pushState: true
  });
  MyApp.Admin.start();
});

次に、データグリッド部分を定義できます。

// This the grid layout module in the admin namespace
MyApp.Admin.module("GridLayout", function(GridLayout, Admin, Backbone, Marionette, $, _) {
  var ItemPageSelectorView, Layout, PagerView, QuickSearchView;

  // The quick search view handle the related fields to do the quick search
  QuickSearchView = Backbone.Marionette.View.extend({
    el: ".gridQuickSearch",

    events: {
      "keyup input": "search"
    },

    // Get the field content and trigger an event with it
    search: function(event) {
      var searchTerm;
      searchTerm = $(event.target).val().trim();
      $("input", this.$el).val(searchTerm);
      Admin.vent.trigger("grid:quickSearch:term", searchTerm);
    }
  });

  // The item page selecto handle the choice of how many rows should be displayed per page
  ItemPageSelectorView = Backbone.Marionette.View.extend({
    el: ".gridItemPageSelector",

    events: {
      "change select": "count"
    },

    // Get the number of items per page that should be displayed
    count: function(event) {
      var count;
      count = $(event.target).val();
      $("select", this.$el).val(count);
      Admin.vent.trigger("grid:itemPageSelector:count", count);
    }
  });

  // The pager view manage the view components to change the page shown in the data grid
  PagerView = Backbone.Marionette.View.extend({
    el: ".gridPager",

    events: {
      "click #next": "next",
      "click #previous": "previous",
      "click #first": "first",
      "click #last": "last",
      "click #page": "page"
    },

    //
    // The following functions triggers events to go to the right pages
    //
    first: function(event) {
      Admin.vent.trigger("grid:pager:first");
    },

    previous: function(event) {
      Admin.vent.trigger("grid:pager:previous");
    },

    page: function(event) {
      Admin.vent.trigger("grid:pager:page");
    },

    next: function(event) {
      Admin.vent.trigger("grid:pager:next");
    },

    last: function(event) {
      Admin.vent.trigger("grid:pager:last");
    }
  });

  // The grid layout with the regions to display the different part of the data grid
  Layout = Backbone.Marionette.Layout.extend({
    template: "layouts/grid",

    regions: {
      gridTable: "#gridTable",
      gridQuickSearch: ".gridQuickSearch",
      gridItemPageSelector: ".gridItemPageSelector",
      gridPager: ".gridPager"
    }
  });

  // Once the layout is rendered, the different views are attached to the right regions
  Admin.vent.on("grid:layout:rendered", function() {
    var itemPageSelectorView, pagerView, quickSearchView;

    quickSearchView = new QuickSearchView();
    Admin.gridLayout.gridQuickSearch.attachView(quickSearchView);

    itemPageSelectorView = new ItemPageSelectorView();
    Admin.gridLayout.gridItemPageSelector.attachView(itemPageSelectorView);

    pagerView = new PagerView();
    Admin.gridLayout.gridPager.attachView(pagerView);
  });

  // Initializer to do at the application start
  GridLayout.addInitializer(function() {
    Admin.addRegions({
      content: ".content-box"
    });

    Admin.gridLayout = new Layout();

    // Trigger the rendered event when the grid layout is shown
    Admin.gridLayout.on("show", function() {
      Admin.vent.trigger("grid:layout:rendered");
    });

    // Manage the collection data change
    Admin.vent.on("grid:collection:change", function(collectionName) {
      // Close the previous view in the grid table region
      Admin.gridLayout.gridTable.close();

      // Trigger an event to fetch the collection
      Admin.vent.trigger("" + collectionName + ":collection:fetch");

      // Show the grid layout if not already done
      if (!this.shown) {
        this.shown = true;
        Admin.content.show(Admin.gridLayout);
      }
    });
  });

  return GridLayout;
});

構造コードが完成しました。これで、コントローラーの1つに移動できます。たとえば、ThemeController:

MyApp.Admin.module("ThemeController", function(ThemeController, Admin, Backbone, Marionette, $, _) {
  // Define the model to use in the collection
  ThemeController.Theme = Backbone.Model.extend();

  // Define the collection with the related url on the server. The collection extends a paginated collection that has the methods to manage the quick search and the pagination
  ThemeController.Themes = Backbone.ExtendedCollection.paginatedCollection.extend({
    url: "/admin/themes",

    model: ThemeController.Theme,

    initialize: function() {
      var _this = this;

      //
      // The following functions handle the events for the quick search and pagination
      //

      Admin.vent.on("grid:quickSearch:term", function(term) {
        _this.quickSearch(term);
      });

      Admin.vent.on("grid:itemPageSelector:count", function(count) {
        _this.perPage(count);
      });

      Admin.vent.on("grid:pager:previous", function() {
        _this.previous();
      });

      Admin.vent.on("grid:pager:next", function() {
        _this.next();
      });

      Admin.vent.on("grid:pager:first", function() {
        _this.first();
      });

      return MyApp.Admin.vent.on("grid:collection:fetched", function() {
        Admin.gridLayout.gridTable.show(new Admin.ThemeView.Table({
          collection: _this
        }));
      });
    }
  });

  // At the application initilization, we need to be sure this controller can 
  // handle the event to fetch the data from the server
  Admin.addInitializer(function() {
    Admin.vent.on("themes:collection:fetch", function() {
      ThemeController.themes = new ThemeController.Themes();

      // Once the data are fetched from the server, trigger an event to display them
      ThemeController.themes.fetch({
        success: function() {
          Admin.vent.trigger("grid:collection:fetched");
        }
      });
    });
  });
});

そして最後に、前のコントローラーのビュー:

MyApp.Admin.module("ThemeView", function(ThemeView, Admin, Backbone, Marionette, $, _) {
  // The view to show one item in a row of the data grid
  ThemeView.Item = Backbone.Marionette.ItemView.extend({
    tagName: "tr",
    template: "admin/themes/theme",
    model: Admin.ThemeController.Theme
  });

  // The view to show the collection of item
  ThemeView.Table = Backbone.Marionette.CompositeView.extend({
    template: "admin/themes/index",
    model: Admin.ThemeController.Theme,
    itemView: ThemeView.Item,
    collection: Admin.ThemeController.Themes,
    itemViewContainer: "#themes",

    // ! I was force to add this to have data in the original format that is used by my templates !
    serializeData: function() {
      return this.data;
    }
  });
});

備考:サブテーマコントローラーとビューファイルには、まったく同じ種類のコードが含まれています。テンプレートと種類のものだけが異なります。

Railsアセットパイプラインを介してコンパイルされたHAMLのグリッドレイアウトは次のようになります。

.gridPager
  %button#first= "<<"
  %button#previous= "<"
  %button#next= ">"
  %button#last= ">>"

%span.gridItemPageSelector= "Item per page"
  %select
    %option= 5
    %option= 10
    %option{"value" => -1}= "All"

%span.gridQuickSearch= "Quick search:"
  %input#gridSearchTerm{"type" => "text"}

#gridTable

%span.gridItemPageSelector= "Item per page"
  %select
    %option= 5
    %option= 10
    %option{"value" => -1}= "All"

%span.gridQuickSearch= "Quick search:"
  %input#gridSearchTerm{"type" => "text"}

.gridPager
  %button#first= "<<"
  %button#previous= "<"
  %button#next= ">"
  %button#last= ">>"

ご覧のとおり、かなりの繰り返しがあります。グリッドの上部と下部をすばやく検索してページ付けしたかったのです。現時点では、これを行う最も簡単な方法は、コードを複製することです。後でその方法を見つけるときに変更します。

テーマを示すテーブルのテンプレート:

%table.table.table-striped
  %thead
    %tr
      %th= "Id"
      %th= "Name"
  %tbody#themes

非常にシンプルで、特別なことは何も言いません。現時点では、ヘッダーはハードコーディングされています。

そして最後に、テーマを表示するためのアイテムビューテンプレート:

%td= this.id
%td= this.name

このテンプレートは本当にシンプルです。

私はそれがかなりうまく機能している状況にあります。たとえば、他のリンクをクリックして表示されているコレクションを変更しても、クイック検索フィールドやそのような種類のものは再初期化されません。そのために、コレクションの状態を追跡するための一種の状態管理を追加したいと思いました。すでに表示されているコレクションに戻ったら、以前と同じように表示したいと思います。

私のソリューションは完璧ではなく、多くのリファクタリングが可能であると確信しています。私はおそらく多くの「初心者」の間違いをしました。ですから、私の提案に気軽に挑戦してください。私は自分の解決策を学び、改善しようと努めており、それが誰かがそのようなことをするのに役立つことを願っています。

4

2 に答える 2

2

私は大した専門家ではありませんが、Marionette と Requirejs を使用して、次のようにしました。

a) コレクション、cols config (各サイクルでテーブル ヘッドをレンダリングする)、行ビューなどのパラメーターを使用して、approuter によって呼び出される汎用グリッド レイアウト ビューを作成しました。

showUsers: function(){

    require(['views/GridGen','collections/user_collection'], function(Grid, UserCollection){

        var Users = new UserCollection();

        App.grid = new Grid({collection: Users ,
                             rowView: 'rowUser',
                             cols_config: App.tables.users});

        App.page.show(App.grid);

    });
},

b) グリッド レイアウトで、onShow イベントを待っているさまざまな部分をレンダリングします。

var Grid = Backbone.Marionette.Layout.extend({

regions: {
            top_controls: "#top_controls",
            table_view: "#table_view",
            pagination_controls: "#pagination_controls",
            bottom_controls: "#bottom_controls",
         },

onShow: function(){              
    this.renderTable(this.collection);
},

renderTable: function(collection){

    collection.fetch({success:function(){

          require(['views/'+self.options.rowView+'.js'],function(iView){

                   var vista = new View({collection: collection, itemView: iView, thead: self.options.cols_config});

                   App.grid.table_view.show(vista);

                   self.renderPagination(collection);

                   collection.pager();
               });
          }});
}

c) 私の一般的なテーブル ビューは、次のようにパラメーターとしてレンダリングするために cols と itemView を取ります。

var View = Backbone.Marionette.CompositeView.extend({

initialize: function(){
        this.itemView = this.options.itemView;           
},
serializeData: function(){
        var data = Backbone.Marionette.ItemView.prototype.serializeData.apply(this, arguments);
        data.thead = this.options.thead;
        return data;
},
appendHtml: function(collectionView, itemView, index){
        collectionView.$("tbody").append(itemView.el);
},

これは単なる一般的な考え方です。これが最善の方法だとは思いませんが、まだより良い解決策が見つかりませんでした。少なくともいくつかのヒントを提供したいと考えています:)

于 2012-08-24T23:51:00.587 に答える
1

すぐに拡張可能で再利用可能なbackgrid コンポーネントを強くお勧めします。

バージョン 0.2.6 のみ - ただし、フォローは良好で、かなり滑らか

于 2013-05-27T19:55:12.913 に答える