Backbone.js управление состоянием / инициализация представления на основе фрагмента url

#javascript #model-view-controller #backbone.js #state-management

#javascript #модель-представление-контроллер #backbone.js #управление состоянием

Вопрос:

Я пытаюсь отслеживать состояние в этом приложении, используя Backbone.js:

введите описание изображения здесь

У меня есть «ChartAppModel» с набором значений по умолчанию:

 ChartAppModel = Backbone.Model.extend({

defaults: { 
    countries : [], 
    selectedCountries : [],
    year : 1970,
},

initialize: function() { 
    loadAbunchOfData();
    checkStartState();
}

});
  

Однако, если задан начальный фрагмент, это состояние по умолчанию должно быть перезаписано:

 var startState = $.deparam.fragment(); //using Ben Alman's BBQ plugin
this.set({selectedCountries: startState.s, year: startState.y});
  

Теперь, например, SidebarView готов к обновлению:

 ChartAppViewSidebar = Backbone.View.extend({

initialize: function(){
      this.model.bind("change:selectedCountries", this.render);
},

render : function(){
      [... render new sidebar with check boxes ...]
},
  

Проблема в том, что у меня также есть обработчик событий на боковой панели, который обновляет модель:

 events: {
"change input[name=country]": "menuChangeHandler",
},

menuChangeHandler : function(){
      [... set selectedCountries on model ...]
},
  

Таким образом, будет цикл обратной связи…
И затем, мне также хотелось бы иметь способ ввода нового состояния — поэтому я слушаю изменения модели:

 ChartAppModel = Backbone.Model.extend({

initialize: function() { 
    this.bind("change", this.setState);
}

});
  

… и относительно скоро этот диспетчер состояний рухнет …

Вопросы:

1) Как мне инициализировать свои представления (например, «какие флажки следует установить») на основе фрагмента? (приветствуются любые подсказки о лучших практиках для состояния / начального состояния, которое не является типичным «маршрутом»)

2) Как я могу избежать установки моими представлениями атрибута в модели, который они сами прослушивают?

3) Как я могу передать новое состояние на основе части модели?

Бонус 🙂

4) Как бы вы изложили код для описанного приложения?

Спасибо!

Ответ №1:

Это один четко определенный вопрос!

Возникает вопрос о том, что такое модель. Я полагаю, что существует определение того, что представляет собой модель в мире backbone, и я не уверен, что ваша стратегия соответствует этому определению. Также вы сохраняете состояние как в URL, так и в модели. Вы можете просто сохранить состояние в URL, как я объясню.

Если бы я делал это, было бы 2 представления. Один для элементов управления вашего приложения и вложенный внутри него для вашего графика: GraphView и AppView. Моделью будут данные, которые вы собираетесь отображать, а не состояние интерфейса.

Используйте контроллер для запуска представления приложения, а также для обработки любого состояния интерфейса, определенного в URL.

Возникает вопрос о рычагах управления состоянием в Backbone. Традиционные веб-приложения использовали ссылку / URL в качестве основного рычага управления состоянием, но сейчас все это меняется. Вот одна из возможных стратегий:

 Checkbox Change Event -> Update location fragment -> trigger route in controller -> update the view
Slider Change Event -> Update location fragment -> trigger route in controller -> update the view
  

Самое замечательное в такой стратегии то, что она учитывает случаи, когда URL-адреса передаются по кругу или добавляются в закладки

 Url specified in address bar -> trigger route in controller -> update the view
  

Я попробую пример псевдокода. Для этого я сделаю некоторые предположения о данных:
Данные представляют собой популяцию собак с течением времени (с детализацией в год), где ползунок должен иметь нижнюю и верхнюю границы, и объем данных там слишком велик, чтобы загрузить все это клиенту сразу.

Сначала давайте посмотрим на модель для представления статистических данных. Для каждой точки на графике нам нужно что-то вроде { население: 27000, год: 2003} Давайте представим это как

 DogStatModel extends Backbone.Model ->
  

и сбор этих данных будет

 DogStatCollection extends Backbone.Collection ->
    model: DogStatModel
    query: null // query sent to server to limit results
    url: function() {
        return "/dogStats?" this.query
    }
  

Теперь давайте посмотрим на контроллер. В этой стратегии, которую я предлагаю, контроллер соответствует своему названию.

 AppController extends Backbone.Controller ->
    dogStatCollection: null,
    appView: null,

    routes: {
         "/:query" : "showChart"
    },

    chart: function(query){
        // 2dani, you described a nice way in your question
        // but will be more complicated if selections are not mutually exclusive
        // countries you could have as countries=sweden;franceamp;fullscreen=true
        queryMap = parse(query) //  
        if (!this.dogStatCollection) dogStatCollection = new DogStatCollection
        dogStatCollection.query = queryMap.serverQuery
        if (!this.appView) {
           appView = new AppView()
           appView.collection = dogStatCollection
        }
        appView.fullScreen = queryMap.fullScreen
        dogStatCollection.fetch(success:function(){
          appView.render()
        })            
    }
  

Комментарии:

1. И это был отличный ответ — я начал пересматривать использование «маршрутов». Но, допустим, у меня есть начальное состояние (URL) с 5 параметрами: year = 1979, stacked = true, layout = полноэкранный режим, selected = [Швеция, Финляндия], zoom = 2. На основе этих параметров должны загружаться данные, вычисляться оси x / y и отображаться представления и т.д. Каким был бы подход с использованием маршрутов?? Псевдокод ОЧЕНЬ ПРИВЕТСТВУЕТСЯ : =)

2. спасибо @dani, не был уверен, насколько ясным было мое объяснение. С другой стороны, наличие ползунка под горизонтальной плоскостью может немного запутать пользователя. Я обновлю исходный ответ некоторым псевдокодом.

Ответ №2:

2) Как я могу избежать установки моими представлениями атрибута в модели, который они сами прослушивают?

Вы этого не делаете. Ваша модель должна выполнять проверку любых атрибутов, которые вы пытаетесь обновить, поэтому ваше представление должно прослушиваться на случай, если ваша настройка атрибута не удалась или проверка изменила значение.

Что делает ваше представление, так это то, что оно пытается установить значение для модели, затем модель либо устанавливает его, изменяет данные и устанавливает его, либо отклоняет его. Ваше представление необходимо соответствующим образом обновить.

3) Как я могу передать новое состояние на основе части модели?

 // for each attribute
_.each(["attribute1", "attribute2", "attribute3", ...], _.bind(function(val) {
    // bind the change
    // bind the setState with `this` and the `value` as the first parameter
    this.bind("change:"   value, _.bind(this.setState, this, value));
}, this));
  

Комментарии:

1. 2) имеет смысл — значит, нет проблем с установкой атрибута модели из представления, а затем прослушиванием изменений в том же самом атрибуте? 3) Итак, я в основном привязываюсь к каждому атрибуту модели, для которого я хотел бы вызвать метод setState? Спасибо! ps. Есть какие-либо данные для инициализации / запуска обработки состояния?

2. @dani в двух других у меня нет ввода, потому что я делал игрушки только с backbone 😉 и я не использую jQuery BBQ

3. В любом случае спасибо за вашу помощь : =) (и эти _.привязки — не игрушки!)