Backbone.js รับและตั้งค่าแอตทริบิวต์วัตถุที่ซ้อนกัน


105

ฉันมีคำถามง่ายๆเกี่ยวกับฟังก์ชันรับและตั้งค่า Backbone.js

1) ด้วยรหัสด้านล่างฉันจะ 'get' หรือ 'set' obj1.myAttribute1 โดยตรงได้อย่างไร?

คำถามอื่น:

2) ในโมเดลนอกเหนือจากอ็อบเจ็กต์ค่าเริ่มต้นฉันจะ / ควรประกาศคุณลักษณะอื่น ๆ ของโมเดลของฉันได้ที่ไหนเพื่อให้สามารถเข้าถึงได้ผ่านเมธอด get and set ของ Backbone

var MyModel = Backbone.Model.extend({
    defaults: {
        obj1 : {
            "myAttribute1" : false,
            "myAttribute2" : true,
        }
    }
})

var MyView = Backbone.View.extend({
    myFunc: function(){
        console.log(this.model.get("obj1"));
        //returns the obj1 object
        //but how do I get obj1.myAttribute1 directly so that it returns false?
    }
});

ฉันรู้ว่าฉันทำได้:

this.model.get("obj1").myAttribute1;

แต่เป็นการปฏิบัติที่ดีหรือไม่?


3
แม้ว่าจะไม่ใช่คำตอบสำหรับคำถาม: เมื่อใดก็ตามที่ระบุวัตถุ (สิ่งที่ส่งผ่านโดยการอ้างอิง) ในdefaults(obj1 ในกรณีนี้) วัตถุเดียวกันนั้นจะถูกแชร์ในทุกอินสแตนซ์ของโมเดล แนวทางปฏิบัติในปัจจุบันคือการกำหนดdefaultsเป็นฟังก์ชันที่ส่งคืนวัตถุที่จะใช้เป็นค่าเริ่มต้น backbonejs.org/#Model-defaults (ดูโน้ตตัวเอียง)
Jonathan F

1
@JonathanF ความคิดเห็นไม่ได้มีไว้สำหรับคำตอบดังนั้นคุณไม่จำเป็นต้องมีการประกาศ :)
TJ

คำตอบ:


144

แม้ว่าthis.model.get("obj1").myAttribute1จะเป็นเรื่องปกติ แต่ก็เป็นปัญหาเล็กน้อยเพราะคุณอาจถูกล่อลวงให้ทำสิ่งเดียวกันกับเซ็ตเช่น

this.model.get("obj1").myAttribute1 = true;

แต่ถ้าคุณทำเช่นนี้คุณจะไม่ได้รับประโยชน์จากโมเดล Backbone สำหรับmyAttribute1เช่นเหตุการณ์การเปลี่ยนแปลงหรือการตรวจสอบความถูกต้อง

ทางออกที่ดีกว่าคืออย่าซ้อน POJSO ("วัตถุ JavaScript เก่าธรรมดา") ในโมเดลของคุณและแทนที่จะซ้อนคลาสโมเดลที่กำหนดเอง มันจะมีลักษณะดังนี้:

var Obj = Backbone.Model.extend({
    defaults: {
        myAttribute1: false,
        myAttribute2: true
    }
});

var MyModel = Backbone.Model.extend({
    initialize: function () {
        this.set("obj1", new Obj());
    }
});

จากนั้นรหัสการเข้าถึงจะเป็น

var x = this.model.get("obj1").get("myAttribute1");

แต่ที่สำคัญกว่านั้นคือรหัสการตั้งค่าจะเป็น

this.model.get("obj1").set({ myAttribute1: true });

ซึ่งจะเริ่มเหตุการณ์การเปลี่ยนแปลงที่เหมาะสมและไม่ชอบ ตัวอย่างการทำงานที่นี่: http://jsfiddle.net/g3U7j/


24
สำหรับคำตอบนี้ฉันจะเพิ่มคำแนะนำว่าโซลูชันนี้มีส่วนเกี่ยวข้องกับการละเมิด Law of Demeter อย่างกว้างขวาง ฉันจะพิจารณาเพิ่มวิธีอำนวยความสะดวกที่ซ่อนการนำทางไปยังวัตถุที่ซ้อนกัน โดยพื้นฐานแล้วผู้โทรของคุณไม่จำเป็นต้องทราบโครงสร้างภายในของโมเดล ท้ายที่สุดมันอาจเปลี่ยนไปและผู้โทรไม่ควรฉลาดกว่า
Bill Eisenhauer

7
ไม่สามารถให้สิ่งนี้ทำงานให้ฉันได้ เกิดข้อผิดพลาด:Uncaught TypeError: Object #<Object> has no method 'set'
wilsonpage

1
@ChristianNunciato, pagewil, Benno: ดูเหมือนคุณจะพลาดจุดที่โพสต์ไปซึ่งก็คือการซ้อนโมเดล Backbone ไว้ในโมเดล Backbone อย่าวางวัตถุธรรมดาไว้ในโมเดล Backbone ตัวอย่างการทำงานที่นี่: jsfiddle.net/g3U7j
Domenic

1
ฉันไม่ได้ตรวจสอบโค้ด backbone.js แต่จากการทดสอบของฉันหากคุณมี Model ที่กำหนดเองที่ซ้อนกันและเปลี่ยนคุณสมบัติของมันด้วย set () โมเดลหลักจะไม่เริ่มเหตุการณ์ 'การเปลี่ยนแปลง' เอง ฉันต้องจุดไฟเหตุการณ์ด้วยตัวเอง ฉันควรตรวจสอบโค้ดจริงๆ แต่นี่เป็นความเข้าใจของคุณด้วยหรือไม่?
พรุ่งนี้

2
@tom ที่ถูกต้อง. Backbone ไม่ได้เป็นกรณีพิเศษสำหรับเมื่อคุณสมบัติของโมเดลเป็นอินสแตนซ์จากBackbone.Modelนั้นเริ่มสร้างเหตุการณ์มหัศจรรย์
Domenic

74

ฉันสร้างbackbone-deep-modelสำหรับสิ่งนี้ - เพียงแค่ขยาย Backbone.DeepModel แทน Backbone.Model จากนั้นคุณสามารถใช้พา ธ เพื่อรับ / ตั้งค่าแอตทริบิวต์โมเดลที่ซ้อนกันได้ มันรักษาเหตุการณ์การเปลี่ยนแปลงด้วย

model.bind('change:user.name.first', function(){...});
model.set({'user.name.first': 'Eric'});
model.get('user.name.first'); //Eric

1
ใช่แล้วถ้าคุณดูAPIจะมีตัวอย่างเช่น//You can use index notation to fetch from arrays console.log(model.get('otherSpies.0.name')) //'Lana'
เตาฮีด

ใช้งานได้ดี! แต่บรรทัดที่ 2 ในตัวอย่างของคุณต้องการเครื่องหมายจุดคู่แทนเครื่องหมายจุลภาคหรือไม่?
mariachi

16

โซลูชันของ Domenic จะใช้งานได้อย่างไรก็ตาม MyModel ใหม่แต่ละตัวจะชี้ไปที่อินสแตนซ์เดียวกันของ Obj เพื่อหลีกเลี่ยงปัญหานี้ MyModel ควรมีลักษณะดังนี้:

var MyModel = Backbone.Model.extend({
  initialize: function() {
     myDefaults = {
       obj1: new Obj()
     } 
     this.set(myDefaults);
  }
});

ดูคำตอบของ c3rin ที่https://stackoverflow.com/a/6364480/1072653สำหรับคำอธิบายแบบเต็ม


1
สำหรับผู้อ่านในอนาคตคำตอบของฉันได้รับการอัปเดตเพื่อรวมคำตอบที่ดีที่สุดของ Rusty
Domenic

2
ผู้ถามควรตั้งค่าสถานะนี้ว่าเป็นคำตอบที่ยอมรับ Domenic เป็นการเริ่มต้นที่ดี แต่สิ่งนี้ช่วยแก้ปัญหาได้
Jon Raasch

5

ฉันใช้แนวทางนี้

หากคุณมีโมเดล Backbone เช่นนี้:

var nestedAttrModel = new Backbone.Model({
    a: {b: 1, c: 2}
});

คุณสามารถตั้งค่าแอตทริบิวต์ "ab" ด้วย:

var _a = _.omit(nestedAttrModel.get('a')); // from underscore.js
_a.b = 3;
nestedAttrModel.set('a', _a);

ตอนนี้โมเดลของคุณจะมีคุณสมบัติเช่น:

{a: {b: 3, c: 2}}

เมื่อเหตุการณ์ "เปลี่ยนแปลง" เริ่มทำงาน


1
คุณแน่ใจเกี่ยวกับเรื่องนี้หรือไม่? สิ่งนี้ไม่ได้ผลสำหรับฉัน meta2= m.get('x'); meta2.id=110; m.set('x', meta2). สิ่งนี้ไม่ทำให้เกิดเหตุการณ์การเปลี่ยนแปลงใด ๆ สำหรับฉัน :(
HungryCoder

1
ฉันเห็นว่ามันใช้งานได้เมื่อฉันโคลนแอตทริบิวต์เช่น_.clone(m.get('x')). ขอบคุณ
HungryCoder

ขอบคุณ @HungryCoder มันใช้งานได้สำหรับฉันเช่นกันเมื่อโคลน กระดูกสันหลังต้องเปรียบเทียบวัตถุที่คุณอยู่settingกับวัตถุที่คุณอยู่gettingในเวลาที่กำหนด ดังนั้นถ้าคุณไม่ทำการโคลนแล้ววัตถุทั้งสองแสดงว่าวัตถุทั้งสองที่กำลังเปรียบเทียบจะเหมือนกันทุกประการในเวลาที่กำหนด
Derek Dahmer

โปรดจำไว้ว่าออบเจ็กต์ถูกส่งผ่านโดยการอ้างอิงและสามารถเปลี่ยนแปลงได้ซึ่งแตกต่างจากสตริงและตัวเลขดั้งเดิม วิธีการตั้งค่าและตัวสร้างของ Backbone พยายามโคลนตื้น ๆ ของการอ้างอิงวัตถุใด ๆ ที่ส่งผ่านเป็นอาร์กิวเมนต์ การอ้างอิงถึงวัตถุอื่น ๆ ในคุณสมบัติของวัตถุนั้นจะไม่ถูกโคลน เมื่อคุณตั้งค่าและดึงข้อมูลอ้างอิงจะเหมือนกันซึ่งหมายความว่าคุณสามารถเปลี่ยนรูปแบบได้โดยไม่ทำให้เกิดการเปลี่ยนแปลง
niall.campbell

3

มีวิธีแก้ปัญหาหนึ่งที่ยังไม่มีใครนึกถึงซึ่งมีให้ใช้มากมาย คุณไม่สามารถตั้งค่าแอตทริบิวต์ที่ซ้อนกันได้โดยตรงเว้นแต่คุณจะใช้ไลบรารีของบุคคลที่สามซึ่งคุณอาจไม่ต้องการ อย่างไรก็ตามสิ่งที่คุณทำได้คือสร้างโคลนของพจนานุกรมต้นฉบับตั้งค่าคุณสมบัติที่ซ้อนกันที่นั่นและตั้งค่าพจนานุกรมทั้งหมดนั้น ชิ้นเค้ก

//How model.obj1 looks like
obj1: {
    myAttribute1: false,
    myAttribute2: true,
    anotherNestedDict: {
        myAttribute3: false
    }
}

//Make a clone of it
var cloneOfObject1 = JSON.parse(JSON.stringify(this.model.get('obj1')));

//Let's day we want to change myAttribute1 to false and myAttribute3 to true
cloneOfObject1.myAttribute2 = false;
cloneOfObject1.anotherNestedDict.myAttribute3 = true;

//And now we set the whole dictionary
this.model.set('obj1', cloneOfObject1);

//Job done, happy birthday

2

ฉันมีปัญหาเดียวกันกับ @pagewil และ @Benno กับวิธีแก้ปัญหาของ @Domenic คำตอบของฉันคือเขียน Backbone คลาสย่อยอย่างง่ายแทนโมเดลที่แก้ไขปัญหา

// Special model implementation that allows you to easily nest Backbone models as properties.
Backbone.NestedModel = Backbone.Model.extend({
    // Define Backbone models that are present in properties
    // Expected Format:
    // [{key: 'courses', model: Course}]
    models: [],

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

        if (_.isObject(key) || key == null) {
            attrs = key;
            options = value;
        } else {
            attrs = {};
            attrs[key] = value;
        }

        _.each(this.models, function(item){
            if (_.isObject(attrs[item.key])) {
                attrs[item.key] = new item.model(attrs[item.key]);
            }
        },this);

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

var Obj = Backbone.Model.extend({
    defaults: {
        myAttribute1: false,
        myAttribute2: true
    }
});

var MyModel = Backbone.NestedModel.extend({
    defaults: {
        obj1: new Obj()
    },

    models: [{key: 'obj1', model: Obj}]
});

สิ่งที่ NestedModel ทำเพื่อคุณคืออนุญาตให้สิ่งเหล่านี้ทำงานได้ (ซึ่งจะเกิดขึ้นเมื่อ myModel ได้รับการตั้งค่าผ่านข้อมูล JSON):

var myModel = new MyModel();
myModel.set({ obj1: { myAttribute1: 'abc', myAttribute2: 'xyz' } });
myModel.set('obj1', { myAttribute1: 123, myAttribute2: 456 });

การสร้างรายการแบบจำลองโดยอัตโนมัติในการเริ่มต้นจะเป็นเรื่องง่าย แต่โซลูชันนี้ดีพอสำหรับฉัน


2

โซลูชันที่เสนอโดย Domenic มีข้อบกพร่องบางประการ สมมติว่าคุณต้องการฟังเหตุการณ์ "เปลี่ยนแปลง" ในกรณีนั้นเมธอด 'initialize' จะไม่ถูกเริ่มทำงานและค่าที่กำหนดเองสำหรับแอตทริบิวต์ของคุณจะถูกแทนที่ด้วยวัตถุ json จากเซิร์ฟเวอร์ ในโครงการของฉันฉันประสบกับปัญหานี้ วิธีแก้ปัญหาของฉันในการแทนที่วิธีการ 'set' ของ Model:

set: function(key, val, options) {
    if (typeof key === 'object') {
        var attrs = key;
        attrs.content = new module.BaseItem(attrs.content || {});
        attrs.children = new module.MenuItems(attrs.children || []);
    }

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

0

ในขณะที่ในบางกรณีการใช้แบบจำลอง Backbone แทนแอตทริบิวต์ Object ที่ซ้อนกันก็สมเหตุสมผลตามที่ Domenic กล่าวไว้ในกรณีที่ง่ายกว่าคุณสามารถสร้างฟังก์ชัน setter ในโมเดล:

var MyModel = Backbone.Model.extend({
    defaults: {
        obj1 : {
            "myAttribute1" : false,
            "myAttribute2" : true,
        }
    },
    setObj1Attribute: function(name, value) {
        var obj1 = this.get('obj1');
        obj1[name] = value;
        this.set('obj1', obj1);
    }
})

0

หากคุณโต้ตอบกับแบ็กเอนด์ซึ่งต้องใช้วัตถุที่มีโครงสร้างซ้อนกัน แต่มีกระดูกสันหลังที่ง่ายกว่าในการทำงานกับโครงสร้างเชิงเส้น

backbone.linearสามารถช่วยคุณได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.