kmalakoff / knockback

Knockback.js provides Knockout.js magic for Backbone.js Models and Collections.
http://kmalakoff.github.com/knockback/
MIT License
1.05k stars 79 forks source link

Bug when passing Backbone Model with array values #45

Closed pheuter closed 12 years ago

pheuter commented 12 years ago

So, I'm giving KB a try for this project I'm working on, and I'm running into this issue where passing a Backbone.Model to a kb.ViewModel which has a key-value pair where the value is of type Array. For example, consider the following code:

model = new Backbone.Model
  text: ["heading.get these rewards"]
  widget: ["sign_up", "rewards"]
  model_data:
    reward:
      top_rewards:
        properties: ["title", "description", "num_points"]
        query:
          type: "active"
          limit: 6

landingPageModel  = kb.viewModel model

landingPageModel.model().toJSON() returns a JSON object where model_data has been populated correctly, but text and widget are empty Arrays.

paulyoung commented 12 years ago

+1

kmalakoff commented 12 years ago

Thank you for letting me know and providing the sample code....I'm on it!

kmalakoff commented 12 years ago

OK. It should be fixed in the main branch (0.16.6) - please test and confirm that it fixes your problem.

I haven't pushed it to npm or NuGet yet because I have a question for you...Knockback does not currently wrap nested attributes in observables meaning the kb.Observable (generated by kb.ViewModel) will only update if you set model_data not if for example, you edit model_data.reward.top_rewards.properties without calling set or trigger.

Do you have a concrete use case where you need to auto-generate observables for nested attributes? If so, would you be able to share it with me and tell me what behavior you need/are expecting?

Cheers,

Kevin

loesak commented 11 years ago

I am also new to Knockback and this was the first problem I ran into. I do have a need for the underlying array attributes to be wrapped as observables. Our services don't return the data as its stored in the DB but more a description of the object. This includes type, display, and validation information. The actual object values are stored as objects in an array. See example below. Without these being bound, I'm not sure how Knockback can suite our needs.

Example:

{
    "type": "Person",
    "parent-ref": null,
    "self-ref": "/url/to/person/resource/_T7UkoSm_EeK3fNOPqvMKAQ",
    "parameters": [
        {
            "name": "id",
            "type": "text",
            "value": "_T7UkoSm_EeK3fNOPqvMKAQ",
            "default": null,
            "collection": false,
            "display": {
                "label": "Id",
                "type": "text",
                "editable": "never",
                "visible": "never"
            }
        },
        {
            "name": "firstName",
            "type": "text",
            "value": null,
            "default": null,
            "collection": false,
            "display": {
                "label": "First Name",
                "type": "text",
                "editable": "always"
            },
            "validation": {
                "blank": true,
                "min": 0,
                "max": 75
            }
        },
        {
            "name": "lastName",
            "type": "text",
            "value": null,
            "default": null,
            "collection": false,
            "display": {
                "label": "Last Name",
                "type": "text",
                "editable": "always"
            },
            "validation": {
                "blank": true,
                "min": 0,
                "max": 1000
            }
        },
        {
            "name": "birthDate",
            "type": "number",
            "value": 0,
            "default": 0,
            "collection": false,
            "display": {
                "label": "Birth Date",
                "type": "date",
                "editable": "always"
            }
        }
    ]
}
kmalakoff commented 11 years ago

Knockback is useful for Models, Collections, and Relationships between them, but unless you are wrapping your attributes within Models or Collections at the named attribute level (typically through Backbone.Relational), it is currently out of the scope.

If you are looking just for some simple wrapping within attributes it should be simple enough to support with Knockout's mapping plugin http://knockoutjs.com/documentation/plugins-mapping.html and a custom Backbone.Model parse/toJSON combination. Try them out and if they don't meet your need, please provide a use case explaining why they do not and what you need.

loesak commented 11 years ago

The only need I can think of off hand is for those that do not use relational databases, like us. We do not have flat object structures like you would in a relational database. Backbone has a few plugins that address this issue with Backbones model structure and I was hoping that Knockback would work with those. I can likely work out data binding for my values that i need to observe but thought this situation would be one Knockback would want to tackle.

kmalakoff commented 11 years ago

I've never needed to encounter this case personally. If you want to explain further which plugins you are talking about, I'm happy to take a look.

If it can be handled through existing solutions (like I mentioned before) or those Backbone plugins, all the better. It is really important to me to keep Knockback lean-and-mean so providing options to support other libraries sounds like a good way to go.

Also, open source is all about contribution! If you'd like to see it tackled, there's always pull requests ;-)

On Thu, Nov 8, 2012 at 8:38 PM, Aaron Loes notifications@github.com wrote:

The only need I can think of off hand is for those that do not use relational databases, like us. We do not have flat object structures like you would in a relational database. Backbone has a few plugins that address this issue with Backbones model structure and I was hoping that Knockback would work with those. I can likely work out data binding for my values that i need to observe but though this situation would be one Knockback would want to tackle.

— Reply to this email directly or view it on GitHubhttps://github.com/kmalakoff/knockback/issues/45#issuecomment-10215414.

loesak commented 11 years ago

I normally would take the time to contribute, and I have contributed occasionally to other projects. Unfortunately at this time, I am swamped trying to put out a custom single page application framework for my company and do not have the time to dive into updating Knockback. Need to spend my time building the solution.

As for those plugins, from here (https://github.com/documentcloud/backbone/wiki/Extensions%2C-Plugins%2C-Resources), check out the following:

Models: backbone-deep-model: https://github.com/powmedia/backbone-deep-model (currently using) Backbone-Nested: http://afeld.github.com/backbone-nested/ (do not recommend) Backbone Dotattr: https://github.com/amccloud/backbone-dotattr (haven't looked at)

Relationships: Nesting.js: https://gist.github.com/1610397 (just saw this, may need to look at this for the solution to my issue)

kmalakoff commented 11 years ago

I really appreciate you putting this list together!

I'm also quite short on time as I've just co-founded a company and it is taking up too much time...

To help you with your decision, I would be interested in supporting another relationship library like Nesting.js (In addition to Backbone.Relational), but after reviewing the nested attribute libraries and knowing how complicated it would be to implement, I think I'll need to pass on it for now.

One thing I was thinking that you could use a custom factory for your nested data. It would look something like this:

class MyViewModel extends kbViewModel
  constructor: (model, options) ->
    super(model, {options: options, factories: {'parameters': (the_array)-> return new MyArrayViewModel(the_array)}})

or even:

class MyViewModel extends kbViewModel
  constructor: (model, options) ->
    super(model, {options: options, factories: {'parameters': Backbone.Collection}})

Of course, Knockback already supports Backbone.Relational so you should be able to already handle your use case if you don't mind using Backbone Relational. It would look something like this:

class Parameter extends Backbone.RelationModel

class MyModel extends Backbone.RelationModel
  relations: [
    {type: Backbone.HasMany, key: 'parameters', relatedModel: Parameter}
   ]

# optionally
class ParameterViewModel
  constructor: (parameter) ->
    # either use the Knockout bindings or generate your observables by hand

This above issue is a closed bug so I think you might be incorrectly connecting your issue with it - sorry I didn't spot this sooner. I use Backbone.Relational with Knockback regularly and it works well. You might even be able to handle some of your nesting needs with it - I'd recommend trying to use the factories option with Backbone.Relational and to see how far you get.