reboundjs / rebound

Automatic data binding for backbone using HTMLBars.
MIT License
43 stars 4 forks source link

HowTo: Data-first approach #27

Closed matthewrobb closed 9 years ago

matthewrobb commented 9 years ago

I am in a situation where I would like to define the data-model of my application on it's own. This might be a complex nested data structure that I'd like to represent as models and collections. In Backbone this is a fairly usual case. In Rebound I am not seeing an obvious way to decouple data from components/views. Any thoughts?

amiller-gh commented 9 years ago

To clarify, are you looking to use rebound-data standalone or use a different data-model library with the rebound-runtime (components/views)?

Rebound-data is already standalone. The documentation isn't up to date, but with rebound-data you get complex nesting of data objects, recursive .toJSON, deep .get and .set (data.set('complex', {data: [{obj: true}]}), data.get('complex.data[0].obj')), reset and toggle methods on models - model.set and fetch can now take {reset: true} as an option, computed properties with data synchronization (ex. if you have a the first model in a collection returned by a computed property, you can set attributes on that computed property and update the original model), automatic promotion of objects, arrays and functions to models, collections and computed properties, and smart merging of complex data objects when setting and fetching, and I'm sure a couple other features I'm forgetting.

The project is built in rjs, cjs and as a contacted library. Its only requirement is underscore/lodash. If you just load the library, the data object classes are on the global Rebound object as Rebound.Model, Rebound.Collection and Rebound.ComputedProperty. Otherwise, feel free to pull it in using requirejs, or tweak the build to spit out a standalone rebound-data library (that old grunt build needs a serious rewrite anyway).

I'm nearly done pulling apart the last few dependancies between rebound-runtime and rebound-data. The idea for v1 is that the only interface rebound-runtime will assume of the data models passed to is get, set and on methods. The on method allowing for backbone style callbacks. Right now there are a couple places still where the runtime uses other rebound-data properties or methods, and the Component object extends Rebound.Model which I do not want to do. The runtime listens for change, add, remove and reset events to re-run the htmlbars helpers. In a later version I want to eventually O.o for all callbacks, but that may take some time.

To get rebound-runtime to take a different type of data-model you would need to re-write how components ingest data. In the compiled templates the data model is passed right to the component's constructor. It then calls a this.set on itself (because it extends Rebound.Model, remember?) When Rebound.Component no longer extends Model, it would call a new Rebound.Model and pass the data object to Model's constructor. Then you can just swap out what lib you're using on the Rebound object to use a different data library, provided it implements get, set and on. Thats what I'm thinking at least, I haven't actually started working on that part. Any thoughts?

matthewrobb commented 9 years ago

Basically what happens right now, it seems, is that a component is essentially a subclass of Model. I am thinking something more along the lines of having existing model instances that can be bound to components. You might have two custom elements bound to a single model for example.

amiller-gh commented 9 years ago

If its a part of the same runtime you can have two elements bound to the same model. You can pass complex objects into components. So if you have two of these on the page, and you pass them the same object they will stay in sync with eachother:

<custom-element someValue={{complexObject}}></custom-element>
<custom-element someValue={{complexObject}}></custom-element>

They aren't literally using the same object because you run into some fun issues with shared memory, but they communicate changes with eachother through change events.

amiller-gh commented 9 years ago

Even using the same primitive value across a couple custom elements will keep them all in sync with eachother:

<div>{{user.firstName}} {{user.lastName}}</div>
<user-card firstName={{user.firstName}} lastName={{user.lastName}} ></user-card>
<some-other-element firstName={{user.firstName}}></some-other-element>

Though in the above scenario you'd typically just pass the whole user object and let the component decide which parts to use.

If you're seeing any behavior to the contrary, its a bug and should be fixed asap.