This exercise will setup a login page and send the credentials using Basic Authentication which will be stored for future SData requests.
1. In argos-template/src/Views
create a new file named Login.js
2. Define the module and inherit the base Edit View
define('Mobile/SalesLogix/Views/Login', [
'dojo/_base/declare',
'Sage/Platform/Mobile/Edit'
], function(
declare,
Edit
) {
return declare('Mobile.SalesLogix.Views.Login', [Edit], {
});
});
3. Add a button element to the widgetTemplate
, while normally we would add a toolbar item for actions like this a Login page doesn't look quite right without a big ol' Login button.
widgetTemplate: new Simplate([
'<div id="{%= $.id %}" title="{%: $.titleText %}" class="panel {%= $.cls %}" hideBackButton="true">',
'<div class="panel-content" data-dojo-attach-point="contentNode"></div>',
'<button class="button actionButton" data-action="authenticate"><span>{%: $.logOnText %}</span></button>',
'</div>'
]),
4. Pointers:
* Use of variables for text strings. This is so localization modules can override the values, no hard-coded strings should be displayed to the user.
* `hideBackButton="true"` on a View you can add this attr to the main div to remove the back button.
* `<button>` elements have fuzzy touch areas on phones increasing usability
* `data-action="function"` place this attribute on any element to have it respond to `onclick` events. The corresponding function name will then be fired in that views' context.
5. Add the localizable text variables:
logOnText: 'Log On',
titleText: 'My App',
6. Add the view properties id to login and busy to false. The busy flag is for Edit views and is tied to their enable()
and disable()
functions for controlling user interactions.
id: 'login',
busy: false,
7. Empty the toolbars, no need for any buttons
createToolLayout: function() {
return this.tools || (this.tools = {
bbar: false,
tbar: false
});
},
8. Create the layout with our login fields
createLayout: function() {
return this.layout || (this.layout = [
{
name: 'username',
label: this.userText,
type: 'text'
},
{
name: 'password',
label: this.passText,
type: 'text',
inputType: 'password'
}
]);
},
9. Add the authenticate
handler for the button:
authenticate: function () {
if (this.busy) return;
var credentials = this.getValues(),
username = credentials && credentials.username;
if (username && /\w+/.test(username))
this.validateCredentials(credentials);
},
10. Feel free to expand the username/password check before firing a request, the above makes sure we have a name that is not just spaces.
11. Add the validateCredentials
function
validateCredentials: function (credentials) {
this.disable();
App.authenticateUser(credentials, {
success: function(result) {
this.enable();
App.navigateToInitialView();
},
failure: function(result) {
this.enable();
if (result.response.status == 403)
alert(this.invalidUserText);
else
alert(this.serverProblemText);
},
scope: this
});
}
12. What the above does is disable the view (so no interactions) and pass along to the Application level authenticate user (we will add this soon), which is a wrapper for a SData request and as such it takes the normal SData request callback options.
13. Before we leave Login.js
lets go back and add the rest of the localizable strings for our fields and messages
logOnText: 'Log On',
passText: 'password',
titleText: 'My App',
userText: 'user name',
invalidUserText: 'The user name or password is invalid.',
serverProblemText: 'A problem occured on the server.',
14. Now go into src/Application.js
and let's add the authenticateUser(credentials, options)
function.
authenticateUser: function(credentials, options) {
var service = this.getService()
.setUserName(credentials.username)
.setPassword(credentials.password || '');
var request = new Sage.SData.Client.SDataServiceOperationRequest(service)
.setContractName('system')
.setOperationName('getCurrentUser');
request.execute({}, {
success: lang.hitch(this, this.onAuthenticateUserSuccess, credentials, options.success, options.scope),
failure: lang.hitch(this, this.onAuthenticateUserFailure, options.failure, options.scope),
aborted: lang.hitch(this, this.onAuthenticateUserFailure, options.failure, options.scope)
});
},
15. The key point here is that it is creating a Sage.SData.Client.SDataServiceOperationRequest
and in the case of SalesLogix SData using the system
contract and the getCurrentUser
operation which ends up looking like:
http://localhost/sdata/slx/system/-/$service/getCurrentUser?format=json
It is also important to note that the getService()
calls returns the instance of the service meaning any changes will affect all future use of the service. Here was are setting the username and password meaning all future calls with this service will use this credential.
16. To process the returned response we have the onAuthenticateUserSuccess
function which is passed the credentials
, options.success
callback and options.scope
. It's last parameter will be the SData result as dojo.hitch
prepends the argument list.
onAuthenticateUserSuccess: function(credentials, callback, scope, result) {
var user = {
'$key': lang.trim(result['response']['userId']),
'$descriptor': result['response']['prettyName'],
'UserName': result['response']['userName']
};
this.context['user' ] = user;
this.context['roles'] = result['response']['roles'];
this.context['securedActions'] = result['response']['securedActions'];
if (this.context['securedActions'])
{
array.forEach(this.context['securedActions'], function(item) {
this[item] = true;
}, (this.context['userSecurity'] = {}));
}
if (callback)
callback.call(scope || this, {user: user});
},
17. That's a lot of code but it really breaks down to three parts:
1. Parse the response to get the Users details and store that information into the App.context object
2. Flatten the securedActions array into an object for quicker lookups
3. Call the callback, which passes control back over to Login.js
, where it gets enable()
ed and calls App.navigateToInitialView()
.
* Implementation of parsing the user details and security will differ on your SData provider.
18. When authentication fails we want to remove the user/pass from the service instance just to be on the safe side:
onAuthenticateUserFailure: function(callback, scope, response) {
var service = this.getService();
if (service)
service
.setUserName(false)
.setPassword(false);
if (callback)
callback.call(scope || this, {response: response});
},
19. In which the callback will alert the user with an error message as defined in Login.js
.
20. Before we go and test we need to make sure that the login page is the first one shown instead of the home page.
21. Still in Application.js
at the very bottom create the function navigateToLoginView
and have it show our login view:
navigateToLoginView: function() {
var view = this.getView('login');
if (view)
view.show();
}
22. Scroll up to the run()
function and change the navigate to home to navigate to login:
run: function() {
this.inherited(arguments);
this.navigateToLoginView();
},
23. We now need to undo the hard coded userName and password that was added in Argos-Template Guide.
24. Open argos-template/configuration/development.js
and remove the userName
and password
keys from the crm
service.
23. Save and run your app, logging in with a valid user (example: "lee" no pass).
24. To verify, after logging in type in the console: App.context.user
and make sure you see the returned information.
You now have a working authentication system with login page. This could be further expanded with a remember me (storing credentials), or any other hooks you need for getting the initial user information. From here all the List, Detail and Edit views are "live" meaning you can set their view properties to point to various SData endpoints.