This is an automated email from the git hooks/post-receive script. New commit to branch develop in repository coselmar. See http://git.codelutin.com/coselmar.git commit 74d3dc1a7bdb5860163e2b6b9070a8d5154a6a7f Author: Yannick Martel <martel@©odelutin.com> Date: Wed Dec 3 17:49:23 2014 +0100 manage users (experts, clients and supervisors) in Question creation UI with auto-search --- .../persistence/entity/CoselmarUserTopiaDao.java | 86 ++++++++++++++++ .../coselmar/converter/BeanEntityConverter.java | 16 +++ .../coselmar/services/v1/UsersWebService.java | 8 +- coselmar-rest/src/main/resources/mapping | 2 +- coselmar-ui/pom.xml | 6 ++ coselmar-ui/src/main/webapp/index.html | 5 + .../src/main/webapp/js/coselmar-controllers.js | 55 ++++++++++- .../main/webapp/js/coselmar-questions-services.js | 10 +- .../main/webapp/views/questions/editquestion.html | 109 ++++++++++++++++++++- pom.xml | 9 ++ 10 files changed, 299 insertions(+), 7 deletions(-) diff --git a/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/CoselmarUserTopiaDao.java b/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/CoselmarUserTopiaDao.java index e691bec..6bcfbbc 100644 --- a/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/CoselmarUserTopiaDao.java +++ b/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/CoselmarUserTopiaDao.java @@ -96,4 +96,90 @@ public class CoselmarUserTopiaDao extends AbstractCoselmarUserTopiaDao<CoselmarU return coselmarUsers; } + /** + * Retrieve all user that are like the example. We search all + * {@link fr.ifremer.coselmar.persistence.entity.CoselmarUser} that have for + * each properties values like ones from Example + * + * For Example, a search with an example {firstName :'John', name: 'Doe'} + * will return users all users with a firstName containing 'John' and name + * containing 'Doe' : + * <ul> + * <li>User with firstName John and name Doe</li> + * <li>User with firstName Johnathan and Name doe</li> + * <li>User with firstName John and name Doenyon</li> + * <li>...</li> + * </ul> + * + * @param example : Prototype of User to find. We search all users that + * can have properties like ones in example. + * + * @return Users thats contains in a property at least all given keywords + * + */ + public List<CoselmarUser> findAllByExample(CoselmarUser example, boolean onlyActive) { + + StringBuilder hqlBuilder = new StringBuilder("FROM " + CoselmarUser.class.getName() + " CU"); + hqlBuilder.append(" WHERE 1=1 "); // Just because next clause will begin with operator + + Map<String, Object> args = new HashMap<>(); + + // only active ? + if (onlyActive) { + String activeCondition = DaoUtils.andAttributeEquals("CU", CoselmarUser.PROPERTY_ACTIVE, args, true); + hqlBuilder.append(activeCondition); + + } else { + String activeCondition = DaoUtils.andAttributeEquals("CU", CoselmarUser.PROPERTY_ACTIVE, args, example.isActive()); + hqlBuilder.append(activeCondition); + + } + + // Search on FirstName ? + if (example.getFirstname() != null) { + String firstNameCondition = DaoUtils.andAttributeLike("CU", CoselmarUser.PROPERTY_FIRSTNAME, args, example.getFirstname()); + hqlBuilder.append(firstNameCondition); + + } + + // Search on Name ? + if (example.getName() != null) { + String nameCondition = DaoUtils.andAttributeLike("CU", CoselmarUser.PROPERTY_NAME, args, example.getName()); + hqlBuilder.append(nameCondition); + + } + + // Search on Mail ? + if (example.getMail() != null) { + String mailCondition = DaoUtils.andAttributeLike("CU", CoselmarUser.PROPERTY_MAIL, args, example.getMail()); + hqlBuilder.append(mailCondition); + + } + + // Search on Organization ? + if (example.getOrganization() != null) { + String organizationCondition = DaoUtils.andAttributeLike("CU", CoselmarUser.PROPERTY_ORGANIZATION, args, example.getOrganization()); + hqlBuilder.append(organizationCondition); + + } + + // Search on Qualification ? + if (example.getQualification() != null) { + String qualificationCondition = DaoUtils.andAttributeLike("CU", CoselmarUser.PROPERTY_QUALIFICATION, args, example.getQualification()); + hqlBuilder.append(qualificationCondition); + + } + + // Search on Qualification ? + if (example.getRole() != null) { + String roleCondition = DaoUtils.andAttributeEquals("CU", CoselmarUser.PROPERTY_ROLE, args, example.getRole()); + hqlBuilder.append(roleCondition); + + } + + List<CoselmarUser> coselmarUsers = forHql(hqlBuilder.toString(), args).findAll(); + + return coselmarUsers; + } + } //CoselmarUserTopiaDao \ No newline at end of file diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/converter/BeanEntityConverter.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/converter/BeanEntityConverter.java index fcebb4a..0eae916 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/converter/BeanEntityConverter.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/converter/BeanEntityConverter.java @@ -29,6 +29,8 @@ import java.util.Date; import fr.ifremer.coselmar.beans.DocumentBean; import fr.ifremer.coselmar.beans.UserBean; import fr.ifremer.coselmar.persistence.entity.CoselmarUser; +import fr.ifremer.coselmar.persistence.entity.CoselmarUserImpl; +import fr.ifremer.coselmar.persistence.entity.CoselmarUserRole; import fr.ifremer.coselmar.persistence.entity.Document; import org.apache.commons.lang3.StringUtils; @@ -82,4 +84,18 @@ public class BeanEntityConverter { user.isActive()); } + public static CoselmarUser fromBean(UserBean userBean) { + CoselmarUser user = new CoselmarUserImpl(); + + user.setFirstname(userBean.getFirstName()); + user.setName(userBean.getName()); + user.setMail(userBean.getMail()); + user.setRole(CoselmarUserRole.valueOf(userBean.getRole())); + user.setQualification(userBean.getQualification()); + user.setOrganization(userBean.getOrganization()); + user.setActive(userBean.isActive()); + + return user; + } + } diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/UsersWebService.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/UsersWebService.java index 42a7770..6007e40 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/UsersWebService.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/UsersWebService.java @@ -92,12 +92,17 @@ public class UsersWebService extends CoselmarWebServiceSupport { return userBean; } - public List<UserBean> getUsers(String searchKeyword, boolean onlyActive) { + public List<UserBean> getUsers(String searchKeyword, UserBean like, boolean onlyActive) { List<CoselmarUser> userList; if (StringUtils.isNotBlank(searchKeyword)) { userList = getCoselmarUserDao().findAllLikeKeywords(Arrays.asList(searchKeyword), onlyActive); + } else if (like != null) { + CoselmarUser example = BeanEntityConverter.fromBean(like); + userList = getCoselmarUserDao().findAllByExample(example, onlyActive); + + } else { if (onlyActive) { userList = getCoselmarUserDao().forActiveEquals(true).findAll(); @@ -105,6 +110,7 @@ public class UsersWebService extends CoselmarWebServiceSupport { } else { userList = getCoselmarUserDao().findAll(); } + } List<UserBean> result = new ArrayList<>(userList.size()); diff --git a/coselmar-rest/src/main/resources/mapping b/coselmar-rest/src/main/resources/mapping index 27b2522..3e698ff 100644 --- a/coselmar-rest/src/main/resources/mapping +++ b/coselmar-rest/src/main/resources/mapping @@ -36,7 +36,7 @@ DELETE /v1/documents/{documentId} DocumentsWebService.deleteDocume # Users Api -GET /v1/users UsersWebService.getUsers +GET /v1/users UsersWebService.getUsers onlyActive=true GET /v1/users/{userId} UsersWebService.getUser POST /v1/users/login UsersWebService.login POST /v1/users/{userId} UsersWebService.modifyUser diff --git a/coselmar-ui/pom.xml b/coselmar-ui/pom.xml index 3b21d3e..01c8fe1 100644 --- a/coselmar-ui/pom.xml +++ b/coselmar-ui/pom.xml @@ -79,6 +79,12 @@ <version>4.2.0-1</version> <scope>runtime</scope> </dependency> + + <dependency> + <groupId>org.webjars</groupId> + <artifactId>angular-ui-select</artifactId> + <scope>runtime</scope> + </dependency> </dependencies> <build> diff --git a/coselmar-ui/src/main/webapp/index.html b/coselmar-ui/src/main/webapp/index.html index 9a7953a..b81be28 100644 --- a/coselmar-ui/src/main/webapp/index.html +++ b/coselmar-ui/src/main/webapp/index.html @@ -35,6 +35,11 @@ <script src="nuiton-js/angular-messages.js"></script> <script src="nuiton-js/angular-ui-bootstrap.js"></script> <script src="nuiton-js/bootstrap.js"></script> + + <!--TODO ymartel 20141203 : extract version, or use wro --> + <script src="webjars/angular-ui-select/0.9.0/select.js"></script> + <link rel="stylesheet" href="webjars/angular-ui-select/0.9.0/select.css"> + <script src="js/angular-jwt.js"></script> <script src="js/coselmar.js"></script> <script src="js/coselmar-constants.js"></script> diff --git a/coselmar-ui/src/main/webapp/js/coselmar-controllers.js b/coselmar-ui/src/main/webapp/js/coselmar-controllers.js index 39243b3..2587787 100644 --- a/coselmar-ui/src/main/webapp/js/coselmar-controllers.js +++ b/coselmar-ui/src/main/webapp/js/coselmar-controllers.js @@ -21,7 +21,7 @@ * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ -var coselmarControllers = angular.module('coselmarControllers', ['ui.bootstrap']); +var coselmarControllers = angular.module('coselmarControllers', ['ui.bootstrap', 'ui.select']); // Controller when the main page/view loads coselmarControllers.controller("HomeCtrl", ['$scope', '$http', '$location', 'userService', 'jwtHelper', @@ -269,7 +269,18 @@ coselmarControllers.controller("UserViewCtrl", // Controller for new question View coselmarControllers.controller("NewQuestionCtrl", ['$scope', '$route', '$location', 'questionsService', function($scope, $route, $location, questionsService){ - $scope.question = {'privacy' : 'PUBLIC', 'themes' : []}; + $scope.question = {'privacy' : 'PUBLIC', 'themes' : [], 'experts' : [], 'clients' : [], 'externalExperts' : []}; + + $scope.users = { 'experts' : [], 'clients': [], 'supervisors' : []}; + questionsService.findUsers({'role': 'EXPERT', 'active': 'true'}, function(users) { + $scope.users.experts = users; + }); + questionsService.findUsers({'role': 'CLIENT', 'active': 'true'}, function(users) { + $scope.users.clients = users; + }); + questionsService.findUsers({'role': 'SUPERVISOR', 'active': 'true'}, function(users) { + $scope.users.supervisors = users; + }); $scope.saveQuestion = function(isValidForm){ @@ -299,3 +310,43 @@ coselmarControllers.controller("NewQuestionCtrl", ['$scope', '$route', '$locatio } }]); + +/** + * AngularJS default filter with the following expression: + * "person in people | filter: {name: $select.search, age: $select.search}" + * performs a AND between 'name: $select.search' and 'age: $select.search'. + * We want to perform a OR. + * + * Extract from angular-ui-select demo : + * https://github.com/angular-ui/ui-select/blob/master/examples/demo.js + */ +coselmarControllers.filter('propsFilter', function() { + return function(items, props) { + var out = []; + + if (angular.isArray(items)) { + items.forEach(function(item) { + var itemMatches = false; + + var keys = Object.keys(props); + for (var i = 0; i < keys.length; i++) { + var prop = keys[i]; + var text = props[prop].toLowerCase(); + if (item[prop] && item[prop].toString().toLowerCase().indexOf(text) !== -1) { + itemMatches = true; + break; + } + } + + if (itemMatches) { + out.push(item); + } + }); + } else { + // Let the output be the input untouched + out = items; + } + + return out; + }; +}); diff --git a/coselmar-ui/src/main/webapp/js/coselmar-questions-services.js b/coselmar-ui/src/main/webapp/js/coselmar-questions-services.js index de81f2b..7b91279 100644 --- a/coselmar-ui/src/main/webapp/js/coselmar-questions-services.js +++ b/coselmar-ui/src/main/webapp/js/coselmar-questions-services.js @@ -8,6 +8,7 @@ function Question(resource, config){ this.resource = resource; var baseURL = config.BASE_URL + "/questions"; + var usersURL = config.BASE_URL + "/users"; this.saveQuestion = function(question, successFunction, failFunction){ @@ -29,5 +30,12 @@ function Question(resource, config){ } }); questionResource.save(null, formData, successFunction, failFunction); - } + }; + + this.findUsers = function(example, successFunction) { + var userResource = resource(usersURL, {'like': example}); + userResource.query().$promise.then(successFunction); + + }; + }; \ No newline at end of file diff --git a/coselmar-ui/src/main/webapp/views/questions/editquestion.html b/coselmar-ui/src/main/webapp/views/questions/editquestion.html index d284563..ebc2702 100644 --- a/coselmar-ui/src/main/webapp/views/questions/editquestion.html +++ b/coselmar-ui/src/main/webapp/views/questions/editquestion.html @@ -82,7 +82,6 @@ <div class="form-group" ng-class="{'has-error' : question.themes.$invalid && question.themes.length < 1 && !questionForm.themes.$pristine}"> - <form name="questionThemesForm" > <label class="col-md-2 control-label">Themes *</label> @@ -98,7 +97,6 @@ <button class="btn btn-primary" value="add" ng-click="addTheme(toAddTheme); toAddTheme=''">add</button> </div> - </form> <div class="col-md-7"> <span ng-repeat="theme in question.themes" class="" aria-hidden="true"> @@ -114,6 +112,113 @@ <!-- End Line with Themes --> + <!-- Line with Experts and clients --> + + <div class="form-group row" > + + <div class=""> + + <label class="col-md-2 control-label">Experts</label> + + <div class="col-md-4"> + + <ui-select multiple ng-model="question.experts" + theme="bootstrap" reset-search-input="true" + ng-disabled="disabled" class="form-control"> + + <ui-select-match placeholder="Select expert..."> + {{$item.firstName}} {{$item.name}} ({{$item.organization}}) + </ui-select-match> + + <ui-select-choices + repeat="expert in users.experts | propsFilter: {name: $select.search, firstName: $select.search, organization: $select.search}"> + {{expert.firstName}} {{expert.name}} ({{expert.organization}}) + </ui-select-choices> + </ui-select> + + </div> + + </div> + + <div class=""> + <label class="col-md-2 control-label">Clients</label> + + <div class="col-md-4"> + + <ui-select multiple ng-model="question.clients" + theme="bootstrap" reset-search-input="true" + ng-disabled="disabled" class="form-control"> + + <ui-select-match placeholder="Select client..."> + {{$item.firstName}} {{$item.name}} ({{$item.organization}}) + </ui-select-match> + + <ui-select-choices + repeat="expert in users.clients | propsFilter: {name: $select.search, firstName: $select.search, organization: $select.search}"> + {{expert.firstName}} {{expert.name}} ({{expert.organization}}) + </ui-select-choices> + </ui-select> + + </div> + + </div> + + + </div> + + <!-- End Line with Experts --> + + + <!-- Line with External Experts and Supervisor --> + + <div class="form-group row" > + + <div class=""> + + <label class="col-md-2 control-label">External Experts</label> + + <div class="col-md-4"> + + <input type="text" ng-model="question.externalExperts" + class="form-control" name="externalExperts" + placeholder="expert1, expert2, ..." list > + </div> + + </div> + + <div class=""> + <label class="col-md-2 control-label">Supervisors</label> + + <div class="col-md-4"> + + <ui-select multiple ng-model="question.supervisors" + theme="bootstrap" reset-search-input="true" + ng-disabled="disabled" class="form-control"> + + <ui-select-match placeholder="Select supervisor..."> + {{$item.firstName}} {{$item.name}} ({{$item.organization}}) + </ui-select-match> + + <ui-select-choices + repeat="expert in users.supervisors | propsFilter: {name: $select.search, firstName: $select.search, organization: $select.search}"> + {{expert.firstName}} {{expert.name}} ({{expert.organization}}) + </ui-select-choices> + </ui-select> + + </div> + + </div> + + + </div> + + <!-- End Line with External Experts and Supervisor --> + + <!-- datalist for Experts --> + <datalist id="expertList"> + <option data-ng-repeat="expert in users.experts" value="{{expert}}"> pouet </option> + </datalist> + <div class="form-group" ng-if="questionForm.$valid && question.themes.length > 0"> <div style="padding-left: 200px"> <input type="submit" value="Validate" class="btn btn-primary" ng-click="saveQuestion(questionForm.$valid)"/> diff --git a/pom.xml b/pom.xml index 9b21f6c..106641c 100644 --- a/pom.xml +++ b/pom.xml @@ -145,6 +145,8 @@ <tomcatEmbedVersion>7.0.50</tomcatEmbedVersion> + <angularUiSelectVersion>0.9.0</angularUiSelectVersion> + </properties> @@ -341,6 +343,13 @@ <version>${slf4jVersion}</version> </dependency> + <!-- JS librairie --> + <dependency> + <groupId>org.webjars</groupId> + <artifactId>angular-ui-select</artifactId> + <version>${angularUiSelectVersion}</version> + </dependency> + <!-- Others --> <dependency> <groupId>com.github.spullara.mustache.java</groupId> -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.