I was trying to understand how a “login” user case scenario would work with Sproutcore.
The sproutweet sample was a good starting point but it took me a while to come to grips with how it works. The complexities of twitter integration took time to understand.
I wrote this bare-bones login sample to experiment on the simplest way to get login to work so that I can use it in my own app.
Get the source code here.
See it in action here.
Use Case
- At app start up, the user is presented with a login screen

- If the user types in admin/admin, the user is taken to the main page.

- If the user types in any other username/password combination the user is presented with an error.

Step 1. Create Controller
Use sc-gen to create our scaffolding.
cd ~/dev/veebs-sprotucore-samples sc-gen controller LoginSample.LoginController
Step 2. Add Controller Code
Now we add our view-model properties to our login.js controller. This is the data that will be bound to the user interface controls (views in sproutcore terminology) on the login page.
username: '', password: '', errorMessage: '', isLoggingIn: NO, onLoginGoToPagePaneName: 'mainPage.mainPane',
Next, add our async Ajax http call.
/**
Start async login process
@returns YES if async call successfully started, NO if it failed. If error, the error message
will be placed in the 'errorMessage' property.
*/
beginLogin: function() {
try {
// Get our data from the properties using the SC 'get' methods
// Need to do this because these properties have been bound/observed.
var username = this.get('username');
if (username == null || username == '') {
throw SC.Error.desc('Username is required');
}
var password = this.get('password');
if (password == null || password == '') {
throw SC.Error.desc('Password is required');
}
// Start login
this.set('isLoggingIn', YES);
// Simulate a HTTP call to check our data.
// If the credentials not admin/admin, then get a bad url so we get 404 error
var url = '/login_sample/en/current/source/resources/main_page.js';
if (username != 'admin' || password != 'admin') {
url = '/login_sample/en/current/source/resources/bad_url.js';
}
SC.Request.getUrl(url)
.notify(this, 'endLogin')
.send();
return YES;
}
catch (err) {
// Set Error
this.set('errorMessage', err.message);
// Finish login processing
this.set('isLoggingIn', NO);
return NO;
}
},
/**
Callback from beginLogin() after we get a response from the server to process
the returned login info.
@param {SC.Response} response The HTTP response
@param {function} callback A function taking SC.Error as an input parameter. null is passed if no error.
*/
endLogin: function(response) {
try {
// Flag finish login processing to unlock screen
this.set('isLoggingIn', NO);
// Check status
SC.Logger.info('HTTP status code: ' + response.status);
if (!SC.ok(response)) {
// Error
throw SC.Error.desc('Invalid username or password. Try admin/admin
');
}
// clear data
this.set('errorMessage', '');
// Go to next page
var pagePaneName = LoginSample.loginController.get('onLoginGoToPagePaneName');
if (pagePaneName != null && pagePaneName != '') {
var pane;
pane = LoginSample.getPath('loginPage.loginPane');
pane.remove();
pane = LoginSample.getPath(pagePaneName);
pane.append();
}
}
catch (err) {
this.set('errorMessage', err.message);
}
}
Note that on a successful login, we move to the main page by removing the login pane and appending the main pane. This demonstrates a “hard-coded” way for moving between screens. A more flexible approach is to use routes.
Step 3. Add Controller Test Case
Add test cases to tests/controllers/login.js.
To run test cases, start sc-server and navigate your browser and go to http://localhost:4020. From the popup window, select tests. Then, from the left hand sidebar, select login_sample. Click on “controller/login”.
Step 4. Crate Login page
Create resources/login_page.js, resources/string.js and resources/login_page.css.
The following listing is of login_page.js.
LoginSample.loginPage = SC.Page.design({
loginPane: SC.MainPane.design({
layout: { width: 360, height: 125, centerX: 0, centerY: 0 },
classNames: ['login-pane'],
childViews: 'boxView'.w(),
boxView: SC.View.design({
childViews: 'username password loginButton loadingImage errorMessage'.w(),
username: SC.View.design({
layout: { left: 17, right: 14, top: 17, height: 26 },
childViews: 'label field'.w(),
label: SC.LabelView.design({
layout: { left: 0, width: 77, height: 18, centerY: 0 },
value: '_Username',
localize: YES,
textAlign: SC.ALIGN_RIGHT
}),
field: SC.TextFieldView.design({
layout: { width: 230, height: 22, right: 3, centerY: 0 },
isEnabledBinding: SC.Binding.from("LoginSample.loginController.isLoggingIn")
.bool()
.transform(function(value, isForward) {
return !value;
}),
valueBinding: 'LoginSample.loginController.username'
})
}),
password: SC.View.design({
layout: { left: 17, right: 14, top: 45, height: 26 },
childViews: 'label field'.w(),
label: SC.LabelView.design({
layout: { left: 0, width: 77, height: 18, centerY: 0 },
value: '_Password',
localize: YES,
textAlign: SC.ALIGN_RIGHT
}),
field: SC.TextFieldView.design({
layout: { width: 230, height: 22, right: 3, centerY: 0 },
isPassword: YES,
isEnabledBinding: SC.Binding.from("LoginSample.loginController.isLoggingIn")
.bool()
.transform(function(value, isForward) {
return !value;
}),
valueBinding: 'LoginSample.loginController.password'
})
}),
loginButton: SC.ButtonView.design({
layout: { height: 24, width: 80, bottom: 17, right: 17 },
title: '_Login',
localize: YES,
isDefault: YES,
isEnabledBinding: SC.Binding.from("LoginSample.loginController.isLoggingIn")
.bool()
.transform(function(value, isForward) {
return !value;
}),
target: 'LoginSample.loginController',
action: 'beginLogin'
}),
loadingImage: SC.ImageView.design({
layout: { width: 16, height: 16, bottom: 20, right: 110 },
value: sc_static('images/loading'),
useImageCache: NO,
isVisibleBinding: 'LoginSample.loginController.isLoggingIn'
}),
errorMessage: SC.LabelView.design({
layout: { height: 40, width: 230, right: 120, bottom: 7 },
classNames: ['error-message'],
valueBinding: 'LoginSample.loginController.errorMessage'
})
}) //contentView
}) //loginPane
}); //loginPage
Points to note:
- Use of strings.js. The value of the username lable is set to “_Username”. This is a lookup of the array in strings.js.
- The value of the username and password fields are bound to the loginController’s username and password properties. When the user enters data in these fields, they are automatically passed to the login controller properties.
- The loginButton’s action is to call the loginController.beingLogin() to start our login process.
- The value of the errorMessage label is set to the loginController’s errorMessage property so that it can show any error messages.
- The login button as well as the username and password fields are disabled while logging in is taking place. This prevents the user from double clicking the login button and triggering login again.
- The spinning “wait for ajax” image is only made visible when LoginSample.loginController.isLoggingIn is YES.
Step 5. Start the User on the Login Page.
In main.js, load the login page first up when the app starts.
LoginSample.getPath('loginPage.loginPane').append() ;
Finish
That’s all in this walk though.
Next, when I get the time, I will try to do a proper login-logout scenario with cookies.
The sc-gen step in your write-up says:
sc-gen controller LoginSample.Login
but the rest of your code refers to LoginController. So I think the sc-gen step should be changed to:
sc-gen controller LoginSample.LoginController
Jeff
Thanks Jeff once again. Fixed.
Hi,
Is this for sproutcore 1.x or 2.0? Glad to see 2.0 being renamed amber, brings clarity for a newbie like me.
thanks
Tim
Hi Tim,
This is for SC 1. For SC 2, see blog.chililog.org.
Thank you very much or these samples.
I’m 5 minutes in to SC. I’ve followed the directions in README.md. Sorry for such a silly question.
How do I run these samples locally? There are no .html files to load in a browser so far as I can see.
Thanks,
San
You need to run sc-server.
Thank you very much or these samples.
I’m 5 minutes in to SC. I’ve followed the directions in README.md. Sorry for such a silly question.
How do I run these samples locally? There are no .html files to load in a browser so far as I can see.
Thanks,
San
You need to run sc-server to build and serve the sproutcore files. See http://guides.sproutcore.com/