2012-01-27 10 views
8

Jest to nieco wymyślny przykład, ale uważam, że ma to znaczenie.Backbone.js - Jak utworzyć kolekcję, która ma właściwości bindable

Powiedzmy, że mam kolekcję samochodów szkieletowych. A do kolekcji potrzebuję właściwości o nazwie isValid. Chcę, aby inne obiekty były w stanie powiązać z isValid i uruchomić funkcję, gdy zmieni się wartość isValid. Kolekcja właściwości właściwośćValid zostanie zmieniona na podstawie modeli w kolekcji jako całości.

Na przykład, jeśli wszystkie drzwi są zablokowane w każdym samochodzie, wartość isValid powinna zmienić się na true. Gdy wartość isValid ulegnie zmianie, wszystkie funkcje, które są powiązane ze zdarzeniem Zmiana wielkości, powinny zostać uruchomione.

Moje pytanie brzmi: jak utworzyć kolekcję, która ma właściwości wiążące, która działa podobnie do właściwości modelu?

To jest kod, który chciałbym dostać do pracy.

var Car = Backbone.Model.extend({}); 
var Cars = Backbone.Collection.extend({ 
    model: Car, 
    url: "Cars", 
    isValid: false, //Here's the property that I want bindable 
        //but do not know how to accomplish this. 
}); 

var cars = new Cars(); 

//Call a function when cars.isValid changes 
cars.bind("change:isValid", carsIsValidChanged, cars) 

//Not sure if this what the function would look like 
//but would need access to cars.isValid or just the car collection 
function carsIsValidChanged(isValid){ 
    console.log(isValid); 
} 


Aktualizacja

Zobacz moje kompletne rozwiązanie jako odpowiedź poniżej.

Odpowiedz

5

Tak więc nie ma "wbudowanego" sposobu reagowania na zmiany właściwości w kolekcji, ponieważ tak naprawdę nie ma obsługiwanej metody (o której mi wiadomo), aby mieć właściwości w kolekcji. Jednak, jak sądzę, wciąż jest to możliwe. (Testowane, ale powinien być dość blisko)

var Car = Backbone.Model.extend({}); 
var Cars = Backbone.Collection.extend({ 
    model: Car, 
    url: "Cars", 
    initialize: function() { 
     var self = this; 
     this.isValid = false; 
     this.bind('add', function() { 
      var curValid = true; 
      self.each(function(car) { 
      if(car.get('is_locked') != true) { 
       curValid = false; 
      } 
      }); 
      if(curValid != self.isValid) { 
      self.isValid = curValid; 
      self.trigger('change:isValid', self.isValid, self); 
      } 
     }); 
    }     
}); 

// instantiate a new Cars collection 
var cars = new Cars(); 


function carsIsValidChanged(isValid){ 
    console.log(isValid); 
} 

//Call a function when cars.isValid changes 
cars.bind("change:isValid", carsIsValidChanged) 

Jedna uwaga: nie chcesz IsValid: wymienione jako właściwości jak byś model lub URL. Wydaje się, że kręgosłup jest dziwny, a twoja klasa isValid może obejmować wszystkie instancje kolekcji. Lepiej zdefiniować je jako inicjator, a następnie za każdym razem, gdy tworzysz instancję tej kolekcji, będziesz mieć instancję isValid dostępną przez this.isValid.

+0

Dzięki za odpowiedź. Wierzę, że wskazałeś mi właściwy kierunek. Doceń informację, że wartośćValid powinna zostać utworzona podczas inicjowania. Kiedy skończę z moim rozwiązaniem roboczym, opublikuję to. –

2

Oto mój kompletne rozwiązanie na to pytanie: jsFiddle Complete example

@spotmat umieścić mnie w odpowiednim kierunku, ale konieczne, aby dodać dodatkową funkcjonalność. Oto kilka rzeczy, które musiały zostać rozwiązane:

  • Collection contructor musi obsługiwać dane pased niej
  • Kiedy model IsLocked nieruchomość jest aktualizowany zbiór musi być ponownie sprawdzane

I nie jestem pewien, czy dodanie właściwości wiążących do kolekcji jest świetnym pomysłem, ale było zabawne, aby dowiedzieć się, i wiele się nauczyłem.

var log = {};//this is for debugging 
_.extend(log, Backbone.Events); 

var Car = Backbone.Model.extend({}); 

var Cars = Backbone.Collection.extend({ 
    model: Car, 
    url: "scripts/data/Cars.json", 
initialize: function() { 
    var _this = this; 

    this.isValid = false; //user should bind to "change:isValid" 
    this._isPending = false; //This is so that 

    this.bind("add", this.modelAdded, this); 
    this.bind("reset", this.modelsAdded, this); 

    if (this.length > 0) { 
     //Model passed in though the constructor will not be binded 
     //so call modelsAdded 
     this.modelsAdded(this); 
    } 
}, 

modelsAdded: function (collection) { 
    this._isPending = true 
    log.trigger("log", ["modelsAdded: " + collection.length]); 
    collection.each(this.modelAdded, this); //Do nut remove "this" param. It need when modelAdded gets called 
    this._isPending = false 
    this._validate(); 
}, 

modelAdded: function (model) { 
    log.trigger("log", ["modelAdded: " + model.get("Model") + "; IsLocked: " + model.get("IsLocked")]); 
    //!!!for each model added to the colleciton bind 
    //its IsLocked property to the collection modelIsLockedChange function 
    model.bind("change:IsLocked", this.modelIsLockedChanged, this); 
    if (this._isPending == false) { 
     this._validate(); 
    } 
}, 

modelIsLockedChanged: function (model) { 
    log.trigger("log", ["modelIsLockedChanged:" + model.get("Model") + "; IsLocked: " + model.get("IsLocked")]); 
    this._validate(); 
}, 

_validate: function() { 
    var isValid = true; 
    this.each(function (model) { 
     if (model.get("IsLocked") == false) { 
      isValid = false 
     } 
    }); 

    if (this.isValid != isValid) { 
     this.isValid = isValid 
     cars.trigger("change:isValid", [this.isValid]) 
    } 
    }, 
}); 

Pogląd ten jest tylko do celów debugowania

var Logger = Backbone.View.extend({ 
    el: $("#output"), 

    initialize: function(){ 
     log.bind("log", this.logMessage, this) 
    }, 

    render: function(){ 
     return $(this.el).html(""); 
    }, 

    logMessage: function(message){ 
     $(this.el).find("ul").append("<li>" + message[0] + "</li>"); 
    } 
}) 

Poniższy kod służy do testowania kolekcji

var logger = new Logger(); 
log.bind("log", function(message){ 
    console.log(message[0]); 
}); 

var carsData = [ 
    { "Model": "Chevy Camero", "Year": 1982, "IsLocked": true }, 
    { "Model": "Ford F-150", "Year": 2011, "IsLocked": false } 
] 

var cars = new Cars(carsData); 
cars.bind("change:isValid", function(isValid){ 
    log.trigger("log", ["change:isValid: " + isValid]); 
}); 

cars.add({ "Model": "Toyota Tacoma", "Year": 2006, "IsLocked": true }); 
cars.at(1).set({ "IsLocked": true }); 
5

Można użyć coś takiego;

Backbone.Collection.prototype.bindableProperty = function (key, val) { 
    var self = this; 
    Object.defineProperty(this, key, { 
     configurable: false, 
     get: function() { return val; }, 
     set: function (v) { 
      val = v; 
      self.trigger('change:'+key, val) 
     } 
    }); 
}; 

Po prostu zrób to, aby dodać właściwość;

var Cars = Backbone.Collection.extend({ 
    model: Car, 
    url: "Cars", 
    initialize: function() { 
    this.bindableProperty('isValid', false); 
    } 
}); 

Ostrzeżenie jednak; starsze wersje IE nie obsługują Object.defineProperty.

+0

Spróbuję tego dziś wieczorem. –

+1

Dziękuję Stephen Belanger za odpowiedź. Dostarczone rozwiązanie jest prawdopodobnie najczystsze, ale jak już wspomniano, nie działa z IE8. Mimo że musiałem napisać trochę więcej kodu, aby mój przykład działał zgodnie z zaleceniami, nie muszę już ręcznie wywoływać następującego kodu, co jest bardzo miłe: 'cars.trigger (" change: isValid " , [this.isValid]) ' Teraz, gdy właściwość isValid zostanie zmieniona, zdarzenie zostanie automatycznie wywołane. [Przykład jsFiddle] (http://jsfiddle.net/BarDev/ae63d/) –

+0

Dla każdego, kto może to przeczytać w przyszłości; działa w IE9 + i częściowo w IE8, jak wyjaśniono tutaj; https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperty#ie8-specific –

Powiązane problemy