-
Notifications
You must be signed in to change notification settings - Fork 0
Scenario Adding and Visualizing a Relationship Between Modem and Users
In this lesson we will add an endpoint and front end for viewing a child relationship a Modem and User entities. We will be introducing the following concepts:
- Projecting data into a view model using
MapMany
to populate an array property. - Backbone Collections
- Marionette Collection Views
- Shaping the collection's data using
parse
For this exercise we'll be expecting this Many To Many relation to exist between the Modem and User tables.
Here is a snippet of Dovetail SchemaEditor's SchemaScript XML to add a relation.
<addRelation name="modem2user" table="modem" inverseRelationName="user2modem" inverseTable="user" type="ManyToMany"/>
Learning Schema Editor is not in the scope of this lesson but if you want to learn more or download the application please visit our support site.
INSERT INTO table_modem VALUES(1, 'Smart Modem 1200', 'Hayes Compatible', 'active', 'akebono.stanford.edu', 1)
-- relate modem 1 to login 'annie' and 'hank'
-- NOTE the MTM table name may be different for your database
DELETE FROM mtm_user125_modem0
INSERT INTO mtm_user125_modem0 VALUES(1,(Select objid from table_user where login_name = 'annie'))
INSERT INTO mtm_user125_modem0 VALUES(1,(Select objid from table_user where login_name = 'hank'))
You'll need to start at the modem
table because we will be querying this model by modem's objid
. Then, MapMany
will define how the users
property gets populated by traversing the modem2user
relation.
<model name="modem-users">
<query from="modem" type="table">
<addProperty key="id" field="objid" dataType="int" propertyType="identifier" />
<addMappedCollection key="users">
<traverseRelation name="modem2user">
<traverseRelation name="user2employee">
<addProperty key="firstName" field="first_name" dataType="string" />
<addProperty key="lastName" field="last_name" dataType="string" />
<addProperty key="name" dataType="string">
<addTransform name="stringConat">
<addArgument name="string0" property="firstName" />
<addArgument name="string1" property="lastName" />
</addTransform>
</addProperty>
<addProperty key="phone" field="phone" dataType="string" />
</traverseRelation>
</traverseRelation>
</addMappedCollection>
</query>
</model>
Next we will add the endpoint action which invokes a model builder for the map we just created.
//Note: Your constructor will need to take a dependency on IModemBuilder
public ModemUserModel get_custom_modems_Id_users(ModemUsersRequest request)
{
return _modelBuilder.GetOne("modem-users", request.Id);
}
Finally we add an input model for our action.
public class ModemUsersRequest
{
public int Id { get; set; }
}
Now that we have an endpoint we can query to get the users for a specific modem, let's walk through how to make a request on that endpoint and then display the received information to the user. This part will introduce:
- Backbone Collections
- Marionette CollectionViews
- Manually parsing a server's response
A collection is simply an object that maintains an array of models. Since we have the possibility of having multiple users, a collection is perfect for handling this. First, we'll need our model that will represent a single user:
var ModemUser = Backbone.Model;
As you can see, our model is nothing special. We don't need a specific url (the collection will handle that) and there are no defaults we need to assign (we could if we wanted to though).
Now that we have our model, let's create a collection that will be responsible for managing an array of those models:
var ModemUsers = Backbone.Collection.extend({
// The url we want the collection to query to get all the users for the modem
url: function() { return app.root + 'modems/' + this.id + '/users'; },
// The type of Model this collection will manage
model: ModemUser,
// Called during construction. We need the modem 'id' to complete our url
initialize: function(models, options) {
this.id = options.id;
},
// Handle the server response correctly
parse: function(response) {
// Return the array that should be managed by the collection
return response.users;
}
});
Just as Collections manage an array of Models, CollectionViews manage an array of ItemViews. So we'll first need to create an ItemView that represents the view for a single user:
var ModemUserView = Marionette.ItemView.extend({
// This template is whatever you want the presentation of one user to look like
template: userItemTpl
});
Straight forward, just giving it the template it needs to populate. Now let's create the CollectionView that will be responsible for managing an array of these ItemViews:
var ModemUsersView = Marionette.CollectionView.extend({
itemView: ModemUserView
});
The CollectionView doesn't have it's own template since it is only responsible for managing ItemViews. The only thing we need to pass to it is what type of ItemView it should manage, namely our ModemUserView
.
Now let's create our Controller and its show
method:
var Controller = Marionette.Controller.extend({
show: function(region, id) {
// Create our collection. The first parameter is the models (none in this case) and
// the second is the options hash. This is what is used in our ModemUsers'
// 'initialize' method to assign the id
var modemUsers = new ModemUsers([], {
id: id
});
// GET the url of the collection. We'll expect a response like:
// {
// id: <modem id>,
// users: [<user objects>]
// }
modemUsers.fetch({
success: function() {
// Create our collection view and pass it our collection
var modemUsersView = new ModemUsersView( {
collection: modemUsers
});
// Render our CollectionView
region.show(modemUsersView);
}
});
}
});
One thing to point out is that Backbone by default expects only an array of objects as a response when fetching a collection, but since our response includes an id
and then the users
array, we have to override Backbone's behavior with the parse
method in our Collection.
Now that we have our modemUser module, let's create a tab to render it inside:
// ...
// Inside the ModemLayout
regions: {
header: '.header',
details: '.details',
// Add a tabs region, this element also needs to be added to the layout template
tabs: '.tabs'
}
// ...
// Inside the show method of the showModem controller
modem.fetch({
success: function() {
// ...
var tabSettings = [{
name: 'Users',
displayName: 'Users',
isSelected: true,
// Called when the tab is activated, return an object that can be used to
// manage the count of items within this tab. This is usually the collection
// created in the tab region, but can also be a model that triggers the
// 'update:count' event.
onLoad: function(tab) {
return modemUsers.show(tab.region, id);
}
}];
// Use the tabComponent module to render the tab, giving it our tabs region we
// added to the layout
tabComponent.show(self.layout.tabs, tabSettings);
// ...
Now we have a 'User' tab that will display a list of the users that are associated with that modem. To summarize, we've create a Model to represent a user, a Collection to manage an array of those Models, an ItemView to be the UI of a single user and a CollectionView to manage an array of those ItemViews. Then we fetched data from the server and populated our collection with that data so that it could be rendered to the DOM. Lastly, we added a tab through Agent's tabComponent module to put the user list in.
We hope you enjoyed this helpful Agent training document. If you see an error or omission please feel free to fork this repo to correct the change, and submit a pull request. Any questions? Contact Support.