Skip to content

Scenario Adding and Visualizing a Relationship Between Modem and Users

Josh Arnold edited this page Jul 12, 2018 · 6 revisions

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

Schema script

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.

Test Modem and MTM data

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'))

Add GET modem users endpoint

Creating a MapMany model map

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>

Create the endpoint

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; }
}

Frontend Module

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:

Model & Collection

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.

Clone this wiki locally