2012-05-02 2 views
17

Многих из моих моделей Backbone часто имеет дело с вложенными моделями и коллекциями, до сих пор я использую комбинацию defaults, parse и toJSON вручную для достижения вложенности:лучшее решения для вложенных коллекций Backbone.js

ACME.Supplier = Backbone.Model.extend({ 
    defaults: function() { 
     return { 
      contacts: new ACME.Contacts(), 
      tags: new ACME.Tags(), 
      attachments: new ACME.Attachments() 
     }; 
    }, 

    parse: function(res) { 
     if (res.contacts) res.contacts = new ACME.Contacts(res.contacts); 
     if (res.tags) res.tags = new ACME.Tags(res.tags); 
     if (res.attachments) res.attachments = new ACME.Attachments(res.attachments); 

     return res; 
    } 
}); 

ACME.Tag = Backbone.Model.extend({ 
    toJSON: function() { 
     return _.pick(this.attributes, 'id', 'name', 'type'); 
    } 
}); 

Я посмотрел на несколько плагинов там, которые в основном делают то же, что и выше, но с гораздо меньшим контролем и большим количеством шаблонов, поэтому мне интересно, есть ли у кого-то более элегантное решение этой общей проблемы Backbone.js.


Edit: я закончил с следующим подходом:

ACME.Supplier = Backbone.Model.extend({ 
    initialize: function(options) { 
     this.tags = new ACME.Tags(options.tags); 
    }, 

    parse: function(res) { 
     res.tags && this.tags.reset(res.tags); 

     return res; 
    } 
}); 

ACME.Tag = Backbone.Model.extend({ 
    toJSON: function() { 
     return _.pick(this.attributes, 'id', 'name', 'type'); 
    } 
}); 

Стоит отметить, что позже я обнаружил, что вам нужно передать вложенные данные модели/коллекции из конструктора в конструктор вложенной модели через объект options.

ответ

8

Я не вижу проблем с вашим подходом.

IMHO Model.parse() метод, если для этого: быть перезаписанным в случае необходимости в специальном анализе поведения.

только думаю, что я бы изменить вещи, как это:

if (res.tags) res.tags = new ACME.Tags(res.tags); 

Для этого:

if (res.tags) this.tags.reset(res.tags); 

Из вас уже есть экземпляр ACME.Tags коллекции я бы повторно использовать его.

Также мне не очень нравится реализация defaults, я использую эту инициализацию в Model.initialize(), но я думаю, что это вопрос вкуса.

+0

Я считаю, что этот подход - единственный способ пойти, не прибегая к плагину или подобное, спасибо. –

+0

спасибо, это было очень полезно – Richard

3

I'v выяснил, что с этим подходом функция toJSON для поставщиков будет устаревать, поэтому было бы неплохо провести повторную сборку своего состояния JSON из этого, и это данные для детей.

ACME.Supplier = Backbone.Model.extend({ 
    initialize: function(options) { 
     this.tags = new ACME.Tags(options.tags); 
    }, 

    parse: function(res) { 
     res.tags && this.tags.reset(res.tags); 

     return res; 
    }, 

    toJSON: function({ 
     return _.extend(
      _.pick(this.attributes, 'id', 'attr1', 'attr2'), { 
      tags: this.tags.toJSON(), 
     }); 
    }) 

});

2

Мы не хотели добавлять другие рамки для достижения этого, поэтому мы отвлекли его в базовом классе модели. Вот как объявить и использовать его (available as a gist):

// Declaration 

window.app.viewer.Model.GallerySection = window.app.Model.BaseModel.extend({ 
    nestedTypes: { 
    background: window.app.viewer.Model.Image, 
    images: window.app.viewer.Collection.MediaCollection 
    } 
}); 

// Usage 

var gallery = new window.app.viewer.Model.GallerySection({ 
    background: { url: 'http://example.com/example.jpg' }, 
    images: [ 
     { url: 'http://example.com/1.jpg' }, 
     { url: 'http://example.com/2.jpg' }, 
     { url: 'http://example.com/3.jpg' } 
    ], 
    title: 'Wow' 
}); // (fetch will work equally well) 

console.log(gallery.get('background')); // window.app.viewer.Model.Image 
console.log(gallery.get('images')); // window.app.viewer.Collection.MediaCollection 
console.log(gallery.get('title')); // plain string 

Он одинаково хорошо работает с set и toJSON.
И вот BaseModel:

window.app.Model.BaseModel = Backbone.Model.extend({ 
    constructor: function() { 
    if (this.nestedTypes) { 
     this.checkNestedTypes(); 
    } 

    Backbone.Model.apply(this, arguments); 
    }, 

    set: function (key, val, options) { 
    var attrs; 

    /* jshint -W116 */ 
    /* jshint -W030 */ 
    // Code below taken from Backbone 1.0 to allow different parameter styles 
    if (key == null) return this; 
    if (typeof key === 'object') { 
     attrs = key; 
     options = val; 
    } else { 
     (attrs = {})[key] = val; 
    } 
    options || (options = {}); 
    // Code above taken from Backbone 1.0 to allow different parameter styles 
    /* jshint +W116 */ 
    /* jshint +W030 */ 

    // What we're trying to do here is to instantiate Backbone models and collections 
    // with types defined in this.nestedTypes, and use them instead of plain objects in attrs. 

    if (this.nestedTypes) { 
     attrs = this.mapAttributes(attrs, this.deserializeAttribute); 
    } 

    return Backbone.Model.prototype.set.call(this, attrs, options); 
    }, 

    toJSON: function() { 
    var json = Backbone.Model.prototype.toJSON.apply(this, arguments); 

    if (this.nestedTypes) { 
     json = this.mapAttributes(json, this.serializeAttribute); 
    } 

    return json; 
    }, 

    mapAttributes: function (attrs, transform) { 
    transform = _.bind(transform, this); 
    var result = {}; 

    _.each(attrs, function (val, key) { 
     result[key] = transform(val, key); 
    }, this); 

    return result; 
    }, 

    serializeAttribute: function (val, key) { 
    var NestedType = this.nestedTypes[key]; 
    if (!NestedType) { 
     return val; 
    } 

    if (_.isNull(val) || _.isUndefined(val)) { 
     return val; 
    } 

    return val.toJSON(); 
    }, 

    deserializeAttribute: function (val, key) { 
    var NestedType = this.nestedTypes[key]; 
    if (!NestedType) { 
     return val; 
    } 

    var isCollection = this.isTypeASubtypeOf(NestedType, Backbone.Collection), 
     child; 

    if (val instanceof Backbone.Model || val instanceof Backbone.Collection) { 
     child = val; 
    } else if (!isCollection && (_.isNull(val) || _.isUndefined(val))) { 
     child = null; 
    } else { 
     child = new NestedType(val); 
    } 

    var prevChild = this.get(key); 

    // Return existing model if it is equal to child's attributes 

    if (!isCollection && child && prevChild && _.isEqual(prevChild.attributes, child.attributes)) { 
     return prevChild; 
    } 

    return child; 
    }, 

    isTypeASubtypeOf: function (DerivedType, BaseType) { 
    // Go up the tree, using Backbone's __super__. 
    // This is not exactly encouraged by the docs, but I found no other way. 

    if (_.isUndefined(DerivedType['__super__'])) { 
     return false; 
    } 

    var ParentType = DerivedType['__super__'].constructor; 
    if (ParentType === BaseType) { 
     return true; 
    } 

    return this.isTypeASubtypeOf(ParentType, BaseType); 
    }, 

    checkNestedTypes: function() { 
    _.each(this.nestedTypes, function (val, key) { 
     if (!_.isFunction(val)) { 
     console.log('Not a function:', val); 
     throw new Error('Invalid nestedTypes declaration for key ' + key + ': expected a function'); 
     } 
    }); 
    }, 
} 
0

Столкнувшись с такой же проблемой, я что-то вроде этого (ниже код выводится машинописного текста компилятором, так что это немного многословный):

var Model = (function (_super) { 
    __extends(Model, _super); 
    function Model() { 
     _super.apply(this, arguments); 
    } 
    Model.prototype.fieldToType = function() { 
     return {}; 
    }; 

    Model.prototype.parse = function (response, options) { 
     _.each(this.fieldToType(), function (type, field) { 
      if (response[field]) { 
       if (_.isArray(response[field])) { 
        response[field] = _.map(response[field], function (value) { 
         return new type(value, { parse: true }); 
        }); 
       } else { 
        response[field] = new type(response[field], { parse: true }); 
       } 
      } 
     }); 
     return _super.prototype.parse.call(this, response, options); 
    }; 
    Model.prototype.toJSON = function() { 
     var j = _super.prototype.toJSON.call(this); 
     _.each(this.fieldToType(), function (type, field) { 
      if (j[field]) { 
       if (_.isArray(j[field])) { 
        j[field] = _.map(j[field], function (value) { 
         return value.toJSON(); 
        }); 
       } else { 
        j[field] = j[field].toJSON(); 
       } 
      } 
     }); 
     return j; 
    }; 
    return Model; 
})(Backbone.Model); 

И тогда я может просто переопределить метод fieldToType для определения типов моих полей:

PendingAssignmentOffer.prototype.fieldToType = function() { 
    return { 
     'creator': User, 
     'task_templates': TaskTemplateModel, 
     'users': User, 
     'school_classes': SchoolClass 
    }; 
}; 
Смежные вопросы