I am using AngularJS in a IBM MobileFirst Platform Foundation app. I have a login form and when I click the Login button it collects some data and passes it to adapter invoke function. If it is successful it never calls success loginSuccess function. I need to click Login button twice so that the success function is called.
Why do I need to click button twice?
app.js
var app = angular.module('myApp', ['ui.router']);
app.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/login');
$stateProvider.state('login', {
url: '/login',
templateUrl: 'view/login.html',
controller: 'loginController'
});
});
login.js
app.controller('loginController', function($scope) {
$scope.login = function(usrName, pass) {
var usrName = angular.element('#usrName').val();
var pass = angular.element('#pass').val();
console.log("Variable values: " + usrName + " : " + pass);
$scope.loginProcedure = {
adapter: 'SQL',
procedure: 'login',
parameters: [usrName, pass]
};
WL.Client.invokeProcedure($scope.loginProcedure, {
onSuccess: $scope.loginSuccess,
onFailure: $scope.loginFailure
});
$scope.loginSuccess = function(data) {
$scope.data = data;
$scope.resultSet = data.resultSet;
console.log('success: ' + JSON.stringify($scope.data + " : " + $scope.resultSet));
};
$scope.loginFailure = function() {
console.log('failed');
};
}
});
SQL-impl.js
/*
* Licensed Materials - Property of IBM
* 5725-I43 (C) Copyright IBM Corp. 2011, 2013. All Rights Reserved.
* US Government Users Restricted Rights - Use, duplication or
* disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
*/
/************************************************************************
* Implementation code for procedure - 'procedure1'
*
*
* #return - invocationResult
*/
var loginStatement = WL.Server.createSQLStatement("select usrname,password from jay1 where usrname = ? and password=? ");
function login(usr, pass) {
return WL.Server.invokeSQLStatement({
preparedStatement: loginStatement,
parameters: [usr, pass]
});
}
/************************************************************************
* Implementation code for procedure - 'procedure2'
*
*
* #return - invocationResult
*/
function procedure2(param) {
return WL.Server.invokeSQLStoredProcedure({
procedure: "storedProcedure2",
parameters: [param]
});
}
index.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>AB</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0">
<!--
<link rel="shortcut icon" href="images/favicon.png">
<link rel="apple-touch-icon" href="images/apple-touch-icon.png">
-->
<link rel="stylesheet" href="css/main.css" />
<link rel="stylesheet" href="css/bootstrap.min.css" />
<link rel="stylesheet" href="css/bootstrap-responsive.min.css" />
<script>
window.$ = window.jQuery = WLJQ;
</script>
</head>
<body ng-app="myApp">
<!--application UI goes here-->
<div id="view" ui-view></div>
<script src="js/initOptions.js"></script>
<script src="js/main.js"></script>
<script src="js/messages.js"></script>
<script src="library/jquery.min.js"></script>
<script src="library/angular.1.4.9.js"></script>
<script src="library/angular-ui-router.min.js"></script>
<script src="library/bootstrap.min.js"></script>
<script src="controller/app.js"></script>
<script src="controller/login.js"></script>
</body>
</html>
login.html
<div ng-controller="loginController" class="col-xs-12 col-sm-6 col-md-4 center-block" id="lgBlock">
<!-- Login Box Start -->
<div class="panel panel-primary">
<div class="panel-heading">Login</div>
<div class="panel-body">
<form class="form-group" name="lgForm">
<input type="text" class="form-control" id="usrName" required />
<input type="password" class="form-control" id="pass" required />
<input type="submit" class="btn btn-primary btn-block" id="submit" value="Login" ng-click="login()" />
</form>
</div>
</div>
<!-- Login Box End -->
<!-- error Modal start -->
<div class="modal" role="modal" id="errorPopup">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">Error</div>
<div class="modal-body"></div>
</div>
</div>
</div>
<!-- error Modal End -->
</div>
Which is correct way to parse JSON data using angularJS $scope?
1) $scope.data = data; $scope.resultSet = data.resultSet;
2) $scope.data = data; $scope.resultSet = $scope.data.resultSet;
You should take advantage of AngularJS two-way binding an update your login controller as follows:
login.js
app.controller('loginController', function($scope) {
$scope.login = function() {
// TODO: if block to validate user input i.e., username and password
$scope.loginProcedure = {
adapter: 'SQL',
procedure: 'login',
parameters: [$scope.username, $scope.password]
};
WL.Client.invokeProcedure($scope.loginProcedure, {
onSuccess: $scope.loginSuccess,
onFailure: $scope.loginFailure
});
console.log($scope.loginProcedure);
$scope.loginSuccess = function(data) {
$scope.data = data;
// resultSet is part of data so you don't need to refrences
// you can use $scope.data.resultSet to access resultSet
console.log('success: ' + JSON.stringify($scope.data + " : " + $scope.data.resultSet));
};
$scope.loginFailure = function() {
console.log('failed');
};
}
});
You should also update your view to declare the binding for username and password
login.html
<div ng-controller="loginController" class="col-xs-12 col-sm-6 col-md-4 center-block" id="lgBlock">
<!-- Login Box Start -->
<div class="panel panel-primary">
<div class="panel-heading">Login</div>
<div class="panel-body">
<form class="form-group" name="lgForm">
<input type="text" class="form-control" id="usrName" ng-model="username" required />
<input type="password" class="form-control" id="pass" ng-model="password" required />
<input type="submit" class="btn btn-primary btn-block" id="submit" value="Login" ng-click="login()" />
</form>
</div>
</div>
<!-- Login Box End -->
<!-- error Modal start -->
<div class="modal" role="modal" id="errorPopup">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">Error</div>
<div class="modal-body"></div>
</div>
</div>
</div>
<!-- error Modal End -->
</div>
The way you pass data to the $scope is totally up to you and your needs. My suggestion is to determine what you need and only bind that to the $scope for example if you only need data.resultSet then you should do $scope.resultSet. On the other hand, if you need complete access to the data object then you should bind data to the $scope and the you can still access the resultSet from there i.e., $scope.data.resultSet
Suggestions:
Only bind the things you need to reference in the view to the $scope for example you don't need access to the loginSuccess nor loginFailure in the view and you are only using those two functions once, so I recommend you update your loginController as follows:
app.controller('loginController', function($scope) {
$scope.login = function() {
// TODO: if block to validate user input i.e., username and password
$scope.loginProcedure = {
adapter: 'SQL',
procedure: 'login',
parameters: [$scope.username, $scope.password]
};
WL.Client.invokeProcedure($scope.loginProcedure, {
onSuccess: function(data) {
$scope.data = data;
// resultSet is part of data so you don't need to refrences
// you can use $scope.data.resultSet to access resultSet
console.log('success: ' + JSON.stringify($scope.data + " : " + $scope.data.resultSet));
},
onFailure: function() {
console.log('failed');
}
});
}
});
i have a form designed with angular js, upon submit it post the data to the spring controller, which intern processes the data to be displayed in the new jsp page.
part 1: form submit through angular js to the spring controller (Completed)
part 2: Spring controller to process the post data and return the json string object (completed)
part 3: The data received in the success part of the spring controller and displaying the data in the new jsp page (yet to completed, need help).
Problem here is that i can post the json data , but could not able to display the json date in the jsp page (AuditDisplayResultPage.jsp), basically i cannot accomplish the part3.
part 1 source code
AuditDisplayPage.jsp
<!DOCTYPE html>
<%# page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%# taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%# taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<html>
<head>
<meta charset="utf-8">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="/LDODashBoard/js/scripts.js" language="JavaScript" type="text/javascript"></script>
<script src="/LDODashBoard/js/AuditDisplayPage.js" language="JavaScript" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-filter/0.5.8/angular-filter.min.js"></script>
<link rel="stylesheet" type="text/css" href="/LDODashBoard/css/mystyle.css" />
<link rel="stylesheet" type="text/css" href="/LDODashBoard/css/AuditDisplayPage.css" />
<body>
<div ng-app="myApp" ng-controller="myCtrl" style="color:white" div align="left">
<br/><br/>
<form name = "audit" novalidate>
<label for="marketArraySel" >Market:</label>
<select id="marketArraySel" ng-model="marketArraySel" ng-options="market as getMarketFullName(market) for market in marketArray | orderBy:'name' track by market.id" ng-change="updateChanges()" ng-selected="selectedExpression()">
<option value="">[NO SELECTION]</option>
</select>
<label for="accountText" >Account:</label>
<input type="text" id="accountText" ng-model="accountName" ng-change="onAccountTextChange()"></input>
<br/>
<label for="marketNameType" >Type the Market:</label>
<input type="text" id="marketNameType" ng-model="marketNameType" ng-change="selectmarketByName(marketNameType)" />
<br/><br/>
<label for="textareavalue" ng-show="marketArraySel.id && accountName">Selected Details:</label>
<textarea id="textareavalue" ng-model="textareavalue" style="color:blue" disabled ng-show="marketArraySel.id && accountName">{{textareavalue}}</textarea>
<br />
<!--
<span size=10><STRONG> Selected Market: {{marketArraySel.name}} </STRONG> </span>
<span id="tab"></span>
<span id="tab"></span>
<span id="tab"></span>
<span id="tab"><STRONG> Selected Account: {{accountName}} </STRONG></span>
-->
<br/> <br/>
<input type="reset" ng-click="reset()" value="RESET"></input>
<input type="button" ng-click="submitfunction()" value="SUBMIT" ng-disabled="!marketArraySel.id && !accountName"></input>
</form>
<br><br>
<span id="tab"></span><span id="tab"></span><span id="tab"></span><span id="tab"></span><span id="tab"></span><span id="tab"></span><span id="tab"></span><span id="tab"></span>
<c:url value="/L1OutputDisplayPage?gcmmLink2=true" var="messageUrl2" />
Click Here
to Close
</div>
</body>
</html>
AuditDisplayPage.js
$scope.submitfunction = function() {
var dataObj = {
name : getById($scope.marketArray,$scope.marketArraySel.id),
account : $scope.accountName,
database : getDatabaseById($scope.marketArray,$scope.marketArraySel.id)
};
$http({
'url' : '/LDODashBoard/AuditDisplayPost',
'method' : 'POST',
'headers': {'Content-Type' : 'application/json'},
'data' : dataObj
})
.success(function(data, status, headers,config,ele) {
window.alert('Success4');
$scope.message = data;
//covert the json object to string
//var message1 = JSON.stringify($scope.message);
window.alert('message1:' + message1);
//window.location("/LDODashBoard/AuditDisplayPostResponse?argument1=" + "hello");
window.location("/LDODashBoard/AuditDisplayPostResponse?argument1=" + message);
/*
Message value
{"cvAuditClassList":[{"db_seq":13084,"operator":"cricha19","action":"I","cv_table":12,"id":3006538,"comments":"Acc:YP2MD;Firm:L;Off:;Sungard:;"},{"db_seq":31244,"operator":"cricha19","action":"I","cv_table":12,"id":3014027,"comments":"Acc:YP2MD;Firm:L;Off:;Sungard:;"},{"db_seq":40739,"operator":"gdennis2","action":"U","cv_table":5,"id":3014027,"comments":"Clair:AAKASH AGARWAL;"},{"db_seq":56740,"operator":"mzak1","action":"I","cv_table":12,"id":3043260,"comments":"Acc:YP3CW;Firm:L;Off:;Sungard:;"},{"db_seq":56748,"operator":"mzak1","action":"I","cv_table":12,"id":3043264,"comments":"Acc:YP3CW;Firm:L;Off:;Sungard:;"},{"db_seq":52647,"operator":"bkamins11","action":"I","cv_table":12,"id":3041524,"comments":"Acc:YP3CW;Firm:L;Off:;Sungard:;"},{"db_seq":76771,"operator":"rmarczak","action":"I","cv_table":12,"id":3053777,"comments":"Acc:YP3CW;Firm:L;Off:;Sungard:;"},{"db_seq":76772,"operator":"rmarczak","action":"I","cv_table":13,"id":3053777,"comments":"Idcp1:CREDIT SUISSE;Idcp2:;Idcp3:;"},{"db_seq":74719,"operator":"iteppel","action":"I","cv_table":13,"id":3043264,"comments":"Idcp1:CREDIT SUISSE;Idcp2:;Idcp3:;"},{"db_seq":74749,"operator":"iteppel","action":"I","cv_table":13,"id":3043260,"comments":"Idcp1:CREDIT SUISSE;Idcp2:;Idcp3:;"},{"db_seq":79437,"operator":"aimierow","action":"I","cv_table":12,"id":3054314,"comments":"Acc:YP2MD;Firm:L;Off:;Sungard:;"},{"db_seq":79438,"operator":"aimierow","action":"I","cv_table":13,"id":3054314,"comments":"Idcp1:CREDIT SUISSE;Idcp2:;Idcp3:;"},{"db_seq":113297,"operator":"iteppel","action":"I","cv_table":12,"id":3106380,"comments":"Acc:B825M;Firm:L;Off:;Sungard:;"},{"db_seq":113298,"operator":"iteppel","action":"I","cv_table":13,"id":3106380,"comments":"Idcp1:CREDIT SUISSE;Idcp2:;Idcp3:;"},{"db_seq":113299,"operator":"iteppel","action":"U","cv_table":5,"id":3106380,"comments":"Clair:cs;"},{"db_seq":113300,"operator":"iteppel","action":"I","cv_table":9,"id":3006538,"comments":"Name:%GI-YP2MD%;Broker:%;Ctr:%;CbType:0;AccOptFut:47;CtrType:;"},{"db_seq":113301,"operator":"iteppel","action":"D","cv_table":9,"id":3006538},{"db_seq":113302,"operator":"iteppel","action":"I","cv_table":9,"id":3006538,"comments":"Name:%GI-YP2MD%;Broker:CSBLO;Ctr:%;CbType:0;AccOptFut:47;CtrType:;"},{"db_seq":113303,"operator":"iteppel","action":"D","cv_table":9,"id":3006538},{"db_seq":113304,"operator":"iteppel","action":"I","cv_table":9,"id":3006538,"comments":"Name:%GI-YP2MD%;Broker:CSILO;Ctr:%;CbType:0;AccOptFut:47;CtrType:;"},{"db_seq":109823,"operator":"mskiba","action":"I","cv_table":12,"id":3082104,"comments":"Acc:YP2MD;Firm:L;Off:;Sungard:;"},{"db_seq":109824,"operator":"mskiba","action":"I","cv_table":13,"id":3082104,"comments":"Idcp1:CREDIT SUISSE;Idcp2:;Idcp3:;"},{"db_seq":125379,"operator":"mpeitsc1","action":"I","cv_table":12,"id":3118253,"comments":"Acc:YP2MD;Firm:L;Off:;Sungard:;"},{"db_seq":125380,"operator":"mpeitsc1","action":"I","cv_table":13,"id":3118253,"comments":"Idcp1:CREDIT SUISSE;Idcp2:;Idcp3:;"},{"db_seq":125382,"operator":"mpeitsc1","action":"I","cv_table":12,"id":3118254,"comments":"Acc:YP2MD;Firm:L;Off:;Sungard:;"},{"db_seq":125383,"operator":"mpeitsc1","action":"I","cv_table":13,"id":3118254,"comments":"Idcp1:CREDIT SUISSE;Idcp2:;Idcp3:;"},{"db_seq":125385,"operator":"mpeitsc1","action":"I","cv_table":12,"id":3118255,"comments":"Acc:YP2MD;Firm:L;Off:;Sungard:;"},{"db_seq":125386,"operator":"mpeitsc1","action":"I","cv_table":13,"id":3118255,"comments":"Idcp1:CREDIT SUISSE;Idcp2:;Idcp3:;"},{"db_seq":125388,"operator":"mpeitsc1","action":"I","cv_table":12,"id":3118256,"comments":"Acc:YP2MD;Firm:L;Off:;Sungard:;"},{"db_seq":125389,"operator":"mpeitsc1","action":"I","cv_table":13,"id":3118256,"comments":"Idcp1:CREDIT SUISSE;Idcp2:;Idcp3:;"}]}
*/
})
.error(function(data, status, headers, config) {
window.alert('Error2');
$scope.message1 = data;
});
window.alert("alert4");
};
part 2 source code
DatabaseController.java
#RequestMapping(value = "/AuditDisplayPost", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody String auditDisplayPost(
#RequestBody AuditDisplayPostClass postClass, UriComponentsBuilder ucb) {
QueryExecutionResults execResults = null;
log.info("inisde AuditDisplayPost method");
log.info("auditdisplaypostclass name:" + postClass.getName());
log.info("auditdisplaypostclass account:" + postClass.getAccount());
log.info("auditdisplaypostclass database:" + postClass.getDatabase());
CVAuditDisplayResponse auditResponse = new CVAuditDisplayResponse();
CVAuditStaticUserParams cvstaticparams = new CVAuditStaticUserParams();
cvstaticparams.setNames(DBCheckoutBean.retrieveStringTokenQuery(postClass.getAccount()," "));
execResults = queryexecutorimplbean.executeQueryWrapperByQueryID(CommonConstants.CVAuditStaticUser,postClass.getDatabase(),cvstaticparams);
List <CVAuditStaticUserClass> listobject = execResults.getCvAuditStaticRowColRslt();
log.info("CVAuditStaticUser execResults list object size:" + listobject.size());
auditResponse.setCvAuditClassList(listobject);
log.info("Converting the auditresponse object to json string");
String json = new Gson().toJson(auditResponse);
log.info("printing the json:" + json);
return json;
}
part 3 source code ( NEED YOUR HELP)
DatabaseController.java
#RequestMapping(value = "/AuditDisplayPostResponse", method = RequestMethod.GET,consumes=MediaType.APPLICATION_JSON_VALUE)
public ModelAndView auditDisplayResponse(
#RequestParam(value = "argument1", required = false) String argument1){
log.info("inisde auditDisplayResponse method");
String json = "";
try {
//convert string to json
json = "[" + argument1 + "]";
log.info("argument1:" + argument1);
log.info("argument1 json:" + json);
}
catch(Exception e){
e.printStackTrace();
}
return (new ModelAndView("AuditDisplayResultPage","DisplayResponse",json));
//return (new ModelAndView("L1OutputDisplayPage","message",json));
}
umm..
log.info("inisde auditDisplayResponse method");
Map<String, String> model = new HashMap<String,String>();
try {
//convert string to json
json = "[" + argument1 + "]";
log.info("argument1:" + argument1);
log.info("argument1 json:" + json);
model.put("jsonData", json);
}
catch(Exception e){
e.printStackTrace();
}
return (new ModelAndView("AuditDisplayResultPage","DisplayResponse",model));
and client side
<html>
<body>
<h2>message : ${jsonData}</h2>
</body>
</html>
Fatal error: Can't use function return
value in write context in line 3,
In which cases such errors get triggered?
My program:
//QUERY VARIABLE
$query="select * form user where user_name='$user_name' and user_password='sha($user_password)'";
//ESTABLISHING CONNECTION
$result=mysqli_query($dbc,$query)or die('Error Querying Database');
while($row=mysqli_num_rows($result)==1)
{
//SET COOKIE
setcookie('user_name',$row['user_name']);
setcookie('user_id',$row['id']);
$query="select * form user";
$result=mysqli_query($dbc,$query)or die('Error Querying Database');
$page_url='http://'.$_SERVER['HTTP_HOST'].dirname($_SERVER['PHP_SELF']).'/index.php';
header('Location'.$page_url);
}
//TERMINATING CONNECTION
mysqli_close($dbc);
}
else
{
$error_msg='Please type both user name and password correctly to login';
}
//}
}
else
{
$page_url='http://'.$_SERVER['HTTP_HOST'].dirname($_SERVER['PHP_SELF']).'/register.php';
header('Location'.$page_url);
}
}
if($output_form)
{
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Mismatch - Log In</title>
<link rel="stylesheet" href="stylesheets/style.css" media="all" />
</head>
<body class="body_style">
<?php
echo('<label class="signin_label">'.$error_msg.'</label>');
?>
<h2>Mismatch - Where Matches Happen...</h2>
<fieldset>
<legend>Mismatch - Log In</legend>
<form method="post" action="<?php $_SERVER['PHP_SELF']; ?>"><!--Self Referencing Form-->
<label class="signin_label">USER NAME</label>
<input type="text" name="user_name" title="Enter Your Account User Name" class="signin_textbox" value="<?php if(!empty($_POST['user_name']))$_POST['user_name']; ?>" /><br />
<label class="signin_label">PASSWORD</label>
<input type="password" name="user_pwd" title="Enter Your Account Password" class="signin_textbox" value="" /><br />
</fieldset>
<input type="submit" name="login_submit" title="Click To Log In" value="Log In" class="button" />
<?php
}
?>
</body>
</html>
Try changing
if(!isset($_COOKIE(user_id)))
to
if(!isset($_COOKIE['user_id']))
^^ ^^
$_COOKIE is an associative array.
Try to look here
Weird PHP error: 'Can't use function return value in write context'
Maybe it helps!
I've tidied up your code here:
<?php
//CLEAR THE ERROR MESSAGE
$error_msg = '';
//CHECK TO SEE IF COOKIE IS SET
if ( ! isset($_COOKIE['user_id']))
{
$output_form = TRUE;
if (isset($_POST['login_submit']))
{
//GRAB DATA
$user_name = $_POST['user_name'];
$user_password = $_POST['user_pwd'];
if ( ! empty($user_name) && ! empty($user_password))
{
//DATABASE CONNECTION VARIABLE
$dbc = mysqli_connect('localhost','root','','mismatch') or die('Error Connecting To Database');
//QUERY VARIABLE
$query = 'SELECT id FROM `user` WHERE user_name = \''.mysql_real_escape_string($user_name).'\' and user_password = \''.sha($user_password).'\'';
//ESTABLISHING CONNECTION
$result = mysqli_query($dbc,$query) or die('Error Querying Database');
if (mysql_num_rows($result)) {
$row = mysql_fetch_assoc($result);
//SET COOKIE
setcookie('user_name', $user_name);
setcookie('user_id', $row['id']);
header('Location: /index.php');
die();
}
else {
$error_msg = 'Please type both user name and password correctly to login';
}
//TERMINATING CONNECTION
mysqli_close($dbc);
}
else {
$error_msg = 'Please type both user name and password correctly to login';
}
}
else {
header('Location: /register.php');
die();
}
}
if ($output_form)
{
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Mismatch - Log In</title>
<link rel="stylesheet" href="stylesheets/style.css" media="all" />
</head>
<body class="body_style">
<?php
echo '<label class="signin_label">'.$error_msg.'</label>';
?>
<h2>Mismatch - Where Matches Happen...</h2>
<fieldset>
<legend>Mismatch - Log In</legend>
<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>"><!--Self Referencing Form-->
<label class="signin_label">USER NAME</label>
<input type="text" name="user_name" title="Enter Your Account User Name" class="signin_textbox" value="<?php if(!empty($_POST['user_name'])) echo $_POST['user_name']; ?>" /><br />
<label class="signin_label">PASSWORD</label>
<input type="password" name="user_pwd" title="Enter Your Account Password" class="signin_textbox" value="" /><br />
</fieldset>
<input type="submit" name="login_submit" title="Click To Log In" value="Log In" class="button" />
</body>
</html>
<?php
}
?>
Some problems I can remember finding:
$_COOKIE(user_id) instead of $_COOKIE['user_id']
No escaping on MySQL input
Big problem with your while() clause. I've replaced this with an if and mysql_fetch_assoc()
SQL queries said form not FROM
There were some others too that I've probably forgotten about.
Enjoy!