Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
353 views
in Technique[技术] by (71.8m points)

javascript - How to bind deeper than one level with rivets.js

rivets.js newbie here. I want to bind to an item that will be changing dynamically (store.ActiveItem). I tried the following approach, but although store.ActiveItem is set, store.ActiveItem.(any property) is always undefined. Is there a standard way to bind deeper than one level?

<div id="editItemDialog" data-modal="store.ActiveItem < .ActiveItem">
    <a data-on-click="store:ClearActiveItem" href="#">close - works</a>
    <div>
        <div>
        <label>name:</label><input data-value="store.ActiveItem.Name < .ActiveItem"/>
        </div>
        <div>
        <label>price:</label><input data-value="store.ActiveItem.Price < .ActiveItem"/>
        </div>
        <div>
        <label>description:</label><textarea data-value="store.ActiveItem.Description < .ActiveItem"></textarea>
        </div>
    </div>
</div>
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

How the binding works is largely determined by the Rivets adapter you are using, although your model could also do the heavy lifting.

Option 1: Smart Model

If you're using Backbone.js, you could take a look at backbone-deep-model, which supports path syntax for nested attributes (Eg. store.get('ActiveItem.Price')), though it is still under development. If that doesn't quite meet your needs, there are other nested model-type options on the Backbone plugins and extensions wiki.

Option 2: Smart Adapter

If that doesn't work for you, you can extend your Rivets adapter to handle path syntax. I've put together a simple example on how to do this at http://jsfiddle.net/zKHYz/2/ with the following naive adapter:

rivets.configure({
    adapter: {
        subscribe: function(obj, keypath, callback) { /* Subscribe here */ },
        unsubscribe: function(obj, keypath, callback) { /* Unsubscribe here */ },
        read: function(obj, keypath) {
            var index = keypath.indexOf('.');
            if (index > -1) {
                var pathA = keypath.slice(0, index);
                var pathB = keypath.slice(index + 1);
                return obj[pathA][pathB];
            } else {
                return obj[keypath];
            }
        },
        publish: function(obj, keypath, value) {
            var index = keypath.indexOf('.');
            if (index > -1) {
                var pathA = keypath.slice(0, index);
                var pathB = keypath.slice(index + 1);
                return obj[pathA][pathB] = value;
            } else {
                return obj[keypath] = value;
            }
        }
    }
});

Option 3: Dirty Hacks

As of version 0.3.2, Rivets supports iteration binding. By creating a Rivets formatter that returns an array, you can "iterate" over your property. Take a look at http://jsfiddle.net/mhsXG/3/ for a working example of this:

rivets.formatters.toArray = function(value) {
    return [value];
};

<div data-each-item="store.ActiveItem | toArray < store.ActiveItem"">
    <label>name:</label><input data-value="item.Name < store.ActiveItem"/>
    ...
</div>

I'm not sure if the computed property syntax is required here; you will have to test this with your model to see what works.

Option 4: Don't bind deeper than one level (Recommended)

The need to bind deeper than one level may be an indication that your design can be improved.

In your example, you have a list of Items in an ItemCollection for a Store. You go about assigning a single Item to the Store's ActiveItem property, setting up events everywhere to try link things together, and then need to be able to bind to the properties of the ActiveItem under the Store, yet have things update whenever the ActiveItem itself changes, etc..

A better way of doing this is by using a view-per-model approach. In your example, you're trying to handle the Store Model, the ItemCollection and the Item Model with a single view. Instead, you could have a parent Store view, a subview for the ItemCollection, and then generate Item views as necessary below that. This way, the views are easier to build and debug, less tightly coupled to your overall Model design, and are more readily reusable throughout your application. In this example, it also simplifies your Model design, as you no longer need the ActiveItem property on the Store to try maintain state; you simply bind the Item View to the selected Item Model, and everything is released with the Item View.

If you're using Backbone.js, take a look at Backbone.View as a starting point; there are many examples online, although I'll be the first to admit that things can get somewhat complex, especially when you have nested views. I have heard good things about Backbone.LayoutManager and how it reduces this complexity, but have not yet had the chance to use it myself.

I've modified your most recent example to use generated Item views at http://jsfiddle.net/EAvXT/8/, and done away with the ActiveItem property accordingly. While I haven't split the Store view from the ItemCollection view, note that I pass their Models into Rivets separately to avoid needing to bind to store.Items.models. Again, it is a fairly naive example, and does not handle the full View lifecycle, such as unbinding Rivets when the View is removed.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...