Подтвердить что ты не робот

Как сделать элементы коллекции Backbone.js уникальными?

Скажем, у меня есть эта модель Backbone.js:

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

var truck1 = new Truck();
var truck2 = new Truck();

truck1.set("brand", "Ford");
truck2.set("brand", "Toyota");
truck3.set("brand", "Honda");
truck4.set("brand", "Ford");

Тогда скажем, у нас есть коллекция Backbone.js:

var TruckList = Backbone.Collection.extend({
  model: Truck,
  comparator: function(truck) {
     return truck.get("brand");
  };

});

Я коллекционер автомобилей, поэтому добавьте каждый автомобиль в свою коллекцию:

Trucks = new TruckList();
Trucks.add(truck1);
Trucks.add(truck2);
Trucks.add(truck3);
Trucks.add(truck4);

Просто сосредоточив внимание на атрибуте бренда, truck4 является дубликатом truck1. У меня нет дубликатов в моей коллекции. Мне нужна моя коллекция, чтобы иметь уникальные значения.

Мой вопрос: как удалить повторяющиеся элементы из моей коллекции Backbone.js?

Должен ли я использовать Underscore.js для этого? Если да, может ли кто-нибудь предоставить рабочий/исполняемый пример того, как это сделать.

Предположим следующее:

1.Сбор не сортируется

  • Удаление должно производиться по значению атрибута бренда

  • Ajax вызов для заполнения каждого экземпляра Truck. Это означает, что при добавлении в коллекцию у вас нет доступа к свойствам Truck.

4b9b3361

Ответ 1

Я бы переопределил метод add в вашей коллекции TruckList и использовал знак подчеркивания для обнаружения дубликатов и отказа от дубликата. Что-то вроде.

TruckList.prototype.add = function(truck) {
    // Using isDupe routine from @Bill Eisenhauer answer
    var isDupe = this.any(function(_truck) { 
        return _truck.get('brand') === truck.get('brand');
    });

    // Up to you either return false or throw an exception or silently ignore
    // NOTE: DEFAULT functionality of adding duplicate to collection is to IGNORE and RETURN. Returning false here is unexpected. ALSO, this doesn't support the merge: true flag.
    // Return result of prototype.add to ensure default functionality of .add is maintained. 
    return isDupe ? false : Backbone.Collection.prototype.add.call(this, truck);
}

Ответ 2

Самый простой способ добиться этого - убедиться, что модели, которые вы добавляете, имеют уникальные идентификаторы. По умолчанию коллекции Backbone не будут добавлять модели с дублирующимися идентификаторами.

test('Collection should not add duplicate models', 1, function() {
    var model1 = {
        id: "1234"
    };
    var model2 = {
        id: "1234"
    };

    this.collection.add([model1, model2]);

    equal(1, this.collection.length, "collection length should be one when trying to add two duplicate models");
});

Ответ 3

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

TruckList.prototype.add = function(newTruck) {
  var isDupe = this.any(function(truck) { 
    return truck.get('brand') === newTruck.get('brand');
  }
  if (isDupe) return;
  Backbone.Collection.prototype.add.call(this, truck);
}

В стороне, я бы, вероятно, написал функцию на Truck, чтобы выполнить проверку обмана, чтобы сборник не знал слишком много об этом условии.

Ответ 4

var TruckList = Backbone.Collection.extend({
model : Truck,

// Using @Peter Lyons' answer
add : function(truck) {
    // Using isDupe routine from @Bill Eisenhauer answer
    var isDupe = this.any(function(_truck) {
        return _truck.get('brand') === truck.get('brand');
    });
    if (isDupe) {
        // Up to you either return false or throw an exception or silently
        // ignore
        return false;
    }
    Backbone.Collection.prototype.add.call(this, truck);
},

comparator : function(truck) {
    return truck.get("brand");
} });

Ответ VassilisB работал отлично, но он переопределит поведение Backbone Collection add(). Поэтому при попытке сделать это могут возникнуть ошибки:

var truckList = new TruckList([{brand: 'Ford'}, {brand: 'Toyota'}]); 

Итак, я добавил немного проверки, чтобы избежать этих ошибок:

var TruckList = Backbone.Collection.extend({
    model : Truck,

    // Using @Peter Lyons' answer
    add : function(trucks) {
        // For array
        trucks = _.isArray(trucks) ? trucks.slice() : [trucks]; //From backbone code itself
        for (i = 0, length = trucks.length; i < length; i++) {
            var truck = ((trucks[i] instanceof this.model) ? trucks[i]  : new this.model(trucks[i] )); // Create a model if it a JS object

            // Using isDupe routine from @Bill Eisenhauer answer
            var isDupe = this.any(function(_truck) {
                return _truck.get('brand') === truck.get('brand');
            });
            if (isDupe) {
                // Up to you either return false or throw an exception or silently
                // ignore
                return false;
            }
            Backbone.Collection.prototype.add.call(this, truck);
       }
    },

    comparator : function(truck) {
        return truck.get("brand");
    }});

Ответ 5

Я делаю FileUpload с той же проблемой, и вот как я это сделал (coffeescript):

File = Backbone.Model.extend
    validate: (args) ->
        result
        if [email protected](args)
            result = 'File already in list'
        result

Files = Backbone.Collection.extend
    model: File

    isUniqueFile: (file) ->
        found
        for f in @models
            if f.get('name') is file.name
                found = f
                break
        if found
            false
        else
            true

... и что это. Объект коллекции автоматически ссылается в файле, и валидация автоматически вызывается, когда действие вызывается в коллекции, которая в этом случае является Add.

Ответ 7

Не уверен, что это обновление для базовой или нижней линии, но функция where() работает в Backbone 0.9.2, чтобы выполнить поиск для вас:

TruckList.prototype.add = function(truck) {
    var matches = this.where({name: truck.get('brand')});

    if (matches.length > 0) {
        //Up to you either return false or throw an exception or silently ignore
        return false;
    }

    Backbone.Collection.prototype.add.call(this, truck);
}

Ответ 8

Я бы предпочел переопределить метод add, подобный этому.

var TruckList = Backbone.Collection.extend({
model : Truck,

// Using @Peter Lyons' answer
add : function(truck) {
    // Using isDupe routine from @Bill Eisenhauer answer
    var isDupe = this.any(function(_truck) {
        return _truck.get('brand') === truck.get('brand');
    });
    if (isDupe) {
        // Up to you either return false or throw an exception or silently
        // ignore
        return false;
    }
    Backbone.Collection.prototype.add.call(this, truck);
},

comparator : function(truck) {
    return truck.get("brand");
} });

Ответ 9

Попробуйте это...

var TruckList = Backbone.Collection.extend({
  model: Truck,
  comparator: function(truck) {
     return truck.get("brand");
  },
  wherePartialUnique: function(attrs) {
    // this method is really only tolerant of string values.  you can't do partial
    // matches on arrays, objects, etc.  use collection.where for that
      if (_.isEmpty(attrs)) return [];
      var seen = [];
      return this.filter(function(model) {
        for (var key in attrs) {
          // sometimes keys are empty. that bad, so let not include it in a unique result set
          // you might want empty keys though, so comment the next line out if you do.
          if ( _.isEmpty(model.get(key).trim()) ) return false;
          // on to the filtering...
          if (model.get(key).toLowerCase().indexOf(attrs[key].toLowerCase()) >= 0) {
            if (seen.indexOf( model.get(key) ) >= 0 ) return false;
            seen.push(model.get(key));
            return true;
          } else {
            return false;
          }
        }
        return true;
      });
    }
});

Несколько вещей, которые нужно запомнить:

  • это основано на методе backbone.collection.where и в отличие от этого метода, он попытается выполнить частичные совпадения атрибутов модели внутри коллекции. Если вы этого не хотите, вам нужно будет изменить его, чтобы он точно соответствовал. Просто подражайте тому, что вы видите в оригинальном методе.

  • он должен иметь возможность принимать несколько совпадений атрибутов, поэтому, если у вас есть атрибуты модели foo и bar, вы должны иметь возможность делать collection.wherePartialUnique({foo: "you", bar: "dude" }). Однако я этого не тестировал.:) Я только что сделал одну пару ключ/значение.

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

  • Этот метод не требует набора уникальных свойств модели, от которых зависит компаратор. Это больше похоже на отдельный запрос sql, но я не парень sql, поэтому не стреляйте в меня, если это плохой пример:)

  • ваша коллекция сортируется с помощью функции компаратора, поэтому одно из ваших предположений о ее сортировке неверно.

Я считаю, что это также касается всех ваших целей:

  • Коллекция не сортируется
  • Удаление должно выполняться по значению атрибута бренда
  • Ajax вызов для заполнения каждого экземпляра Truck. Это означает, что при добавлении в коллекцию у вас нет доступа к свойствам Truck.

Ответ 10

Кажется, что элегантным решением будет использование _.findWhere, если у вас есть уникальный атрибут (бренд в вашем случае). _.findWhere вернет совпадение, являющееся объектом JavaScript, и, следовательно, правдивым или undefined, который является ложным. Таким образом, вы можете использовать один оператор if.

var TruckList = Backbone.Collection.extend({
  model: Truck,

  add: function (truck) {
    if (!this.findWhere({ brand: truck.get('brand') })) {
      Backbone.Collection.prototype.add.call(this, truck);
    }
  }
});

Ответ 11

Я действительно недоволен принятым ответом на это решение. Он содержит многочисленные ошибки. Я отредактировал оригинальное решение, чтобы подчеркнуть мои проблемы, но я предлагаю следующее решение, предполагающее, что вы хорошо загрязняете свое дублирующее свойство id/cid:

TruckList.prototype.add = function(truckToAdd, options) {
    //  Find duplicate truck by brand:
    var duplicateTruck = this.find(function(truck){
        return truck.get('brand') === truckToAdd.get('brand');
    });

    //  Make truck an actual duplicate by ID: 
    //  TODO: This modifies truckToAdd ID. This could be expanded to preserve the ID while also taking into consideration any merge: true options.
    if(duplicateTruck !== undefined){
        if(duplicateTruck.has('id')){
            truckToAdd.set('id', duplicateTruck.get('id'), { silent: true });
        }
        else {
            truckToAdd.cid = duplicateTruck.cid;
        }
    }

    //  Allow Backbone to handle the duplicate instead of trying to do it manually.
    return Backbone.Collection.prototype.add.call(this, truckToAdd, options);
}

Единственный недостаток этого заключается в том, что идентификатор truckToAdd ID/cid не сохраняется. Однако это сохраняет все ожидаемые функциональные возможности добавления элемента в коллекцию, включая передачу merge: true.

Ответ 12

Я не был удовлетворен предоставленными ответами по нескольким причинам:

  • Неверное изменение возвращаемого значения add.
  • Не поддерживается { merge: true }.

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

add: function (models, options) {
    var preparedModels;

    if (models instanceof Backbone.Collection) {
        preparedModels = models.map(this._prepareModelToAdd.bind(this));
    }
    else if (_.isArray(models)) {
        preparedModels = _.map(models, this._prepareModelToAdd.bind(this));
    } else if (!_.isNull(models) && !_.isUndefined(models)) {
        preparedModels = this._prepareModelToAdd(models);
    } else {
        preparedModels = models;
    }

    //  Call the original add method using preparedModels which have updated their IDs to match any existing models.
    return Backbone.Collection.prototype.add.call(this, preparedModels, options);
},

//  Return a copy of the given model attributes with the id or cid updated to match any pre-existing model.
//  If no existing model is found then this function is a no-op.
//  NOTE: _prepareModel is reserved by Backbone and should be avoided.
_prepareModelToAdd: function (model) {
    //  If an existing model was not found then just use the given reference.
    var preparedModel = model;
    var existingModel = this._getExistingModel(model);

    //  If an existing model was found then clone the given reference and update its id.
    if (!_.isUndefined(existingModel)) {
        preparedModel = this._clone(model);
        this._copyId(preparedModel, existingModel);
    }

    return preparedModel;
},

//  Try to find an existing model in the collection based on the given model brand.
_getExistingModel: function (model) {
    var brand = model instanceof Backbone.Model ? model.get('brand') : model.brand;
    var existingModel = this._getByBrand(brand);
    return existingModel;
},

_getByBrand: function (brand) {
    return this.find(function (model) {
        return model.get('brand') === brand;
    });
},

_clone: function (model) {
    //  Avoid calling model.clone because re-initializing the model could cause side-effects.
    //  Avoid calling model.toJSON because the method may have been overidden.
    return model instanceof Backbone.Model ? _.clone(model.attributes) : _.clone(model);
},

//  Copy the model id or cid onto attributes to ensure Backbone.Collection.prototype.add treats attributes as a duplicate.
_copyId: function (attributes, model) {
    if (model.has('id')) {
        attributes.id = model.get('id');
    } else {
        attributes.cid = model.cid;
    }
}