The following doesn't give you a step-by-step guide of what to do. It gives you a number of individual pieces and you need to put them together in order to meet the main feature requirement of the User Card Blade:
When an interaction occurs within the application that indicates more information about a user is to be shown the User Card will appear and show that information
The User Card Blade uses two services which will enable you to achieve this functionality:
user-selected
interactionsWe're nearly ready to start coding. But before we do let's do a little bit of setup.
As we said, a nice team have put together some fake services to help us develop our Blade's functionality. In order to configure the services you need to know the require path to these implementations.
Open the aliases.xml
configuration file for the Workbench,
blades/usercard/workbench/resources/aliases.xml
, and set the content as:
<aliases xmlns="http://schema.caplin.com/CaplinTrader/aliases" useScenario="dev">
<alias name="user.service" class="userservice.FakeUserService"/>
</aliases>
Yes, it's XML. What can we say! Anyway, we've now actually configured:
user.service
to use a fake User Service implementationNow that the helper service is in place, we can get to work.
By this we mean bind to user-selected
that have been triggered by other parts
of the application that know doing this will result in other parts of the app
doing something. In this case it's the User Card displaying information about a
selected user.
In order for this to work the publisher and subscriber need to know three things:
user
user-selected
From earlier you'll remember that the latter is in the form:
{
userId: String
position: {
x: Number,
y: Number
}
}
So, here are the pieces of the puzzle that you need to put together in order for the User Card to be informed of these events:
Firstly, you need to get hold of the EventHub
from the ServiceRegistry
:
var ServiceRegistry = require( 'br/ServiceRegistry' );
this._eventHub = ServiceRegistry.getService( 'br.event-hub' );
Channels can be retrieved from the EventHub:
var channel = this._eventHub.channel( 'user' );
Events are bound to on the Channel as follows, with the second parameter being
the handling function and the third the content. By using this
as the context
we can ensure it will refer to the UsercardViewModel
instance so we easily have
access to other member variables. From earlier, you'll already have a userSelected
function:
channel.on( 'user-selected', this.userSelected, this );
Now you should be able to build a good picture of the functionality to handle
user-selected
events.
ServiceRegistry.getService( 'br.event-hub' )
.channel( 'user' )
.trigger( 'user-selected', { userId: 'testUser' } );
UsercardViewModel
is correcting interacting with the EventHub
service.In order to display additional user information you need to get that information from the User Service.
Get the User Service instance that's been set up from the `ServiceRegistry:
var ServiceRegistry = require( 'br/ServiceRegistry' );
this._userService = ServiceRegistry.getService( 'user.service' );
The userSelected
callback provides access to a unique identifier to the user -
the data.userId
property - so we need to get hold of that an use it with the
User Service getUser( userId, listener )
function.
UsercardViewModel.prototype.userSelected = function( data ) {
this._userService.getUser( data.userId, this );
// Leave existing positioning handling code here.
};
The fact that there's a listener
object being passed in tells you that this is
an asynchronous call. In JavaScript there are a number of ways to listen for callbacks.
We've found that although passing in listeners that have to implement contracts
(in the same way as services do) can be a bit more effort, it can result in
a much more robust solution. There are of course times where passing in a function
will be fine.
Anyway, in this case we need to pass in an object that implements two functions:
userRetrieved( user )
called when the user is successfully retrieveduserRetrievalFailed( code, message )
called if there is a problem - we don't expect thisOne way of achieving this is by making UsercardViewModel
a listener by implementing these functions
and passing a reference to this
in as the listener
.
UsercardViewModel.prototype.userRetrieved = function( user ) {
// TODO: update the user properties with the appropriate values
// TODO: show the User Card
};
UsercardViewModel.prototype.userRetrievalFailed = function( code, message ) {
// Something has gone wrong!
// TODO: Panic!
};
The user
object provides the required information via a data
property and
sub-properties:
{
data: {
login: String // the same as the provided userId
avatar_url: String // a Url to an avatar
name: String, // display name
email: String,
company: String,
location: String
}
}
You'll find there's even more information on the data
property. Full details
can be found on the GitHub getUsers Get a single user docs.
This should provide you with enough information to get all the appropriate
moving parts in place. Don't worry, the provided FakeUserService
is set up
to make development easier by providing fake user information for any userId
.
The FakeUserService
knows how to get data from GitHub - where the live app will
get its data. You can turn this on by calling setUserDataFetcher( 'github' )
on
the User Service instance.
To keep your application code clean you should set this within your Workbench
(workbench/index.html
).
var userService = ServiceRegistry.getService( 'user.service' );
userService.setUserDataFetcher( 'github' );
When you do this you'll need to ensure that the userId
you provide is a valid
GitHub username or the userRetrievalFailed
callback will be called.
Before we start, you need to copy the contents of aliases.xml
from the Workbench
into usercard/test-unit/resources/aliases.xml
.
In the services overview we talked about how using MVVM and Services allows us to test full features in isolation. In this part of the exercise we're going to do exactly that. We'll demonstrate how to achieve two types of test:
This way we're testing how UI interactions result in service interactions and how service events are reflected in UI state.
Let's start by creating a new file called UsercardFeatureTest.js
in usercard/test-unit/tests/
and update it to look as follows to add the Test Suite:
'use strict';
require( 'jasmine' );
var UsercardViewModel = require( 'modularapp/usercard/UsercardViewModel');
var ServiceRegistry = require( 'br/ServiceRegistry' );
describe( 'The User Card', function() {
} );
Note: You could call this file UsercardSpecTest.js.
The first thing to notice is that we're using Jasmine, and specifcially we're using Jasmine 1.3 as it ships with BRJS.
As with anything in software it's possible to achieve the same thing in multiple ways. For example, we can test service interactions by:
ServiceRegistry
with a Mock objectSince we're using Jasmine, we'll use Spies, but we'll also demonstrate how the Fake service, that was developed really to help our development within the Workbench, is also useful here.
Whoops! Since the User Card doesn't allow for any user action that results in a service interaction, we can't write a test for this scenario.
But we can make up for it by seeing how the two services the User Card uses
can impact the state of the View Model. The User Card uses both the EventHub,
to see the affect of user-selected
events, and the User Service, to ensure
user data is shown as expected.
From earlier we know all about user selection interactions and how they are indicated
within the app by an event being triggered on the EventHub
. Well, let's test
that scenario and ensure that the User Card is shown.
In order to do this we need to trigger the appropriate event on the EventHub
and
pass event data in the correct format. Also, the User Service must return with
a valid user for this to work.
For this test we can let the FakeUserService
help us out by always returning
user data for any userId
. However, we will need to call upon Jasmine's very handy
jasmine.Clock
to help us work around the User Service getUser
function being
asynchronous.
Here's the test template - just fill in the blanks:
describe( 'The User Card', function() {
it( 'Should show the User Card when a User Selected event occurs', function() {
jasmine.Clock.useMock();
var eventHub = ServiceRegistry.getService( 'br.event-hub' );
var usercardViewModel = new UsercardViewModel();
// TODO: trigger 'user-selected' event on 'user' channel
jasmine.Clock.tick( 1 );
// Assert
// expect( ... );
} );
} );
Add another spec to The User Card test suite. This time you should ensure that the information that the User Service returns.
In order to do this we again need to call upon the FakeUserService
. This time
we can use the addUser
function that some of the other teams will have been using
during their development. In order for it to behave appropriately you'll need
to ensure that the user
object has both a userId
and data
property with some
of the expected data set e.g.
var user = {
userId: 'crazyLegs',
data: {
avatar_url: 'http://stream1.gifsoup.com/view2/1414597/crazy-legs-o.gif',
login: 'crazylegs',
company: 'Disney',
name: 'Dancin\' Dude!'
}
};
userService.addUser( user );
Now you're ready to complete the test.
it( 'Should show the user information that the User Service provides', function() {
jasmine.Clock.useMock();
var eventHub = ServiceRegistry.getService( 'br.event-hub' );
var userService = ServiceRegistry.getService( 'user.service' );
// TODO: add fake data to User Service
var usercardViewModel = new UsercardViewModel();
// TODO: trigger 'user-selected' event on 'user' channel as done earlier
jasmine.Clock.tick( 1 );
// Assert
// expect( usercardViewModel.name() ).toBe( ... );
// expect( ... )
} );
Our Blade is now interacting with two services and we've tested Services through to the UI, but we didn't get a chance to test View Model through to Service. If you've time you can take a look at the other Blades & Services excercises!
For now, it's time to commit those changes and push them to github:
git add blades/usercard
git commit -m 'integrating user card blade with services'
git pull origin master
git push origin master
If you've time you can take a look at the other Blades & Services exercises so that you can see View through to Service testing. Or you can see how the other teams in your company are getting on. Can you help them out?
Then, we'll see how our application looks.