NIHVIVO-2279 Flesh out the UI for editing UserAccount, and associating with Individual Profiles

This commit is contained in:
j2blake 2011-07-01 16:19:53 +00:00
parent 50b159710b
commit 89a91757c0
7 changed files with 172 additions and 48 deletions

View file

@ -10,9 +10,10 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -21,10 +22,14 @@ import com.hp.hpl.jena.ontology.OntModel;
import edu.cornell.mannlib.vitro.webapp.beans.PermissionSet;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.beans.VClass;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.ParamMap;
import edu.cornell.mannlib.vitro.webapp.dao.DataPropertyStatementDao;
import edu.cornell.mannlib.vitro.webapp.dao.IndividualDao;
import edu.cornell.mannlib.vitro.webapp.dao.UserAccountsDao;
import edu.cornell.mannlib.vitro.webapp.dao.VClassDao;
import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
import edu.cornell.mannlib.vitro.webapp.dao.jena.OntModelSelector;
import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailFactory;
@ -35,6 +40,8 @@ import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailFactory;
public abstract class UserAccountsPage {
private static final Log log = LogFactory.getLog(UserAccountsPage.class);
private static final String PERSON_CLASS_URI = "http://xmlns.com/foaf/0.1/Person";
/**
* After the account is created, or the password is reset, the user has this
* many days to repond to the email.
@ -45,6 +52,9 @@ public abstract class UserAccountsPage {
protected final ServletContext ctx;
protected final OntModel userAccountsModel;
protected final UserAccountsDao userAccountsDao;
protected final VClassDao vclassDao;
protected final IndividualDao indDao;
protected final DataPropertyStatementDao dpsDao;
protected UserAccountsPage(VitroRequest vreq) {
this.vreq = vreq;
@ -57,6 +67,9 @@ public abstract class UserAccountsPage {
WebappDaoFactory wdf = (WebappDaoFactory) this.ctx
.getAttribute("webappDaoFactory");
userAccountsDao = wdf.getUserAccountsDao();
vclassDao = wdf.getVClassDao();
indDao = wdf.getIndividualDao();
dpsDao = wdf.getDataPropertyStatementDao();
}
protected boolean isEmailEnabled() {
@ -95,8 +108,8 @@ public abstract class UserAccountsPage {
* Treat the presence of a certain parameter, with a desired value, as a
* boolean flag.
*
* An example would be radio buttons with values of "yes" and
* "no". The expected value would be "yes".
* An example would be radio buttons with values of "yes" and "no". The
* expected value would be "yes".
*/
protected boolean isParameterAsExpected(String key, String expected) {
return expected.equals(getStringParameter(key, ""));
@ -117,6 +130,27 @@ public abstract class UserAccountsPage {
return list;
}
/**
* Create a list of possible profile types.
*
* TODO Right now, these are foaf:Person and it's sub-classes. What will it
* be for Vitro?
*/
protected SortedMap<String, String> buildProfileTypesList() {
String seedClassUri = PERSON_CLASS_URI;
List<String> classUris = vclassDao.getAllSubClassURIs(seedClassUri);
classUris.add(seedClassUri);
SortedMap<String, String> types = new TreeMap<String, String>();
for (String classUri: classUris) {
VClass vclass = vclassDao.getVClassByURI(classUri);
if (vclass != null) {
types.put(classUri, vclass.getName());
}
}
return types;
}
/**
* Make these URLs available to all of the pages.
*/
@ -129,7 +163,8 @@ public abstract class UserAccountsPage {
map.put("myAccount", UrlBuilder.getUrl("/accounts/myAccount"));
map.put("createPassword", UrlBuilder.getUrl("/accounts/createPassword"));
map.put("resetPassword", UrlBuilder.getUrl("/accounts/resetPassword"));
map.put("firstTimeExternal", UrlBuilder.getUrl("/accounts/firstTimeExternal"));
map.put("firstTimeExternal",
UrlBuilder.getUrl("/accounts/firstTimeExternal"));
map.put("accountsAjax", UrlBuilder.getUrl("/accountsAjax"));
return map;

View file

@ -6,6 +6,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import edu.cornell.mannlib.vitro.webapp.beans.SelfEditingConfiguration;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount.Status;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
@ -26,7 +27,7 @@ public class UserAccountsAddPage extends UserAccountsPage {
private static final String PARAMETER_FIRST_NAME = "firstName";
private static final String PARAMETER_LAST_NAME = "lastName";
private static final String PARAMETER_ROLE = "role";
private static final String PARAMETER_ASSOCIATE_WITH_PROFILE = "associate";
private static final String PARAMETER_ASSOCIATED_PROFILE_URI = "associatedProfileUri";
private static final String ERROR_NO_EMAIL = "errorEmailIsEmpty";
private static final String ERROR_EMAIL_IN_USE = "errorEmailInUse";
@ -39,6 +40,7 @@ public class UserAccountsAddPage extends UserAccountsPage {
private static final String TEMPLATE_NAME = "userAccounts-add.ftl";
private final UserAccountsAddPageStrategy strategy;
private final boolean matchingIsEnabled;
/* The request parameters */
private boolean submit;
@ -47,7 +49,7 @@ public class UserAccountsAddPage extends UserAccountsPage {
private String firstName = "";
private String lastName = "";
private String selectedRoleUri = "";
private boolean associateWithProfile;
private String associatedProfileUri = "";
/** The result of validating a "submit" request. */
private String errorCode = "";
@ -61,6 +63,9 @@ public class UserAccountsAddPage extends UserAccountsPage {
this.strategy = UserAccountsAddPageStrategy.getInstance(vreq, this,
isEmailEnabled());
this.matchingIsEnabled = SelfEditingConfiguration.getBean(vreq)
.isConfigured();
parseRequestParameters();
if (submit) {
@ -75,8 +80,8 @@ public class UserAccountsAddPage extends UserAccountsPage {
firstName = getStringParameter(PARAMETER_FIRST_NAME, "");
lastName = getStringParameter(PARAMETER_LAST_NAME, "");
selectedRoleUri = getStringParameter(PARAMETER_ROLE, "");
associateWithProfile = isParameterAsExpected(
PARAMETER_ASSOCIATE_WITH_PROFILE, "yes");
associatedProfileUri = getStringParameter(
PARAMETER_ASSOCIATED_PROFILE_URI, "");
strategy.parseAdditionalParameters();
}
@ -125,26 +130,33 @@ public class UserAccountsAddPage extends UserAccountsPage {
}
public void createNewAccount() {
// Assemble the fields into a new UserAccount
UserAccount u = new UserAccount();
u.setEmailAddress(emailAddress);
u.setFirstName(firstName);
u.setLastName(lastName);
u.setExternalAuthId(externalAuthId);
u.setMd5Password("");
u.setOldPassword("");
u.setPasswordChangeRequired(false);
u.setPasswordLinkExpires(0);
u.setLoginCount(0);
u.setStatus(Status.INACTIVE);
u.setPermissionSetUris(Collections.singleton(selectedRoleUri));
strategy.setAdditionalProperties(u);
// Create the account.
String uri = userAccountsDao.insertUserAccount(u);
this.addedAccount = userAccountsDao.getUserAccountByUri(uri);
// Associate the profile, as appropriate.
if (matchingIsEnabled) {
SelfEditingConfiguration.getBean(vreq)
.associateIndividualWithUserAccount(indDao, dpsDao,
this.addedAccount, associatedProfileUri);
}
strategy.notifyUser();
}
@ -156,16 +168,18 @@ public class UserAccountsAddPage extends UserAccountsPage {
body.put("firstName", firstName);
body.put("lastName", lastName);
body.put("selectedRole", selectedRoleUri);
if (associateWithProfile) {
body.put("associate", Boolean.TRUE);
}
body.put("roles", buildRolesList());
body.put("profileTypes", buildProfileTypesList());
body.put("formUrls", buildUrlsMap());
if (!errorCode.isEmpty()) {
body.put(errorCode, Boolean.TRUE);
}
if (matchingIsEnabled) {
body.put("showAssociation", Boolean.TRUE);
}
strategy.addMoreBodyValues(body);
return new TemplateResponseValues(TEMPLATE_NAME, body);

View file

@ -10,6 +10,7 @@ import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.beans.SelfEditingConfiguration;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.accounts.UserAccountsPage;
@ -45,6 +46,7 @@ public class UserAccountsEditPage extends UserAccountsPage {
private static final String TEMPLATE_NAME = "userAccounts-edit.ftl";
private final UserAccountsEditPageStrategy strategy;
private final boolean matchingIsEnabled;
/* The request parameters */
private boolean submit;
@ -70,6 +72,9 @@ public class UserAccountsEditPage extends UserAccountsPage {
this.strategy = UserAccountsEditPageStrategy.getInstance(vreq, this,
isEmailEnabled());
this.matchingIsEnabled = SelfEditingConfiguration.getBean(vreq)
.isConfigured();
parseRequestParameters();
validateUserAccountInfo();
@ -168,6 +173,8 @@ public class UserAccountsEditPage extends UserAccountsPage {
public final ResponseValues showPage() {
Map<String, Object> body = new HashMap<String, Object>();
body.put("userUri", userUri);
if (isSubmit()) {
body.put("emailAddress", emailAddress);
body.put("externalAuthId", externalAuthId);
@ -186,12 +193,17 @@ public class UserAccountsEditPage extends UserAccountsPage {
body.put("roles", buildRolesList());
}
body.put("profileTypes", buildProfileTypesList());
body.put("formUrls", buildUrlsMapWithEditUrl());
if (!errorCode.isEmpty()) {
body.put(errorCode, Boolean.TRUE);
}
if (matchingIsEnabled) {
body.put("showAssociation", Boolean.TRUE);
}
strategy.addMoreBodyValues(body);
return new TemplateResponseValues(TEMPLATE_NAME, body);
@ -231,7 +243,11 @@ public class UserAccountsEditPage extends UserAccountsPage {
userAccountsDao.updateUserAccount(userAccount);
// Associate the profile, as appropriate.
UserAccountsAssociatedProfileHelper.reconcile(userAccount, associatedProfileUri);
if (matchingIsEnabled) {
SelfEditingConfiguration.getBean(vreq)
.associateIndividualWithUserAccount(indDao, dpsDao,
userAccount, associatedProfileUri);
}
// Tell the user.
strategy.notifyUser();

View file

@ -9,6 +9,7 @@ var associateProfileFields = {
if (this.disableFormInUnsupportedBrowsers()) {
return;
}
this.mixIn();
this.initObjects();
this.initPage();
@ -44,17 +45,19 @@ var associateProfileFields = {
this.associatedArea = $('#associated');
this.associatedProfileNameSpan = $('#associatedProfileName');
this.verifyAssociatedProfileLink = $('#verifyProfileLink');
this.changeAssociatedProfileLink = $('#changeProfileLink');
this.associatedProfileUriField = $('#associatedProfileUri')
// We want to associate a profile
this.associationOptionsArea = $('#associationOptions');
this.associateProfileNameField = $('#associateProfileName');
},
// Initial page setup. Called only at page load.
initPage: function() {
this.checkForAssociatedProfile();
this.bindEventListeners();
this.initAutocomplete();
},
bindEventListeners: function() {
@ -72,6 +75,39 @@ var associateProfileFields = {
return false;
});
this.changeAssociatedProfileLink.click(function() {
associateProfileFields.associatedProfileUriField.val('');
associateProfileFields.associateProfileNameField.val('');
associateProfileFields.showExternalAuthIdNotRecognized();
return false;
});
},
initAutocomplete: function() {
this.associateProfileNameField.autocomplete({
minLength: 3,
source: function(request, response) {
$.ajax({
url: associateProfileFields.ajaxUrl,
dataType: 'json',
data: {
function: "autoCompleteProfile",
term: request.term,
externalAuthId: associateProfileFields.externalAuthIdField.val()
},
complete: function(xhr, status) {
console.log('response text' + xhr.responseText);
var results = jQuery.parseJSON(xhr.responseText);
response(results);
}
});
},
select: function(event, ui) {
associateProfileFields.showSelectedProfile(ui.item);
}
});
},
checkForAssociatedProfile: function() {
@ -80,7 +116,7 @@ var associateProfileFields = {
dataType: "json",
data: {
function: "checkExternalAuth",
userAccountUri: "",
userAccountUri: associateProfileFields.userUri,
externalAuthId: associateProfileFields.externalAuthIdField.val()
},
complete: function(xhr, status) {
@ -88,7 +124,7 @@ var associateProfileFields = {
if (results.idInUse) {
associateProfileFields.showExternalAuthIdInUse()
} else if (results.matchesProfile) {
associateProfileFields.showExternalAuthIdMatchesProfile(results.profileUri, results.profileUri, results.profileLabel)
associateProfileFields.showExternalAuthIdMatchesProfile(results.profileUri, results.profileUrl, results.profileLabel)
} else {
associateProfileFields.showExternalAuthIdNotRecognized()
}
@ -122,13 +158,17 @@ var associateProfileFields = {
this.externalAuthIdInUseMessage.hide();
this.associatedArea.hide();
if (this.externalAuthIdField.val().length > 0) {
if (this.associationEnabled && this.externalAuthIdField.val().length > 0) {
this.associationOptionsArea.show();
} else {
this.associationOptionsArea.hide();
}
},
showSelectedProfile: function(item) {
this.showExternalAuthIdMatchesProfile(item.uri, item.url, item.label);
},
}
$(document).ready(function() {

View file

@ -68,6 +68,8 @@
<label for="external-auth-id">External authorization ID</label>
<input type="text" name="externalAuthId" value="${externalAuthId}" id="external-auth-id" role="input "/>
<#include "userAccounts-associateProfilePanel.ftl">
<p>Roles<span class="requiredHint"> *</span> </p>
<#list roles as role>
<input type="radio" name="role" value="${role.uri}" role="radio" <#if selectedRole = role.uri>checked</#if> />
@ -75,29 +77,26 @@
<br />
</#list>
<#if !emailIsEnabled??>
<label for="initial-password">Initial password<span class="requiredHint"> *</span></label>
<input type="password" name="initialPassword" value="${initialPassword}" id="initial-password" role="input "/>
<p>Minimum of ${minimumLength} characters in length.</p>
<label for="confirm-password">Confirm initial password<span class="requiredHint"> *</span></label>
<input type="text" name="confirmPassword" value="${confirmPassword}" id="confirm-password" role="input "/>
</#if>
<p>Associate a profile with this account</p>
<input type="radio" name="associate" value="yes" role="radio" <#if associate??>checked</#if> id="associate" />
<label class="inline" for="associate"> Yes</label>
<input type="radio" name="associate" value="no" role="radio" <#if !associate??>checked</#if> id="no-associate" />
<label class="inline" for="no-associate"> No</label>
<#if emailIsEnabled??>
<p class="note">
Note: An email will be sent to the address entered above
notifying that an account has been created.
It will include instructions for activating the account and creating a password.
</p>
<#else>
<table>
<tr>
<td>
<label for="initial-password">Initial password<span class="requiredHint"> *</span></label>
<input type="password" name="initialPassword" value="${initialPassword}" id="initial-password" role="input "/>
</td>
<td>
<label for="confirm-password">Confirm initial password<span class="requiredHint"> *</span></label>
<input type="password" name="confirmPassword" value="${confirmPassword}" id="confirm-password" role="input" />
</td>
</tr>
</table>
<p>Minimum of ${minimumLength} characters in length.</p>
</#if>
<input type="submit" name="submitAdd" value="Add new account" class="submit"/> or <a class="cancel" href="${formUrls.list}">Cancel</a>

View file

@ -21,6 +21,7 @@
<label for="associatedProfileName">Associated profile:</label>
<span class="acSelectionInfo" id="associatedProfileName"></span>
<a href="" id="verifyProfileLink">(verify this match)</a>
<a href="" id="changeProfileLink">(change profile)</a>
</p>
<input type="hidden" id="associatedProfileUri" name="associatedProfileUri" value="" />
</div>
@ -36,7 +37,9 @@
<label for="">Create an associated profile</label>
<select name="degreeUri" id="degreeUri" >
<option value="" selected="selected">Select one</option>
<option value="" disabled>Bogus</option>
<#list profileTypes?keys as key>
<option value="${key}" >${profileTypes[key]}</option>
</#list>
</select>
</p>
</div>
@ -47,9 +50,20 @@
<script type="text/javascript">
var associateProfileFieldsData = {
<#if userUri??>
userUri: '${userUri}' ,
<#else>
userUri: '' ,
</#if>
<#if showAssociation??>
associationEnabled: true ,
ajaxUrl: '${formUrls.accountsAjax}'
</#if>
};
</script>
${scripts.add('<script type="text/javascript" src="${urls.base}/js/account/accountAssociateProfile.js"></script>')}
${scripts.add('<script type="text/javascript" src="${urls.base}/js/jquery.js"></script>',
'<script type="text/javascript" src="${urls.base}/js/jquery-ui/js/jquery-ui-1.8.9.custom.min.js"></script>',
'<script type="text/javascript" src="${urls.base}/js/account/accountAssociateProfile.js"></script>')}

View file

@ -86,14 +86,20 @@
be reset until the user follows the link provided in this email.
</p>
<#else>
<table>
<tr>
<td>
<label for="new-password">New password<span class="requiredHint"> *</span></label>
<input type="password" name="newPassword" value="${newPassword}" id="new-password" role="input" />
<p>Minimum of ${minimumLength} characters in length.</p>
<p>Leaving this blank means that the password will not be changed.</p>
</td>
<td>
<label for="confirm-password">Confirm initial password<span class="requiredHint"> *</span></label>
<input type="password" name="confirmPassword" value="${confirmPassword}" id="confirm-password" role="input" />
</td>
</tr>
</table>
<p>Minimum of ${minimumLength} characters in length.</p>
<p>Leaving this blank means that the password will not be changed.</p>
</#if>
<input type="submit" name="submitEdit" value="Save changes" class="submit" /> or <a class="cancel" href="${formUrls.list}">Cancel</a>